Skip to content

Instantly share code, notes, and snippets.

@tsu-nera
Last active August 29, 2015 14:21
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 tsu-nera/a3298e614d4d358a740a to your computer and use it in GitHub Desktop.
Save tsu-nera/a3298e614d4d358a740a to your computer and use it in GitHub Desktop.
Nand to tetris assembler
# coding: utf-8
require 'pp'
module CommandType
A_COMMAND = 1
C_COMMAND = 2
L_COMMAND = 3
end
class Parser
include CommandType
attr_accessor :current_line
def initialize(file_path)
# read raw data
@file = File.open(file_path)
@data = @file.readlines
@file.close
@current_line = 0
ignore_comments
trim_commands
end
def ignore_comments
@data.map! { |line| line.chomp }
.select! { |line| line != "" && line[0,2] != "//" }
end
def trim_commands
@data.map! { |line|
if current_commands.include?("@")
commands = line.split("//")
commands[0]
end
}
.map! { |line| line.strip }
end
def more_commands?
@current_line != @data.size
end
def advance
@current_line+=1
end
def current_commands
@data[@current_line]
end
def command_type
if current_commands.include?("@")
A_COMMAND
elsif current_commands.include?("=")
C_COMMAND
elsif current_commands.include?(";")
C_COMMAND
else
L_COMMAND
end
end
def symbol
if command_type == A_COMMAND
current_commands.delete("@")
else
current_commands
.delete("(")
.delete(")")
end
end
def dest
if !current_commands.include?("=")
"null"
elsif current_commands =~ /^(\w+)=/
$1.strip
end
end
def comp
if current_commands.include?("=") && !current_commands.include?(";")
split_commands = current_commands.split("=")
split_commands[1]
elsif !current_commands.include?("=") && current_commands.include?(";")
split_commands = current_commands.split(";")
split_commands[0]
end
end
def jump
if !current_commands.include?(";")
"null"
else
split_commands = current_commands.split(";")
split_commands[1]
end
end
end
class Code
def initialize
# dest => d1 d2 d3
@destinations = {
"null" => "000",
"M" => "001",
"D" => "010",
"MD" => "011",
"A" => "100",
"AM" => "101",
"AD" => "110",
"AMD" => "111"
}
# operations
# command => a c1 c2 c3 c4 c5 c6
@operations = {
"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"
}
# jump => j1 j2 j3
@jumps = {
"null" => "000",
"JGT" => "001",
"JEQ" => "010",
"JGE" => "011",
"JLT" => "100",
"JNE" => "101",
"JLE" => "110",
"JMP" => "111"
}
@symbol_table = {
"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,
"SP" => 0,
"LCL" => 1,
"ARG" => 2,
"THIS" => 3,
"THAT" => 4,
"SCREEN" => 16384,
"KBD" => 24576
}
end
def symbol(str)
@symbol_table[str]
end
def contain_symbol?(str)
@symbol_table.key?(str)
end
def dest(str)
@destinations[str]
end
def comp(str)
@operations[str]
end
def jump(str)
@jumps[str]
end
end
class SymbolTable
def initialize
@table = Hash.new
end
def add_entry(symbol, address)
@table[symbol] = address
end
def contains?(symbol)
@table.key?(symbol)
end
def get_address(symbol)
@table[symbol]
end
end
###
# main programm
if __FILE__ == $PROGRAM_NAME
# infile_path = "add/Add.asm"
# outfile_path = "add/Add.hack"
# infile_path = "max/MaxL.asm"
# infile_path = "max/Max.asm"
# outfile_path = "max/Max.hack"
# infile_path = "rect/RectL.asm"
# infile_path = "rect/Rect.asm"
# outfile_path = "rect/Rect.hack"
# infile_path = "pong/PongL.asm"
infile_path = "pong/Pong.asm"
outfile_path = "pong/Pong.hack"
# first path
parser = Parser.new(infile_path)
table = SymbolTable.new
current_address = 0
while parser.more_commands?
case parser.command_type
when CommandType::A_COMMAND, CommandType::C_COMMAND
current_address += 1
when CommandType::L_COMMAND
symbol = parser.symbol
address = current_address
table.add_entry(symbol, address)
end
parser.advance
end
parser = nil
# second path
parser = Parser.new(infile_path)
code = Code.new
outfile = File.open(outfile_path,"w")
raw_address = 16
while parser.more_commands?
case parser.command_type
when CommandType::A_COMMAND
symbol = parser.symbol
symbol_i = symbol.to_i
if symbol_i == 0 && symbol != "0"
if code.contain_symbol?(symbol)
symbol_i = code.symbol(symbol)
elsif table.contains?(symbol)
symbol_i = table.get_address(symbol)
else
symbol_i = raw_address
table.add_entry(symbol, raw_address)
raw_address += 1
end
end
outfile.puts sprintf("%016b", symbol_i)
when CommandType::C_COMMAND
dest = parser.dest
comp = parser.comp
jump = parser.jump
# 111accccccdddjjj
outfile.puts ("111" + code.comp(comp) + code.dest(dest) + code.jump(jump))
end
parser.advance
end
# teardown
outfile.close
end
# coding: utf-8
require 'minitest/test'
require './assembler'
MiniTest::Unit.autorun
class TestParserAdd < MiniTest::Unit::TestCase
def setup
@parser = Parser.new("add/Add.asm")
end
def teardown
@parser = nil
end
def test_more_commands
assert_equal @parser.more_commands?, true
end
def test_more_commands2
@parser.current_line = 6
assert_equal @parser.more_commands?, false
end
def test_advance
@parser.advance
assert_equal 1, @parser.current_line
end
def test_commandTypeA
assert_equal 1, @parser.command_type
end
def test_commandTypeC
@parser.advance
assert_equal 2, @parser.command_type
end
def test_symbol
assert_equal "2", @parser.symbol
end
def test_dest
@parser.advance
assert_equal "D", @parser.dest
end
def test_comp
@parser.advance
assert_equal "A", @parser.comp
end
def test_comp2
@parser.advance
@parser.advance
@parser.advance
assert_equal "D+A", @parser.comp
end
end
class TestParserMaxL < MiniTest::Unit::TestCase
def setup
@parser = Parser.new("max/MaxL.asm")
end
def test_commandTypeC
@parser.advance
assert_equal 2, @parser.command_type
end
def test_commandTypeC
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
assert_equal 2, @parser.command_type
end
def test_comp
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
assert_equal "D", @parser.comp
end
def test_jump
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
assert_equal "JGT", @parser.jump
end
def test_jump2
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
assert_equal "JMP", @parser.jump
end
def test_symbol
@parser.advance
@parser.advance
assert_equal "1", @parser.symbol
end
end
class TestParserRectL < MiniTest::Unit::TestCase
def setup
@parser = Parser.new("rect/RectL.asm")
end
def test_comp
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
@parser.advance
assert_equal "-1", @parser.comp
end
end
class TestCode < MiniTest::Unit::TestCase
def setup
@code = Code.new
end
def test_dest
assert_equal "010", @code.dest("D")
end
# def test_comp
# assert_equal "0110000", @code.comp("A")
# end
end
class TestSymvbolTable < MiniTest::Unit::TestCase
def setup
@table = SymbolTable.new
end
def test_contain
@table.add_entry("i", 1)
assert_equal @table.contains?("i"), true
assert_equal @table.get_address("i"), 1
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment