Skip to content
74 changes: 74 additions & 0 deletions bin/assembler
Original file line number Diff line number Diff line change
Expand Up @@ -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)
45 changes: 45 additions & 0 deletions lib/code.rb
Original file line number Diff line number Diff line change
@@ -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
71 changes: 70 additions & 1 deletion lib/parser.rb
Original file line number Diff line number Diff line change
@@ -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
15 changes: 14 additions & 1 deletion lib/symbol_table.rb
Original file line number Diff line number Diff line change
@@ -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