diff --git a/bin/assembler b/bin/assembler index 836ed1f..3b553c7 100755 --- a/bin/assembler +++ b/bin/assembler @@ -4,4 +4,78 @@ require_relative '../lib/parser' require_relative '../lib/code' require_relative '../lib/symbol_table' +HARDCODED_ADDRESSES = { + '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' => 0x4000, + 'KBD' => 0x6000 +} + +def parse_labels(input) + parser = Parser.new(input) + table = SymbolTable.new HARDCODED_ADDRESSES + icount = 0 + + parser.each_command do |command_type| + if command_type == Parser::L_COMMAND + table.add_entry parser.symbol, icount + else + icount += 1 + end + end + + table +end + +def generate_code(input, table) + parser = Parser.new(input) + # Start placing variables from address 16 onwards + next_address = 16 + + get_address = ->(sym) do + if sym =~ /^\d+$/ + sym + elsif table.contains? sym + table.get_address sym + else + table.add_entry sym, next_address + address, next_address = next_address, next_address + 1 + end + end + + parser.each_command do |command_type| + case command_type + when Parser::A_COMMAND then + puts "0%015b" % get_address.(parser.symbol) + when Parser::C_COMMAND then + comp = Code.comp(parser.comp) + dest = Code.dest(parser.dest) + jump = Code.jump(parser.jump) + + puts "111#{comp}#{dest}#{jump}" + end + end +end + input = ARGF.read +table = parse_labels(input) +generate_code(input, table) diff --git a/lib/code.rb b/lib/code.rb index 646c2ea..30bb19d 100644 --- a/lib/code.rb +++ b/lib/code.rb @@ -1,2 +1,47 @@ module Code + def self.dest mnemonic + d1 = mnemonic.index('A') ? '1' : '0' + d2 = mnemonic.index('D') ? '1' : '0' + d3 = mnemonic.index('M') ? '1' : '0' + d1 + d2 + d3 + end + + def self.jump mnemonic + return "111" if mnemonic == "JMP" + return "101" if mnemonic == "JNE" + j1 = mnemonic.index('L') ? '1' : '0' + j2 = mnemonic.index('E') ? '1' : '0' + j3 = mnemonic.index('G') ? '1' : '0' + j1 + j2 + j3 + end + + def self.comp mnemonic + address = mnemonic.index('M') ? '1' : '0' + + # Swap M for A so the lookup table doesn't need to account for both + comp = case mnemonic.sub('M', 'A') + when "0" then '101010' + when '0' then '101010' + when '1' then '111111' + when '-1' then '111010' + when 'D' then '001100' + when 'A' then '110000' + when '!D' then '001101' + when '!A' then '110001' + when '-D' then '001111' + when '-A' then '110011' + when 'D+1' then '011111' + when 'A+1' then '110111' + when 'D-1' then '001110' + when 'A-1' then '110010' + when 'D+A' then '000010' + when 'D-A' then '010011' + when 'A-D' then '000111' + when 'D&A' then '000000' + when 'D|A' then '010101' + else raise "Unknown mnemonic: '#{mnemonic}'" + end + + address + comp + end end diff --git a/lib/parser.rb b/lib/parser.rb index 7311279..6d48915 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -1,2 +1,71 @@ -module Parser +class Parser + A_COMMAND = 4 + L_COMMAND = 1 + C_COMMAND = 8 + + def initialize input + @remaining = input + end + + def has_more_commands? + @remaining =~ /^\s*[^\s\/]+.*$/ + end + + def advance + begin + line, _, @remaining = @remaining.partition("\n") + # Strip trailing comments + @current = line.partition('//').first.strip + end while @current.empty? + end + + def command_type + case @current.lstrip[0] + when '@' then Parser::A_COMMAND + when '(' then Parser::L_COMMAND + else Parser::C_COMMAND + end + end + + def each_command + while has_more_commands? + advance + yield command_type + end + end + + def symbol + case command_type + when Parser::A_COMMAND then @current.strip[1..-1] + when Parser::L_COMMAND then @current.strip[1..-2] + else wrong_command_type + end + end + + def dest + wrong_command_type unless command_type == Parser::C_COMMAND + + dest, match, _ = @current.partition('=') + match.empty? ? "" : dest + end + + def jump + wrong_command_type unless command_type == Parser::C_COMMAND + + _, match, jump = @current.rpartition(';') + match.empty? ? "": jump + end + + def comp + wrong_command_type unless command_type == Parser::C_COMMAND + + comp, _, _ = @current.partition(';') + comp.rpartition('=')[2] + end + + private + def wrong_command_type + f = caller[0][/`.*'/][1..-2] + raise "#{self.class}##{f} not defined for current command_type" + end end diff --git a/lib/symbol_table.rb b/lib/symbol_table.rb index 9d0005c..374d221 100644 --- a/lib/symbol_table.rb +++ b/lib/symbol_table.rb @@ -1,2 +1,15 @@ -module SymbolTable +class SymbolTable + def initialize table = {} + @table = table + end + + def add_entry symbol, value + @table[symbol] = value + end + + def get_address symbol + @table[symbol] + end + + alias contains? get_address end