Skip to content

Commit a103c84

Browse files
committed
chore: refactors assembler
1 parent 3c882cc commit a103c84

File tree

5 files changed

+293
-142
lines changed

5 files changed

+293
-142
lines changed

assembler/assembler.test.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { assertEquals } from "jsr:@std/assert";
2+
import Assembler from "./assembler.ts";
3+
4+
// Helper: Convert string to ReadableStream<string>
5+
function stringToStream(str: string): ReadableStream<string> {
6+
return new ReadableStream({
7+
start(controller) {
8+
str.split("\n").forEach((line) => controller.enqueue(line));
9+
controller.close();
10+
},
11+
});
12+
}
13+
14+
// Helper: Collect output from TransformStream
15+
async function collectStream(stream: TransformStream): Promise<string> {
16+
const reader = stream.readable.getReader();
17+
let result = "";
18+
while (true) {
19+
const { value, done } = await reader.read();
20+
if (done) break;
21+
result += new TextDecoder().decode(value);
22+
}
23+
return result;
24+
}
25+
26+
// Sample Mult.asm (simple version)
27+
const MULT_ASM = `
28+
@R1
29+
D=M
30+
@diff
31+
M=D
32+
@product
33+
M=0
34+
(LOOP)
35+
@diff
36+
D=M
37+
@STOP
38+
D;JLE
39+
@diff
40+
M=D-1
41+
@product
42+
D=M
43+
@R0
44+
D=D+M
45+
@product
46+
M=D
47+
@LOOP
48+
0;JMP
49+
(STOP)
50+
@product
51+
D=M
52+
@R2
53+
M=D
54+
(END)
55+
@END
56+
0;JMP
57+
`.trim();
58+
59+
const MULT_HACK = `
60+
0000000000000001
61+
1111110000010000
62+
0000000000010000
63+
1110001100001000
64+
0000000000010001
65+
1110101010001000
66+
0000000000010000
67+
1111110000010000
68+
0000000000010100
69+
1110001100000110
70+
0000000000010000
71+
1110001110001000
72+
0000000000010001
73+
1111110000010000
74+
0000000000000000
75+
1111000010010000
76+
0000000000010001
77+
1110001100001000
78+
0000000000000110
79+
1110101010000111
80+
0000000000010001
81+
1111110000010000
82+
0000000000000010
83+
1110001100001000
84+
0000000000011000
85+
1110101010000111
86+
`.trim() + "\n";
87+
88+
Deno.test("Assembler produces correct output for Mult.asm", async () => {
89+
const inputStream = stringToStream(MULT_ASM);
90+
const assembler = new Assembler(inputStream);
91+
92+
await assembler.init();
93+
94+
const output = await collectStream(assembler.output);
95+
96+
assertEquals(output, MULT_HACK);
97+
});
98+
99+
// Additional tests (optional)
100+
Deno.test("Assembler handles empty input", async () => {
101+
const inputStream = stringToStream("");
102+
const assembler = new Assembler(inputStream);
103+
104+
await assembler.init();
105+
106+
const output = await collectStream(assembler.output);
107+
108+
assertEquals(output, "");
109+
});
110+
111+
Deno.test("Assembler handles only labels", async () => {
112+
const inputStream = stringToStream("(LOOP)\n(END)");
113+
const assembler = new Assembler(inputStream);
114+
115+
await assembler.init();
116+
117+
const output = await collectStream(assembler.output);
118+
119+
assertEquals(output, "");
120+
});

assembler/assembler.ts

Lines changed: 67 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,82 @@
11
import Parser from "./parse/parse.ts";
22
import Code from "./code/code.ts";
33
import SymbolTable from "./symbol_table/symbol_table.ts";
4-
import * as path from "jsr:@std/path";
5-
6-
const prog = Deno.args[0];
7-
if (!prog) Deno.exit(1);
8-
9-
const file = await Deno.open(
10-
`${import.meta.dirname}/${prog.replace("asm", "hack")}`,
11-
{
12-
write: true,
13-
create: true,
14-
},
15-
);
164

175
const symbolTable = new SymbolTable();
186
const code = new Code();
197

