# returns pair [sum, carry]
def four_bit_adder(a, b)
  a_bits = binary_string_to_bits(a,4)
  b_bits = binary_string_to_bits(b,4)

  s0, c0 = full_adder(a_bits[0], b_bits[0],  0)
  s1, c1 = full_adder(a_bits[1], b_bits[1], c0)
  s2, c2 = full_adder(a_bits[2], b_bits[2], c1)
  s3, c3 = full_adder(a_bits[3], b_bits[3], c2)

  [bits_to_binary_string([s0, s1, s2, s3]), c3.to_s]
end

# returns pair [sum, carry]
def full_adder(a, b, c0)
  s, c = half_adder(c0, a)
  s, c1 = half_adder(s, b)
  [s, _or(c,c1)]
end

# returns pair [sum, carry]
def half_adder(a, b)
  [xor(a, b), _and(a,b)]
end

def xor(a, b)
  _or(_and(a, _not(b)), _and(_not(a), b))
end

# "and", "or" and "not" are Ruby keywords
def _and(a, b)  a & b  end
def _or(a, b)   a | b  end
def _not(a)    ~a & 1  end

def int_to_binary_string(n, length)
  "%0#{length}b" % n
end

def binary_string_to_bits(s, length)
  ("%#{length}s" % s).reverse.chars.map(&:to_i)
end

def bits_to_binary_string(bits)
  bits.map(&:to_s).reverse.join
end

puts " A    B      A      B   C    S  sum"
0.upto(15) do |a|
  0.upto(15) do |b|
    bin_a = int_to_binary_string(a, 4)
    bin_b = int_to_binary_string(b, 4)
    sum, carry = four_bit_adder(bin_a, bin_b)
    puts "%2d + %2d = %s + %s = %s %s = %2d" %
         [a, b, bin_a, bin_b, carry, sum, (carry + sum).to_i(2)]
  end
end
