Skip to content

Instantly share code, notes, and snippets.

@jsomers
Created July 28, 2012 19:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsomers/3194538 to your computer and use it in GitHub Desktop.
Save jsomers/3194538 to your computer and use it in GitHub Desktop.
Three-pass Hack assembler (a la http://www1.idc.ac.il/tecs/projects/06/index.htm)
$rom_counter = 0
$ram_counter = 16
$symbols = {
"SP" => "0",
"LCL" => "1",
"ARG" => "10",
"THIS" => "11",
"THAT" => "100",
"R0" => "0",
"R1" => "1",
"R2" => "10",
"R3" => "11",
"R4" => "100",
"R5" => "101",
"R6" => "110",
"R7" => "111",
"R8" => "1000",
"R9" => "1001",
"R10" => "1010",
"R11" => "1011",
"R12" => "1100",
"R13" => "1101",
"R14" => "1110",
"R15" => "1111",
"SCREEN" => "100000000000000",
"KBD" => "110000000000000"
}
class Integer
def to_binary
self.to_s(2)
end
end
class String
def user_defined?
!self.match /^[0-9]/
end
end
class Parser
def initialize(path)
@file = File.open(path)
end
def lines
@file.readlines.inject([]) do |lines, line|
line = line.chomp("\r\n").strip
lines << Line.new(line.gsub(/\/\/.*/, "").strip) unless line.start_with? "//" or line.empty?
lines
end
end
end
class Line
def initialize(str)
@str = str
end
def a_instruction?
@str.start_with? "@"
end
def l_instruction?
@str.match /\(.*?\)/
end
def instruction
a_instruction? ? A.new(@str) : (l_instruction? ? L.new(@str) : C.new(@str))
end
end
class A
def initialize(str)
@symbol = str.split("@").last
if @symbol.user_defined?
if $symbols.has_key? @symbol
@symbol = $symbols[@symbol]
else
@symbol = $symbols[@symbol] = $ram_counter.to_binary
$ram_counter += 1
end
else
@symbol = @symbol.to_i.to_binary
end
end
def to_s
"0" + @symbol.rjust(15, "0")
end
end
class L
def initialize(str)
symbol = str[/\((.*?)\)/, 1]
if symbol.user_defined?
$symbols[symbol] ||= $rom_counter.to_binary
end
end
end
class C
@@dests = {
nil => "000",
"M" => "001",
"D" => "010",
"MD" => "011",
"A" => "100",
"AM" => "101",
"AD" => "110",
"AMD" => "111"
}
@@comps = {
"0" => "101010",
"1" => "111111",
"-1" => "111010",
"D" => "001100",
"A" => "110000",
"M" => "110000",
"!D" => "001101",
"!A" => "110001",
"!M" => "110001",
"-D" => "001111",
"-A" => "110011",
"-M" => "110011",
"D+1" => "011111",
"A+1" => "110111",
"M+1" => "110111",
"D-1" => "001110",
"A-1" => "110010",
"M-1" => "110010",
"D+A" => "000010",
"D+M" => "000010",
"D-A" => "010011",
"D-M" => "010011",
"A-D" => "000111",
"M-D" => "000111",
"D&A" => "000000",
"D&M" => "000000",
"D|A" => "010101",
"D|M" => "010101",
nil => "000000"
}
@@jumps = {
"null" => "000",
"JGT" => "001",
"JEQ" => "010",
"JGE" => "011",
"JLT" => "100",
"JNE" => "101",
"JLE" => "110",
"JMP" => "111",
nil => "000"
}
def initialize(str)
@dest, @comp, @jump, @str = nil, nil, nil, str
parse
end
def parse
if @str.include? "="
@dest, @comp = @str.split("=")
else
@comp, @jump = @str.split(";")
end
end
def to_s
a = (@comp.include?("M") ? "1" : "0")
dest, comp, jump = @@dests[@dest], @@comps[@comp], @@jumps[@jump]
"111" + a + comp + dest + jump
end
end
lines = Parser.new("./RectL.asm").lines
# First pass: populate symbol table from L_INSTRUCTIONs.
$rom_counter = 0
lines.each do |line|
if line.l_instruction?
line.instruction
else
$rom_counter += 1
end
end
# Second pass: populate symbol table from A_INSTRUCTIONs.
lines.each do |line|
if line.a_instruction?
line.instruction
end
end
# Final pass: parse each line.
lines.each do |line|
puts line.instruction unless line.l_instruction?
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment