#!/path/to/ruby
symbolhash = {SP: 0, LCL: 1, ARG: 2, THIS:3, THAT:4, R0: 0, R1: 1, R2: 2, R3: 3, R4: 4, 
              R5: 5, R6: 6, R7: 7, R8: 8, R9: 9, R10:10, R11: 11, R12: 12, R13: 13, R14: 14, 
              R15: 15, SCREEN: 16384, KBD: 24576}

comphash = {"0": "0101010", "1": "0111111", "-1": "0111010", D: "0001100", A: "0110000",
            "!D": "0001101", "!A": "0110001", "-D": "0001111", "-A": "0110011", 
            "D+1": "0011111", "A+1": "0110111", "D-1": "0001110", "A-1": "0110010", 
            "D+A": "0000010", "D-A": "0010011", "A-D": "0000111", "D&A": "0000000", 
            "D|A": "0010101", M: "1110000", "!M": "1110001", "-M": "1110011", 
            "M+1": "1110111", "M-1": "1110010", "D+M": "1000010", "D-M": "1010011", 
            "M-D": "1000111", "D&M": "1000000", "D|M": "1010101"}

desthash = {"0": "000", M: "001", D: "010", MD: "011", A: "100", AM: "101", AD: "110", 
            AMD: 111}

jumphash = {"0": "000", JGT: "001", JEQ: "010", JGE: "011", JLT: "100", JNE: "101", 
            JLE: "110", JMP: "111"}

output_file = File.dirname(ARGV.first) + "/" + File.basename(ARGV.first) + ".hack"
file = File.read(ARGV.first)
counter = 0
next_address = 16

file.each_line do |line|
  next unless line = line.gsub(/[[:space:]]/, '')
                         .split("//")[0]

  if line.match(/@.+/) || 
     line.match(/.+=.+/) || 
     line.match(/.+;.+/) || 
     line.match(/.+=.+;.+/)
    counter += 1
  end

  if match = line.match(/\((?<label>.+)\)/)
    unless symbolhash[match[:label].to_sym]
      symbolhash[match[:label].to_sym] = counter
    end
  end
end

File.delete(output_file) if File.exists?(output_file)

File.open(output_file, "a") do |output|
  file.each_line do |line|
    next unless line = line.gsub(/[[:space:]]/, '')
                           .split("//")[0]

    if match = line.match(/@(?<addr>.+)/)
      if line.match(/@(\D+)/)
        if address = symbolhash[match[:addr].to_sym]
          output.puts address.to_s(2).rjust(16, '0')
        else
          symbolhash[match[:addr].to_sym] = next_address
          output.puts next_address.to_s(2).rjust(16, '0')
          next_address += 1
        end
      else
        output.puts match[:addr].to_i.to_s(2).rjust(16, '0')
      end
    end

    if match = line.match(/(?<dest>.+)=(?<comp>.+)/)
      output.puts "111" + comphash[match[:comp].to_sym] +
                          desthash[match[:dest].to_sym] + "000"
    end

    if match = line.match(/(?<comp>.+);(?<jump>.+)/)
      output.puts "111" + comphash[match[:comp].to_sym] +
                  "000" + jumphash[match[:jump].to_sym]
    end

    if match = line.match(/(?<dest>.+)=(?<comp>.+);(?<jump>.+)/)
      output.puts "111" + comphash[match[:comp].to_sym] +
                          desthash[match[:dest].to_sym] +
                          jumphash[match[:jump].to_sym]
    end
  end
end