Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assembler Impl #2

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
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