-
Notifications
You must be signed in to change notification settings - Fork 2
/
hack-assembler.js
170 lines (143 loc) · 4.67 KB
/
hack-assembler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
const readline = require('readline');
const fs = require('fs');
const file = process.argv[2];
const lookup_tables = require('./lookup-tables');
const {
C_BINARY,
C_A_BINARY,
DESTINATION_BINARY,
JUMP_BINARY,
SYMBOL_MEM_LOC
} = lookup_tables;
const outfile_name = file
.split('/')
.find((file) => file.includes('.asm'))
.replace(/.asm/, '.hack');
out_file = fs.openSync(outfile_name, 'w');
const parse_a_instruction = init_a_parser(16);
const parse_c_instruction = init_c_parser();
const init = init_read_file();
init();
function init_read_file () {
const rl = readline.createInterface({
input: fs.createReadStream(file),
output: process.stdout,
terminal: false
});
const all_asm = [];
let line_count = 0;
return function read_file() {
rl.on('line', (asm_line) => {
const asm_trimmed = strip_inline_comment(asm_line.trim());
const ignore_line = should_ignore_line(asm_trimmed);
if (ignore_line) return;
const is_label = asm_trimmed.startsWith('(');
if (is_label) {
let symbol = asm_trimmed.slice(1, -1);
SYMBOL_MEM_LOC[symbol] = line_count;
return;
}
line_count += 1;
all_asm.push(asm_trimmed);
}).on('close', () => {
write_file(all_asm);
});
}
}
async function write_file(all_asm) {
for (const asm_line of all_asm) {
const parsed_asm = assemble(asm_line);
try {
await write_line(parsed_asm);
} catch(e) {
console.log(`error writing parsed asm: ${e}`);
}
}
}
function write_line(parsed_asm) {
return new Promise((resolve, reject) => {
fs.write(out_file, `${parsed_asm}\n`, (err) => {
if (err) {
reject(err);
}
resolve();
});
});
}
function assemble(asm_line) {
let hack;
if (asm_line.startsWith('@')) hack = parse_a_instruction(asm_line);
else hack = parse_c_instruction(asm_line);
return hack;
};
function init_a_parser(var_address_start) {
let variable_address = var_address_start;
const OP_CODE = '0';
return function parse_a_instruction(asm_line) {
// can be variable e.g. @foo or number e.g. @34
let a_instruction = asm_line.split('@')[1];
let num_binary;
let num_to_binary;
const a_num = Number(a_instruction);
const a_is_number = !Number.isNaN(a_num);
if (a_is_number) {
num_to_binary = a_num;
} else {
let address;
let is_new_symbol = SYMBOL_MEM_LOC[a_instruction] || SYMBOL_MEM_LOC[a_instruction] === 0;
if (is_new_symbol) {
address = SYMBOL_MEM_LOC[a_instruction];
} else {
address = variable_address;
SYMBOL_MEM_LOC[a_instruction] = address;
variable_address += 1;
}
num_to_binary = address;
}
num_binary = num_to_binary.toString(2).padStart(15, '0');
return `${OP_CODE}${num_binary}`
}
}
function init_c_parser() {
const OP_CODE = '111';
return function parse_c_instruction(asm_line) {
const is_assignment = asm_line.includes('=');
const is_jump = asm_line.includes(';');
let jump_bits = '000';
let dest_bits = '000';
let a = 0;
let alu_bits;
if (is_assignment) {
let is_m = asm_line.split('=')[1].includes('M');
if (!is_m) alu_bits = binary_lookup({ asm_line, table: C_BINARY, sign: '=' });
if (is_m) {
a = 1;
alu_bits = binary_lookup({ asm_line, table: C_A_BINARY, sign: '='});
}
dest_bits = binary_lookup({ asm_line, table: DESTINATION_BINARY, sign: '=', pos: 0 });
}
if (is_jump) {
alu_bits = binary_lookup({ asm_line, table: C_BINARY, sign: ';', pos: 0 });
jump_bits = binary_lookup({ asm_line, table: JUMP_BINARY, sign: ';' });
}
return `${OP_CODE}${a}${alu_bits}${dest_bits}${jump_bits}`;
}
}
function binary_lookup({ asm_line, table, sign, pos = 1 }) {
let code = asm_line.split(sign)[pos];
return table[code];
}
function strip_inline_comment(asm_line) {
const has_inline_comment = asm_line.includes('//');
if (has_inline_comment) {
return asm_line.split('//')[0].trim();
}
return asm_line;
}
function should_ignore_line(asm_line) {
return is_comment(asm_line) || asm_line === '';
}
function is_comment(asm_line) {
const comment_re = /^\/\//;
return comment_re.test(asm_line);
}