20-
let parser = new Parser(path.join(import.meta.dirname!, prog));
21-
let lInstructions = 0;
22-
for (let i = 0;; i++) {
23-
await parser.advance();
24-
if (!parser.hasMoreLines) break;
25-
if (!parser.currentInstruction) continue;
8+
export default class Assembler {
9+
#firstParser: InstanceType<typeof Parser>;
10+
#secondParser: InstanceType<typeof Parser>;
11+
#input: ReadableStream<string>;
12+
output: TransformStream;
13+
14+
constructor(input: ReadableStream<string>) {
15+
this.#input = input;
2616

27-
if (parser.instructionType === "L_INSTRUCTION") {
28-
const symbol = parser.symbol;
17+
const [firstReader, secondReader] = this.#input.tee();
2918

30-
if (symbol) symbolTable.addEntry(symbol, i - lInstructions);
31-
lInstructions++;
19+
this.#firstParser = new Parser(firstReader.getReader());
20+
this.#secondParser = new Parser(secondReader.getReader());
21+
22+
this.output = new TransformStream();
3223
}
33-
}
3424

35-
const writer = file.writable.getWriter();
36-
// Second Pass
37-
38-
parser = new Parser(path.join(import.meta.dirname!, prog));
39-
while (true) {
40-
await parser.advance();
41-
if (!parser.hasMoreLines) break;
42-
if (!parser.currentInstruction) continue;
43-
44-
let output = undefined;
45-
46-
if (parser.instructionType === "A_INSTRUCTION") {
47-
if (!parser.symbol) break;
48-
49-
// Label
50-
let symbolValue;
51-
const parsed = parseInt(parser.symbol);
52-
if (Number.isNaN(parsed)) {
53-
if (symbolTable.contains(parser.symbol)) {
54-
symbolValue = symbolTable.getAddress(parser.symbol);
55-
} else {
56-
symbolTable.addEntry(parser.symbol);
57-
symbolValue = symbolTable.getAddress(parser.symbol);
58-
}
59-
} else {
60-
symbolValue = parsed;
61-
}
25+
async init() {
26+
await this.#firstPass();
6227

63-
output = symbolValue.toString(2).padStart(16, "0");
64-
} else if (parser.instructionType === "C_INSTRUCTION") {
65-
const comp = code.comp(parser.comp);
66-
const dest = code.dest(parser.dest);
67-
const jump = code.jump(parser.jump);
68-
output = `111${comp}${dest}${jump}`;
28+
await this.#secondPass(this.output.writable.getWriter());
6929
}
7030

71-
if (output) writer.write(new TextEncoder().encode(output + "\n"));
31+
async #firstPass() {
32+
let lInstructions = 0;
33+
for (let i = 0;; i++) {
34+
await this.#firstParser.advance();
35+
if (!this.#firstParser.hasMoreLines) break;
36+
if (this.#firstParser.instructionType !== "L_INSTRUCTION") continue;
37+
const symbol = this.#firstParser.symbol;
38+
39+
if (symbol) symbolTable.addEntry(symbol, i - lInstructions);
40+
lInstructions++;
41+
}
42+
}
43+
44+
async #secondPass(streamWriter: WritableStreamDefaultWriter) {
45+
while (true) {
46+
await this.#secondParser.advance();
47+
if (!this.#secondParser.hasMoreLines) break;
48+
if (!this.#secondParser.currentInstruction) continue;
49+
50+
let output = undefined;
51+
52+
if (this.#secondParser.instructionType === "A_INSTRUCTION") {
53+
if (!this.#secondParser.symbol) break;
54+
55+
// Label
56+
let symbolValue;
57+
const parsed = parseInt(this.#secondParser.symbol);
58+
if (Number.isNaN(parsed)) {
59+
if (symbolTable.contains(this.#secondParser.symbol)) {
60+
symbolValue = symbolTable.getAddress(this.#secondParser.symbol);
61+
} else {
62+
symbolTable.addEntry(this.#secondParser.symbol);
63+
symbolValue = symbolTable.getAddress(this.#secondParser.symbol);
64+
}
65+
} else {
66+
symbolValue = parsed;
67+
}
68+
69+
output = symbolValue.toString(2).padStart(16, "0");
70+
} else if (this.#secondParser.instructionType === "C_INSTRUCTION") {
71+
const comp = code.comp(this.#secondParser.comp);
72+
const dest = code.dest(this.#secondParser.dest);
73+
const jump = code.jump(this.#secondParser.jump);
74+
output = `111${comp}${dest}${jump}`;
75+
}
76+
77+
if (output) console.log(output);
78+
if (output) streamWriter.write(new TextEncoder().encode(output + "\n"));
79+
}
80+
streamWriter.close();
81+
}
7282
}

0 commit comments

Comments
 (0)