hack_assembler 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +9 -1
- data/{hack2asm.gemspec → hack_assembler.gemspec} +0 -0
- data/lib/hack_assembler.rb +6 -1
- data/lib/hack_assembler/assembler.rb +60 -4
- data/lib/hack_assembler/symbol_table.rb +48 -0
- data/lib/hack_assembler/version.rb +1 -1
- data/test/assembler_test.rb +72 -27
- data/test/symbol_table_test.rb +146 -0
- metadata +6 -6
- data/Pong.asm +0 -28374
- data/PongL.asm +0 -27490
- data/out.hack +0 -27483
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16849ad49ea5724294483935e3bd416de48576fa
|
4
|
+
data.tar.gz: eb36db703661f2acc60e2c8e7781581e6998b4fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92a36fcda84358674b2c0699bf548639604312bf7a874da237d3fb7203cb4c5c71dbd04e1870915c226861cb61349bfcc0744b9f485eb0b73ef71ddcab4a9e61
|
7
|
+
data.tar.gz: 93822f4774998d3d4ba30ed8b0aa7df089ebedd4573f7a988845d96f2ede6492615e695aac88d6f3b3f30bd490f2e937781f9ebfc5d7fe737e82a288859f191f
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[](https://travis-ci.org/Fuffi/hack-assembler)
|
2
2
|
|
3
3
|
# Hack Assembler
|
4
4
|
|
@@ -21,8 +21,16 @@ Or install it yourself as:
|
|
21
21
|
## Usage
|
22
22
|
|
23
23
|
After installing the Gem you can run this command from your terminal:
|
24
|
+
|
24
25
|
hack_assembler <input-file.asm> <output-file.hack>
|
25
26
|
|
27
|
+
## TO DOs
|
28
|
+
|
29
|
+
* Refactor code and tests
|
30
|
+
* Add better error reporting
|
31
|
+
* Make sure any valid symbol name is recognized
|
32
|
+
|
33
|
+
|
26
34
|
## Contributing
|
27
35
|
|
28
36
|
1. Fork it
|
File without changes
|
data/lib/hack_assembler.rb
CHANGED
@@ -6,13 +6,18 @@ require_relative 'hack_assembler/destination_translator'
|
|
6
6
|
require_relative 'hack_assembler/jump_translator'
|
7
7
|
require_relative 'hack_assembler/c_instruction'
|
8
8
|
require_relative 'hack_assembler/assembler'
|
9
|
+
require_relative 'hack_assembler/symbol_table'
|
9
10
|
|
10
11
|
module HackAssembler
|
11
12
|
def self.translate_file(input_file, output_file)
|
12
13
|
source_file = File.open(input_file)
|
13
14
|
source_code = source_file.read
|
14
15
|
|
15
|
-
|
16
|
+
symbol_table = SymbolTable.new
|
17
|
+
|
18
|
+
label_less_code = Assembler.scan_and_remove_labels(source_code, symbol_table)
|
19
|
+
processed_source = Assembler.process_symbols(label_less_code, symbol_table)
|
20
|
+
machine_code = Assembler.translate(processed_source)
|
16
21
|
|
17
22
|
File.open(output_file, 'w') { |file| file.write(machine_code); file.close }
|
18
23
|
|
@@ -1,13 +1,56 @@
|
|
1
1
|
module HackAssembler
|
2
2
|
module Assembler
|
3
|
+
def self.scan_and_remove_labels(source_code, symbol_table)
|
4
|
+
machine_code_line_number = 0
|
5
|
+
label_less_code = ''
|
6
|
+
source_code.each_line do |line|
|
7
|
+
if is_empty_line?(line) || is_comment_line?(line)
|
8
|
+
label_less_code << line
|
9
|
+
next
|
10
|
+
end
|
11
|
+
|
12
|
+
clean_line = line.strip
|
13
|
+
|
14
|
+
match = /\((.*)\)/.match(clean_line)
|
15
|
+
if match
|
16
|
+
label = match[1]
|
17
|
+
|
18
|
+
symbol_table.add_label_address(label, machine_code_line_number)
|
19
|
+
label_less_code << "\n"
|
20
|
+
else
|
21
|
+
machine_code_line_number += 1
|
22
|
+
label_less_code << line
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
label_less_code
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.process_symbols(source_code, symbol_table)
|
30
|
+
processed_code = ''
|
31
|
+
source_code.each_line do |line|
|
32
|
+
clean_line = line.strip
|
33
|
+
|
34
|
+
match = /@([^0-9].*)/.match(clean_line)
|
35
|
+
if match
|
36
|
+
symbol = match[1]
|
37
|
+
|
38
|
+
address = symbol_table.get_address(symbol)
|
39
|
+
line = "@#{address}\n"
|
40
|
+
end
|
41
|
+
|
42
|
+
processed_code << line
|
43
|
+
end
|
44
|
+
|
45
|
+
processed_code
|
46
|
+
end
|
47
|
+
|
3
48
|
def self.translate(source_code)
|
4
49
|
machine_code = ''
|
5
50
|
|
6
|
-
|
51
|
+
source_code.each_line do |line|
|
52
|
+
next if is_empty_line?(line) || is_comment_line?(line)
|
7
53
|
|
8
|
-
source_code_extract.each_line do |line|
|
9
|
-
next if line.start_with? '//'
|
10
|
-
|
11
54
|
clean_line = line.strip
|
12
55
|
|
13
56
|
instruction = clean_line.start_with?('@') ? AInstruction : CInstruction
|
@@ -17,5 +60,18 @@ module HackAssembler
|
|
17
60
|
|
18
61
|
machine_code
|
19
62
|
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def self.is_empty_line?(line)
|
66
|
+
line =~ /^[\s]*$\n/
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.is_comment_line?(line)
|
70
|
+
line.start_with? '//'
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.is_label_line?(line)
|
74
|
+
line =~ /\(.*\)/
|
75
|
+
end
|
20
76
|
end
|
21
77
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module HackAssembler
|
2
|
+
class SymbolTable
|
3
|
+
def initialize
|
4
|
+
@num_variable_symbols = 0
|
5
|
+
|
6
|
+
@symbol_table = {
|
7
|
+
'R0' => '0',
|
8
|
+
'R1' => '1',
|
9
|
+
'R2' => '2',
|
10
|
+
'R3' => '3',
|
11
|
+
'R4' => '4',
|
12
|
+
'R5' => '5',
|
13
|
+
'R6' => '6',
|
14
|
+
'R7' => '7',
|
15
|
+
'R8' => '8',
|
16
|
+
'R9' => '9',
|
17
|
+
'R10' => '10',
|
18
|
+
'R11' => '11',
|
19
|
+
'R12' => '12',
|
20
|
+
'R13' => '13',
|
21
|
+
'R14' => '14',
|
22
|
+
'R15' => '15',
|
23
|
+
'SCREEN' => '16384',
|
24
|
+
'KBD' => '24576',
|
25
|
+
'SP' => '0',
|
26
|
+
'LCL' => '1',
|
27
|
+
'ARG' => '2',
|
28
|
+
'THIS' => '3',
|
29
|
+
'THAT' => '4'
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_address(symbol)
|
34
|
+
address = @symbol_table[symbol]
|
35
|
+
if address.nil?
|
36
|
+
address = (16 + @num_variable_symbols).to_s
|
37
|
+
@symbol_table[symbol] = address
|
38
|
+
@num_variable_symbols += 1
|
39
|
+
end
|
40
|
+
|
41
|
+
address
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_label_address(symbol, address)
|
45
|
+
@symbol_table[symbol] = address.to_s
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/test/assembler_test.rb
CHANGED
@@ -1,27 +1,72 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class AssemblerTest < Minitest::Test
|
4
|
-
def test_translates_a_and_c_instructions
|
5
|
-
source_code = "@4\n" << "0;JMP\n"
|
6
|
-
bytecode = Assembler.translate(source_code)
|
7
|
-
assert_equal "0000000000000100\n" << "1110101010000111\n", bytecode
|
8
|
-
end
|
9
|
-
|
10
|
-
def test_ignores_comments
|
11
|
-
source_code = "// A comment\n" << "@4\n" << "// Another comment\n"
|
12
|
-
bytecode = Assembler.translate(source_code)
|
13
|
-
assert_equal "0000000000000100\n", bytecode
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_ignores_empty_lines
|
17
|
-
source_code = "\n@4\n\n"
|
18
|
-
bytecode = Assembler.translate(source_code)
|
19
|
-
assert_equal "0000000000000100\n", bytecode
|
20
|
-
end
|
21
|
-
|
22
|
-
def test_ignores_indentation_and_trailing_spaces
|
23
|
-
source_code = " @4 \n"
|
24
|
-
bytecode = Assembler.translate(source_code)
|
25
|
-
assert_equal "0000000000000100\n", bytecode
|
26
|
-
end
|
27
|
-
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class AssemblerTest < Minitest::Test
|
4
|
+
def test_translates_a_and_c_instructions
|
5
|
+
source_code = "@4\n" << "0;JMP\n"
|
6
|
+
bytecode = Assembler.translate(source_code)
|
7
|
+
assert_equal "0000000000000100\n" << "1110101010000111\n", bytecode
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_ignores_comments
|
11
|
+
source_code = "// A comment\n" << "@4\n" << "// Another comment\n"
|
12
|
+
bytecode = Assembler.translate(source_code)
|
13
|
+
assert_equal "0000000000000100\n", bytecode
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_ignores_empty_lines
|
17
|
+
source_code = "\n@4\n\n"
|
18
|
+
bytecode = Assembler.translate(source_code)
|
19
|
+
assert_equal "0000000000000100\n", bytecode
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_ignores_indentation_and_trailing_spaces
|
23
|
+
source_code = " @4 \n"
|
24
|
+
bytecode = Assembler.translate(source_code)
|
25
|
+
assert_equal "0000000000000100\n", bytecode
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_that_scan_and_remove_labels_adds_labels_to_symbol_table
|
29
|
+
symbol_table = SymbolTable.new
|
30
|
+
|
31
|
+
source_code = "(LOOP)\n" << "@LOOP\n" << " \n" << "(END_LOOP)\n" << "@END_LOOP\n"
|
32
|
+
Assembler.scan_and_remove_labels(source_code, symbol_table)
|
33
|
+
|
34
|
+
loop_address = symbol_table.get_address('LOOP')
|
35
|
+
end_address = symbol_table.get_address('END_LOOP')
|
36
|
+
|
37
|
+
assert_equal '0', loop_address
|
38
|
+
assert_equal '1', end_address
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_that_it_processes_built_in_symbols
|
42
|
+
symbol_table = SymbolTable.new
|
43
|
+
|
44
|
+
source_code = "@R0\n" << "@R15\n"
|
45
|
+
processed_code = Assembler.process_symbols(source_code, symbol_table)
|
46
|
+
|
47
|
+
assert_equal "@0\n" << "@15\n", processed_code
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_that_it_does_not_change_symbols
|
51
|
+
symbol_table = SymbolTable.new
|
52
|
+
|
53
|
+
source_code = "@256\n" << "@0\n"
|
54
|
+
processed_code = Assembler.process_symbols(source_code, symbol_table)
|
55
|
+
|
56
|
+
assert_equal "@256\n" << "@0\n", processed_code
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_that_it_translates_code_with_symbols_and_labels
|
60
|
+
symbol_table = SymbolTable.new
|
61
|
+
|
62
|
+
source_code = "(START_LOOP)\n" << "@START_LOOP\n" << "(END_LOOP)\n" << "@END_LOOP"
|
63
|
+
|
64
|
+
label_less_code = Assembler.scan_and_remove_labels(source_code, symbol_table)
|
65
|
+
|
66
|
+
processed_code = Assembler.process_symbols(label_less_code, symbol_table)
|
67
|
+
|
68
|
+
bytecode = Assembler.translate(processed_code)
|
69
|
+
|
70
|
+
assert_equal "0000000000000000\n" << "0000000000000001\n", bytecode
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SymbolTableTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
@symbol_table = SymbolTable.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_that_adds_one_variable_symbol
|
9
|
+
address = @symbol_table.get_address('index')
|
10
|
+
assert_equal '16', address
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_that_adds_two_variable_symbols
|
14
|
+
@symbol_table.get_address('index')
|
15
|
+
address = @symbol_table.get_address('count')
|
16
|
+
assert_equal '17', address
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_that_addresses_dont_change
|
20
|
+
@symbol_table.get_address('index')
|
21
|
+
@symbol_table.get_address('count')
|
22
|
+
address = @symbol_table.get_address('index')
|
23
|
+
assert_equal '16', address
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_add_one_label_address
|
27
|
+
@symbol_table.add_label_address('END', 32)
|
28
|
+
address = @symbol_table.get_address('END')
|
29
|
+
assert '32', address
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_reserved_symbol_r0
|
33
|
+
address = @symbol_table.get_address('R0')
|
34
|
+
assert_equal '0', address
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_reserved_symbol_r1
|
38
|
+
address = @symbol_table.get_address('R1')
|
39
|
+
assert_equal '1', address
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_reserved_symbol_r2
|
43
|
+
address = @symbol_table.get_address('R2')
|
44
|
+
assert_equal '2', address
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_reserved_symbol_r3
|
48
|
+
address = @symbol_table.get_address('R3')
|
49
|
+
assert_equal '3', address
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_reserved_symbol_r4
|
53
|
+
address = @symbol_table.get_address('R4')
|
54
|
+
assert_equal '4', address
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_reserved_symbol_r5
|
58
|
+
address = @symbol_table.get_address('R5')
|
59
|
+
assert_equal '5', address
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_reserved_symbol_r6
|
63
|
+
address = @symbol_table.get_address('R6')
|
64
|
+
assert_equal '6', address
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_reserved_symbol_r7
|
68
|
+
address = @symbol_table.get_address('R7')
|
69
|
+
assert_equal '7', address
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_reserved_symbol_r8
|
73
|
+
address = @symbol_table.get_address('R8')
|
74
|
+
assert_equal '8', address
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_reserved_symbol_r9
|
78
|
+
address = @symbol_table.get_address('R9')
|
79
|
+
assert_equal '9', address
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_reserved_symbol_r10
|
83
|
+
address = @symbol_table.get_address('R10')
|
84
|
+
assert_equal '10', address
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_reserved_symbol_r11
|
88
|
+
address = @symbol_table.get_address('R11')
|
89
|
+
assert_equal '11', address
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_reserved_symbol_r12
|
93
|
+
address = @symbol_table.get_address('R12')
|
94
|
+
assert_equal '12', address
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_reserved_symbol_r13
|
98
|
+
address = @symbol_table.get_address('R13')
|
99
|
+
assert_equal '13', address
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_reserved_symbol_r14
|
103
|
+
address = @symbol_table.get_address('R14')
|
104
|
+
assert_equal '14', address
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_reserved_symbol_r15
|
108
|
+
address = @symbol_table.get_address('R15')
|
109
|
+
assert_equal '15', address
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_reserved_symbol_screen
|
113
|
+
address = @symbol_table.get_address('SCREEN')
|
114
|
+
assert_equal '16384', address
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_reserved_symbol_kbd
|
118
|
+
address = @symbol_table.get_address('KBD')
|
119
|
+
assert_equal '24576', address
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_reserved_symbol_sp
|
123
|
+
address = @symbol_table.get_address('SP')
|
124
|
+
assert_equal '0', address
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_reserved_symbol_lcl
|
128
|
+
address = @symbol_table.get_address('LCL')
|
129
|
+
assert_equal '1', address
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_reserved_symbol_arg
|
133
|
+
address = @symbol_table.get_address('ARG')
|
134
|
+
assert_equal '2', address
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_reserved_symbol_this
|
138
|
+
address = @symbol_table.get_address('THIS')
|
139
|
+
assert_equal '3', address
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_reserved_symbol_that
|
143
|
+
address = @symbol_table.get_address('THAT')
|
144
|
+
assert_equal '4', address
|
145
|
+
end
|
146
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hack_assembler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Federico Rebora
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -64,12 +64,10 @@ files:
|
|
64
64
|
- ".travis.yml"
|
65
65
|
- Gemfile
|
66
66
|
- LICENSE.txt
|
67
|
-
- Pong.asm
|
68
|
-
- PongL.asm
|
69
67
|
- README.md
|
70
68
|
- Rakefile
|
71
69
|
- bin/hack_assembler
|
72
|
-
-
|
70
|
+
- hack_assembler.gemspec
|
73
71
|
- lib/hack_assembler.rb
|
74
72
|
- lib/hack_assembler/a_instruction.rb
|
75
73
|
- lib/hack_assembler/assembler.rb
|
@@ -78,8 +76,8 @@ files:
|
|
78
76
|
- lib/hack_assembler/destination_translator.rb
|
79
77
|
- lib/hack_assembler/jump_translator.rb
|
80
78
|
- lib/hack_assembler/parser_error.rb
|
79
|
+
- lib/hack_assembler/symbol_table.rb
|
81
80
|
- lib/hack_assembler/version.rb
|
82
|
-
- out.hack
|
83
81
|
- test/a_instruction_test.rb
|
84
82
|
- test/assembler_test.rb
|
85
83
|
- test/c_instruction_test.rb
|
@@ -87,6 +85,7 @@ files:
|
|
87
85
|
- test/destination_translator_test.rb
|
88
86
|
- test/hack2asm_test.rb
|
89
87
|
- test/jump_translator_test.rb
|
88
|
+
- test/symbol_table_test.rb
|
90
89
|
- test/test_helper.rb
|
91
90
|
homepage: ''
|
92
91
|
licenses:
|
@@ -120,4 +119,5 @@ test_files:
|
|
120
119
|
- test/destination_translator_test.rb
|
121
120
|
- test/hack2asm_test.rb
|
122
121
|
- test/jump_translator_test.rb
|
122
|
+
- test/symbol_table_test.rb
|
123
123
|
- test/test_helper.rb
|