Last active
August 29, 2015 14:21
-
-
Save tsu-nera/a3298e614d4d358a740a to your computer and use it in GitHub Desktop.
Nand to tetris assembler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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