├── 10
├── README.md
├── analyzer.js
├── compilation.js
└── tokenizer.js
├── 11
├── README.md
├── VMWriter.js
├── analyzer.js
├── compilation.js
├── symbolTable.js
└── tokenizer.js
├── 12
├── Array.jack
├── Keyboard.jack
├── Math.jack
├── Memory.jack
├── Output.jack
├── README.md
├── Screen.jack
├── String.jack
└── Sys.jack
├── 01
├── And.hdl
├── And16.hdl
├── DMux.hdl
├── DMux4Way.hdl
├── DMux8Way.hdl
├── Mux.hdl
├── Mux16.hdl
├── Mux4Way16.hdl
├── Mux8Way16.hdl
├── Not.hdl
├── Not16.hdl
├── Or.hdl
├── Or16.hdl
├── Or8Way.hdl
└── Xor.hdl
├── 02
├── ALU.hdl
├── Add16.hdl
├── FullAdder.hdl
├── HalfAdder.hdl
├── Inc16.hdl
├── IsNeg.hdl
└── Or16Way.hdl
├── 03
├── a
│ ├── Bit.hdl
│ ├── PC.hdl
│ ├── RAM64.hdl
│ ├── RAM8.hdl
│ └── Register.hdl
└── b
│ ├── RAM16K.hdl
│ ├── RAM4K.hdl
│ └── RAM512.hdl
├── 04
├── fill
│ ├── Fill.asm
│ └── Fill.hack
└── mult
│ ├── mult.asm
│ └── mult.hack
├── 05
├── CPU.hdl
├── Computer.hdl
└── Memory.hdl
├── 06
├── README.md
├── assembler.js
├── code.js
├── parser.js
└── symbol-table.js
├── 07
├── README.md
├── code-writer.js
├── parser.js
└── vm-translator.js
├── 08
├── README.md
├── code-writer.js
├── parser.js
└── vm-translator.js
├── 09
├── README.md
├── jc.cmd
└── test
│ ├── Generate.jack
│ ├── Generate.vm
│ ├── Main.jack
│ └── Main.vm
├── LICENSE
├── README.md
├── img
├── wx.jpg
└── zfb.jpg
└── nand2tetris.zip
/01/And.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * And gate:
3 | * out = 1 if (a == 1 and b == 1)
4 | * 0 otherwise
5 | * 思路: And = Nand(Nand(a, b), Nand(a, b))
6 | */
7 |
8 | CHIP And {
9 | IN a, b;
10 | OUT out;
11 |
12 | PARTS:
13 | Nand(a = a, b = b, out = o1);
14 | Nand(a = a, b = b, out = o2);
15 | Nand(a = o1, b = o2, out = out);
16 | }
17 |
--------------------------------------------------------------------------------
/01/And16.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 16-bit bitwise And:
3 | * for i = 0..15: out[i] = (a[i] and b[i])
4 | */
5 |
6 | CHIP And16 {
7 | IN a[16], b[16];
8 | OUT out[16];
9 |
10 | PARTS:
11 | And(a = a[0], b = b[0], out = out[0]);
12 | And(a = a[1], b = b[1], out = out[1]);
13 | And(a = a[2], b = b[2], out = out[2]);
14 | And(a = a[3], b = b[3], out = out[3]);
15 | And(a = a[4], b = b[4], out = out[4]);
16 | And(a = a[5], b = b[5], out = out[5]);
17 | And(a = a[6], b = b[6], out = out[6]);
18 | And(a = a[7], b = b[7], out = out[7]);
19 | And(a = a[8], b = b[8], out = out[8]);
20 | And(a = a[9], b = b[9], out = out[9]);
21 | And(a = a[10], b = b[10], out = out[10]);
22 | And(a = a[11], b = b[11], out = out[11]);
23 | And(a = a[12], b = b[12], out = out[12]);
24 | And(a = a[13], b = b[13], out = out[13]);
25 | And(a = a[14], b = b[14], out = out[14]);
26 | And(a = a[15], b = b[15], out = out[15]);
27 | }
--------------------------------------------------------------------------------
/01/DMux.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Demultiplexor:
3 | * {a, b} = {in, 0} if sel == 0
4 | * {0, in} if sel == 1
5 | * 思路: out: a = And(in, Not(sel)), b = And(in, sel)
6 | * and操作 用1 and 谁就返回谁 例如 1 and 1 = 1; 1 and 0 = 0;
7 | */
8 |
9 | CHIP DMux {
10 | IN in, sel;
11 | OUT a, b;
12 |
13 | PARTS:
14 | Not(in = sel, out = n1);
15 | And(a = in, b = n1, out = a);
16 | And(a = in, b = sel, out = b);
17 | }
18 |
--------------------------------------------------------------------------------
/01/DMux4Way.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 4-way demultiplexor:
3 | * {a, b, c, d} = {in, 0, 0, 0} if sel == 00
4 | * {0, in, 0, 0} if sel == 01
5 | * {0, 0, in, 0} if sel == 10
6 | * {0, 0, 0, in} if sel == 11
7 | * 从上述代码可以发现 ab 的第二位都是 0,cd 的第二位都是 1
8 | * 利用这一特性先对 sel 的第二位进行一个 DMux 操作
9 | * 如果第二位是 0,它只有两种可能:ab,如果是 1,则是 cd
10 | * 然后再对 sel 的第一位进行操作,对于 ab 来说,第一位为 0 则是 a,否则为 b
11 | * 对于 cd 来说,第一位为 0 则是 c,否则为 d
12 | */
13 |
14 | CHIP DMux4Way {
15 | IN in, sel[2];
16 | OUT a, b, c, d;
17 |
18 | PARTS:
19 | DMux(in = in, sel = sel[1], a = o1, b = o2);
20 |
21 | DMux(in = o1, sel = sel[0], a = a, b = b);
22 | DMux(in = o2, sel = sel[0], a = c, b = d);
23 | }
--------------------------------------------------------------------------------
/01/DMux8Way.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 8-way demultiplexor:
3 | * {a, b, c, d, e, f, g, h} = {in, 0, 0, 0, 0, 0, 0, 0} if sel == 000
4 | * {0, in, 0, 0, 0, 0, 0, 0} if sel == 001
5 | * etc.
6 | * {0, 0, 0, 0, 0, 0, 0, in} if sel == 111
7 | */
8 |
9 | CHIP DMux8Way {
10 | IN in, sel[3];
11 | OUT a, b, c, d, e, f, g, h;
12 |
13 | PARTS:
14 | DMux4Way(in = in, sel = sel[1..2], a = o1, b = o2, c = o3, d = o4);
15 | DMux(in = o1, sel = sel[0], a = a, b = b);
16 | DMux(in = o2, sel = sel[0], a = c, b = d);
17 | DMux(in = o3, sel = sel[0], a = e, b = f);
18 | DMux(in = o4, sel = sel[0], a = g, b = h);
19 | }
--------------------------------------------------------------------------------
/01/Mux.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Multiplexor:
3 | * out = a if sel == 0
4 | * b otherwise
5 | * 思路:Mux = Or(And(b, sel), And(a, Not(sel)))
6 | */
7 |
8 | CHIP Mux {
9 | IN a, b, sel;
10 | OUT out;
11 |
12 | PARTS:
13 | And(a = b, b = sel, out = o1);
14 |
15 | Not(in = sel, out = notSel);
16 | And(a = a, b = notSel, out = o2);
17 |
18 | Or(a = o1, b = o2, out = out);
19 | }
--------------------------------------------------------------------------------
/01/Mux16.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 16-bit multiplexor:
3 | * for i = 0..15 out[i] = a[i] if sel == 0
4 | * b[i] if sel == 1
5 | */
6 |
7 | CHIP Mux16 {
8 | IN a[16], b[16], sel;
9 | OUT out[16];
10 |
11 | PARTS:
12 | Mux(a = a[0], b = b[0], sel = sel, out = out[0]);
13 | Mux(a = a[1], b = b[1], sel = sel, out = out[1]);
14 | Mux(a = a[2], b = b[2], sel = sel, out = out[2]);
15 | Mux(a = a[3], b = b[3], sel = sel, out = out[3]);
16 | Mux(a = a[4], b = b[4], sel = sel, out = out[4]);
17 | Mux(a = a[5], b = b[5], sel = sel, out = out[5]);
18 | Mux(a = a[6], b = b[6], sel = sel, out = out[6]);
19 | Mux(a = a[7], b = b[7], sel = sel, out = out[7]);
20 | Mux(a = a[8], b = b[8], sel = sel, out = out[8]);
21 | Mux(a = a[9], b = b[9], sel = sel, out = out[9]);
22 | Mux(a = a[10], b = b[10], sel = sel, out = out[10]);
23 | Mux(a = a[11], b = b[11], sel = sel, out = out[11]);
24 | Mux(a = a[12], b = b[12], sel = sel, out = out[12]);
25 | Mux(a = a[13], b = b[13], sel = sel, out = out[13]);
26 | Mux(a = a[14], b = b[14], sel = sel, out = out[14]);
27 | Mux(a = a[15], b = b[15], sel = sel, out = out[15]);
28 | }
29 |
--------------------------------------------------------------------------------
/01/Mux4Way16.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 4-way 16-bit multiplexor:
3 | * out = a if sel == 00
4 | * b if sel == 01
5 | * c if sel == 10
6 | * d if sel == 11
7 | * 从上述思路可以发现 sel 的第二位决定了是 ab 还是 cd
8 | * 如果是 sel[1] 是 0 就是结果只能是 ab 中的一个
9 | * 同理,sel[1] 是 1 就是结果只能是 cd 中的一个
10 | * 所以我们可以先通过 sel[0] 做两次选择, 从 ab 中选一个,再从cd中选一个
11 | * 再根据 sel[1] 选出真正的数
12 | */
13 |
14 | CHIP Mux4Way16 {
15 | IN a[16], b[16], c[16], d[16], sel[2];
16 | OUT out[16];
17 |
18 | PARTS:
19 | Mux16(a = a, b = b, sel = sel[0], out = o1);
20 | Mux16(a = c, b = d, sel = sel[0], out = o2);
21 |
22 | Mux16(a = o1, b = o2, sel = sel[1], out = out);
23 | }
--------------------------------------------------------------------------------
/01/Mux8Way16.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 8-way 16-bit multiplexor:
3 | * out = a if sel == 000
4 | * b if sel == 001
5 | * etc.
6 | * h if sel == 111
7 | */
8 |
9 | CHIP Mux8Way16 {
10 | IN a[16], b[16], c[16], d[16],
11 | e[16], f[16], g[16], h[16],
12 | sel[3];
13 | OUT out[16];
14 |
15 | PARTS:
16 | Mux4Way16(a = a, b = b, c = c, d = d, sel = sel[0..1], out = o1);
17 | Mux4Way16(a = e, b = f, c = g, d = h, sel = sel[0..1], out = o2);
18 |
19 | Mux16(a = o1, b = o2, sel = sel[2], out = out);
20 | }
--------------------------------------------------------------------------------
/01/Not.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Not gate:
3 | * out = not in
4 | */
5 |
6 | CHIP Not {
7 | IN in;
8 | OUT out;
9 |
10 | PARTS:
11 | Nand(a = in, b = in, out = out);
12 | }
--------------------------------------------------------------------------------
/01/Not16.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 16-bit Not:
3 | * for i=0..15: out[i] = not in[i]
4 | */
5 |
6 | CHIP Not16 {
7 | IN in[16];
8 | OUT out[16];
9 |
10 | PARTS:
11 | Not(in = in[0], out = out[0]);
12 | Not(in = in[1], out = out[1]);
13 | Not(in = in[2], out = out[2]);
14 | Not(in = in[3], out = out[3]);
15 | Not(in = in[4], out = out[4]);
16 | Not(in = in[5], out = out[5]);
17 | Not(in = in[6], out = out[6]);
18 | Not(in = in[7], out = out[7]);
19 | Not(in = in[8], out = out[8]);
20 | Not(in = in[9], out = out[9]);
21 | Not(in = in[10], out = out[10]);
22 | Not(in = in[11], out = out[11]);
23 | Not(in = in[12], out = out[12]);
24 | Not(in = in[13], out = out[13]);
25 | Not(in = in[14], out = out[14]);
26 | Not(in = in[15], out = out[15]);
27 | }
--------------------------------------------------------------------------------
/01/Or.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Or gate:
3 | * out = 1 if (a == 1 or b == 1)
4 | * 0 otherwise
5 | * 思路: Or = Nand(Not(a), Not(b))
6 | */
7 |
8 | CHIP Or {
9 | IN a, b;
10 | OUT out;
11 |
12 | PARTS:
13 | Not(in = a, out = o1);
14 | Not(in = b, out = o2);
15 | Nand(a = o1, b = o2, out = out);
16 | }
17 |
--------------------------------------------------------------------------------
/01/Or16.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 16-bit bitwise Or:
3 | * for i = 0..15 out[i] = (a[i] or b[i])
4 | */
5 |
6 | CHIP Or16 {
7 | IN a[16], b[16];
8 | OUT out[16];
9 |
10 | PARTS:
11 | Or(a = a[0], b = b[0], out = out[0]);
12 | Or(a = a[1], b = b[1], out = out[1]);
13 | Or(a = a[2], b = b[2], out = out[2]);
14 | Or(a = a[3], b = b[3], out = out[3]);
15 | Or(a = a[4], b = b[4], out = out[4]);
16 | Or(a = a[5], b = b[5], out = out[5]);
17 | Or(a = a[6], b = b[6], out = out[6]);
18 | Or(a = a[7], b = b[7], out = out[7]);
19 | Or(a = a[8], b = b[8], out = out[8]);
20 | Or(a = a[9], b = b[9], out = out[9]);
21 | Or(a = a[10], b = b[10], out = out[10]);
22 | Or(a = a[11], b = b[11], out = out[11]);
23 | Or(a = a[12], b = b[12], out = out[12]);
24 | Or(a = a[13], b = b[13], out = out[13]);
25 | Or(a = a[14], b = b[14], out = out[14]);
26 | Or(a = a[15], b = b[15], out = out[15]);
27 | }
--------------------------------------------------------------------------------
/01/Or8Way.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 8-way Or:
3 | * out = (in[0] or in[1] or ... or in[7])
4 | */
5 |
6 | CHIP Or8Way {
7 | IN in[8];
8 | OUT out;
9 |
10 | PARTS:
11 | Or(a = in[0], b = in[1], out = o1);
12 | Or(a = o1, b = in[2], out = o2);
13 | Or(a = o2, b = in[3], out = o3);
14 | Or(a = o3, b = in[4], out = o4);
15 | Or(a = o4, b = in[5], out = o5);
16 | Or(a = o5, b = in[6], out = o6);
17 | Or(a = o6, b = in[7], out = out);
18 | }
--------------------------------------------------------------------------------
/01/Xor.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Exclusive-or gate:
3 | * out = not (a == b)
4 | * 思路: Xor = Or(And(a, Not(b)), And(Not(a), b))
5 | */
6 |
7 | CHIP Xor {
8 | IN a, b;
9 | OUT out;
10 |
11 | PARTS:
12 | Not(in = b, out = n0);
13 | And(a = a, b = n0, out = a0);
14 | Not(in = a, out = n1);
15 | And(a = n1, b = b, out = a1);
16 | Or(a = a0, b = a1, out = out);
17 | }
--------------------------------------------------------------------------------
/02/ALU.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * The ALU (Arithmetic Logic Unit).
3 | * Computes one of the following functions:
4 | * x+y, x-y, y-x, 0, 1, -1, x, y, -x, -y, !x, !y,
5 | * x+1, y+1, x-1, y-1, x&y, x|y on two 16-bit inputs,
6 | * according to 6 input bits denoted zx,nx,zy,ny,f,no.
7 | * In addition, the ALU computes two 1-bit outputs:
8 | * if the ALU output == 0, zr is set to 1; otherwise zr is set to 0;
9 | * if the ALU output < 0, ng is set to 1; otherwise ng is set to 0.
10 | */
11 |
12 | // Implementation: the ALU logic manipulates the x and y inputs
13 | // and operates on the resulting values, as follows:
14 | // if (zx == 1) set x = 0 // 16-bit constant
15 | // if (nx == 1) set x = !x // bitwise not
16 | // if (zy == 1) set y = 0 // 16-bit constant
17 | // if (ny == 1) set y = !y // bitwise not
18 | // if (f == 1) set out = x + y // integer 2's complement addition
19 | // if (f == 0) set out = x & y // bitwise and
20 | // if (no == 1) set out = !out // bitwise not
21 | // if (out == 0) set zr = 1
22 | // if (out < 0) set ng = 1
23 |
24 | CHIP ALU {
25 | IN
26 | x[16], y[16], // 16-bit inputs
27 | zx, // zero the x input?
28 | nx, // negate the x input?
29 | zy, // zero the y input?
30 | ny, // negate the y input?
31 | f, // compute out = x + y (if 1) or x & y (if 0)
32 | no; // negate the out output?
33 |
34 | OUT
35 | out[16], // 16-bit output
36 | zr, // 1 if (out == 0), 0 otherwise
37 | ng; // 1 if (out < 0), 0 otherwise
38 |
39 | PARTS:
40 | // zx
41 | Mux16(a = x, b = false, sel = zx, out = x1);
42 |
43 | // nx
44 | Not16(in = x1, out = x2);
45 | Mux16(a = x1, b = x2, sel = nx, out = x3);
46 |
47 | // zy
48 | Mux16(a = y, b = false, sel = zy, out = y1);
49 |
50 | // ny
51 | Not16(in = y1, out = y2);
52 | Mux16(a = y1, b = y2, sel = ny, out = y3);
53 |
54 | // f
55 | And16(a = x3, b = y3, out = f1);
56 | Add16(a = x3, b = y3, out = f2);
57 | Mux16(a = f1, b = f2, sel = f, out = o1);
58 |
59 | // no
60 | Not16(in = o1, out = o2);
61 | Mux16(a = o1, b = o2, sel = no, out = out, out = o3);
62 |
63 | // zr
64 | Or16Way(in = o3, out = zr1);
65 | Not(in = zr1, out = zr);
66 |
67 | // ng
68 | IsNeg(in = o3, out = ng);
69 | }
--------------------------------------------------------------------------------
/02/Add16.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Adds two 16-bit values.
3 | * The most significant carry bit is ignored.
4 | */
5 |
6 | CHIP Add16 {
7 | IN a[16], b[16];
8 | OUT out[16];
9 |
10 | PARTS:
11 | HalfAdder(a = a[0], b = b[0], sum = out[0], carry = c0);
12 | FullAdder(a = a[1], b = b[1], c = c0, sum = out[1], carry = c1);
13 | FullAdder(a = a[2], b = b[2], c = c1, sum = out[2], carry = c2);
14 | FullAdder(a = a[3], b = b[3], c = c2, sum = out[3], carry = c3);
15 | FullAdder(a = a[4], b = b[4], c = c3, sum = out[4], carry = c4);
16 | FullAdder(a = a[5], b = b[5], c = c4, sum = out[5], carry = c5);
17 | FullAdder(a = a[6], b = b[6], c = c5, sum = out[6], carry = c6);
18 | FullAdder(a = a[7], b = b[7], c = c6, sum = out[7], carry = c7);
19 | FullAdder(a = a[8], b = b[8], c = c7, sum = out[8], carry = c8);
20 | FullAdder(a = a[9], b = b[9], c = c8, sum = out[9], carry = c9);
21 | FullAdder(a = a[10], b = b[10], c = c9, sum = out[10], carry = c10);
22 | FullAdder(a = a[11], b = b[11], c = c10, sum = out[11], carry = c11);
23 | FullAdder(a = a[12], b = b[12], c = c11, sum = out[12], carry = c12);
24 | FullAdder(a = a[13], b = b[13], c = c12, sum = out[13], carry = c13);
25 | FullAdder(a = a[14], b = b[14], c = c13, sum = out[14], carry = c14);
26 | FullAdder(a = a[15], b = b[15], c = c14, sum = out[15], carry = c15);
27 | }
--------------------------------------------------------------------------------
/02/FullAdder.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Computes the sum of three bits.
3 | */
4 |
5 | CHIP FullAdder {
6 | IN a, b, c; // 1-bit inputs
7 | OUT sum, // Right bit of a + b + c
8 | carry; // Left bit of a + b + c
9 |
10 | PARTS:
11 | HalfAdder(a = a, b = b, sum = s1, carry = c1);
12 | HalfAdder(a = s1, b = c, sum = sum, carry = c2);
13 | Xor(a = c1, b = c2, out = carry);
14 | }
--------------------------------------------------------------------------------
/02/HalfAdder.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Computes the sum of two bits.
3 | */
4 |
5 | CHIP HalfAdder {
6 | IN a, b; // 1-bit inputs
7 | OUT sum, // Right bit of a + b
8 | carry; // Left bit of a + b
9 |
10 | PARTS:
11 | Xor(a = a, b = b, out = sum);
12 | And(a = a, b = b, out = carry);
13 | }
14 |
--------------------------------------------------------------------------------
/02/Inc16.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 16-bit incrementer:
3 | * out = in + 1 (arithmetic addition)
4 | */
5 |
6 | CHIP Inc16 {
7 | IN in[16];
8 | OUT out[16];
9 |
10 | PARTS:
11 | Add16(a = in, b[0] = true, b[1..15] = false, out = out);
12 | }
--------------------------------------------------------------------------------
/02/IsNeg.hdl:
--------------------------------------------------------------------------------
1 | CHIP IsNeg {
2 |
3 | IN in[16];
4 | OUT out;
5 |
6 | PARTS:
7 | Or(a = in[15], b = false, out = out);
8 | }
--------------------------------------------------------------------------------
/02/Or16Way.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 16-way Or:
3 | * out = (in[0] or in[1] or ... or in[15])
4 | */
5 |
6 | CHIP Or16Way {
7 | IN in[16];
8 | OUT out;
9 |
10 | PARTS:
11 | Or(a = in[0], b = in[1], out = o1);
12 | Or(a = o1, b = in[2], out = o2);
13 | Or(a = o2, b = in[3], out = o3);
14 | Or(a = o3, b = in[4], out = o4);
15 | Or(a = o4, b = in[5], out = o5);
16 | Or(a = o5, b = in[6], out = o6);
17 | Or(a = o6, b = in[7], out = o7);
18 | Or(a = o7, b = in[8], out = o8);
19 | Or(a = o8, b = in[9], out = o9);
20 | Or(a = o9, b = in[10], out = o10);
21 | Or(a = o10, b = in[11], out = o11);
22 | Or(a = o11, b = in[12], out = o12);
23 | Or(a = o12, b = in[13], out = o13);
24 | Or(a = o13, b = in[14], out = o14);
25 | Or(a = o14, b = in[15], out = out);
26 | }
--------------------------------------------------------------------------------
/03/a/Bit.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 1-bit register:
3 | * If load[t] == 1 then out[t+1] = in[t]
4 | * else out does not change (out[t+1] = out[t])
5 | * DFF 触发器的特点是当前时钟的输出等于下一时钟的输出。
6 | * 所以 a = preOut 里的 preOut 就是上一时钟的输出,out = preOut 就是把当前时钟的输出赋值给 preOut。
7 | */
8 |
9 | CHIP Bit {
10 | IN in, load;
11 | OUT out;
12 |
13 | PARTS:
14 | Mux(a = preOut, b = in, sel = load, out = o1);
15 | DFF(in = o1, out = preOut, out = out);
16 | }
17 |
--------------------------------------------------------------------------------
/03/a/PC.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * A 16-bit counter with load and reset control bits.
3 | * if (reset[t] == 1) out[t+1] = 0
4 | * else if (load[t] == 1) out[t+1] = in[t]
5 | * else if (inc[t] == 1) out[t+1] = out[t] + 1 (integer addition)
6 | * else out[t+1] = out[t]
7 | */
8 |
9 | CHIP PC {
10 | IN in[16],load,inc,reset;
11 | OUT out[16];
12 |
13 | PARTS:
14 | Mux16(a = preOut, b = addOut, sel = inc, out = o1);
15 |
16 | Mux16(a = o1, b = in, sel = load, out = o2);
17 |
18 | Mux16(a = o2, b = false, sel = reset, out = o3);
19 |
20 | Register(in = o3, load = true, out = preOut, out = out);
21 |
22 | Inc16(in = preOut, out = addOut);
23 | }
24 |
--------------------------------------------------------------------------------
/03/a/RAM64.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Memory of 64 registers, each 16 bit-wide. Out holds the value
3 | * stored at the memory location specified by address. If load==1, then
4 | * the in value is loaded into the memory location specified by address
5 | * (the loaded value will be emitted to out from the next time step onward).
6 | */
7 |
8 | CHIP RAM64 {
9 | IN in[16], load, address[6];
10 | OUT out[16];
11 |
12 | PARTS:
13 | DMux8Way(in = load, sel = address[3..5], a = loadA, b = loadB, c = loadC, d = loadD, e = loadE, f = loadF, g = loadG, h = loadH);
14 |
15 | RAM8(in = in, load = loadA, address = address[0..2], out = o1);
16 | RAM8(in = in, load = loadB, address = address[0..2], out = o2);
17 | RAM8(in = in, load = loadC, address = address[0..2], out = o3);
18 | RAM8(in = in, load = loadD, address = address[0..2], out = o4);
19 | RAM8(in = in, load = loadE, address = address[0..2], out = o5);
20 | RAM8(in = in, load = loadF, address = address[0..2], out = o6);
21 | RAM8(in = in, load = loadG, address = address[0..2], out = o7);
22 | RAM8(in = in, load = loadH, address = address[0..2], out = o8);
23 |
24 | Mux8Way16(a = o1, b = o2, c = o3, d = o4, e = o5, f = o6, g = o7, h = o8, sel = address[3..5], out = out);
25 | }
--------------------------------------------------------------------------------
/03/a/RAM8.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Memory of 8 registers, each 16 bit-wide. Out holds the value
3 | * stored at the memory location specified by address. If load==1, then
4 | * the in value is loaded into the memory location specified by address
5 | * (the loaded value will be emitted to out from the next time step onward).
6 | */
7 |
8 | CHIP RAM8 {
9 | IN in[16], load, address[3];
10 | OUT out[16];
11 |
12 | PARTS:
13 | DMux8Way(in = load, sel = address, a = loadA, b = loadB, c = loadC, d = loadD, e = loadE, f = loadF, g = loadG, h = loadH);
14 |
15 | Register(in = in, load = loadA, out = o1);
16 | Register(in = in, load = loadB, out = o2);
17 | Register(in = in, load = loadC, out = o3);
18 | Register(in = in, load = loadD, out = o4);
19 | Register(in = in, load = loadE, out = o5);
20 | Register(in = in, load = loadF, out = o6);
21 | Register(in = in, load = loadG, out = o7);
22 | Register(in = in, load = loadH, out = o8);
23 |
24 | Mux8Way16(a = o1, b = o2, c = o3, d = o4, e = o5, f = o6, g = o7, h = o8, sel = address, out = out);
25 | }
--------------------------------------------------------------------------------
/03/a/Register.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * 16-bit register:
3 | * If load[t] == 1 then out[t+1] = in[t]
4 | * else out does not change
5 | */
6 |
7 | CHIP Register {
8 | IN in[16], load;
9 | OUT out[16];
10 |
11 | PARTS:
12 | Bit(in = in[0], load = load, out = out[0]);
13 | Bit(in = in[1], load = load, out = out[1]);
14 | Bit(in = in[2], load = load, out = out[2]);
15 | Bit(in = in[3], load = load, out = out[3]);
16 | Bit(in = in[4], load = load, out = out[4]);
17 | Bit(in = in[5], load = load, out = out[5]);
18 | Bit(in = in[6], load = load, out = out[6]);
19 | Bit(in = in[7], load = load, out = out[7]);
20 | Bit(in = in[8], load = load, out = out[8]);
21 | Bit(in = in[9], load = load, out = out[9]);
22 | Bit(in = in[10], load = load, out = out[10]);
23 | Bit(in = in[11], load = load, out = out[11]);
24 | Bit(in = in[12], load = load, out = out[12]);
25 | Bit(in = in[13], load = load, out = out[13]);
26 | Bit(in = in[14], load = load, out = out[14]);
27 | Bit(in = in[15], load = load, out = out[15]);
28 | }
29 |
--------------------------------------------------------------------------------
/03/b/RAM16K.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Memory of 16K registers, each 16 bit-wide. Out holds the value
3 | * stored at the memory location specified by address. If load==1, then
4 | * the in value is loaded into the memory location specified by address
5 | * (the loaded value will be emitted to out from the next time step onward).
6 | */
7 |
8 | CHIP RAM16K {
9 | IN in[16], load, address[14];
10 | OUT out[16];
11 |
12 | PARTS:
13 | DMux4Way(in = load, sel = address[12..13], a = loadA, b = loadB, c = loadC, d = loadD);
14 |
15 | RAM4K(in = in, load = loadA, address = address[0..11], out = o1);
16 | RAM4K(in = in, load = loadB, address = address[0..11], out = o2);
17 | RAM4K(in = in, load = loadC, address = address[0..11], out = o3);
18 | RAM4K(in = in, load = loadD, address = address[0..11], out = o4);
19 |
20 | Mux4Way16(a = o1, b = o2, c = o3, d = o4, sel = address[12..13], out = out);
21 | }
--------------------------------------------------------------------------------
/03/b/RAM4K.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Memory of 4K registers, each 16 bit-wide. Out holds the value
3 | * stored at the memory location specified by address. If load==1, then
4 | * the in value is loaded into the memory location specified by address
5 | * (the loaded value will be emitted to out from the next time step onward).
6 | */
7 |
8 | CHIP RAM4K {
9 | IN in[16], load, address[12];
10 | OUT out[16];
11 |
12 | PARTS:
13 | DMux8Way(in = load, sel = address[9..11], a = loadA, b = loadB, c = loadC, d = loadD, e = loadE, f = loadF, g = loadG, h = loadH);
14 |
15 | RAM512(in = in, load = loadA, address = address[0..8], out = o1);
16 | RAM512(in = in, load = loadB, address = address[0..8], out = o2);
17 | RAM512(in = in, load = loadC, address = address[0..8], out = o3);
18 | RAM512(in = in, load = loadD, address = address[0..8], out = o4);
19 | RAM512(in = in, load = loadE, address = address[0..8], out = o5);
20 | RAM512(in = in, load = loadF, address = address[0..8], out = o6);
21 | RAM512(in = in, load = loadG, address = address[0..8], out = o7);
22 | RAM512(in = in, load = loadH, address = address[0..8], out = o8);
23 |
24 | Mux8Way16(a = o1, b = o2, c = o3, d = o4, e = o5, f = o6, g = o7, h = o8, sel = address[9..11], out = out);
25 | }
--------------------------------------------------------------------------------
/03/b/RAM512.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * Memory of 512 registers, each 16 bit-wide. Out holds the value
3 | * stored at the memory location specified by address. If load==1, then
4 | * the in value is loaded into the memory location specified by address
5 | * (the loaded value will be emitted to out from the next time step onward).
6 | */
7 |
8 | CHIP RAM512 {
9 | IN in[16], load, address[9];
10 | OUT out[16];
11 |
12 | PARTS:
13 | DMux8Way(in = load, sel = address[6..8], a = loadA, b = loadB, c = loadC, d = loadD, e = loadE, f = loadF, g = loadG, h = loadH);
14 |
15 | RAM64(in = in, load = loadA, address = address[0..5], out = o1);
16 | RAM64(in = in, load = loadB, address = address[0..5], out = o2);
17 | RAM64(in = in, load = loadC, address = address[0..5], out = o3);
18 | RAM64(in = in, load = loadD, address = address[0..5], out = o4);
19 | RAM64(in = in, load = loadE, address = address[0..5], out = o5);
20 | RAM64(in = in, load = loadF, address = address[0..5], out = o6);
21 | RAM64(in = in, load = loadG, address = address[0..5], out = o7);
22 | RAM64(in = in, load = loadH, address = address[0..5], out = o8);
23 |
24 | Mux8Way16(a = o1, b = o2, c = o3, d = o4, e = o5, f = o6, g = o7, h = o8, sel = address[6..8], out = out);
25 | }
--------------------------------------------------------------------------------
/04/fill/Fill.asm:
--------------------------------------------------------------------------------
1 | // Runs an infinite loop that listens to the keyboard input.
2 | // When a key is pressed (any key), the program blackens the screen,
3 | // i.e. writes "black" in every pixel;
4 | // the screen should remain fully black as long as the key is pressed.
5 | // When no key is pressed, the program clears the screen, i.e. writes
6 | // "white" in every pixel;
7 | // the screen should remain fully clear as long as no key is pressed.
8 |
9 | // 24576为键盘的地址 16384-24575 刚好8K 为屏幕的地址
10 | @24575
11 | D = A
12 |
13 | // R0存储屏幕最大地址
14 | @0
15 | M = D
16 |
17 | // R1存储屏幕当前地址
18 | @SCREEN
19 | D = A
20 | @1
21 | M = D
22 | (LOOP)
23 | @KBD
24 | D = M
25 | @FILL
26 | D;JGT
27 |
28 | @CLEAR
29 | 0;JMP
30 | (FILL)
31 | // 判断屏幕是否为满
32 | @0
33 | D = M
34 | @1
35 | D = D - M
36 | @LOOP
37 | D;JLT
38 |
39 | @1
40 | // 缓存当前地址
41 | D = M
42 | // 将当前地址推入A
43 | A = M
44 | // 将地址对应的屏幕位置变黑
45 | // 如果使用 1,翻译成二进制是 0000000000000001
46 | // -1 翻译成二进制是 1111111111111111
47 | // 如果表示在屏幕上,1 有 15 位是空白的,1位是黑的
48 | // -1 则屏幕上的 16 位全是黑色
49 | M = -1
50 |
51 | // 当前地址+1
52 | @1
53 | M = D + 1
54 |
55 | @LOOP
56 | 0;JMP
57 | (CLEAR)
58 | // 判断屏幕是否为空
59 | @SCREEN
60 | D = A
61 | @1
62 | D = D - M
63 | @LOOP
64 | D;JGT
65 |
66 | @1
67 | // 缓存当前地址
68 | D = M
69 | // 将当前地址推入A
70 | A = M
71 | // 将地址对应的屏幕位置变白
72 | M = 0
73 |
74 | // 当前地址-1
75 | @1
76 | M = D - 1
77 |
78 | @LOOP
79 | 0;JMP
80 |
--------------------------------------------------------------------------------
/04/fill/Fill.hack:
--------------------------------------------------------------------------------
1 | 0101111111111111
2 | 1110110000010000
3 | 0000000000000000
4 | 1110001100001000
5 | 0100000000000000
6 | 1110110000010000
7 | 0000000000000001
8 | 1110001100001000
9 | 0110000000000000
10 | 1111110000010000
11 | 0000000000001110
12 | 1110001100000001
13 | 0000000000011100
14 | 1110101010000111
15 | 0000000000000000
16 | 1111110000010000
17 | 0000000000000001
18 | 1111010011010000
19 | 0000000000001000
20 | 1110001100000100
21 | 0000000000000001
22 | 1111110000010000
23 | 1111110000100000
24 | 1110111010001000
25 | 0000000000000001
26 | 1110011111001000
27 | 0000000000001000
28 | 1110101010000111
29 | 0100000000000000
30 | 1110110000010000
31 | 0000000000000001
32 | 1111010011010000
33 | 0000000000001000
34 | 1110001100000001
35 | 0000000000000001
36 | 1111110000010000
37 | 1111110000100000
38 | 1110101010001000
39 | 0000000000000001
40 | 1110001110001000
41 | 0000000000001000
42 | 1110101010000111
43 |
--------------------------------------------------------------------------------
/04/mult/mult.asm:
--------------------------------------------------------------------------------
1 | // Multiplies R0 and R1 and stores the result in R2.
2 | // (R0, R1, R2 refer to RAM[0], RAM[1], and RAM[2], respectively.)
3 | // 3*2 换算成 2个3相加
4 | // 初始化
5 | @2
6 | M = 0
7 |
8 | // 判断是否小于0
9 | @0
10 | D = M
11 | @END
12 | D;JLT
13 | (LOOP)
14 | @1
15 | M = M - 1
16 | D = M
17 | @END
18 | D;JLT
19 |
20 | @0
21 | D = M
22 | @2
23 | M = M + D
24 |
25 | @LOOP
26 | 0;JMP
27 | (END)
28 | @END
29 | 0;JMP
--------------------------------------------------------------------------------
/04/mult/mult.hack:
--------------------------------------------------------------------------------
1 | 0000000000000010
2 | 1110101010001000
3 | 0000000000000000
4 | 1111110000010000
5 | 0000000000010001
6 | 1110001100000100
7 | 0000000000000001
8 | 1111110010001000
9 | 1111110000010000
10 | 0000000000010001
11 | 1110001100000100
12 | 0000000000000000
13 | 1111110000010000
14 | 0000000000000010
15 | 1111000010001000
16 | 0000000000000110
17 | 1110101010000111
18 | 0000000000010001
19 | 1110101010000111
20 |
--------------------------------------------------------------------------------
/05/CPU.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * The Hack CPU (Central Processing unit), consisting of an ALU,
3 | * two registers named A and D, and a program counter named PC.
4 | * The CPU is designed to fetch and execute instructions written in
5 | * the Hack machine language. In particular, functions as follows:
6 | * Executes the inputted instruction according to the Hack machine
7 | * language specification. The D and A in the language specification
8 | * refer to CPU-resident registers, while M refers to the external
9 | * memory location addressed by A, i.e. to Memory[A]. The inM input
10 | * holds the value of this location. If the current instruction needs
11 | * to write a value to M, the value is placed in outM, the address
12 | * of the target location is placed in the addressM output, and the
13 | * writeM control bit is asserted. (When writeM==0, any value may
14 | * appear in outM). The outM and writeM outputs are combinational:
15 | * they are affected instantaneously by the execution of the current
16 | * instruction. The addressM and pc outputs are clocked: although they
17 | * are affected by the execution of the current instruction, they commit
18 | * to their new values only in the next time step. If reset==1 then the
19 | * CPU jumps to address 0 (i.e. pc is set to 0 in next time step) rather
20 | * than to the address resulting from executing the current instruction.
21 | */
22 |
23 | CHIP CPU {
24 |
25 | IN inM[16], // M value input (M = contents of RAM[A])
26 | instruction[16], // Instruction for execution
27 | reset; // Signals whether to re-start the current
28 | // program (reset==1) or continue executing
29 | // the current program (reset==0).
30 |
31 | OUT outM[16], // M value output
32 | writeM, // Write to M?
33 | addressM[15], // Address in data memory (of M)
34 | pc[15]; // address of next instruction
35 |
36 | PARTS:
37 | // 指令的最大地址位 如果为1则是C指令 为0则是A指令
38 | Not(in = instruction[15], out = isA);
39 | // 对isA再次取反 判断是否C指令 读者可以自行用1或0代入想像一下 isC实际上就是instruction[15]的值
40 | Not(in = isA, out = isC);
41 |
42 | // 如果是C指令并且指令指定ALU输出存到AR 则将ALU的输出 输入到AR 否则将指令输入到AR
43 | And(a = isC, b = instruction[5], out = isLoadAluOut);
44 | Mux16(a = instruction, b = outALU, sel = isLoadAluOut, out = inAR);
45 |
46 | // 如果为A指令或指令指定输出到A处理器 则将AR的load位置1
47 | Or(a = isA, b = instruction[5], out = isLoadAR);
48 | ARegister(in = inAR, load = isLoadAR, out = outAR, out[0..14] = addressM);
49 |
50 | // 根据指令中的a位域判断将AR的输出或者inM输入到ALU
51 | Mux16(a = outAR, b = inM, sel = instruction[12], out = outAM);
52 |
53 | // 如果是C指令并且规定写入到M
54 | And(a = isC, b = instruction[3], out = writeM);
55 |
56 | // 如果是C指令并且规定写入到DR
57 | And(a = instruction[4], b = isC, out = isLoadDR);
58 | DRegister(in = outALU, load = isLoadDR, out = outDR);
59 |
60 |
61 | And(a = isC, b = instruction[6], out = no);
62 | And(a = isC, b = instruction[7], out = f);
63 | And(a = isC, b = instruction[8], out = ny);
64 | And(a = isC, b = instruction[9], out = zy);
65 | And(a = isC, b = instruction[10], out = nx);
66 | And(a = isC, b = instruction[11], out = zx);
67 |
68 | ALU(x = outDR, y = outAM, zx = zx, nx = nx, zy = zy, ny = ny, f = f, no = no, out = outALU, out = outM, zr=zr, ng=ng);
69 |
70 | // 根据j位域和ALU的zr、ng位来判断跳转
71 | And(a = isC, b = instruction[0], out = isGT);
72 | And(a = isC, b = instruction[1], out = isEQ);
73 | And(a = isC, b = instruction[2], out = isLT);
74 |
75 | And(a = ng, b = isLT, out = isLtJump);
76 | And(a = zr, b = isEQ, out = isEqJump);
77 |
78 | // 输出是否大于0
79 | Not(in = ng, out = notNg);
80 | Not(in = zr, out = notZr);
81 | And(a = notNg, b = notZr, out = isOutGt);
82 |
83 | And(a = isOutGt, b = isGT, out = isGtJump);
84 |
85 | Or(a = isLtJump, b = isEqJump, out = isJump);
86 | Or(a = isJump, b = isGtJump, out = jump);
87 |
88 | PC(in = outAR, load = jump, inc = true, reset = reset, out[0..14] = pc);
89 | }
90 |
--------------------------------------------------------------------------------
/05/Computer.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * The HACK computer, including CPU, ROM and RAM.
3 | * When reset is 0, the program stored in the computer's ROM executes.
4 | * When reset is 1, the execution of the program restarts.
5 | * Thus, to start a program's execution, reset must be pushed "up" (1)
6 | * and "down" (0). From this point onward the user is at the mercy of
7 | * the software. In particular, depending on the program's code, the
8 | * screen may show some output and the user may be able to interact
9 | * with the computer via the keyboard.
10 | */
11 |
12 | CHIP Computer {
13 |
14 | IN reset;
15 |
16 | PARTS:
17 | CPU(reset = reset, inM = outMemory, instruction = outROM, outM = outMvalue, writeM = isLoadValue, addressM = outAddressM, pc = outPC);
18 | Memory(in = outMvalue, load = isLoadValue, address = outAddressM, out = outMemory);
19 | ROM32K(address = outPC, out = outROM);
20 | }
21 |
--------------------------------------------------------------------------------
/05/Memory.hdl:
--------------------------------------------------------------------------------
1 | /**
2 | * The complete address space of the Hack computer's memory,
3 | * including RAM and memory-mapped I/O.
4 | * The chip facilitates read and write operations, as follows:
5 | * Read: out(t) = Memory[address(t)](t)
6 | * Write: if load(t-1) then Memory[address(t-1)](t) = in(t-1)
7 | * In words: the chip always outputs the value stored at the memory
8 | * location specified by address. If load==1, the in value is loaded
9 | * into the memory location specified by address. This value becomes
10 | * available through the out output from the next time step onward.
11 | * Address space rules:
12 | * Only the upper 16K+8K+1 words of the Memory chip are used.
13 | * Access to address>0x6000 is invalid. Access to any address in
14 | * the range 0x4000-0x5FFF results in accessing the screen memory
15 | * map. Access to address 0x6000 results in accessing the keyboard
16 | * memory map. The behavior in these addresses is described in the
17 | * Screen and Keyboard chip specifications given in the book.
18 | */
19 |
20 | CHIP Memory {
21 | IN in[16], load, address[15];
22 | OUT out[16];
23 |
24 | PARTS:
25 | // 根据最大的两个地址选出对应的操作 00 01都是RAM 10是屏幕 11是键盘
26 | DMux4Way(in = load, sel = address[13..14], a = loadA, b = loadB, c = loadS, d = loadK);
27 |
28 | // 00|01
29 | Or(a = loadA, b = loadB, out = loadRam);
30 |
31 | RAM16K(in = in, load = loadRam, address = address[0..13], out = outRam);
32 | Keyboard(out = outK);
33 | Screen(in = in, load = loadS, address = address[0..12], out = outS);
34 |
35 | // 根据最大的两个地址选出对应的操作 00 01都是RAM 10是屏幕 11是键盘
36 | Mux4Way16(a = outRam, b = outRam, c = outS, d = outK, sel = address[13..14], out = out);
37 | }
--------------------------------------------------------------------------------
/06/README.md:
--------------------------------------------------------------------------------
1 | ## 使用方法
2 | 需要先下载nodejs
3 | ```
4 | node assembler.js xxx.asm
5 | ```
6 | 将会生成一个与汇编文件同名的hack文件
7 |
8 | ## 注意
9 | 在 JACK 中,所有的数字最终都会转成 A 指令的形式。例如:
10 | ```js
11 | 100 + 111;
12 | ```
13 | 会转变成(VM):
14 | ```js
15 | push constant 100
16 | push constant 111
17 | add
18 | ```
19 | 再转成汇编:
20 | ```js
21 | @100
22 | @111
23 | ...
24 | ```
25 | 最后转成机器指令时,还需要将 `@100` `@111` 中的数字转成二进制数字。
26 |
27 | 执行机器指令:
28 | 1. 这两个数字会用 A 指令的形式去读取并推入栈中;
29 | 2. 然后在栈中对它们进行相加;
30 |
--------------------------------------------------------------------------------
/06/assembler.js:
--------------------------------------------------------------------------------
1 | const table = require('./symbol-table')
2 | const fs = require('fs')
3 | const parser = require('./parser')
4 |
5 | let fileName = process.argv[2]
6 |
7 | fs.readFile(fileName, 'utf-8', (err, data) => {
8 | if (err) {
9 | throw err
10 | }
11 | // 每行指令
12 | data = data.split('\r\n')
13 |
14 | // 首次解析收集符号
15 | parser([...data], true)
16 |
17 | // 真正的解析指令
18 | const binaryOut = parser(data)
19 |
20 | fileName = fileName.split('.')[0]
21 |
22 | fs.writeFile(fileName + '.hack', binaryOut, (err) => {
23 | if (err) {
24 | throw err
25 | }
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/06/code.js:
--------------------------------------------------------------------------------
1 | // jump域集合
2 | const jSet = {
3 | JGT: '001',
4 | JEQ: '010',
5 | JGE: '011',
6 | JLT: '100',
7 | JNE: '101',
8 | JLE: '110',
9 | JMP: '111',
10 | }
11 | // comp域集合
12 | const cSet = {
13 | '0': '0101010',
14 | '1': '0111111',
15 | '-1': '0111010',
16 | 'D': '0001100',
17 | 'A': '0110000',
18 | 'M': '1110000',
19 | '!D': '0001101',
20 | '!A': '0110001',
21 | '!M': '1110001',
22 | '-D': '0001111',
23 | '-A': '0110011',
24 | '-M': '1110011',
25 | 'D+1': '0011111',
26 | 'A+1': '0110111',
27 | 'M+1': '1110111',
28 | 'D-1': '0001110',
29 | 'A-1': '0110010',
30 | 'M-1': '1110010',
31 | 'D+A': '0000010',
32 | 'D+M': '1000010',
33 | 'D-A': '0010011',
34 | 'D-M': '1010011',
35 | 'A-D': '0000111',
36 | 'M-D': '1000111',
37 | 'D&A': '0000000',
38 | 'D&M': '1000000',
39 | 'D|A': '0010101',
40 | 'D|M': '1010101',
41 | }
42 |
43 | // 解析dest域
44 | function dest(instruction) {
45 | if (instruction.includes('=')) {
46 | let dest = instruction.split('=')[0].trim()
47 | const temp = ['0', '0', '0']
48 | Array.from(dest).forEach(key => {
49 | switch (key) {
50 | case 'A':
51 | temp[0] = '1'
52 | break
53 | case 'D':
54 | temp[1] = '1'
55 | break
56 | case 'M':
57 | temp[2] = '1'
58 | break
59 | }
60 | })
61 |
62 | return temp.join('')
63 | }
64 |
65 | return '000'
66 | }
67 |
68 | // 解析comp域
69 | function comp(instruction) {
70 | let comp = instruction
71 | let binary
72 | if (comp.includes('=')) {
73 | comp = comp.split('=')[1].trim()
74 | }
75 | if (comp.includes(';')) {
76 | comp = comp.split(';')[0].trim()
77 | }
78 | comp = comp.split(' ').join('')
79 | binary = cSet[comp]
80 | if (binary !== undefined) {
81 | return binary
82 | } else if (comp.includes('+') || comp.includes('|') || comp.includes('&')) {
83 | comp = comp.split('').reverse().join('')
84 | binary = cSet[comp]
85 | if (binary !== undefined) {
86 | return binary
87 | }
88 | }
89 |
90 | throw '无效指令'
91 | }
92 |
93 | // 解析jump域
94 | function jump(instruction) {
95 | if (instruction.includes(';')) {
96 | let jump = instruction.split(';')[1].trim()
97 |
98 | return jSet[jump]
99 | }
100 |
101 | return '000'
102 | }
103 |
104 | module.exports = {
105 | dest,
106 | comp,
107 | jump
108 | }
--------------------------------------------------------------------------------
/06/parser.js:
--------------------------------------------------------------------------------
1 | const table = require('./symbol-table')
2 | const {addEntry, contains, getAddress} = table
3 | let {ramAddress} = table
4 | const code = require('./code')
5 | const {dest, comp, jump} = code
6 |
7 | // 程序计数器 每遇到A或C指令+1
8 | let pc = -1
9 |
10 | function parser(instructions, isFirst) {
11 | return advance(instructions, isFirst)
12 | }
13 |
14 | function hasMoreCommands(instructions) {
15 | return instructions.length > 0? true : false
16 | }
17 |
18 | // 匹配指令中的注释
19 | const reg3 = /(\/\/).+/
20 | function advance(instructions, isFirst) {
21 | let current, type, binaryOut = ''
22 | while (hasMoreCommands(instructions)) {
23 | current = instructions.shift().trim()
24 |
25 | if (isInstructionInvalid(current)) {
26 | continue
27 | }
28 | // 如果指令右边有注释 则删除
29 | current = current.replace(reg3, '').trim()
30 | type = commandType(current)
31 |
32 | // isFirst 首次解析不会解析指令 只会收集符号 格式:(xxx)
33 | switch (type) {
34 | case 'C':
35 | if (!isFirst) {
36 | let d = dest(current)
37 | let c = comp(current)
38 | let j = jump(current)
39 | binaryOut += '111' + c + d + j + '\r\n'
40 | } else {
41 | pc++
42 | }
43 |
44 | break
45 | case 'A':
46 | if (!isFirst) {
47 | let token = symbol(current, type)
48 | let binary
49 | if (isNaN(parseInt(token))) {
50 | if (contains(token)) {
51 | binary = int2Binary(getAddress(token))
52 | } else {
53 | binary = int2Binary(ramAddress)
54 | addEntry(token, ramAddress++)
55 | }
56 | } else {
57 | binary = int2Binary(token)
58 | }
59 | binaryOut += binary + '\r\n'
60 | } else {
61 | pc++
62 | }
63 |
64 | break
65 | case 'L':
66 | if (isFirst) {
67 | let token = symbol(current, type)
68 | addEntry(token, pc + 1)
69 | }
70 | break
71 | }
72 | }
73 |
74 | return binaryOut
75 | }
76 | // 返回指令类型
77 | function commandType(instruction) {
78 | if (instruction.charAt(0) === '@') {
79 | return 'A'
80 | } else if (instruction.charAt(0) === '(') {
81 | return 'L'
82 | } else {
83 | return 'C'
84 | }
85 | }
86 | // 提取@xxx 或 (xxx) 中的xxx
87 | const reg1 = /^\((.+)\)$/
88 | function symbol(instruction, type) {
89 | if (type === 'A') {
90 | return instruction.substr(1)
91 | } else if (type === 'L') {
92 | return instruction.replace(reg1, '$1')
93 | }
94 | }
95 | // 将十进制数转为二进制指令
96 | function int2Binary(num) {
97 | let str = parseInt(num).toString(2)
98 |
99 | while (str.length !== 16) {
100 | str = '0' + str
101 | }
102 |
103 | return str
104 | }
105 | // 匹配以注释开头的句子
106 | const reg2 = /^(\/\/)/
107 | // 指令是否有效
108 | function isInstructionInvalid(instruction) {
109 | if (instruction == '' || reg2.test(instruction)) {
110 | return true
111 | }
112 |
113 | return false
114 | }
115 |
116 | module.exports = parser
--------------------------------------------------------------------------------
/06/symbol-table.js:
--------------------------------------------------------------------------------
1 | // 初始化符号表
2 | const table = {}
3 | let ramAddress = 16
4 | table.SP = 0
5 | table.LCL = 1
6 | table.ARG = 2
7 | table.THIS = 3
8 | table.THAT = 4
9 | table.SCREEN = 16384
10 | table.KBD = 24576
11 |
12 | let num = 16
13 | let key
14 | // R0 - R15
15 | while (num--) {
16 | key = `R${num}`
17 | table[key] = num
18 | }
19 |
20 | // 将符号添加到表
21 | function addEntry(symbol, address) {
22 | table[symbol] = address
23 | }
24 | // 表是否包含符号
25 | function contains(symbol) {
26 | return table[symbol] !== undefined? true : false
27 | }
28 |
29 | function getAddress(symbol) {
30 | return table[symbol]
31 | }
32 |
33 | module.exports = {
34 | table,
35 | ramAddress,
36 | addEntry,
37 | contains,
38 | getAddress,
39 | }
40 |
--------------------------------------------------------------------------------
/07/README.md:
--------------------------------------------------------------------------------
1 | 使用方法
2 | 需要先下载nodejs
3 | ```
4 | // 处理单个文件
5 | node vm-translator.js xxx.vm
6 | // 处理目录
7 | node vm-translator.js xxx
8 | ```
9 | 将会生成一个与vm文件或目录同名的asm文件
10 |
--------------------------------------------------------------------------------
/07/code-writer.js:
--------------------------------------------------------------------------------
1 | // 存放生成的symbol
2 | const symbols = []
3 |
4 | const types = ['add', 'sub', 'neg', 'eq', 'gt', 'lt', 'and', 'or', 'not']
5 | let index = 0
6 | function writeArithmetic(command) {
7 | if (types.includes(command)) {
8 | let output
9 |
10 | let output1 = '@SP\r\n'
11 | + 'AM=M-1\r\n'
12 | + 'D=M\r\n'
13 | + 'A=A-1\r\n'
14 |
15 | let output2 = '@SP\r\n'
16 | + 'AM=M-1\r\n'
17 |
18 | let output3 = '@SP\r\n'
19 | + 'M=M+1\r\n'
20 |
21 | switch (command) {
22 | case 'add':
23 | output = output1 + 'M=M+D\r\n'
24 | break
25 | case 'sub':
26 | output = output1 + 'M=M-D\r\n'
27 | break
28 | case 'neg':
29 | output = output2 + 'M=-M\r\n' + output3
30 | break
31 | case 'eq':
32 | output = createJudgementString('JEQ', index++)
33 | break
34 | case 'gt':
35 | output = createJudgementString('JGT', index++)
36 | break
37 | case 'lt':
38 | output = createJudgementString('JLT', index++)
39 | break
40 | case 'and':
41 | output = output1 + 'M=M&D\r\n'
42 | break
43 | case 'or':
44 | output = output1 + 'M=M|D\r\n'
45 | break
46 | case 'not':
47 | output = output2 + 'M=!M\r\n' + output3
48 | break
49 | }
50 |
51 | return output
52 | }
53 | }
54 |
55 | function writePushPop(command, type, fileName) {
56 | let v1 = arg1(command, type).toUpperCase()
57 | let v2 = arg2(command, type)
58 |
59 | return processSegment(v1, v2, type, fileName)
60 | }
61 |
62 |
63 | function createJudgementString(judge, index) {
64 | // 先将两个数相减 再根据给出条件 大于 等于 小于 来处理
65 | // 因为判断大小需要用到跳转 所以得随机产生两个不同的symbol作标签
66 | let str = '@SP\r\n'
67 | + 'AM=M-1\r\n'
68 | + 'D=M\r\n'
69 | + 'A=A-1\r\n'
70 | + 'D=M-D\r\n'
71 | + '@TRUE' + index + '\r\n' // 如果符合条件判断 则跳转到symbol1标记的地址
72 | + 'D;' + judge + '\r\n' // 否则接着往下处理
73 | + '@SP\r\n'
74 | + 'AM=M-1\r\n'
75 | + 'M=0\r\n' // 没跳转,说明结果为 false(0,即二进制全为 0)
76 | + '@SP\r\n'
77 | + 'M=M+1\r\n'
78 | + '@CONTINUE' + index + '\r\n'
79 | + '0;JMP\r\n'
80 | + '(TRUE' + index + ')\r\n'
81 | + '@SP\r\n'
82 | + 'AM=M-1\r\n'
83 | + 'M=-1\r\n' // 已跳转,说明结果为 true(-1,即二进制全为 1)
84 | + '@SP\r\n'
85 | + 'M=M+1\r\n'
86 | + '(CONTINUE' + index + ')\r\n'
87 | return str
88 | }
89 |
90 | function processSegment(v1, v2, type, fileName) {
91 | let output
92 | switch (v1) {
93 | case 'CONSTANT':
94 | output = '@' + v2 + '\r\n'
95 | + 'D=A\r\n'
96 | + '@SP\r\n'
97 | + 'A=M\r\n'
98 | + 'M=D\r\n'
99 | + '@SP\r\n'
100 | + 'M=M+1\r\n'
101 | break
102 | case 'STATIC':
103 | if (type == 'push') {
104 | output = '@' + fileName + '.' + v2 + '\r\n'
105 | + 'D=M\r\n'
106 | + '@SP\r\n'
107 | + 'A=M\r\n'
108 | + 'M=D\r\n'
109 | + '@SP\r\n'
110 | + 'M=M+1\r\n'
111 | } else {
112 | output = '@SP\r\n'
113 | + 'AM=M-1\r\n'
114 | + 'D=M\r\n'
115 | + '@' + fileName + '.' + v2 + '\r\n'
116 | + 'M=D\r\n'
117 | }
118 | break
119 | case 'POINTER':
120 | if (v2 == 0) {
121 | v1 = 'THIS'
122 | } else if (v2 == 1) {
123 | v1 = 'THAT'
124 | }
125 |
126 | if (type == 'push') {
127 | output = '@' + v1 + '\r\n'
128 | + 'D=M\r\n'
129 | + '@SP\r\n'
130 | + 'A=M\r\n'
131 | + 'M=D\r\n'
132 | + '@SP\r\n'
133 | + 'M=M+1\r\n'
134 | } else {
135 | output = '@' + v1 + '\r\n'
136 | + 'D=A\r\n'
137 | + '@R13\r\n'
138 | + 'M=D\r\n'
139 | + '@SP\r\n'
140 | + 'AM=M-1\r\n'
141 | + 'D=M\r\n'
142 | + '@R13\r\n'
143 | + 'A=M\r\n'
144 | + 'M=D\r\n'
145 | }
146 | break
147 | case 'TEMP':
148 | if (type == 'push') {
149 | output = pushTemplate('@R5\r\n', v2, false)
150 | } else {
151 | output = popTemplate('@R5\r\n', v2, false)
152 | }
153 |
154 | break
155 | default:
156 | let str
157 | if (v1 == 'LOCAL') {
158 | str = '@LCL\r\n'
159 | } else if (v1 == 'ARGUMENT') {
160 | str = '@ARG\r\n'
161 | } else {
162 | str = '@' + v1 + '\r\n'
163 | }
164 |
165 | if (type == 'push') {
166 | output = pushTemplate(str, v2, true)
167 | } else {
168 | output = popTemplate(str, v2, true)
169 | }
170 | }
171 |
172 | return output
173 | }
174 |
175 |
176 | function arg1(command, type) {
177 | if (type == 'arith') {
178 | return command
179 | } else {
180 | const tempArry = command.split(' ').slice(1)
181 | let arg = tempArry.shift()
182 | while (arg === '') {
183 | arg = tempArry.shift()
184 | }
185 |
186 | return arg
187 | }
188 | }
189 |
190 | let arg2Arry = ['push', 'pop', 'function', 'call']
191 |
192 | function arg2(command, type) {
193 | if (arg2Arry.includes(type)) {
194 | return command.split(' ').pop()
195 | }
196 | }
197 |
198 | function popTemplate(str, v2, flag) {
199 | let str1 = flag? 'D=M\r\n' : 'D=A\r\n'
200 | let output = str
201 | + str1
202 | + '@' + v2 + '\r\n'
203 | + 'D=D+A\r\n'
204 | + '@R13\r\n'
205 | + 'M=D\r\n'
206 | + '@SP\r\n'
207 | + 'AM=M-1\r\n'
208 | + 'D=M\r\n'
209 | + '@R13\r\n'
210 | + 'A=M\r\n'
211 | + 'M=D\r\n'
212 |
213 | return output
214 | }
215 |
216 | function pushTemplate(str, v2, flag) {
217 | let str1 = flag? 'D=M\r\n' : 'D=A\r\n'
218 | let output = str
219 | + str1
220 | + '@' + v2 + '\r\n'
221 | + 'A=D+A\r\n'
222 | + 'D=M\r\n'
223 | + '@SP\r\n'
224 | + 'A=M\r\n'
225 | + 'M=D\r\n'
226 | + '@SP\r\n'
227 | + 'M=M+1\r\n'
228 |
229 | return output
230 | }
231 |
232 | module.exports = {
233 | writePushPop,
234 | writeArithmetic
235 | }
--------------------------------------------------------------------------------
/07/parser.js:
--------------------------------------------------------------------------------
1 | const {writeArithmetic, writePushPop} = require('./code-writer')
2 |
3 | function parser(commands, fileName) {
4 | let output = ''
5 | while (hasMoreCommands(commands)) {
6 | // 逐条弹出命令 再处理
7 | let command = commands.shift().trim()
8 | // 如果命令有效
9 | if (isValidCommand(command)) {
10 | output += advance(command, fileName)
11 | }
12 | }
13 |
14 | return output
15 | }
16 |
17 | // 匹配指令中的注释
18 | const reg1 = /(\/\/).+/
19 |
20 | function advance(command, fileName) {
21 | let output
22 | command = command.replace(reg1, '').trim()
23 | let type = commandType(command)
24 |
25 | switch (type) {
26 | case 'push':
27 | case 'pop':
28 | output = writePushPop(command, type, fileName)
29 | break
30 | case 'arith':
31 | output = writeArithmetic(command)
32 | break
33 | }
34 |
35 | return output
36 | }
37 |
38 | function hasMoreCommands(commands) {
39 | return commands.length > 0? true : false
40 | }
41 |
42 | const rePush = /^(push)/
43 | const rePop = /^(pop)/
44 |
45 | function commandType(command) {
46 | if (rePush.test(command)) {
47 | return 'push'
48 | } else if (rePop.test(command)) {
49 | return 'pop'
50 | } else {
51 | return 'arith'
52 | }
53 | }
54 |
55 | // 匹配以注释开关的句子
56 | const reg2 = /^(\/\/)/
57 |
58 | function isValidCommand(command) {
59 | if (command === '' || reg2.test(command)) {
60 | return false
61 | }
62 | return true
63 | }
64 |
65 | module.exports = {
66 | parser
67 | }
--------------------------------------------------------------------------------
/07/vm-translator.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const {parser} = require('./parser')
3 |
4 | const fileName = process.argv[2]
5 | // 判断是否目录
6 | const isDirectory = fs.lstatSync(fileName).isDirectory()
7 |
8 | // 最终要输入文件的字符串
9 | let assembleOut = ''
10 | // 最终输出的文件名
11 | let outputFileName
12 |
13 |
14 | if (isDirectory) {
15 | outputFileName = fileName
16 | fs.readdir(fileName, (err, files) => {
17 | if (err) {
18 | throw err
19 | }
20 |
21 | // 循环处理目录中的文件
22 | files.forEach(file => {
23 | let tempArry = file.split('.')
24 | if (tempArry.pop() == 'vm') {
25 | let preName = tempArry.join('.')
26 | let data = fs.readFileSync(`${fileName}/${file}`, 'utf-8')
27 | processFileData(data, preName)
28 | }
29 | })
30 |
31 | setFileName()
32 | })
33 | } else {
34 | // 处理类似a.b.c这种格式的文件 保留a.b
35 | let tempArry = fileName.split('.')
36 | tempArry.pop()
37 | let preName = tempArry.join('.')
38 | outputFileName = preName
39 | let data = fs.readFileSync(fileName, 'utf-8')
40 | processFileData(data, preName)
41 |
42 | setFileName()
43 | }
44 |
45 | // 处理文件数据
46 | function processFileData(data, preName) {
47 | data = data.split('\r\n')
48 | assembleOut += parser(data, preName)
49 | }
50 |
51 | // 输出文件
52 | function setFileName() {
53 | fs.writeFile(outputFileName + '.asm', assembleOut, (err) => {
54 | if (err) {
55 | throw err
56 | }
57 | })
58 | }
--------------------------------------------------------------------------------
/08/README.md:
--------------------------------------------------------------------------------
1 | 使用方法
2 | 需要先下载nodejs
3 | ```
4 | // 处理单个文件
5 | node vm-translator.js xxx.vm
6 | // 处理目录
7 | node vm-translator.js xxx
8 | ```
9 | 将会生成一个与vm文件或目录同名的asm文件
10 |
11 | 注意换行符的不同
12 | 如果换行符为 lf 要换为 crlf
13 |
--------------------------------------------------------------------------------
/08/code-writer.js:
--------------------------------------------------------------------------------
1 | // 存放生成的symbol
2 | const symbols = []
3 |
4 | // 初始化
5 | function writeInit() {
6 | let output = '@256\r\n'
7 | + 'D=A\r\n'
8 | + '@SP\r\n'
9 | + 'M=D\r\n'
10 |
11 | output += writeCall('call Sys.init 0', 'call')
12 | return output
13 | }
14 |
15 | let index = 0
16 | function writeArithmetic(command) {
17 | let output
18 |
19 | let output1 = '@SP\r\n'
20 | + 'AM=M-1\r\n'
21 | + 'D=M\r\n'
22 | + 'A=A-1\r\n'
23 |
24 | let output2 = '@SP\r\n'
25 | + 'AM=M-1\r\n'
26 |
27 | let output3 = '@SP\r\n'
28 | + 'M=M+1\r\n'
29 |
30 | switch (command) {
31 | case 'add':
32 | output = output1 + 'M=M+D\r\n'
33 | break
34 | case 'sub':
35 | output = output1 + 'M=M-D\r\n'
36 | break
37 | case 'neg':
38 | output = output2 + 'M=-M\r\n' + output3
39 | break
40 | case 'eq':
41 | output = createJudgementString('JEQ', index++)
42 | break
43 | case 'gt':
44 | output = createJudgementString('JGT', index++)
45 | break
46 | case 'lt':
47 | output = createJudgementString('JLT', index++)
48 | break
49 | case 'and':
50 | output = output1 + 'M=M&D\r\n'
51 | break
52 | case 'or':
53 | output = output1 + 'M=M|D\r\n'
54 | break
55 | case 'not':
56 | output = output2 + 'M=!M\r\n' + output3
57 | break
58 | }
59 |
60 | return output
61 | }
62 |
63 | function writePushPop(command, type, fileName) {
64 | let v1 = arg1(command).toUpperCase()
65 | let v2 = arg2(command, type)
66 |
67 | return processSegment(v1, v2, type, fileName)
68 | }
69 |
70 | function writeLabel(command) {
71 | let label = command.split(' ').pop()
72 | let output = '(' + label + ')\r\n'
73 |
74 | return output
75 | }
76 |
77 | function writeGoto(command) {
78 | let label = command.split(' ').pop()
79 | let output = '@' + label + '\r\n'
80 | + '0;JMP\r\n'
81 |
82 | return output
83 | }
84 |
85 | function writeIf(command) {
86 | let label = command.split(' ').pop()
87 | let output = '@SP\r\n'
88 | + 'AM=M-1\r\n'
89 | + 'D=M\r\n'
90 | + '@' + label + '\r\n'
91 | + 'D;JNE\r\n'
92 |
93 | return output
94 | }
95 |
96 | let callIndex = -1
97 | const callArry = ['LCL', 'ARG', 'THIS', 'THAT']
98 | function writeCall(command, type) {
99 | callIndex++
100 | let funcName = arg1(command).toUpperCase()
101 | let argumentNum = arg2(command, type)
102 | let output = '@' + funcName + 'RETURN_ADDRESS' + callIndex + '\r\n'
103 | + 'D=A\r\n'
104 | + '@SP\r\n'
105 | + 'A=M\r\n'
106 | + 'M=D\r\n'
107 | + '@SP\r\n'
108 | + 'M=M+1\r\n'
109 |
110 | callArry.forEach(symbol => {
111 | output += '@' + symbol + '\r\n'
112 | + 'D=M\r\n'
113 | + '@SP\r\n'
114 | + 'A=M\r\n'
115 | + 'M=D\r\n'
116 | + '@SP\r\n'
117 | + 'M=M+1\r\n'
118 | })
119 |
120 | output += '@' + argumentNum + '\r\n'
121 | + 'D=A\r\n'
122 | + '@5\r\n'
123 | + 'D=D+A\r\n'
124 | + '@SP\r\n'
125 | + 'D=M-D\r\n'
126 | + '@ARG\r\n'
127 | + 'M=D\r\n'
128 | + '@SP\r\n'
129 | + 'D=M\r\n'
130 | + '@LCL\r\n'
131 | + 'M=D\r\n'
132 | + '@' + funcName + '\r\n'
133 | + '0;JMP\r\n'
134 | + '(' + funcName + 'RETURN_ADDRESS' + callIndex + ')\r\n'
135 |
136 | return output
137 | }
138 |
139 | const pointerArry = ['THAT', 'THIS', 'ARG', 'LCL']
140 | function writeReturn(command) {
141 | let str = ''
142 | pointerArry.forEach(symbol=> {
143 | str += '@R13\r\n'
144 | + 'D=M-1\r\n'
145 | + 'AM=D\r\n'
146 | + 'D=M\r\n'
147 | + '@' + symbol + '\r\n'
148 | + 'M=D\r\n'
149 | })
150 |
151 | let output = '@LCL\r\n'
152 | + 'D=M\r\n'
153 | + '@R13\r\n'
154 | + 'M=D\r\n'
155 | + '@5\r\n'
156 | + 'A=D-A\r\n'
157 | + 'D=M\r\n'
158 | + '@R14\r\n' // 保存返回地址
159 | + 'M=D\r\n'
160 | + '@SP\r\n'
161 | + 'AM=M-1\r\n'
162 | + 'D=M\r\n'
163 | + '@ARG\r\n'
164 | + 'A=M\r\n'
165 | + 'M=D\r\n'
166 | + '@ARG\r\n'
167 | + 'D=M+1\r\n'
168 | + '@SP\r\n'
169 | + 'M=D\r\n'
170 | + str
171 | + '@R14\r\n'
172 | + 'A=M\r\n'
173 | + '0;JMP\r\n'
174 |
175 | return output
176 | }
177 |
178 | function writeFunction(command, type) {
179 | let funcName = arg1(command).toUpperCase()
180 | let localNum = arg2(command, type)
181 | let output = '(' + funcName + ')\r\n'
182 |
183 | while (localNum--) {
184 | output += writePushPop('push constant 0', 'push')
185 | }
186 |
187 | return output
188 | }
189 |
190 | function createJudgementString(judge, index) {
191 | // 先将两个数相减 再根据给出条件 大于 等于 小于 来处理
192 | // 因为判断大小需要用到跳转 所以得随机产生两个不同的symbol作标签
193 | let str = '@SP\r\n'
194 | + 'AM=M-1\r\n'
195 | + 'D=M\r\n'
196 | + 'A=A-1\r\n'
197 | + 'D=M-D\r\n'
198 | + '@TRUE' + index + '\r\n' // 如果符合条件判断 则跳转到symbol1标记的地址
199 | + 'D;' + judge + '\r\n' // 否则接着往下处理
200 | + '@SP\r\n'
201 | + 'A=M-1\r\n'
202 | + 'M=0\r\n'
203 | + '@CONTINUE' + index + '\r\n'
204 | + '0;JMP\r\n'
205 | + '(TRUE' + index + ')\r\n'
206 | + '@SP\r\n'
207 | + 'A=M-1\r\n'
208 | + 'M=-1\r\n'
209 | + '(CONTINUE' + index + ')\r\n'
210 | return str
211 | }
212 |
213 | function processSegment(v1, v2, type, fileName) {
214 | let output
215 | switch (v1) {
216 | case 'CONSTANT':
217 | output = '@' + v2 + '\r\n'
218 | + 'D=A\r\n'
219 | + '@SP\r\n'
220 | + 'A=M\r\n'
221 | + 'M=D\r\n'
222 | + '@SP\r\n'
223 | + 'M=M+1\r\n'
224 | break
225 | case 'STATIC':
226 | if (type == 'push') {
227 | output = '@' + fileName + '.' + v2 + '\r\n'
228 | + 'D=M\r\n'
229 | + '@SP\r\n'
230 | + 'A=M\r\n'
231 | + 'M=D\r\n'
232 | + '@SP\r\n'
233 | + 'M=M+1\r\n'
234 | } else {
235 | output = '@SP\r\n'
236 | + 'AM=M-1\r\n'
237 | + 'D=M\r\n'
238 | + '@' + fileName + '.' + v2 + '\r\n'
239 | + 'M=D\r\n'
240 | }
241 | break
242 | case 'POINTER':
243 | if (v2 == 0) {
244 | v1 = 'THIS'
245 | } else if (v2 == 1) {
246 | v1 = 'THAT'
247 | }
248 |
249 | if (type == 'push') {
250 | output = '@' + v1 + '\r\n'
251 | + 'D=M\r\n'
252 | + '@SP\r\n'
253 | + 'A=M\r\n'
254 | + 'M=D\r\n'
255 | + '@SP\r\n'
256 | + 'M=M+1\r\n'
257 | } else {
258 | output = '@' + v1 + '\r\n'
259 | + 'D=A\r\n'
260 | + '@R13\r\n'
261 | + 'M=D\r\n'
262 | + '@SP\r\n'
263 | + 'AM=M-1\r\n'
264 | + 'D=M\r\n'
265 | + '@R13\r\n'
266 | + 'A=M\r\n'
267 | + 'M=D\r\n'
268 | }
269 | break
270 | case 'TEMP':
271 | if (type == 'push') {
272 | output = pushTemplate('@R5\r\n', v2, false)
273 | } else {
274 | output = popTemplate('@R5\r\n', v2, false)
275 | }
276 |
277 | break
278 | default:
279 | let str
280 | if (v1 == 'LOCAL') {
281 | str = '@LCL\r\n'
282 | } else if (v1 == 'ARGUMENT') {
283 | str = '@ARG\r\n'
284 | } else {
285 | str = '@' + v1 + '\r\n'
286 | }
287 |
288 | if (type == 'push') {
289 | output = pushTemplate(str, v2, true)
290 | } else {
291 | output = popTemplate(str, v2, true)
292 | }
293 | }
294 |
295 | return output
296 | }
297 |
298 |
299 | function arg1(command, type) {
300 | if (type == 'arith') {
301 | return command
302 | } else {
303 | const tempArry = command.split(' ').slice(1)
304 | let arg = tempArry.shift()
305 | while (arg === '') {
306 | arg = tempArry.shift()
307 | }
308 |
309 | return arg
310 | }
311 | }
312 |
313 | let arg2Arry = ['push', 'pop', 'function', 'call']
314 | function arg2(command, type) {
315 | if (arg2Arry.includes(type)) {
316 | return command.split(' ').pop()
317 | }
318 | }
319 |
320 |
321 | function popTemplate(str, v2, flag) {
322 | let str1 = flag? 'D=M\r\n' : 'D=A\r\n'
323 | let output = str
324 | + str1
325 | + '@' + v2 + '\r\n'
326 | + 'D=D+A\r\n'
327 | + '@R13\r\n'
328 | + 'M=D\r\n'
329 | + '@SP\r\n'
330 | + 'AM=M-1\r\n'
331 | + 'D=M\r\n'
332 | + '@R13\r\n'
333 | + 'A=M\r\n'
334 | + 'M=D\r\n'
335 |
336 | return output
337 | }
338 |
339 | function pushTemplate(str, v2, flag) {
340 | let str1 = flag? 'D=M\r\n' : 'D=A\r\n'
341 | let output = str
342 | + str1
343 | + '@' + v2 + '\r\n'
344 | + 'A=D+A\r\n'
345 | + 'D=M\r\n'
346 | + '@SP\r\n'
347 | + 'A=M\r\n'
348 | + 'M=D\r\n'
349 | + '@SP\r\n'
350 | + 'M=M+1\r\n'
351 |
352 | return output
353 | }
354 |
355 | module.exports = {
356 | writePushPop,
357 | writeArithmetic,
358 | writeLabel,
359 | writeGoto,
360 | writeIf,
361 | writeCall,
362 | writeReturn,
363 | writeFunction,
364 | writeInit,
365 | }
--------------------------------------------------------------------------------
/08/parser.js:
--------------------------------------------------------------------------------
1 | const {
2 | writePushPop,
3 | writeArithmetic,
4 | writeLabel,
5 | writeGoto,
6 | writeIf,
7 | writeCall,
8 | writeReturn,
9 | writeFunction} = require('./code-writer')
10 |
11 | function parser(commands, fileName) {
12 | let output = ''
13 | while (hasMoreCommands(commands)) {
14 | // 逐条弹出命令 再处理
15 | let command = commands.shift().trim()
16 | // 如果命令有效
17 | if (isValidCommand(command)) {
18 | output += advance(command, fileName)
19 | }
20 | }
21 |
22 | return output
23 | }
24 |
25 | // 匹配指令中的注释
26 | const reg1 = /(\/\/).+/
27 |
28 | function advance(command, fileName) {
29 | let output
30 | command = command.replace(reg1, '').trim()
31 | let type = commandType(command)
32 |
33 | switch (type) {
34 | case 'push':
35 | case 'pop':
36 | output = writePushPop(command, type, fileName)
37 | break
38 | case 'label':
39 | output = writeLabel(command, type, fileName)
40 | break
41 | case 'goto':
42 | output = writeGoto(command, type, fileName)
43 | break
44 | case 'if':
45 | output = writeIf(command, type, fileName)
46 | break
47 | case 'return':
48 | output = writeReturn(command)
49 | break
50 | case 'function':
51 | output = writeFunction(command, type)
52 | break
53 | case 'call':
54 | output = writeCall(command, type)
55 | break
56 | case 'arith':
57 | output = writeArithmetic(command)
58 | break
59 | }
60 |
61 | return output
62 | }
63 |
64 | function hasMoreCommands(commands) {
65 | return commands.length > 0? true : false
66 | }
67 |
68 | const rePush = /^(push)/
69 | const rePop = /^(pop)/
70 | const reLabel = /^(label)/
71 | const reGoto = /^(goto)/
72 | const reIfGoto = /^(if-goto)/
73 | const reReturn = /^(return)/
74 | const reFunction = /^(function)/
75 | const reCall = /^(call)/
76 |
77 | const types = ['add', 'sub', 'neg', 'eq', 'gt', 'lt', 'and', 'or', 'not']
78 | function commandType(command) {
79 | if (rePush.test(command)) {
80 | return 'push'
81 | } else if (rePop.test(command)) {
82 | return 'pop'
83 | } else if (reLabel.test(command)) {
84 | return 'label'
85 | } else if (reGoto.test(command)) {
86 | return 'goto'
87 | } else if (reIfGoto.test(command)) {
88 | return 'if'
89 | } else if (reReturn.test(command)) {
90 | return 'return'
91 | } else if (reFunction.test(command)) {
92 | return 'function'
93 | } else if (reCall.test(command)) {
94 | return 'call'
95 | } else if (types.includes(command)) {
96 | return 'arith'
97 | }
98 | }
99 |
100 | // 匹配以注释开关的句子
101 | const reg2 = /^(\/\/)/
102 |
103 | function isValidCommand(command) {
104 | if (command === '' || reg2.test(command)) {
105 | return false
106 | }
107 | return true
108 | }
109 |
110 | module.exports = {
111 | parser
112 | }
--------------------------------------------------------------------------------
/08/vm-translator.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const {parser} = require('./parser')
3 | const {writeInit} = require('./code-writer')
4 |
5 | const fileName = process.argv[2]
6 | // 判断是否目录
7 | const isDirectory = fs.lstatSync(fileName).isDirectory()
8 |
9 | // 最终要输入文件的字符串
10 | let assembleOut = ''
11 | // 最终输出的文件名
12 | let outputFileName
13 |
14 | if (isDirectory) {
15 | outputFileName = fileName
16 | fs.readdir(fileName, (err, files) => {
17 | if (err) {
18 | throw err
19 | }
20 |
21 | let index = files.indexOf('Sys.vm')
22 |
23 | if (index > -1) {
24 | assembleOut += writeInit()
25 | }
26 |
27 | // 循环处理目录中的文件
28 | files.forEach(file => {
29 | let tempArry = file.split('.')
30 | if (tempArry.pop() == 'vm') {
31 | let preName = tempArry.join('.')
32 | let data = fs.readFileSync(`${fileName}/${file}`, 'utf-8')
33 | processFileData(data, preName)
34 | }
35 | })
36 |
37 | setFileName()
38 | })
39 | } else {
40 | // 处理类似a.b.c这种格式的文件 保留a.b
41 | let tempArry = fileName.split('.')
42 | tempArry.pop()
43 | let preName = tempArry.join('.')
44 | outputFileName = preName
45 | let data = fs.readFileSync(fileName, 'utf-8')
46 | processFileData(data, preName)
47 |
48 | setFileName()
49 | }
50 |
51 | // 处理文件数据
52 | function processFileData(data, preName) {
53 | data = data.split('\r\n')
54 | assembleOut += parser(data, preName)
55 | }
56 |
57 | // 输出文件
58 | function setFileName() {
59 | fs.writeFile(outputFileName + '.asm', assembleOut, (err) => {
60 | if (err) {
61 | throw err
62 | }
63 | })
64 | }
--------------------------------------------------------------------------------
/09/README.md:
--------------------------------------------------------------------------------
1 | ## 解决在 windows 下无法打开 `JackCompiler.bat` 工具的问题
2 |
3 | 首先建立一个文件,命名为 `jc.cmd`,输入:
4 | ```
5 | @echo off
6 | pushd .
7 | call F:\nand2tetris\tools\JackCompiler.bat %cd%\%1
8 | popd
9 | ```
10 | 保存,将 `jc.cmd` 拖到你要执行命令的目录里。假设当前路径下有一个 `test` 目录或 `test.jack` 文件。在当前目录打开命令行,输入:
11 | ```
12 | jc test
13 | ```
14 | 即可编译成功。如果你是 win10 系统:
15 | ```
16 | .\jc.cmd test
17 | ```
18 |
19 | **注意:** `F:\nand2tetris\tools\JackCompiler.bat` 这个是工具的路径,你需要将它替换成自己工具的路径。另外,**不要使用中文路径**,否则执行报错。
20 |
21 |
--------------------------------------------------------------------------------
/09/jc.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | pushd .
3 | call F:\nand2tetris\tools\JackCompiler.bat %cd%\%1
4 | popd
--------------------------------------------------------------------------------
/09/test/Generate.jack:
--------------------------------------------------------------------------------
1 | class Generate {
2 | field String str;
3 | static String str1;
4 | constructor Generate new(String s) {
5 | let str = s;
6 | return this;
7 | }
8 |
9 | method String getString() {
10 | return str;
11 | }
12 |
13 | function String func() {
14 | let str1 = "func test";
15 | return str1;
16 | }
17 |
18 | function String local() {
19 | var String str2;
20 | let str2 = "local test";
21 | return str2;
22 | }
23 |
24 | function String newTest() {
25 | var String newTest;
26 | let newTest = Generate.oneMoreTime();
27 | return newTest;
28 | }
29 |
30 | function String oneMoreTime() {
31 | return "oneMoreTime";
32 | }
33 | }
--------------------------------------------------------------------------------
/09/test/Generate.vm:
--------------------------------------------------------------------------------
1 | function Generate.new 0
2 | push constant 1
3 | call Memory.alloc 1
4 | pop pointer 0
5 | push argument 0
6 | pop this 0
7 | push pointer 0
8 | return
9 | function Generate.getString 0
10 | push argument 0
11 | pop pointer 0
12 | push this 0
13 | return
14 | function Generate.func 0
15 | push constant 9
16 | call String.new 1
17 | push constant 102
18 | call String.appendChar 2
19 | push constant 117
20 | call String.appendChar 2
21 | push constant 110
22 | call String.appendChar 2
23 | push constant 99
24 | call String.appendChar 2
25 | push constant 32
26 | call String.appendChar 2
27 | push constant 116
28 | call String.appendChar 2
29 | push constant 101
30 | call String.appendChar 2
31 | push constant 115
32 | call String.appendChar 2
33 | push constant 116
34 | call String.appendChar 2
35 | pop static 0
36 | push static 0
37 | return
38 | function Generate.local 1
39 | push constant 10
40 | call String.new 1
41 | push constant 108
42 | call String.appendChar 2
43 | push constant 111
44 | call String.appendChar 2
45 | push constant 99
46 | call String.appendChar 2
47 | push constant 97
48 | call String.appendChar 2
49 | push constant 108
50 | call String.appendChar 2
51 | push constant 32
52 | call String.appendChar 2
53 | push constant 116
54 | call String.appendChar 2
55 | push constant 101
56 | call String.appendChar 2
57 | push constant 115
58 | call String.appendChar 2
59 | push constant 116
60 | call String.appendChar 2
61 | pop local 0
62 | push local 0
63 | return
64 | function Generate.newTest 1
65 | call Generate.oneMoreTime 0
66 | pop local 0
67 | push local 0
68 | return
69 | function Generate.oneMoreTime 0
70 | push constant 11
71 | call String.new 1
72 | push constant 111
73 | call String.appendChar 2
74 | push constant 110
75 | call String.appendChar 2
76 | push constant 101
77 | call String.appendChar 2
78 | push constant 77
79 | call String.appendChar 2
80 | push constant 111
81 | call String.appendChar 2
82 | push constant 114
83 | call String.appendChar 2
84 | push constant 101
85 | call String.appendChar 2
86 | push constant 84
87 | call String.appendChar 2
88 | push constant 105
89 | call String.appendChar 2
90 | push constant 109
91 | call String.appendChar 2
92 | push constant 101
93 | call String.appendChar 2
94 | return
95 |
--------------------------------------------------------------------------------
/09/test/Main.jack:
--------------------------------------------------------------------------------
1 | class Main {
2 | function void main() {
3 | var Generate str;
4 | var String mainStr;
5 | var Array a;
6 | let a = Array.new(10);
7 | let a[1+2] = 10;
8 | let mainStr = Main.mainStr();
9 | let str = Generate.new("this is a test");
10 | do Output.printString(Generate.func());
11 | do Output.println();
12 | do Output.printString(str.getString());
13 | do Output.println();
14 | do Output.printString(Generate.local());
15 | do Output.println();
16 | do Output.printString(Generate.newTest());
17 | do Output.println();
18 | do Output.printString(mainStr);
19 | do Output.println();
20 | do Output.printInt(a[3]);
21 | return;
22 | }
23 |
24 | function String mainStr() {
25 | return "good job!";
26 | }
27 | }
--------------------------------------------------------------------------------
/09/test/Main.vm:
--------------------------------------------------------------------------------
1 | function Main.main 3
2 | push constant 10
3 | call Array.new 1
4 | pop local 2
5 | push constant 1
6 | push constant 2
7 | add
8 | push local 2
9 | add
10 | push constant 10
11 | pop temp 0
12 | pop pointer 1
13 | push temp 0
14 | pop that 0
15 | call Main.mainStr 0
16 | pop local 1
17 | push constant 14
18 | call String.new 1
19 | push constant 116
20 | call String.appendChar 2
21 | push constant 104
22 | call String.appendChar 2
23 | push constant 105
24 | call String.appendChar 2
25 | push constant 115
26 | call String.appendChar 2
27 | push constant 32
28 | call String.appendChar 2
29 | push constant 105
30 | call String.appendChar 2
31 | push constant 115
32 | call String.appendChar 2
33 | push constant 32
34 | call String.appendChar 2
35 | push constant 97
36 | call String.appendChar 2
37 | push constant 32
38 | call String.appendChar 2
39 | push constant 116
40 | call String.appendChar 2
41 | push constant 101
42 | call String.appendChar 2
43 | push constant 115
44 | call String.appendChar 2
45 | push constant 116
46 | call String.appendChar 2
47 | call Generate.new 1
48 | pop local 0
49 | call Generate.func 0
50 | call Output.printString 1
51 | pop temp 0
52 | call Output.println 0
53 | pop temp 0
54 | push local 0
55 | call Generate.getString 1
56 | call Output.printString 1
57 | pop temp 0
58 | call Output.println 0
59 | pop temp 0
60 | call Generate.local 0
61 | call Output.printString 1
62 | pop temp 0
63 | call Output.println 0
64 | pop temp 0
65 | call Generate.newTest 0
66 | call Output.printString 1
67 | pop temp 0
68 | call Output.println 0
69 | pop temp 0
70 | push local 1
71 | call Output.printString 1
72 | pop temp 0
73 | call Output.println 0
74 | pop temp 0
75 | push constant 3
76 | push local 2
77 | add
78 | pop pointer 1
79 | push that 0
80 | call Output.printInt 1
81 | pop temp 0
82 | push constant 0
83 | return
84 | function Main.mainStr 0
85 | push constant 9
86 | call String.new 1
87 | push constant 103
88 | call String.appendChar 2
89 | push constant 111
90 | call String.appendChar 2
91 | push constant 111
92 | call String.appendChar 2
93 | push constant 100
94 | call String.appendChar 2
95 | push constant 32
96 | call String.appendChar 2
97 | push constant 106
98 | call String.appendChar 2
99 | push constant 111
100 | call String.appendChar 2
101 | push constant 98
102 | call String.appendChar 2
103 | push constant 33
104 | call String.appendChar 2
105 | return
106 |
--------------------------------------------------------------------------------
/10/README.md:
--------------------------------------------------------------------------------
1 | ### 编译
2 | ```
3 | // 处理单个文件
4 | node analyzer.js xxx.jack
5 | // 处理目录
6 | node analyzer.js xxx
7 | ```
8 |
9 | ### 比较
10 | 在tool文件夹里 打开命令行 并将要对比的两个文件拖进来 输入如下命令进行对比
11 | ```
12 | TextComparer xxx1.xml xxx2.xml
13 | ```
14 |
--------------------------------------------------------------------------------
/10/analyzer.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const JackTokenizer = require('./tokenizer.js')
3 | const CompilationEngine = require('./compilation.js')
4 |
5 | const fileName = process.argv[2]
6 |
7 |
8 | // 判断是否目录
9 | const isDirectory = fs.lstatSync(fileName).isDirectory()
10 |
11 | if (isDirectory) {
12 | fs.readdir(fileName, (err, files) => {
13 | if (err) {
14 | throw err
15 | }
16 |
17 | // 循环处理目录中的文件
18 | files.forEach(file => {
19 | let tempArry = file.split('.')
20 | if (tempArry.pop() == 'jack') {
21 | let preName = tempArry.join('.')
22 | let data = fs.readFileSync(`${fileName}/${file}`, 'utf-8')
23 |
24 | processFileData(data, `${fileName}/${preName}`)
25 | }
26 | })
27 | })
28 | } else {
29 | // 处理类似a.b.c这种格式的文件 保留a.b
30 | let tempArry = fileName.split('.')
31 | if (tempArry.pop() == 'jack') {
32 | let preName = tempArry.join('.')
33 | let data = fs.readFileSync(fileName, 'utf-8')
34 | processFileData(data, preName)
35 | }
36 | }
37 |
38 | // 处理文件数据
39 | function processFileData(data, path) {
40 | data = data.split(/\r\n|\n/)
41 | const tokens = new JackTokenizer(data, path).getTokens()
42 | new CompilationEngine(tokens, path)
43 | }
44 |
45 | // 输出文件
46 | function setFileName(name, data) {
47 | fs.writeFile(name + '.vm', data, (err) => {
48 | if (err) {
49 | throw err
50 | }
51 | })
52 | }
--------------------------------------------------------------------------------
/10/compilation.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const opObj = {
3 | '+': 'add',
4 | '-': 'sub',
5 | '&': 'and',
6 | '|': 'or',
7 | '<': 'lt',
8 | '>': 'gt',
9 | '=': 'eq',
10 | '/': 'call Math.divide 2',
11 | '*': 'call Math.multiply 2'
12 | }
13 |
14 | function CompilationEngine(tokens, fileName) {
15 | this.outputPath = fileName + '.xml'
16 | this.rawFile = fileName + '.jack'
17 | this.tokens = tokens
18 | this.len = tokens.length
19 | this.output = ''
20 | // index
21 | this.i = -1
22 | this._compileClass()
23 | }
24 |
25 | CompilationEngine.prototype = {
26 | _compileClass() {
27 | const tokens = this.tokens
28 | let key, val, temp
29 |
30 | temp = this._getNextToken()
31 | key = temp[0]
32 | val = temp[1]
33 |
34 | if (val == 'class') {
35 | this.output += '\r\n'
36 | this.output += `<${key}> ${val} ${key}>\r\n`
37 |
38 | temp = this._getNextToken()
39 | key = temp[0]
40 | val = temp[1]
41 |
42 | if (key == 'identifier') {
43 | this.output += `<${key}> ${val} ${key}>\r\n`
44 |
45 | temp = this._getNextToken()
46 | key = temp[0]
47 | val = temp[1]
48 |
49 | if (val == '{') {
50 | this.output += `<${key}> ${val} ${key}>\r\n`
51 |
52 | while (this.i < this.len) {
53 | let line
54 | temp = this._getNextToken()
55 | key = temp[0]
56 | val = temp[1]
57 | line = temp[2]
58 |
59 | if (val == '}') {
60 | this.output += `<${key}> ${val} ${key}>\r\n`
61 | break
62 | }
63 |
64 | switch (val) {
65 | case 'static':
66 | case 'field':
67 | this._compileClassVarDec(key, val)
68 | break
69 | case 'constructor':
70 | case 'function':
71 | case 'method':
72 | this._compileSubroutine(key, val)
73 | break
74 | default:
75 | this._error(key, val, 'static | field | constructor | function | method')
76 | }
77 | }
78 | } else {
79 | this._error(key, val, '{')
80 | }
81 | } else {
82 | this._error(key, val, 'identifier')
83 | }
84 |
85 | this.output += '\r\n'
86 | this.createXMLFile()
87 | } else {
88 | this._error(key, val, 'class')
89 | }
90 | },
91 |
92 | _compileClassVarDec(key, val) {
93 | let temp
94 |
95 | this.output += '\r\n'
96 | + `<${key}> ${val} ${key}>\r\n`
97 |
98 | temp = this._getNextToken()
99 | key = temp[0]
100 | val = temp[1]
101 | if (key == 'keyword' || key == 'identifier') {
102 | this.output += `<${key}> ${val} ${key}>\r\n`
103 |
104 | temp = this._getNextToken()
105 | key = temp[0]
106 | val = temp[1]
107 |
108 | if (key !== 'identifier') {
109 | this._error(key, val, 'identifier')
110 | }
111 |
112 | while (key == 'identifier') {
113 | this.output += `<${key}> ${val} ${key}>\r\n`
114 |
115 | temp = this._getNextToken()
116 | key = temp[0]
117 | val = temp[1]
118 |
119 | if (val == ';') {
120 | this.output += `<${key}> ${val} ${key}>\r\n`
121 | break
122 | } else if (val == ',') {
123 | this.output += `<${key}> ${val} ${key}>\r\n`
124 | temp = this._getNextToken()
125 | key = temp[0]
126 | val = temp[1]
127 | } else {
128 | this._error(key, val, ',|;')
129 | }
130 | }
131 | } else {
132 | this._error(key, val, 'keyword')
133 | }
134 |
135 | this.output += '\r\n'
136 | },
137 |
138 | _compileSubroutine(key, val) {
139 | let temp
140 |
141 | this.output += '\r\n'
142 | + `<${key}> ${val} ${key}>\r\n`
143 |
144 | temp = this._getNextToken()
145 | key = temp[0]
146 | val = temp[1]
147 | if (key != 'identifier' && key != 'keyword') {
148 | this._error(key, val, 'identifier | keyword')
149 | }
150 |
151 | while (key == 'identifier' || key == 'keyword') {
152 | this.output += `<${key}> ${val} ${key}>\r\n`
153 | temp = this._getNextToken()
154 | key = temp[0]
155 | val = temp[1]
156 | }
157 |
158 | if (val == '(') {
159 | this.output += `<${key}> ${val} ${key}>\r\n`
160 | this._compileParameterList()
161 |
162 | temp = this._getNextToken()
163 | key = temp[0]
164 | val = temp[1]
165 | if (val == ')') {
166 | this.output += `<${key}> ${val} ${key}>\r\n`
167 |
168 | temp = this._getNextToken()
169 | key = temp[0]
170 | val = temp[1]
171 |
172 | if (val == '{') {
173 | this._compileSubroutineBody(key, val)
174 | } else {
175 | this._error(key, val, '{')
176 | }
177 | } else {
178 | this._error(key, val, ')')
179 | }
180 | } else {
181 | this._error(key, val, '(')
182 | }
183 |
184 | this.output += '\r\n'
185 | },
186 |
187 | _compileSubroutineBody(key, val) {
188 | let temp, line
189 |
190 | this.output += '\r\n'
191 | + `<${key}> ${val} ${key}>\r\n`
192 |
193 | temp = this._getNextToken()
194 | key = temp[0]
195 | val = temp[1]
196 | line = temp[2]
197 |
198 | while (true) {
199 | if (val == 'var') {
200 | this._compileVarDec(key, val)
201 | } else {
202 | this._compileStatements(key, val, line)
203 | }
204 |
205 | temp = this._getNextToken()
206 | key = temp[0]
207 | val = temp[1]
208 | line = temp[2]
209 |
210 | if (val == '}') {
211 | this.output += `<${key}> ${val} ${key}>\r\n`
212 | break
213 | } else {
214 | switch (val) {
215 | case 'if':
216 | case 'while':
217 | case 'do':
218 | case 'return':
219 | case 'let':
220 | case 'var':
221 | break
222 | default:
223 | this._error(key, val, '}')
224 | }
225 | }
226 | }
227 |
228 | this.output += '\r\n'
229 | },
230 |
231 | _compileParameterList() {
232 | let key, val, temp
233 |
234 |
235 | this.output += '\r\n'
236 | temp = this._getNextToken()
237 | key = temp[0]
238 | val = temp[1]
239 |
240 | if (val !== ')') {
241 | if (key !== 'keyword') {
242 | error(key, val, 'keyword')
243 | } else {
244 | while (key == 'keyword') {
245 | this.output += `<${key}> ${val} ${key}>\r\n`
246 |
247 | temp = this._getNextToken()
248 | key = temp[0]
249 | val = temp[1]
250 | if (key == 'identifier') {
251 | this.output += `<${key}> ${val} ${key}>\r\n`
252 |
253 | temp = this._getNextToken()
254 | key = temp[0]
255 | val = temp[1]
256 | if (val == ',') {
257 | this.output += `<${key}> ${val} ${key}>\r\n`
258 | temp = this._getNextToken()
259 | key = temp[0]
260 | val = temp[1]
261 | }
262 | } else {
263 | error(key, val, 'identifier')
264 | }
265 | }
266 | }
267 | }
268 |
269 | this.output += '\r\n'
270 | this.i--
271 | },
272 |
273 | _compileVarDec(key, val) {
274 | let temp
275 |
276 | this.output += '\r\n'
277 | + `<${key}> ${val} ${key}>\r\n`
278 | temp = this._getNextToken()
279 | key = temp[0]
280 | val = temp[1]
281 |
282 | if (key == 'keyword' || key == 'identifier') {
283 | this.output += `<${key}> ${val} ${key}>\r\n`
284 | temp = this._getNextToken()
285 | key = temp[0]
286 | val = temp[1]
287 |
288 | if (key !== 'identifier') {
289 | error(key, val, 'identifier')
290 | }
291 |
292 | while (key == 'identifier') {
293 | this.output += `<${key}> ${val} ${key}>\r\n`
294 | temp = this._getNextToken()
295 | key = temp[0]
296 | val = temp[1]
297 |
298 | if (val == ',') {
299 | this.output += `<${key}> ${val} ${key}>\r\n`
300 | temp = this._getNextToken()
301 | key = temp[0]
302 | val = temp[1]
303 | } else if (val == ';') {
304 | this.output += `<${key}> ${val} ${key}>\r\n`
305 | break
306 | } else {
307 | error(key, val, ', | ;')
308 | }
309 | }
310 | } else {
311 | error(key, val, 'keyword | identifier')
312 | }
313 |
314 | this.output += '\r\n'
315 |
316 | temp = this._getNextToken()
317 | key = temp[0]
318 | val = temp[1]
319 |
320 | if (val == 'var') {
321 | this._compileVarDec(key, val)
322 | } else {
323 | this.i--
324 | }
325 | },
326 |
327 | _compileStatements(key, val, line) {
328 | let temp
329 |
330 | this.output += '\r\n'
331 |
332 | while (val !== '}') {
333 | switch (val) {
334 | case 'if':
335 | this._compileIf(key, val)
336 | break
337 | case 'while':
338 | this._compileWhile(key, val)
339 | break
340 | case 'do':
341 | this._compileDo(key, val)
342 | break
343 | case 'return':
344 | this._compileReturn(key, val)
345 | break
346 | case 'let':
347 | this._compileLet(key, val)
348 | break
349 | default:
350 | this._error(key, val, 'if | while | do | return | let')
351 | }
352 |
353 | temp = this._getNextToken()
354 | key = temp[0]
355 | val = temp[1]
356 | line = temp[2]
357 | }
358 | this.i--
359 | this.output += '\r\n'
360 | },
361 |
362 | _compileDo(key, val) {
363 | let temp
364 |
365 | this.output += '\r\n'
366 | + `<${key}> ${val} ${key}>\r\n`
367 |
368 | temp = this._getNextToken()
369 | key = temp[0]
370 | val = temp[1]
371 |
372 | if (key == 'identifier') {
373 | this.output += `<${key}> ${val} ${key}>\r\n`
374 | temp = this._getNextToken()
375 | key = temp[0]
376 | val = temp[1]
377 | this._compilePartOfCall(key, val)
378 |
379 | temp = this._getNextToken()
380 | key = temp[0]
381 | val = temp[1]
382 | if (val == ';') {
383 | this.output += `<${key}> ${val} ${key}>\r\n`
384 | } else {
385 | this._error(key, val, ';')
386 | }
387 | } else {
388 | this._error(key, val, 'identifier')
389 | }
390 | this.output += '\r\n'
391 | },
392 |
393 | _compileLet(key, val) {
394 | let temp
395 |
396 | this.output += '\r\n'
397 | + `<${key}> ${val} ${key}>\r\n`
398 |
399 | temp = this._getNextToken()
400 | key = temp[0]
401 | val = temp[1]
402 |
403 | if (key == 'identifier') {
404 | this.output += `<${key}> ${val} ${key}>\r\n`
405 |
406 | temp = this._getNextToken()
407 | key = temp[0]
408 | val = temp[1]
409 |
410 | if (val == '[') {
411 | this.output += `<${key}> ${val} ${key}>\r\n`
412 | this._compileExpression()
413 | temp = this._getNextToken()
414 | key = temp[0]
415 | val = temp[1]
416 |
417 | if (val == ']') {
418 | this.output += `<${key}> ${val} ${key}>\r\n`
419 | temp = this._getNextToken()
420 | key = temp[0]
421 | val = temp[1]
422 |
423 | if (val == '=') {
424 | this.output += `<${key}> ${val} ${key}>\r\n`
425 | this._compileExpression()
426 | } else {
427 | this._error(key, val, '=')
428 | }
429 | } else {
430 | this._error(key, val, ']')
431 | }
432 | } else if (val == '=') {
433 | this.output += `<${key}> ${val} ${key}>\r\n`
434 | this._compileExpression()
435 | } else {
436 | this._error(key, val, '[ | =')
437 | }
438 |
439 | temp = this._getNextToken()
440 | key = temp[0]
441 | val = temp[1]
442 |
443 | if (val == ';') {
444 | this.output += `<${key}> ${val} ${key}>\r\n`
445 | } else {
446 | this._error(key, val, ';')
447 | }
448 | } else {
449 | this._error(key, val, 'identifier')
450 | }
451 | this.output += '\r\n'
452 | },
453 |
454 | _compileWhile(key, val) {
455 | let temp
456 |
457 | this.output += '\r\n'
458 | + `<${key}> ${val} ${key}>\r\n`
459 |
460 | temp = this._getNextToken()
461 | key = temp[0]
462 | val = temp[1]
463 |
464 | if (val == '(') {
465 | this.output += `<${key}> ${val} ${key}>\r\n`
466 | this._compileExpression()
467 |
468 | temp = this._getNextToken()
469 | key = temp[0]
470 | val = temp[1]
471 |
472 | if (val == ')') {
473 | this.output += `<${key}> ${val} ${key}>\r\n`
474 | temp = this._getNextToken()
475 | key = temp[0]
476 | val = temp[1]
477 | line = temp[2]
478 |
479 | if (val == '{') {
480 | this.output += `<${key}> ${val} ${key}>\r\n`
481 |
482 | temp = this._getNextToken()
483 | key = temp[0]
484 | val = temp[1]
485 | line = temp[2]
486 | this._compileStatements(key, val, line)
487 |
488 | temp = this._getNextToken()
489 | key = temp[0]
490 | val = temp[1]
491 |
492 | if (val == '}') {
493 | this.output += `<${key}> ${val} ${key}>\r\n`
494 | } else {
495 | this._error(key, val, '}')
496 | }
497 | } else {
498 | this._error(key, val, '{')
499 | }
500 | } else {
501 | this._error(key, val, ')')
502 | }
503 | } else {
504 | this._error(key, val, '(')
505 | }
506 | this.output += '\r\n'
507 | },
508 |
509 | _compileIf(key, val) {
510 | let temp
511 |
512 | this.output += '\r\n'
513 | + `<${key}> ${val} ${key}>\r\n`
514 |
515 | temp = this._getNextToken()
516 | key = temp[0]
517 | val = temp[1]
518 |
519 | if (val == '(') {
520 | this.output += `<${key}> ${val} ${key}>\r\n`
521 | this._compileExpression()
522 |
523 | temp = this._getNextToken()
524 | key = temp[0]
525 | val = temp[1]
526 |
527 | if (val == ')') {
528 | this.output += `<${key}> ${val} ${key}>\r\n`
529 |
530 | temp = this._getNextToken()
531 | key = temp[0]
532 | val = temp[1]
533 |
534 | if (val == '{') {
535 | this.output += `<${key}> ${val} ${key}>\r\n`
536 | temp = this._getNextToken()
537 | let line
538 | key = temp[0]
539 | val = temp[1]
540 | line = temp[2]
541 |
542 | this._compileStatements(key, val, line)
543 |
544 | temp = this._getNextToken()
545 | key = temp[0]
546 | val = temp[1]
547 |
548 | if (val == '}') {
549 | this.output += `<${key}> ${val} ${key}>\r\n`
550 |
551 | temp = this._getNextToken()
552 | key = temp[0]
553 | val = temp[1]
554 |
555 | if (val == 'else') {
556 | this._compileElse(key, val)
557 | } else {
558 | this.i--
559 | }
560 | } else {
561 | this._error(key, val, '}')
562 | }
563 | } else {
564 | this._error(key, val, ')')
565 | }
566 | } else {
567 | this._error(key, val, ')')
568 | }
569 | } else {
570 | this._error(key, val, '(')
571 | }
572 |
573 | this.output += '\r\n'
574 | },
575 |
576 | _compileElse(key, val) {
577 | let temp, line
578 | this.output += `<${key}> ${val} ${key}>\r\n`
579 |
580 | temp = this._getNextToken()
581 | key = temp[0]
582 | val = temp[1]
583 |
584 | if (val == '{') {
585 | this.output += `<${key}> ${val} ${key}>\r\n`
586 | temp = this._getNextToken()
587 | key = temp[0]
588 | val = temp[1]
589 | line = temp[2]
590 | this._compileStatements(key, val, line)
591 |
592 | temp = this._getNextToken()
593 | key = temp[0]
594 | val = temp[1]
595 |
596 | if (val == '}') {
597 | this.output += `<${key}> ${val} ${key}>\r\n`
598 | } else {
599 | this._error(key, val, '}')
600 | }
601 | } else {
602 | this._error(key, val, '{')
603 | }
604 | },
605 |
606 | _compileReturn(key, val) {
607 | let temp
608 |
609 | this.output += '\r\n'
610 | + `<${key}> ${val} ${key}>\r\n`
611 |
612 | temp = this._getNextToken()
613 | key = temp[0]
614 | val = temp[1]
615 |
616 | if (val == ';') {
617 | this.output += `<${key}> ${val} ${key}>\r\n`
618 | } else {
619 | this.i--
620 | this._compileExpression()
621 | temp = this._getNextToken()
622 | key = temp[0]
623 | val = temp[1]
624 |
625 | if (val == ';') {
626 | this.output += `<${key}> ${val} ${key}>\r\n`
627 | } else {
628 | this._error(key, val, ';')
629 | }
630 | }
631 |
632 | this.output += '\r\n'
633 | },
634 |
635 | _compileExpression() {
636 | this.output += '\r\n'
637 | this._compileTerm()
638 |
639 | let key, val, temp, op
640 |
641 | while (true) {
642 | temp = this._getNextToken()
643 | key = temp[0]
644 | val = temp[1]
645 |
646 | if (opObj[val] !== undefined) {
647 | this.output += `<${key}> ${val} ${key}>\r\n`
648 | this._compileTerm()
649 | } else {
650 | this.i--
651 | break
652 | }
653 | }
654 |
655 | this.output += '\r\n'
656 | },
657 |
658 | _compileTerm() {
659 | let key, val, temp
660 | this.output += '\r\n'
661 |
662 | temp = this._getNextToken()
663 | key = temp[0]
664 | val = temp[1]
665 |
666 | if (key == 'identifier') {
667 | this.output += `<${key}> ${val} ${key}>\r\n`
668 |
669 | temp = this._getNextToken()
670 | key = temp[0]
671 | val = temp[1]
672 |
673 | switch (val) {
674 | case '(':
675 | case '.':
676 | this._compilePartOfCall(key, val)
677 | break
678 | case '[':
679 | this.output += `<${key}> ${val} ${key}>\r\n`
680 | this._compileExpression()
681 | temp = this._getNextToken()
682 | key = temp[0]
683 | val = temp[1]
684 |
685 | if (val == ']') {
686 | this.output += `<${key}> ${val} ${key}>\r\n`
687 | } else {
688 | this._error(key, val, ']')
689 | }
690 | break
691 | default:
692 | this.i--
693 | }
694 | } else if (key == 'integerConstant') {
695 | this.output += `<${key}> ${val} ${key}>\r\n`
696 | } else if (key == 'stringConstant') {
697 | this.output += `<${key}> ${val} ${key}>\r\n`
698 | } else {
699 | switch (val) {
700 | case '(':
701 | this.output += `<${key}> ${val} ${key}>\r\n`
702 | this._compileExpression()
703 |
704 | temp = this._getNextToken()
705 | key = temp[0]
706 | val = temp[1]
707 |
708 | if (val == ')') {
709 | this.output += `<${key}> ${val} ${key}>\r\n`
710 | } else {
711 | this._error(key, val, ')')
712 | }
713 | break
714 | case '-':
715 | this.output += `<${key}> ${val} ${key}>\r\n`
716 | this._compileTerm()
717 | break
718 | case '~':
719 | this.output += `<${key}> ${val} ${key}>\r\n`
720 | this._compileTerm()
721 | break
722 | case 'null':
723 | case 'false':
724 | this.output += `<${key}> ${val} ${key}>\r\n`
725 | break
726 | case 'true':
727 | this.output += `<${key}> ${val} ${key}>\r\n`
728 | break
729 | case 'this':
730 | this.output += `<${key}> ${val} ${key}>\r\n`
731 | break
732 | default:
733 | this._error(key, val, 'Unknown symbol')
734 | }
735 | }
736 |
737 | this.output += '\r\n'
738 | },
739 |
740 | _compilePartOfCall(key, val) {
741 | let temp
742 | if (val == '(') {
743 | this.output += `<${key}> ${val} ${key}>\r\n`
744 | this._compileExpressionList()
745 | temp = this._getNextToken()
746 | key = temp[0]
747 | val = temp[1]
748 |
749 | if (val == ')') {
750 | this.output += `<${key}> ${val} ${key}>\r\n`
751 | } else {
752 | this._error(key, val, ')')
753 | }
754 | } else if (val == '.') {
755 | this.output += `<${key}> ${val} ${key}>\r\n`
756 | temp = this._getNextToken()
757 | key = temp[0]
758 | val = temp[1]
759 |
760 | if (key == 'identifier') {
761 | this.output += `<${key}> ${val} ${key}>\r\n`
762 | temp = this._getNextToken()
763 | key = temp[0]
764 | val = temp[1]
765 | if (val == '(') {
766 | this.output += `<${key}> ${val} ${key}>\r\n`
767 | this._compileExpressionList()
768 | temp = this._getNextToken()
769 | key = temp[0]
770 | val = temp[1]
771 |
772 | if (val == ')') {
773 | this.output += `<${key}> ${val} ${key}>\r\n`
774 | } else {
775 | this._error(key, val, ')')
776 | }
777 | } else {
778 | this.error(key, val, '(')
779 | }
780 | } else {
781 | this.error(key, val, 'identifier')
782 | }
783 | } else {
784 | this._error(key, val, '( | .')
785 | }
786 | },
787 |
788 | _compileExpressionList() {
789 | let key, val, temp
790 |
791 | temp = this._getNextToken()
792 | key = temp[0]
793 | val = temp[1]
794 |
795 | this.output += '\r\n'
796 |
797 | if (val == ')' || val == ',') {
798 | this.i--
799 | } else {
800 | this.i--
801 | while (true) {
802 | this._compileExpression()
803 | temp = this._getNextToken()
804 | key = temp[0]
805 | val = temp[1]
806 |
807 | if (val == ',') {
808 | this.output += `<${key}> ${val} ${key}>\r\n`
809 | } else if (val == ')') {
810 | this.i--
811 | break
812 | }
813 | }
814 | }
815 |
816 | this.output += '\r\n'
817 | },
818 |
819 | createXMLFile() {
820 | fs.writeFileSync(this.outputPath, this.output)
821 | },
822 |
823 | _getNextToken() {
824 | this.i++
825 | let obj = this.tokens[this.i]
826 | let key = Object.keys(obj)[0]
827 | let val = obj[key]
828 |
829 | return [key, val, obj.line]
830 | },
831 |
832 | _error(key, val, type) {
833 | let error = 'line:' + this.tokens[this.i].line + ' syntax error: {' + key + ': ' + val
834 | + '}\r\nExpect the type of key to be ' + type + '\r\nat ' + this.rawFile
835 | throw error
836 | }
837 | }
838 |
839 | module.exports = CompilationEngine
--------------------------------------------------------------------------------
/10/tokenizer.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | function JackTokenizer(data, fileName) {
4 | this.data = data
5 | this.rawData = data.slice()
6 | this.tokens = []
7 | this.rawFile = fileName + '.jack'
8 | this.outputPath = fileName + 'T.xml'
9 | // 单行注释
10 | this.notesRe1 = /^(\/\/)/
11 | /* */
12 | this.notesRe2 = /^(\/\*).*(\*\/)$/
13 | /** */
14 | this.notesRe3 = /^(\/\*\*).*(\*\/)$/
15 | // 匹配行中的注释
16 | this.re1 = /(\/\/).*/
17 | this.re2 = /(\/\*).*(\*\/)/
18 | this.re3 = /(\/\*\*).*(\*\/)/
19 | // 匹配多行注释
20 | this.reg1 = /^(\/\*)/
21 | this.reg2 = /^(\/\*\*)/
22 | this.reg3 = /^\*[^\/]/
23 | this.reg4 = /^(\*\/)/
24 | this.reg5 = /^\*[^\/].*(\*\/)$/
25 |
26 | // 单个字符匹配
27 | this.wordRe = /\w|_/
28 |
29 | // 标识符
30 | this.identifierRe = /^[^\d].*/
31 |
32 | // 字符串常量
33 | this.strRe = /^".*"$/
34 | this.strRe1 = /^"/
35 | this.strRe2 = /"$/
36 |
37 | // 行数
38 | this.line = 0
39 | this.keywordType = [
40 | 'class', 'constructor', 'function', 'method', 'field', 'static', 'var', 'int', 'char', 'boolean',
41 | 'void', 'true', 'false', 'null', 'this', 'let', 'do', 'if', 'else', 'while', 'return'
42 | ]
43 | this.symbolType = [
44 | '{', '}', '(', ')', '[', ']', '.', ',', ';', '+', '-', '*', '/', '&', '|', '<', '>', '=', '~',
45 | '<', '>', '&'
46 | ]
47 |
48 | this._init()
49 | }
50 |
51 | JackTokenizer.prototype = {
52 | _init() {
53 | const data = this.data
54 |
55 | while (this._hasMoreTokens(data)) {
56 | let str = data.shift().trim()
57 | this.line++
58 |
59 | if (this._isVaildStr(str)) {
60 | // 清除字符串中的注释
61 | str = str.replace(this.re1, '')
62 | str = str.replace(this.re2, '')
63 | str = str.replace(this.re3, '').trim()
64 | this._lexicalAnalysis(str)
65 | }
66 | }
67 |
68 | this.createXMLFile()
69 | },
70 |
71 | _hasMoreTokens(data) {
72 | return data.length > 0? true : false
73 | },
74 |
75 | _advance(token) {
76 | const type = this._tokenType(token)
77 |
78 | switch (type) {
79 | case 'keyword':
80 | this._keyword(token)
81 | break
82 | case 'symbol':
83 | this._symbol(token)
84 | break
85 | case 'int-const':
86 | this._intVal(token)
87 | break
88 | case 'identifier':
89 | this._identifier(token)
90 | break
91 | case 'string_const':
92 | this._stringVal(token)
93 | break
94 | }
95 | },
96 |
97 | _tokenType(token) {
98 | if (this.keywordType.includes(token)) {
99 | return 'keyword'
100 | } else if (this.symbolType.includes(token)) {
101 | return 'symbol'
102 | } else if (this.strRe.test(token)) {
103 | return 'string_const'
104 | } else if (this.identifierRe.test(token)) {
105 | return 'identifier'
106 | } else if (0 <= parseFloat(token) <= 32767) {
107 | return 'int-const'
108 | } else {
109 | let error = 'line:' + this.line + ' syntax error:' + token + '\r\n'
110 | + this.rawData[this.line - 1].trim() + '\r\nat ' + this.rawFile
111 | throw error
112 | }
113 | },
114 |
115 | _keyword(token) {
116 | this.tokens.push({keyword: token, line: this.line})
117 | },
118 |
119 | _symbol(token) {
120 | this.tokens.push({symbol: token, line: this.line})
121 | },
122 |
123 | _identifier(token) {
124 | this.tokens.push({identifier: token, line: this.line})
125 | },
126 |
127 | _intVal(token) {
128 | this.tokens.push({integerConstant: token, line: this.line})
129 | },
130 |
131 | _stringVal(token) {
132 | token = token.replace(this.strRe1, '')
133 | token = token.replace(this.strRe2, '')
134 |
135 | this.tokens.push({stringConstant: token, line: this.line})
136 | },
137 |
138 | _isVaildStr(str) {
139 | if (this.notesRe1.test(str) || this.notesRe2.test(str) || this.notesRe3.test(str)) {
140 | return false
141 | } else if (this.reg1.test(str) || this.reg2.test(str)) {
142 |
143 | while (this._hasMoreTokens(this.data)) {
144 | this.line++
145 | str = this.data.shift().trim()
146 |
147 | if (this.reg4.test(str) || this.reg5.test(str)) {
148 | break
149 | } else if (this.reg3.test(str)) {
150 | continue
151 | }
152 | }
153 |
154 | return false
155 | } else if (str === '') {
156 | return false
157 | }
158 |
159 | return true
160 | },
161 |
162 | _lexicalAnalysis(str) {
163 | // c=a+b; 分割成 ['c', '=', 'a', '+', 'b', ';']
164 | const tokens = str.split('')
165 | let i = 0
166 | let len = tokens.length
167 | let token = ''
168 | let word
169 |
170 | while (true) {
171 | word = tokens[i]
172 |
173 | if (this.wordRe.test(word)) {
174 | token += word
175 | i++
176 | } else if (word === ' ') {
177 | if (token !== '') {
178 | this._advance(token)
179 | token = ''
180 | i++
181 | } else {
182 | i++
183 | }
184 | } else if (this.symbolType.includes(word)) {
185 | switch (word) {
186 | case '>':
187 | word = '>'
188 | break
189 | case '<':
190 | word = '<'
191 | break
192 | case '&':
193 | word = '&'
194 | break
195 | }
196 |
197 | if (token !== '') {
198 | this._advance(token)
199 | this._advance(word)
200 | token = ''
201 | i++
202 | } else {
203 | this._advance(word)
204 | i++
205 | }
206 |
207 | } else if (word === '"') {
208 | while (true) {
209 | token += word
210 | i++
211 | word = tokens[i]
212 | if (word === '"') {
213 | token += word
214 | this._advance(token)
215 | token = ''
216 | i++
217 | break
218 | }
219 |
220 | if (i >= len) {
221 | if (token !== '') {
222 | this._advance(token)
223 | }
224 | break
225 | }
226 | }
227 | }
228 |
229 | if (i >= len) {
230 | if (token !== '') {
231 | this._advance(token)
232 | }
233 | break
234 | }
235 | }
236 | },
237 |
238 | getTokens() {
239 | return this.tokens
240 | },
241 |
242 | createXMLFile() {
243 | let output = '\r\n'
244 | this.tokens.forEach(token => {
245 | const key = Object.keys(token)[0]
246 | const value = token[key]
247 | output += `<${key}> ${value} ${key}>\r\n`
248 | })
249 | output += ''
250 | fs.writeFileSync(this.outputPath, output)
251 | }
252 | }
253 |
254 |
255 | module.exports = JackTokenizer
--------------------------------------------------------------------------------
/11/README.md:
--------------------------------------------------------------------------------
1 | ### 编译
2 | ```
3 | // 处理单个文件
4 | node analyzer.js xxx.jack
5 | // 处理目录
6 | node analyzer.js xxx
7 | ```
8 | 第10章是 文本流->词法分析->tokens->语法分析->抽象语法树
9 | 本章 文本流->词法分析->tokens->语法分析->vm代码
10 |
11 | 编译负数时,推入一个值大小相等的正数再取反
12 |
--------------------------------------------------------------------------------
/11/VMWriter.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | function VMWriter(fileName) {
4 | this.output = ''
5 | this.outputPath = fileName + '.vm'
6 | }
7 |
8 | VMWriter.prototype = {
9 | writePush(segment, index) {
10 | this.output += `push ${segment} ${index}\r\n`
11 | },
12 |
13 | writePop(segment, index) {
14 | this.output += `pop ${segment} ${index}\r\n`
15 | },
16 |
17 | writeArithmetic(command) {
18 | this.output += command + '\r\n'
19 | },
20 |
21 | writeLabel(label) {
22 | this.output += `label ${label}\r\n`
23 | },
24 |
25 | writeGoto(label) {
26 | this.output += `goto ${label}\r\n`
27 | },
28 |
29 | writeIf(label) {
30 | this.output += `if-goto ${label}\r\n`
31 | },
32 |
33 | writeCall(name, nArgs) {
34 | this.output += `call ${name} ${nArgs}\r\n`
35 | },
36 |
37 | writeFunction(name, nArgs) {
38 | this.output += `function ${name} ${nArgs}\r\n`
39 | },
40 |
41 | writeReturn() {
42 | this.output += `return\r\n`
43 | },
44 |
45 | createVMFile() {
46 | fs.writeFileSync(this.outputPath, this.output)
47 | }
48 | }
49 |
50 | module.exports = VMWriter
--------------------------------------------------------------------------------
/11/analyzer.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const JackTokenizer = require('./tokenizer.js')
3 | const CompilationEngine = require('./compilation.js')
4 |
5 | const fileName = process.argv[2]
6 |
7 |
8 | // 判断是否目录
9 | const isDirectory = fs.lstatSync(fileName).isDirectory()
10 |
11 | if (isDirectory) {
12 | fs.readdir(fileName, (err, files) => {
13 | if (err) {
14 | throw err
15 | }
16 |
17 | // 循环处理目录中的文件
18 | files.forEach(file => {
19 | let tempArry = file.split('.')
20 | if (tempArry.pop() == 'jack') {
21 | let preName = tempArry.join('.')
22 | let data = fs.readFileSync(`${fileName}/${file}`, 'utf-8')
23 |
24 | processFileData(data, `${fileName}/${preName}`)
25 | }
26 | })
27 | })
28 | } else {
29 | // 处理类似a.b.c这种格式的文件 保留a.b
30 | let tempArry = fileName.split('.')
31 | if (tempArry.pop() == 'jack') {
32 | let preName = tempArry.join('.')
33 | let data = fs.readFileSync(fileName, 'utf-8')
34 | processFileData(data, preName)
35 | }
36 | }
37 |
38 | // 处理文件数据
39 | function processFileData(data, path) {
40 | data = data.split(/\r\n|\n/)
41 | const tokens = new JackTokenizer(data, path).getTokens()
42 | new CompilationEngine(tokens, path)
43 | }
44 |
45 | // 输出文件
46 | function setFileName(name, data) {
47 | fs.writeFile(name + '.vm', data, (err) => {
48 | if (err) {
49 | throw err
50 | }
51 | })
52 | }
--------------------------------------------------------------------------------
/11/compilation.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const SymbolTable = require('./symbolTable')
3 | const VMWriter = require('./VMWriter')
4 | // 类表
5 | let mainTable
6 |
7 | // 子程序表
8 | let subTable
9 |
10 | // VMWriter实例
11 | let vm
12 |
13 | // 调用方法的变量
14 | let variableOfCall = ''
15 |
16 | const ifTrueLabel = 'IF_TRUE'
17 | const ifFalseLabel = 'IF_FALSE'
18 | const ifEndLabel = 'IF_END'
19 |
20 | const whileLabel = 'WHILE_EXP'
21 | const whileEndLabel = 'WHILE_END'
22 |
23 | // if
24 | let ifIndex = 0
25 | // while
26 | let whileIndex = 0
27 | const opObj = {
28 | '+': 'add',
29 | '-': 'sub',
30 | '&': 'and',
31 | '|': 'or',
32 | '<': 'lt',
33 | '>': 'gt',
34 | '=': 'eq',
35 | '/': 'call Math.divide 2',
36 | '*': 'call Math.multiply 2'
37 | }
38 |
39 | function CompilationEngine(tokens, fileName) {
40 | this.fileName = fileName
41 | // 类名
42 | this.class = ''
43 | this.outputPath = fileName + '.xml'
44 | this.rawFile = fileName + '.jack'
45 | this.tokens = tokens
46 | this.len = tokens.length
47 | // 当前key
48 | this.key = ''
49 | // 当前val
50 | this.val = ''
51 | // 当前line
52 | this.line = 0
53 | // index
54 | this.i = -1
55 |
56 | this._init()
57 | }
58 |
59 | CompilationEngine.prototype = {
60 | _init() {
61 | mainTable = new SymbolTable()
62 | subTable = new SymbolTable()
63 | vm = new VMWriter(this.fileName)
64 |
65 | this._compileClass()
66 | vm.createVMFile()
67 | },
68 |
69 | _compileClass() {
70 | const tokens = this.tokens
71 | this._getNextToken()
72 |
73 | if (this.val == 'class') {
74 | this._getNextToken()
75 |
76 | if (this.key == 'identifier') {
77 | this.class = this.val
78 | this._getNextToken()
79 |
80 | if (this.val == '{') {
81 | while (this.i < this.len) {
82 | this._getNextToken()
83 |
84 | if (this.val == '}') {
85 | break
86 | }
87 |
88 | switch (this.val) {
89 | case 'static':
90 | case 'field':
91 | this._compileClassVarDec()
92 | break
93 | case 'constructor':
94 | case 'function':
95 | case 'method':
96 | let isCtr, isMethod
97 |
98 | if (this.val == 'constructor') {
99 | isCtr = true
100 | isMethod = false
101 | } else if (this.val == 'method') {
102 | isCtr = false
103 | isMethod = true
104 | } else {
105 | isMethod = false
106 | isCtr = false
107 | }
108 | // 重置子符号表
109 | subTable.startSubroutine()
110 | this._compileSubroutine(isCtr, isMethod)
111 | break
112 | default:
113 | this._error('static | field | constructor | function | method')
114 | }
115 | }
116 | } else {
117 | this._error('{')
118 | }
119 | } else {
120 | this._error('identifier')
121 | }
122 |
123 | } else {
124 | this._error('class')
125 | }
126 | },
127 |
128 | _compileClassVarDec() {
129 | // type, kind 变量的类型和种类
130 | let type, kind
131 | kind = this.val
132 |
133 | this._getNextToken()
134 |
135 | if (this.key == 'keyword' || this.key == 'identifier') {
136 | type = this.val
137 | this._getNextToken()
138 |
139 | if (this.key !== 'identifier') {
140 | this._error('identifier')
141 | }
142 |
143 | while (this.key == 'identifier') {
144 | // 符号表收集变量的各个参数
145 | mainTable.define(this.val, type, kind)
146 | this._getNextToken()
147 |
148 | if (this.val == ';') {
149 | break
150 | } else if (this.val == ',') {
151 | this._getNextToken()
152 | } else {
153 | this._error(', | ;')
154 | }
155 | }
156 | } else {
157 | this._error('keyword | identifier')
158 | }
159 | },
160 |
161 | _compileSubroutine(isCtr, isMethod) {
162 | let funcName = ''
163 | this._getNextToken()
164 | if (this.key == 'identifier' || this.key == 'keyword') {
165 | if (isCtr) {
166 | funcName += this.val
167 | }
168 |
169 | this._getNextToken()
170 | if (this.key == 'identifier') {
171 | if (isCtr) {
172 | funcName += '.' + this.val
173 | } else {
174 | // 函数名
175 | funcName = this.class + '.' + this.val
176 | }
177 |
178 | this._getNextToken()
179 | } else {
180 | this._error('identifier')
181 | }
182 | } else {
183 | this._error('identifier | keyword')
184 | }
185 |
186 | if (this.val == '(') {
187 | this._compileParameterList(isMethod)
188 | this._getNextToken()
189 | if (this.val == ')') {
190 | this._getNextToken()
191 |
192 | if (this.val == '{') {
193 | this._compileSubroutineBody(funcName, isCtr, isMethod)
194 | } else {
195 | this._error('{')
196 | }
197 | } else {
198 | this._error(')')
199 | }
200 | } else {
201 | this._error('(')
202 | }
203 | },
204 |
205 | _compileSubroutineBody(funcName, isCtr, isMethod) {
206 | let localNum = 0
207 | this._getNextToken()
208 |
209 | while (true) {
210 | if (this.val == 'var') {
211 | localNum = this._compileVarDec(localNum)
212 | } else {
213 | vm.writeFunction(funcName, localNum)
214 | if (isCtr) {
215 | // 构造函数
216 | let fieldNum = mainTable.varCount('field')
217 | vm.writePush('constant', fieldNum)
218 | vm.writeCall('Memory.alloc', 1)
219 | vm.writePop('pointer', 0)
220 | } else if (isMethod) {
221 | // 方法
222 | vm.writePush('argument', 0)
223 | vm.writePop('pointer', 0)
224 | }
225 |
226 | this._compileStatements()
227 | }
228 |
229 | this._getNextToken()
230 |
231 | if (this.val == '}') {
232 | break
233 | } else {
234 | switch (this.val) {
235 | case 'if':
236 | case 'while':
237 | case 'do':
238 | case 'return':
239 | case 'let':
240 | case 'var':
241 | break
242 | default:
243 | this._error('}')
244 | }
245 | }
246 | }
247 | },
248 |
249 | _compileParameterList(isMethod) {
250 | let type
251 | this._getNextToken()
252 |
253 | if (this.val !== ')') {
254 | if (this.key !== 'keyword' && this.key !== 'identifier') {
255 | this._error('keyword | identifier')
256 | } else {
257 | if (isMethod) {
258 | subTable.define('this', 'object', 'argument')
259 | }
260 | while (this.key == 'keyword' || this.key == 'identifier') {
261 | // 类型
262 | type = this.val
263 |
264 | this._getNextToken()
265 | if (this.key == 'identifier') {
266 | // 种类
267 | subTable.define(this.val, type, 'argument')
268 |
269 | this._getNextToken()
270 | if (this.val == ',') {
271 | this._getNextToken()
272 | }
273 | } else {
274 | this._error('identifier')
275 | }
276 | }
277 | }
278 | }
279 | this.i--
280 | },
281 |
282 | _compileVarDec(localNum) {
283 | let type
284 |
285 | this._getNextToken()
286 | if (this.key == 'keyword' || this.key == 'identifier') {
287 | // 类型
288 | type = this.val
289 |
290 | this._getNextToken()
291 | if (this.key !== 'identifier') {
292 | this._error('identifier')
293 | }
294 |
295 | while (this.key == 'identifier') {
296 | // 定义局部变量
297 | subTable.define(this.val, type, 'local')
298 | // 局部变量个数+1
299 | localNum++
300 |
301 | this._getNextToken()
302 | if (this.val == ',') {
303 | this._getNextToken()
304 | } else if (this.val == ';') {
305 | break
306 | } else {
307 | this._error(', | ;')
308 | }
309 | }
310 | } else {
311 | this._error('keyword | identifier')
312 | }
313 |
314 | this._getNextToken()
315 |
316 | if (this.val == 'var') {
317 | localNum = this._compileVarDec(localNum)
318 | } else {
319 | this.i--
320 | }
321 |
322 | return localNum
323 | },
324 |
325 | _compileStatements() {
326 | while (this.val !== '}') {
327 | switch (this.val) {
328 | case 'if':
329 | this._compileIf()
330 | break
331 | case 'while':
332 | this._compileWhile()
333 | break
334 | case 'do':
335 | this._compileDo()
336 | vm.writePop('temp', 0)
337 | break
338 | case 'return':
339 | this._compileReturn()
340 | break
341 | case 'let':
342 | this._compileLet()
343 | break
344 | default:
345 | this._error('if | while | do | return | let')
346 | }
347 |
348 | this._getNextToken()
349 | }
350 | this.i--
351 | },
352 |
353 | _compileDo() {
354 | let funcTempArry
355 | let funcName = ''
356 | let isMethod
357 | this._getNextToken()
358 | if (this.key == 'identifier') {
359 | // 变量or类
360 | funcTempArry = this._getTypeOfVariable(this.val)
361 | if (funcTempArry[1]) {
362 | isMethod = true
363 | funcName += funcTempArry[0]
364 | variableOfCall = this.val
365 | } else {
366 | funcName += this.val
367 | }
368 |
369 | this._getNextToken()
370 | if (this.val == '.') {
371 | this._compilePartOfCall(funcName, isMethod)
372 | } else if (this.val == '(') {
373 | isMethod = true
374 | funcName = this.class + '.' + funcName
375 | this._compilePartOfCall(funcName, isMethod)
376 | } else {
377 | this._error('. | )')
378 | }
379 |
380 |
381 | this._getNextToken()
382 | if (this.val != ';') {
383 | this._error(';')
384 | }
385 | } else {
386 | this._error('identifier')
387 | }
388 | },
389 |
390 | _compileLet() {
391 | let variable = ''
392 |
393 | this._getNextToken()
394 | if (this.key == 'identifier') {
395 | variable += this.val
396 |
397 | this._getNextToken()
398 | if (this.val == '[') {
399 | this._compileExpression()
400 | this._getNextToken()
401 | if (this.val == ']') {
402 | this._getNextToken()
403 | this._writeVariable(variable)
404 | vm.writeArithmetic('add')
405 | if (this.val == '=') {
406 | this._compileExpression()
407 |
408 | vm.writePop('temp', 0)
409 | vm.writePop('pointer', 1)
410 | vm.writePush('temp', 0)
411 | vm.writePop('that', 0)
412 | } else {
413 | this._error('=')
414 | }
415 | } else {
416 | this._error(']')
417 | }
418 | } else if (this.val == '=') {
419 | this._compileExpression()
420 | // pop
421 | this._writeVariable(variable, true)
422 | } else {
423 | this._error('[ | =')
424 | }
425 |
426 | this._getNextToken()
427 | if (this.val != ';') {
428 | this._error(';')
429 | }
430 | } else {
431 | this._error('identifier')
432 | }
433 | },
434 |
435 | _compileWhile() {
436 | let tempIndex = whileIndex++
437 |
438 | this._getNextToken()
439 | vm.writeLabel(whileLabel + tempIndex)
440 | if (this.val == '(') {
441 | this._compileExpression()
442 | vm.writeArithmetic('not')
443 | vm.writeIf(whileEndLabel + tempIndex)
444 |
445 | this._getNextToken()
446 | if (this.val == ')') {
447 | this._getNextToken()
448 | if (this.val == '{') {
449 | this._getNextToken()
450 | this._compileStatements()
451 | this._getNextToken()
452 | vm.writeGoto(whileLabel + tempIndex)
453 | vm.writeLabel(whileEndLabel + tempIndex)
454 |
455 | if (this.val != '}') {
456 | this._error('}')
457 | }
458 | } else {
459 | this._error('{')
460 | }
461 | } else {
462 | this._error(')')
463 | }
464 | } else {
465 | this._error('(')
466 | }
467 | },
468 |
469 | _compileIf() {
470 | let tempIndex = ifIndex++
471 | this._getNextToken()
472 | if (this.val == '(') {
473 | this._compileExpression()
474 |
475 | this._getNextToken()
476 | if (this.val == ')') {
477 | this._getNextToken()
478 | vm.writeIf(ifTrueLabel + tempIndex)
479 | vm.writeGoto(ifFalseLabel + tempIndex)
480 |
481 | if (this.val == '{') {
482 | this._getNextToken()
483 | vm.writeLabel(ifTrueLabel + tempIndex)
484 | this._compileStatements()
485 | this._getNextToken()
486 | if (this.val == '}') {
487 | this._getNextToken()
488 | if (this.val == 'else') {
489 | vm.writeGoto(ifEndLabel + tempIndex)
490 | vm.writeLabel(ifFalseLabel + tempIndex)
491 | this._compileElse()
492 | vm.writeLabel(ifEndLabel + tempIndex)
493 | } else {
494 | vm.writeLabel(ifFalseLabel + tempIndex)
495 | this.i--
496 | }
497 | } else {
498 | this._error('}')
499 | }
500 | } else {
501 | this._error(')')
502 | }
503 | } else {
504 | this._error(')')
505 | }
506 | } else {
507 | this._error('(')
508 | }
509 | },
510 |
511 | _compileElse() {
512 | this._getNextToken()
513 | if (this.val == '{') {
514 | this._getNextToken()
515 | this._compileStatements()
516 | this._getNextToken()
517 | if (this.val != '}') {
518 | this._error('}')
519 | }
520 | } else {
521 | this._error('{')
522 | }
523 | },
524 |
525 | _compileReturn() {
526 | this._getNextToken()
527 | if (this.val == ';') {
528 | vm.writePush('constant', 0)
529 | vm.writeReturn()
530 | } else if (this.val == 'this') {
531 | this.i--
532 | this._compileExpression()
533 |
534 | this._getNextToken()
535 | if (this.val == ';') {
536 | vm.writeReturn()
537 | } else {
538 | this._error(';')
539 | }
540 | } else {
541 | this.i--
542 | this._compileExpression()
543 | this._getNextToken()
544 | if (this.val == ';') {
545 | vm.writeReturn()
546 | } else {
547 | this._error(';')
548 | }
549 | }
550 | },
551 |
552 | _compileExpression() {
553 | this._compileTerm()
554 | let op
555 |
556 | while (true) {
557 | this._getNextToken()
558 | if (opObj[this.val] !== undefined) {
559 | op = opObj[this.val]
560 | this._compileTerm()
561 | vm.writeArithmetic(op)
562 | } else {
563 | this.i--
564 | break
565 | }
566 | }
567 | },
568 |
569 | _compileTerm() {
570 | let tempName
571 | let isMethod
572 | let funcName = ''
573 | this._getNextToken()
574 | if (this.key == 'identifier') {
575 | tempName = this.val
576 | this._getNextToken()
577 | switch (this.val) {
578 | case '(':
579 | isMethod = true
580 | funcName = this.class + '.' + tempName
581 | this._compilePartOfCall(funcName, isMethod)
582 | break
583 | case '.':
584 | let funcTempArry = this._getTypeOfVariable(tempName)
585 | if (funcTempArry[1]) {
586 | isMethod = true
587 | funcName += funcTempArry[0]
588 | variableOfCall = tempName
589 | } else {
590 | funcName += tempName
591 | }
592 |
593 | this._compilePartOfCall(funcName, isMethod)
594 | break
595 | case '[':
596 | this._compileExpression()
597 | this._getNextToken()
598 | if (this.val == ']') {
599 | this._writeVariable(tempName)
600 | vm.writeArithmetic('add')
601 | vm.writePop('pointer', 1)
602 | vm.writePush('that', 0)
603 | } else {
604 | this._error(']')
605 | }
606 | break
607 | default:
608 | this.i--
609 | this._writeVariable(tempName)
610 | }
611 | } else if (this.key == 'integerConstant') {
612 | vm.writePush('constant', this.val)
613 | } else if (this.key == 'stringConstant') {
614 | let strArry = [...this.val]
615 | let length = strArry.length
616 | let code
617 |
618 | vm.writePush('constant', length)
619 | vm.writeCall('String.new', 1)
620 | strArry.forEach(s => {
621 | code = s.charCodeAt()
622 | vm.writePush('constant', code)
623 | vm.writeCall('String.appendChar', 2)
624 | })
625 | } else {
626 | switch (this.val) {
627 | case '(':
628 | this._compileExpression()
629 | this._getNextToken()
630 | if (this.val != ')') {
631 | this._error(')')
632 | }
633 | break
634 | case '-':
635 | this._compileTerm()
636 | vm.writeArithmetic('neg')
637 | break
638 | case '~':
639 | this._compileTerm()
640 | vm.writeArithmetic('not')
641 | break
642 | case 'null':
643 | case 'false':
644 | vm.writePush('constant', 0)
645 | break
646 | case 'true':
647 | vm.writePush('constant', 0)
648 | vm.writeArithmetic('not')
649 | break
650 | case 'this':
651 | vm.writePush('pointer', 0)
652 | break
653 | default:
654 | this._error('Unknown symbol')
655 | }
656 | }
657 | },
658 |
659 | _compilePartOfCall(funcName, isMethod) {
660 | let nArgs
661 | if (this.val == '(') {
662 | // 如果是方法根据是变量.xxx或类.xxx来传入第一个参数
663 | if (isMethod) {
664 | nArgs++
665 | if (variableOfCall) {
666 | this._writeVariable(variableOfCall)
667 | variableOfCall = ''
668 | } else {
669 | vm.writePush('pointer', 0)
670 | }
671 | }
672 |
673 | nArgs = this._compileExpressionList()
674 |
675 | if (isMethod) {
676 | nArgs++
677 | }
678 |
679 | this._getNextToken()
680 | if (this.val != ')') {
681 | this._error(')')
682 | }
683 | } else if (this.val == '.') {
684 | funcName += this.val
685 |
686 | this._getNextToken()
687 | if (this.key == 'identifier') {
688 | funcName += this.val
689 |
690 | this._getNextToken()
691 | if (this.val == '(') {
692 | // 如果是方法根据是变量.xxx或类.xxx来传入第一个参数
693 | if (isMethod) {
694 | nArgs++
695 | if (variableOfCall) {
696 | this._writeVariable(variableOfCall)
697 | variableOfCall = ''
698 | } else {
699 | vm.writePush('pointer', 0)
700 | }
701 | }
702 |
703 | nArgs = this._compileExpressionList()
704 |
705 | if (isMethod) {
706 | nArgs++
707 | }
708 |
709 | this._getNextToken()
710 | if (this.val != ')') {
711 | this._error(')')
712 | }
713 | } else {
714 | this.error(key, val, '(')
715 | }
716 | } else {
717 | this.error(key, val, 'identifier')
718 | }
719 | } else {
720 | this._error('( | .')
721 | }
722 |
723 | vm.writeCall(funcName, nArgs)
724 | },
725 |
726 | _compileExpressionList() {
727 | let nArgs = 0
728 | this._getNextToken()
729 | if (this.val == ')' || this.val == ',') {
730 | this.i--
731 | } else {
732 | this.i--
733 | while (true) {
734 | nArgs++
735 | this._compileExpression()
736 | this._getNextToken()
737 | if (this.val == ')') {
738 | this.i--
739 | break
740 | }
741 | }
742 | }
743 | return nArgs
744 | },
745 |
746 | createXMLFile() {
747 | fs.writeFileSync(this.outputPath, this.output)
748 | },
749 |
750 | _getNextToken() {
751 | this.i++
752 | let obj = this.tokens[this.i]
753 | let key = Object.keys(obj)[0]
754 | let val = obj[key]
755 |
756 | this.key = key
757 | this.val = val
758 | this.line = obj.line
759 | },
760 |
761 | _error(type) {
762 | let error = 'line:' + this.tokens[this.i].line + ' syntax error: {' + this.key + ': ' + this.val
763 | + '}\r\nExpect the type of key to be ' + type + '\r\nat ' + this.rawFile
764 | throw error
765 | },
766 |
767 | _writeVariable(val, flag) {
768 | let segment = subTable.kindOf(val)
769 | if (segment == 'none') {
770 | segment = mainTable.kindOf(val)
771 | if (segment != 'none') {
772 | if (segment == 'field') {
773 | segment = 'this'
774 | }
775 | if (flag) {
776 | vm.writePop(segment, mainTable.indexOf(val))
777 | } else {
778 | vm.writePush(segment, mainTable.indexOf(val))
779 | }
780 | }
781 | } else {
782 | if (flag) {
783 | vm.writePop(segment, subTable.indexOf(val))
784 | } else {
785 | vm.writePush(segment, subTable.indexOf(val))
786 | }
787 | }
788 | },
789 |
790 | _getTypeOfVariable(name) {
791 | let type = subTable.typeOf(name)
792 | if (type == 'none') {
793 | type = mainTable.typeOf(name)
794 | if (type == 'none') {
795 | return [name, false]
796 | } else {
797 | return [type, true]
798 | }
799 | } else {
800 | return [type, true]
801 | }
802 | }
803 | }
804 |
805 | module.exports = CompilationEngine
--------------------------------------------------------------------------------
/11/symbolTable.js:
--------------------------------------------------------------------------------
1 | function SymbolTable() {
2 | this.table = {}
3 | this.kindIndex = {
4 | static: 0,
5 | field: 0,
6 | argument: 0,
7 | local: 0
8 | }
9 | }
10 |
11 | SymbolTable.prototype = {
12 | startSubroutine() {
13 | this.table = {}
14 | this.kindIndex = {
15 | static: 0,
16 | field: 0,
17 | argument: 0,
18 | local: 0
19 | }
20 | },
21 |
22 | define(name, type, kind) {
23 | this.table[name] = [type, kind, this.kindIndex[kind]++]
24 | },
25 |
26 | varCount(kind) {
27 | return this.kindIndex[kind]
28 | },
29 |
30 | kindOf(name) {
31 | let val = this.table[name]
32 | if (val === undefined) {
33 | return 'none'
34 | } else {
35 | return val[1]
36 | }
37 | },
38 |
39 | typeOf(name) {
40 | let val = this.table[name]
41 | if (val === undefined) {
42 | return 'none'
43 | } else {
44 | return val[0]
45 | }
46 | },
47 |
48 | indexOf(name) {
49 | let val = this.table[name]
50 | if (val === undefined) {
51 | return 'none'
52 | } else {
53 | return val[2]
54 | }
55 | }
56 | }
57 |
58 | module.exports = SymbolTable
--------------------------------------------------------------------------------
/11/tokenizer.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | function JackTokenizer(data, fileName) {
4 | this.data = data
5 | this.rawData = data.slice()
6 | this.tokens = []
7 | this.rawFile = fileName + '.jack'
8 | this.outputPath = fileName + 'T.xml'
9 | // 单行注释
10 | this.notesRe1 = /^(\/\/)/
11 | /* */
12 | this.notesRe2 = /^(\/\*).*(\*\/)$/
13 | /** */
14 | this.notesRe3 = /^(\/\*\*).*(\*\/)$/
15 | // 匹配行中的注释
16 | this.re1 = /(\/\/).*/
17 | this.re2 = /(\/\*).*(\*\/)/
18 | this.re3 = /(\/\*\*).*(\*\/)/
19 | // 匹配多行注释
20 | this.reg1 = /^(\/\*)/
21 | this.reg2 = /^(\/\*\*)/
22 | this.reg3 = /^\*[^\/]/
23 | this.reg4 = /^(\*\/)/
24 | this.reg5 = /^\*[^\/].*(\*\/)$/
25 |
26 | // 单个字符匹配
27 | this.wordRe = /\w|_/
28 |
29 | // 标识符
30 | this.identifierRe = /^[^\d].*/
31 |
32 | // 字符串常量
33 | this.strRe = /^".*"$/
34 | this.strRe1 = /^"/
35 | this.strRe2 = /"$/
36 |
37 | // 行数
38 | this.line = 0
39 | this.keywordType = [
40 | 'class', 'constructor', 'function', 'method', 'field', 'static', 'var', 'int', 'char', 'boolean',
41 | 'void', 'true', 'false', 'null', 'this', 'let', 'do', 'if', 'else', 'while', 'return'
42 | ]
43 | this.symbolType = [
44 | '{', '}', '(', ')', '[', ']', '.', ',', ';', '+', '-', '*', '/', '&', '|', '<', '>', '=', '~',
45 | '<', '>', '&'
46 | ]
47 |
48 | this._init()
49 | }
50 |
51 | JackTokenizer.prototype = {
52 | _init() {
53 | const data = this.data
54 |
55 | while (this._hasMoreTokens(data)) {
56 | let str = data.shift().trim()
57 | this.line++
58 |
59 | if (this._isVaildStr(str)) {
60 | // 清除字符串中的注释
61 | str = str.replace(this.re1, '')
62 | str = str.replace(this.re2, '')
63 | str = str.replace(this.re3, '').trim()
64 | this._lexicalAnalysis(str)
65 | }
66 | }
67 | },
68 |
69 | _hasMoreTokens(data) {
70 | return data.length > 0? true : false
71 | },
72 |
73 | _advance(token) {
74 | const type = this._tokenType(token)
75 |
76 | switch (type) {
77 | case 'keyword':
78 | this._keyword(token)
79 | break
80 | case 'symbol':
81 | this._symbol(token)
82 | break
83 | case 'int-const':
84 | this._intVal(token)
85 | break
86 | case 'identifier':
87 | this._identifier(token)
88 | break
89 | case 'string_const':
90 | this._stringVal(token)
91 | break
92 | }
93 | },
94 |
95 | _tokenType(token) {
96 | if (this.keywordType.includes(token)) {
97 | return 'keyword'
98 | } else if (this.symbolType.includes(token)) {
99 | return 'symbol'
100 | } else if (this.strRe.test(token)) {
101 | return 'string_const'
102 | } else if (this.identifierRe.test(token)) {
103 | return 'identifier'
104 | } else if (0 <= parseFloat(token) <= 32767) {
105 | return 'int-const'
106 | } else {
107 | let error = 'line:' + this.line + ' syntax error:' + token + '\r\n'
108 | + this.rawData[this.line - 1].trim() + '\r\nat ' + this.rawFile
109 | throw error
110 | }
111 | },
112 |
113 | _keyword(token) {
114 | this.tokens.push({keyword: token, line: this.line})
115 | },
116 |
117 | _symbol(token) {
118 | this.tokens.push({symbol: token, line: this.line})
119 | },
120 |
121 | _identifier(token) {
122 | this.tokens.push({identifier: token, line: this.line})
123 | },
124 |
125 | _intVal(token) {
126 | this.tokens.push({integerConstant: token, line: this.line})
127 | },
128 |
129 | _stringVal(token) {
130 | token = token.replace(this.strRe1, '')
131 | token = token.replace(this.strRe2, '')
132 |
133 | this.tokens.push({stringConstant: token, line: this.line})
134 | },
135 |
136 | _isVaildStr(str) {
137 | if (this.notesRe1.test(str) || this.notesRe2.test(str) || this.notesRe3.test(str)) {
138 | return false
139 | } else if (this.reg1.test(str) || this.reg2.test(str)) {
140 |
141 | while (this._hasMoreTokens(this.data)) {
142 | this.line++
143 | str = this.data.shift().trim()
144 |
145 | if (this.reg4.test(str) || this.reg5.test(str)) {
146 | break
147 | } else if (this.reg3.test(str)) {
148 | continue
149 | }
150 | }
151 |
152 | return false
153 | } else if (str === '') {
154 | return false
155 | }
156 |
157 | return true
158 | },
159 |
160 | _lexicalAnalysis(str) {
161 | // c=a+b; 分割成 ['c', '=', 'a', '+', 'b', ';']
162 | const tokens = str.split('')
163 | let i = 0
164 | let len = tokens.length
165 | let token = ''
166 | let word
167 |
168 | while (true) {
169 | word = tokens[i]
170 |
171 | if (this.wordRe.test(word)) {
172 | token += word
173 | i++
174 | } else if (word === ' ') {
175 | if (token !== '') {
176 | this._advance(token)
177 | token = ''
178 | i++
179 | } else {
180 | i++
181 | }
182 | } else if (this.symbolType.includes(word)) {
183 | switch (word) {
184 | case '>':
185 | word = '>'
186 | break
187 | case '<':
188 | word = '<'
189 | break
190 | case '&':
191 | word = '&'
192 | break
193 | }
194 |
195 | if (token !== '') {
196 | this._advance(token)
197 | this._advance(word)
198 | token = ''
199 | i++
200 | } else {
201 | this._advance(word)
202 | i++
203 | }
204 |
205 | } else if (word === '"') {
206 | while (true) {
207 | token += word
208 | i++
209 | word = tokens[i]
210 | if (word === '"') {
211 | token += word
212 | this._advance(token)
213 | token = ''
214 | i++
215 | break
216 | }
217 |
218 | if (i >= len) {
219 | if (token !== '') {
220 | this._advance(token)
221 | }
222 | break
223 | }
224 | }
225 | }
226 |
227 | if (i >= len) {
228 | if (token !== '') {
229 | this._advance(token)
230 | }
231 | break
232 | }
233 | }
234 | },
235 |
236 | getTokens() {
237 | return this.tokens
238 | },
239 |
240 | createXMLFile() {
241 | let output = '\r\n'
242 | this.tokens.forEach(token => {
243 | const key = Object.keys(token)[0]
244 | const value = token[key]
245 | output += `<${key}> ${value} ${key}>\r\n`
246 | })
247 | output += ''
248 | fs.writeFileSync(this.outputPath, output)
249 | }
250 | }
251 |
252 |
253 | module.exports = JackTokenizer
--------------------------------------------------------------------------------
/12/Array.jack:
--------------------------------------------------------------------------------
1 | /**
2 | * Represents an array.
3 | * In the Jack language, arrays are instances of the Array class.
4 | * Once declared, the array entries can be accessed using the usual
5 | * syntax arr[i]. Each array entry can hold a primitive data type as
6 | * well as any object type. Different array entries can have different
7 | * data types.
8 | */
9 | class Array {
10 | /** Constructs a new Array of the given size. */
11 | function Array new(int size) {
12 | return Memory.alloc(size);
13 | }
14 |
15 | /** Disposes this array. */
16 | method void dispose() {
17 | do Memory.deAlloc(this);
18 | return;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/12/Keyboard.jack:
--------------------------------------------------------------------------------
1 | /**
2 | * A library for handling user input from the keyboard.
3 | */
4 | class Keyboard {
5 |
6 | /** Initializes the keyboard. */
7 | function void init() {
8 | return;
9 | }
10 |
11 | /**
12 | * Returns the character of the currently pressed key on the keyboard;
13 | * if no key is currently pressed, returns 0.
14 | *
15 | * Recognizes all ASCII characters, as well as the following keys:
16 | * new line = 128 = String.newline()
17 | * backspace = 129 = String.backspace()
18 | * left arrow = 130
19 | * up arrow = 131
20 | * right arrow = 132
21 | * down arrow = 133
22 | * home = 134
23 | * End = 135
24 | * page up = 136
25 | * page down = 137
26 | * insert = 138
27 | * delete = 139
28 | * ESC = 140
29 | * F1 - F12 = 141 - 152
30 | */
31 | function char keyPressed() {
32 | return Memory.peek(24576);
33 | }
34 |
35 | /**
36 | * Waits until a key is pressed on the keyboard and released,
37 | * then echoes the key to the screen, and returns the character
38 | * of the pressed key.
39 | */
40 | function char readChar() {
41 | var char c, temp;
42 |
43 | do Output.printChar(0);
44 |
45 | while (true) {
46 | let temp = Keyboard.keyPressed();
47 |
48 | if (temp > 0) {
49 | let c = temp;
50 | while (true) {
51 | let temp = Keyboard.keyPressed();
52 | if (temp = 0) {
53 | do Output.backSpace();
54 | if (~(c = 129) & (~(c = 128))) {
55 | do Output.printChar(c);
56 | }
57 | return c;
58 | }
59 | }
60 | }
61 | }
62 | return 0;
63 | }
64 |
65 | /**
66 | * Displays the message on the screen, reads from the keyboard the entered
67 | * text until a newline character is detected, echoes the text to the screen,
68 | * and returns its value. Also handles user backspaces.
69 | */
70 | function String readLine(String message) {
71 | var String s;
72 | var char c;
73 |
74 | do Output.printString(message);
75 | let s = String.new(80);
76 |
77 | while (true) {
78 | let c = Keyboard.readChar();
79 |
80 | if (c = 128) {
81 | do Output.println();
82 | return s;
83 | }
84 |
85 | if (c = 129) {
86 | do s.eraseLastChar();
87 | do Output.backSpace();
88 | } else {
89 | let s = s.appendChar(c);
90 | }
91 | }
92 | return 0;
93 | }
94 |
95 | /**
96 | * Displays the message on the screen, reads from the keyboard the entered
97 | * text until a newline character is detected, echoes the text to the screen,
98 | * and returns its integer value (until the first non-digit character in the
99 | * entered text is detected). Also handles user backspaces.
100 | */
101 | function int readInt(String message) {
102 | var String s;
103 |
104 | let s = Keyboard.readLine(message);
105 | return s.intValue();
106 | }
107 | }
--------------------------------------------------------------------------------
/12/Math.jack:
--------------------------------------------------------------------------------
1 | /**
2 | * A library of commonly used mathematical functions.
3 | * Note: Jack compilers implement multiplication and division using OS method calls.
4 | */
5 | class Math {
6 | static Array bitArray;
7 | /** Initializes the library. */
8 | function void init() {
9 | let bitArray = Array.new(16);
10 | let bitArray[0] = 1;
11 | let bitArray[1] = 2;
12 | let bitArray[2] = 4;
13 | let bitArray[3] = 8;
14 | let bitArray[4] = 16;
15 | let bitArray[5] = 32;
16 | let bitArray[6] = 64;
17 | let bitArray[7] = 128;
18 | let bitArray[8] = 256;
19 | let bitArray[9] = 512;
20 | let bitArray[10] = 1024;
21 | let bitArray[11] = 2048;
22 | let bitArray[12] = 4096;
23 | let bitArray[13] = 8192;
24 | let bitArray[14] = 16384;
25 | let bitArray[15] = 16384 + 16384;
26 | return;
27 | }
28 |
29 | /** Returns the absolute value of x. */
30 | function int abs(int x) {
31 | if (x < 0) {
32 | return -x;
33 | }
34 | return x;
35 | }
36 |
37 | /** Returns the product of x and y.
38 | * When a Jack compiler detects the multiplication operator '*' in the
39 | * program's code, it handles it by invoking this method. In other words,
40 | * the Jack expressions x*y and multiply(x,y) return the same value.
41 | */
42 | function int multiply(int x, int y) {
43 | var int sum, index, mask;
44 |
45 | let mask = 1;
46 | while (index < 16) {
47 | if (y & mask) {
48 | let sum = sum + x;
49 | }
50 | let x = x + x;
51 | let index = index + 1;
52 | let mask = mask + mask;
53 | }
54 |
55 | return sum;
56 | }
57 |
58 | /** Returns the integer part of x/y.
59 | * When a Jack compiler detects the multiplication operator '/' in the
60 | * program's code, it handles it by invoking this method. In other words,
61 | * the Jack expressions x/y and divide(x,y) return the same value.
62 | */
63 | function int divide(int x, int y) {
64 | var int q;
65 |
66 | if (y = 0) {
67 | do Sys.error(3);
68 | return 0;
69 | }
70 |
71 | let q = Math._div(Math.abs(x), Math.abs(y));
72 |
73 | if ((x < 0) = (y < 0)) {
74 | return q;
75 | } else {
76 | return -q;
77 | }
78 | }
79 |
80 | function int _div(int x, int y) {
81 | var int q, result, sum;
82 |
83 | if (y > x) {
84 | return 0;
85 | }
86 |
87 | if (y < 16384) {
88 | let q = Math._div(x, y + y);
89 | } else {
90 | let q = 0;
91 | }
92 |
93 | let sum = q + q;
94 |
95 | if ((x - (sum * y)) < y) {
96 | let result = sum;
97 | } else {
98 | let result = sum + 1;
99 | }
100 |
101 | return result;
102 | }
103 |
104 | /** Returns the integer part of the square root of x. */
105 | function int sqrt(int x) {
106 | var int y, j, temp, tempTwo;
107 |
108 | if (x < 0) {
109 | do Sys.error(4);
110 | return 0;
111 | }
112 |
113 | let y = 0;
114 | let j = 7;
115 |
116 | while (~(j < 0)) {
117 | let temp = y + bitArray[j];
118 | let tempTwo = temp * temp;
119 |
120 | if (~(tempTwo > x) & (tempTwo > 0)) {
121 | let y = temp;
122 | }
123 | let j = j - 1;
124 | }
125 | return y;
126 | }
127 |
128 | /** Returns the greater number. */
129 | function int max(int a, int b) {
130 | if (a > b) {
131 | return a;
132 | }
133 | return b;
134 | }
135 |
136 | /** Returns the smaller number. */
137 | function int min(int a, int b) {
138 | if (a < b) {
139 | return a;
140 | }
141 | return b;
142 | }
143 |
144 | function int mod(int a, int b) {
145 | return a - (Math.divide(a, b) * b);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/12/Memory.jack:
--------------------------------------------------------------------------------
1 | /**
2 | * This library provides two services: direct access to the computer's main
3 | * memory (RAM), and allocation and recycling of memory blocks. The Hack RAM
4 | * consists of 32,768 words, each holding a 16-bit binary number.
5 | */
6 | class Memory {
7 | static Array memoryBaseAddress, freeList;
8 |
9 | /** Initializes the class. */
10 | function void init() {
11 | var int len;
12 | // heap address 2048--16383
13 |
14 | let memoryBaseAddress = 0;
15 | let len = 16383 - 2048 + 1;
16 | let freeList = 2048;
17 |
18 | // 0 next segment
19 | // 1 segment block size
20 | let freeList[0] = null;
21 | let freeList[1] = len - 2;
22 |
23 | return;
24 | }
25 |
26 | /** Returns the RAM value at the given address. */
27 | function int peek(int address) {
28 | return memoryBaseAddress[address];
29 | }
30 |
31 | /** Sets the RAM value at the given address to the given value. */
32 | function void poke(int address, int value) {
33 | let memoryBaseAddress[address] = value;
34 | return;
35 | }
36 |
37 | /** Finds an available RAM block of the given size and returns
38 | * a reference to its base address. */
39 | function int alloc(int size) {
40 | var int allocSize;
41 | var Array segment;
42 |
43 | let allocSize = size + 2;
44 |
45 | if (freeList[1] > allocSize) {
46 | let freeList[1] = freeList[1] - allocSize;
47 | let segment = freeList + 2 + freeList[1];
48 | let segment[0] = null;
49 | let segment[1] = size;
50 | } else {
51 | let segment = Memory.bestFit(size);
52 | }
53 |
54 | // block
55 | return segment + 2;
56 | }
57 |
58 | /** De-allocates the given object (cast as an array) by making
59 | * it available for future allocations. */
60 | function void deAlloc(Array o) {
61 | var Array segment, pre, next, temp;
62 |
63 | let segment = o - 2;
64 | let pre = freeList;
65 | let next = freeList[0];
66 |
67 | while (~(next = null) & (next < segment)) {
68 | let pre = next;
69 | let next = next[0];
70 | }
71 |
72 | if (next = null) {
73 | let segment[0] = null;
74 | let pre[0] = segment;
75 | } else {
76 | let temp = pre[0];
77 | let pre[0] = segment;
78 | let segment[0] = temp;
79 | }
80 |
81 | if ((segment + segment[1] + 2) = next) {
82 | let segment[1] = segment[1] + next[1] + 2;
83 | let segment[0] = next[0];
84 | }
85 |
86 | if ((pre + pre[1] + 2) = segment) {
87 | let pre[1] = pre[1] + segment[1] + 2;
88 | let pre[0] = segment[0];
89 | }
90 | return;
91 | }
92 |
93 |
94 | function Array bestFit(int size) {
95 | var Array temp, segment;
96 | var int tempSize;
97 |
98 | let tempSize = size + 2;
99 | let temp = freeList;
100 |
101 | while (temp[1] < tempSize) {
102 | // next address
103 | if (temp[0] = null) {
104 | do Sys.error(5);
105 | return 0;
106 | } else {
107 | let temp = temp[0];
108 | }
109 | }
110 |
111 | let temp[1] = temp[1] - tempSize;
112 | let segment = temp + 2 + temp[1];
113 | let segment[0] = null;
114 | let segment[1] = size;
115 |
116 | return segment;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/12/Output.jack:
--------------------------------------------------------------------------------
1 | /**
2 | * A library of functions for writing text on the screen.
3 | * The Hack physical screen consists of 512 rows of 256 pixels each.
4 | * The library uses a fixed font, in which each character is displayed
5 | * within a frame which is 11 pixels high (including 1 pixel for inter-line
6 | * spacing) and 8 pixels wide (including 2 pixels for inter-character spacing).
7 | * The resulting grid accommodates 23 rows (indexed 0..22, top to bottom)
8 | * of 64 characters each (indexed 0..63, left to right). The top left
9 | * character position on the screen is indexed (0,0). A cursor, implemented
10 | * as a small filled square, indicates where the next character will be displayed.
11 | */
12 | class Output {
13 |
14 | // Character map for displaying characters
15 | static Array charMaps;
16 | static int screenRow, screenCol;
17 | static Array screenAddress;
18 | /** Initializes the screen, and locates the cursor at the screen's top-left. */
19 | function void init() {
20 | // screen 16384 -- 24575
21 | let screenAddress = 16384;
22 |
23 | do Output.initMap();
24 | do Output.moveCursor(0, 0);
25 | return;
26 | }
27 |
28 | // Initializes the character map array
29 | function void initMap() {
30 | let charMaps = Array.new(127);
31 |
32 | // Black square, used for displaying non-printable characters.
33 | do Output.create(0,63,63,63,63,63,63,63,63,63,0,0);
34 |
35 | // Assigns the bitmap for each character in the charachter set.
36 | // The first parameter is the character index, the next 11 numbers
37 | // are the values of each row in the frame that represents this character.
38 | do Output.create(32,0,0,0,0,0,0,0,0,0,0,0); //
39 | do Output.create(33,12,30,30,30,12,12,0,12,12,0,0); // !
40 | do Output.create(34,54,54,20,0,0,0,0,0,0,0,0); // "
41 | do Output.create(35,0,18,18,63,18,18,63,18,18,0,0); // #
42 | do Output.create(36,12,30,51,3,30,48,51,30,12,12,0); // $
43 | do Output.create(37,0,0,35,51,24,12,6,51,49,0,0); // %
44 | do Output.create(38,12,30,30,12,54,27,27,27,54,0,0); // &
45 | do Output.create(39,12,12,6,0,0,0,0,0,0,0,0); // '
46 | do Output.create(40,24,12,6,6,6,6,6,12,24,0,0); // (
47 | do Output.create(41,6,12,24,24,24,24,24,12,6,0,0); // )
48 | do Output.create(42,0,0,0,51,30,63,30,51,0,0,0); // *
49 | do Output.create(43,0,0,0,12,12,63,12,12,0,0,0); // +
50 | do Output.create(44,0,0,0,0,0,0,0,12,12,6,0); // ,
51 | do Output.create(45,0,0,0,0,0,63,0,0,0,0,0); // -
52 | do Output.create(46,0,0,0,0,0,0,0,12,12,0,0); // .
53 | do Output.create(47,0,0,32,48,24,12,6,3,1,0,0); // /
54 |
55 | do Output.create(48,12,30,51,51,51,51,51,30,12,0,0); // 0
56 | do Output.create(49,12,14,15,12,12,12,12,12,63,0,0); // 1
57 | do Output.create(50,30,51,48,24,12,6,3,51,63,0,0); // 2
58 | do Output.create(51,30,51,48,48,28,48,48,51,30,0,0); // 3
59 | do Output.create(52,16,24,28,26,25,63,24,24,60,0,0); // 4
60 | do Output.create(53,63,3,3,31,48,48,48,51,30,0,0); // 5
61 | do Output.create(54,28,6,3,3,31,51,51,51,30,0,0); // 6
62 | do Output.create(55,63,49,48,48,24,12,12,12,12,0,0); // 7
63 | do Output.create(56,30,51,51,51,30,51,51,51,30,0,0); // 8
64 | do Output.create(57,30,51,51,51,62,48,48,24,14,0,0); // 9
65 |
66 | do Output.create(58,0,0,12,12,0,0,12,12,0,0,0); // :
67 | do Output.create(59,0,0,12,12,0,0,12,12,6,0,0); // ;
68 | do Output.create(60,0,0,24,12,6,3,6,12,24,0,0); // <
69 | do Output.create(61,0,0,0,63,0,0,63,0,0,0,0); // =
70 | do Output.create(62,0,0,3,6,12,24,12,6,3,0,0); // >
71 | do Output.create(64,30,51,51,59,59,59,27,3,30,0,0); // @
72 | do Output.create(63,30,51,51,24,12,12,0,12,12,0,0); // ?
73 |
74 | do Output.create(65,12,30,51,51,63,51,51,51,51,0,0); // A ** TO BE FILLED **
75 | do Output.create(66,31,51,51,51,31,51,51,51,31,0,0); // B
76 | do Output.create(67,28,54,35,3,3,3,35,54,28,0,0); // C
77 | do Output.create(68,15,27,51,51,51,51,51,27,15,0,0); // D
78 | do Output.create(69,63,51,35,11,15,11,35,51,63,0,0); // E
79 | do Output.create(70,63,51,35,11,15,11,3,3,3,0,0); // F
80 | do Output.create(71,28,54,35,3,59,51,51,54,44,0,0); // G
81 | do Output.create(72,51,51,51,51,63,51,51,51,51,0,0); // H
82 | do Output.create(73,30,12,12,12,12,12,12,12,30,0,0); // I
83 | do Output.create(74,60,24,24,24,24,24,27,27,14,0,0); // J
84 | do Output.create(75,51,51,51,27,15,27,51,51,51,0,0); // K
85 | do Output.create(76,3,3,3,3,3,3,35,51,63,0,0); // L
86 | do Output.create(77,33,51,63,63,51,51,51,51,51,0,0); // M
87 | do Output.create(78,51,51,55,55,63,59,59,51,51,0,0); // N
88 | do Output.create(79,30,51,51,51,51,51,51,51,30,0,0); // O
89 | do Output.create(80,31,51,51,51,31,3,3,3,3,0,0); // P
90 | do Output.create(81,30,51,51,51,51,51,63,59,30,48,0);// Q
91 | do Output.create(82,31,51,51,51,31,27,51,51,51,0,0); // R
92 | do Output.create(83,30,51,51,6,28,48,51,51,30,0,0); // S
93 | do Output.create(84,63,63,45,12,12,12,12,12,30,0,0); // T
94 | do Output.create(85,51,51,51,51,51,51,51,51,30,0,0); // U
95 | do Output.create(86,51,51,51,51,51,30,30,12,12,0,0); // V
96 | do Output.create(87,51,51,51,51,51,63,63,63,18,0,0); // W
97 | do Output.create(88,51,51,30,30,12,30,30,51,51,0,0); // X
98 | do Output.create(89,51,51,51,51,30,12,12,12,30,0,0); // Y
99 | do Output.create(90,63,51,49,24,12,6,35,51,63,0,0); // Z
100 |
101 | do Output.create(91,30,6,6,6,6,6,6,6,30,0,0); // [
102 | do Output.create(92,0,0,1,3,6,12,24,48,32,0,0); // \
103 | do Output.create(93,30,24,24,24,24,24,24,24,30,0,0); // ]
104 | do Output.create(94,8,28,54,0,0,0,0,0,0,0,0); // ^
105 | do Output.create(95,0,0,0,0,0,0,0,0,0,63,0); // _
106 | do Output.create(96,6,12,24,0,0,0,0,0,0,0,0); // `
107 |
108 | do Output.create(97,0,0,0,14,24,30,27,27,54,0,0); // a
109 | do Output.create(98,3,3,3,15,27,51,51,51,30,0,0); // b
110 | do Output.create(99,0,0,0,30,51,3,3,51,30,0,0); // c
111 | do Output.create(100,48,48,48,60,54,51,51,51,30,0,0); // d
112 | do Output.create(101,0,0,0,30,51,63,3,51,30,0,0); // e
113 | do Output.create(102,28,54,38,6,15,6,6,6,15,0,0); // f
114 | do Output.create(103,0,0,30,51,51,51,62,48,51,30,0); // g
115 | do Output.create(104,3,3,3,27,55,51,51,51,51,0,0); // h
116 | do Output.create(105,12,12,0,14,12,12,12,12,30,0,0); // i
117 | do Output.create(106,48,48,0,56,48,48,48,48,51,30,0); // j
118 | do Output.create(107,3,3,3,51,27,15,15,27,51,0,0); // k
119 | do Output.create(108,14,12,12,12,12,12,12,12,30,0,0); // l
120 | do Output.create(109,0,0,0,29,63,43,43,43,43,0,0); // m
121 | do Output.create(110,0,0,0,29,51,51,51,51,51,0,0); // n
122 | do Output.create(111,0,0,0,30,51,51,51,51,30,0,0); // o
123 | do Output.create(112,0,0,0,30,51,51,51,31,3,3,0); // p
124 | do Output.create(113,0,0,0,30,51,51,51,62,48,48,0); // q
125 | do Output.create(114,0,0,0,29,55,51,3,3,7,0,0); // r
126 | do Output.create(115,0,0,0,30,51,6,24,51,30,0,0); // s
127 | do Output.create(116,4,6,6,15,6,6,6,54,28,0,0); // t
128 | do Output.create(117,0,0,0,27,27,27,27,27,54,0,0); // u
129 | do Output.create(118,0,0,0,51,51,51,51,30,12,0,0); // v
130 | do Output.create(119,0,0,0,51,51,51,63,63,18,0,0); // w
131 | do Output.create(120,0,0,0,51,30,12,12,30,51,0,0); // x
132 | do Output.create(121,0,0,0,51,51,51,62,48,24,15,0); // y
133 | do Output.create(122,0,0,0,63,27,12,6,51,63,0,0); // z
134 |
135 | do Output.create(123,56,12,12,12,7,12,12,12,56,0,0); // {
136 | do Output.create(124,12,12,12,12,12,12,12,12,12,0,0); // |
137 | do Output.create(125,7,12,12,12,56,12,12,12,7,0,0); // }
138 | do Output.create(126,38,45,25,0,0,0,0,0,0,0,0); // ~
139 |
140 | return;
141 | }
142 |
143 | // Creates the character map array of the given character index, using the given values.
144 | function void create(int index, int a, int b, int c, int d, int e,
145 | int f, int g, int h, int i, int j, int k) {
146 | var Array map;
147 |
148 | let map = Array.new(11);
149 | let charMaps[index] = map;
150 |
151 | let map[0] = a;
152 | let map[1] = b;
153 | let map[2] = c;
154 | let map[3] = d;
155 | let map[4] = e;
156 | let map[5] = f;
157 | let map[6] = g;
158 | let map[7] = h;
159 | let map[8] = i;
160 | let map[9] = j;
161 | let map[10] = k;
162 |
163 | return;
164 | }
165 |
166 | // Returns the character map (array of size 11) of the given character.
167 | // If the given character is invalid or non-printable, returns the
168 | // character map of a black square.
169 | function Array getMap(char c) {
170 | if ((c < 32) | (c > 126)) {
171 | let c = 0;
172 | }
173 | return charMaps[c];
174 | }
175 |
176 | /** Moves the cursor to the j-th column of the i-th row,
177 | * and erases the character displayed there. */
178 | function void moveCursor(int i, int j) {
179 | var int address, mask, temp, index;
180 |
181 | let screenRow = i;
182 | let screenCol = j;
183 |
184 | if ((i > 22) | (j > 63) | (i < 0) | (j < 0)) {
185 | do Sys.error(4);
186 | return;
187 | }
188 |
189 | let temp = j * 8;
190 | let address = (i * 11 * 32) + (temp / 16);
191 | let mask = temp & 15;
192 |
193 | if (mask = 0) {
194 | while (index < 11) {
195 | let screenAddress[address] = screenAddress[address] & (~255);
196 | let index = index + 1;
197 | let address = address + 32;
198 | }
199 | } else {
200 | while (index < 11) {
201 | let screenAddress[address] = screenAddress[address] & 255;
202 | let index = index + 1;
203 | let address = address + 32;
204 | }
205 | }
206 |
207 | return;
208 | }
209 |
210 | /** Displays the given character at the cursor location,
211 | * and advances the cursor one column forward. */
212 | function void printChar(char c) {
213 | var int temp, mask, address, index;
214 | var Array map;
215 |
216 | let temp = screenCol * 8;
217 | let address = (screenRow * 11 * 32) + (temp / 16);
218 | let mask = temp & 15;
219 | let map = Output.getMap(c);
220 |
221 | if (mask = 0) {
222 | while (index < 11) {
223 | let screenAddress[address] = screenAddress[address] | map[index];
224 | let index = index + 1;
225 | let address = address + 32;
226 | }
227 | } else {
228 | while (index < 11) {
229 | let screenAddress[address] = screenAddress[address] | (map[index] * 256);
230 | let index = index + 1;
231 | let address = address + 32;
232 | }
233 | }
234 |
235 | let screenCol = screenCol + 1;
236 |
237 | if (screenCol < 64) {
238 | do Output.moveCursor(screenRow, screenCol);
239 | } else {
240 | let screenRow = screenRow + 1;
241 | let screenCol = 0;
242 | if (screenRow > 22) {
243 | let screenRow = 0;
244 | }
245 | do Output.moveCursor(screenRow, screenCol);
246 | }
247 |
248 | return;
249 | }
250 |
251 | /** displays the given string starting at the cursor location,
252 | * and advances the cursor appropriately. */
253 | function void printString(String s) {
254 | var int len, i;
255 |
256 | let len = s.length();
257 |
258 | while (len > i) {
259 | do Output.printChar(s.charAt(i));
260 | let i = i + 1;
261 | }
262 |
263 | return;
264 | }
265 |
266 | /** Displays the given integer starting at the cursor location,
267 | * and advances the cursor appropriately. */
268 | function void printInt(int n) {
269 | var String s;
270 | var int len, i;
271 |
272 | let s = String.new(6);
273 | do s.setInt(n);
274 | let len = s.length();
275 |
276 | while (len > i) {
277 | do Output.printChar(s.charAt(i));
278 | let i = i + 1;
279 | }
280 |
281 | do s.dispose();
282 |
283 | return;
284 | }
285 |
286 | /** Advances the cursor to the beginning of the next line. */
287 | function void println() {
288 | let screenRow = screenRow + 1;
289 | let screenCol = 0;
290 |
291 | if (screenRow > 22) {
292 | let screenRow = 0;
293 | }
294 | do Output.moveCursor(screenRow, screenCol);
295 |
296 | return;
297 | }
298 |
299 | /** Moves the cursor one column back. */
300 | function void backSpace() {
301 | if ((screenCol = 0) & (screenRow = 0)) {
302 | return;
303 | }
304 |
305 | let screenCol = screenCol - 1;
306 |
307 | if (screenCol < 0) {
308 | let screenCol = 63;
309 | let screenRow = screenRow - 1;
310 | }
311 | do Output.moveCursor(screenRow, screenCol);
312 | return;
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/12/README.md:
--------------------------------------------------------------------------------
1 | * 尽量用位掩码操作和加减法代替乘除法
2 | * 尽量少使用函数调用 开销太高
3 |
4 | #### 内存管理
5 | [内存管理算法](http://nand2tetris-questions-and-answers-forum.32033.n3.nabble.com/New-Heap-Management-algorithm-Coursera-version-td4032026.html)
6 |
7 | #### 圆 直线
8 | bresenham算法画圆
9 |
--------------------------------------------------------------------------------
/12/Screen.jack:
--------------------------------------------------------------------------------
1 | /**
2 | * A library of functions for displaying graphics on the screen.
3 | * The Hack physical screen consists of 512 rows (indexed 0..511, top to bottom)
4 | * of 256 pixels each (indexed 0..255, left to right). The top left pixel on
5 | * the screen is indexed (0,0).
6 | */
7 | class Screen {
8 | static boolean screenColor;
9 | static Array screenAddress;
10 | static Array bitArray;
11 | /** Initializes the Screen. */
12 | function void init() {
13 | // screen 16384 -- 24575
14 | let screenAddress = 16384;
15 | let screenColor = true;
16 |
17 | let bitArray = Array.new(17);
18 | let bitArray[0] = 1;
19 | let bitArray[1] = 2;
20 | let bitArray[2] = 4;
21 | let bitArray[3] = 8;
22 | let bitArray[4] = 16;
23 | let bitArray[5] = 32;
24 | let bitArray[6] = 64;
25 | let bitArray[7] = 128;
26 | let bitArray[8] = 256;
27 | let bitArray[9] = 512;
28 | let bitArray[10] = 1024;
29 | let bitArray[11] = 2048;
30 | let bitArray[12] = 4096;
31 | let bitArray[13] = 8192;
32 | let bitArray[14] = 16384;
33 | let bitArray[15] = 16384 + 16384;
34 | let bitArray[16] = 0;
35 | return;
36 | }
37 |
38 | /** Erases the entire screen. */
39 | function void clearScreen() {
40 | var int i;
41 | let i = 0;
42 |
43 | while (i < 8192) {
44 | let screenAddress[i] = false;
45 | let i = i + 1;
46 | }
47 |
48 | return;
49 | }
50 |
51 | /** Sets the current color, to be used for all subsequent drawXXX commands.
52 | * Black is represented by true, white by false. */
53 | function void setColor(boolean b) {
54 | let screenColor = b;
55 | return;
56 | }
57 |
58 | /** Draws the (x,y) pixel, using the current color. */
59 | function void drawPixel(int x, int y) {
60 | var int address, mask;
61 |
62 | // RAM[16394 + 1 * 32 + 1 / 16],由于屏幕由 512 行,256 列像素组成
63 | // 所以在纵向上有 512 / 16 = 32 字,在横向上有 256 / 16 = 16 字
64 | // 为了提升效率,hack 采用按字进行逻辑运算来改变颜色
65 | let address = (y * 32) + (x / 16);
66 | let mask = bitArray[x & 15];
67 |
68 | if (screenColor) {
69 | let screenAddress[address] = screenAddress[address] | mask;
70 | } else {
71 | let screenAddress[address] = screenAddress[address] & ~mask;
72 | }
73 |
74 | return;
75 | }
76 |
77 | /** Draws a line from pixel (x1,y1) to pixel (x2,y2), using the current color. */
78 | function void drawLine(int x1, int y1, int x2, int y2) {
79 | var int dx, dy, a, b, temp, adyMinusbdx;
80 |
81 | // coordinate exchange
82 | if (x1 > x2) {
83 | let temp = x1;
84 | let x1 = x2;
85 | let x2 = temp;
86 |
87 | let temp = y1;
88 | let y1 = y2;
89 | let y2 = temp;
90 | }
91 |
92 | let dx = x2 - x1;
93 | let dy = y2 - y1;
94 |
95 | if (dx = 0) {
96 | // VerticalLine
97 | do Screen.drawVerticalLine(x1, y1, y2);
98 | return;
99 | } else {
100 | if (dy = 0) {
101 | // HorizontalLine
102 | do Screen.drawHorizontalLine(x1, x2, y1);
103 | return;
104 | }
105 | }
106 |
107 | let a = 0;
108 | let b = 0;
109 | let adyMinusbdx = 0;
110 |
111 | if (y2 > y1) {
112 | while (~(a > dx) & ~(b > dy)) {
113 | do Screen.drawPixel(x1 + a, y1 + b);
114 |
115 | if (adyMinusbdx < 0) {
116 | let b = b + 1;
117 | let adyMinusbdx = adyMinusbdx + dx;
118 | } else {
119 | let a = a + 1;
120 | let adyMinusbdx = adyMinusbdx - dy;
121 | }
122 | }
123 | } else {
124 | // y1 > y2
125 | let dy = -dy;
126 | while (~(a > dx) & ~(b > dy)) {
127 | do Screen.drawPixel(x1 + a, y1 - b);
128 |
129 | if (adyMinusbdx < 0) {
130 | let a = a + 1;
131 | let adyMinusbdx = adyMinusbdx + dy;
132 | } else {
133 | let b = b + 1;
134 | let adyMinusbdx = adyMinusbdx - dx;
135 | }
136 | }
137 | }
138 |
139 | return;
140 | }
141 |
142 | /** Draws a filled rectangle whose top left corner is (x1, y1)
143 | * and bottom right corner is (x2,y2), using the current color. */
144 | function void drawRectangle(int x1, int y1, int x2, int y2) {
145 | var int temp, address1, address2, i, leftMask, rightMask, mask, tempA1, tempA2;
146 | if (y1 > y2) {
147 | let temp = y1;
148 | let y1 = y2;
149 | let y2 = temp;
150 | }
151 |
152 | if (x1 > x2) {
153 | let temp = x1;
154 | let x1 = x2;
155 | let x2 = temp;
156 | }
157 |
158 | let address1 = (y1 * 32) + (x1 / 16);
159 | let address2 = (y1 * 32) + (x2 / 16);
160 |
161 | let i = x1 & 15;
162 | let leftMask = ~(bitArray[i] - 1);
163 |
164 | let i = x2 & 15;
165 | let rightMask = bitArray[i + 1] - 1;
166 |
167 | let mask = leftMask & rightMask;
168 |
169 | while (~(y2 < y1)) {
170 | let tempA1 = address1;
171 | let tempA2 = address2;
172 |
173 | if (tempA1 = tempA2) {
174 | if (screenColor) {
175 | let screenAddress[tempA1] = screenAddress[tempA1] | mask;
176 | } else {
177 | let screenAddress[tempA1] = screenAddress[tempA1] & ~mask;
178 | }
179 | } else {
180 | if (screenColor) {
181 | let screenAddress[tempA1] = screenAddress[tempA1] | leftMask;
182 | let screenAddress[tempA2] = screenAddress[tempA2] | rightMask;
183 | } else {
184 | let screenAddress[tempA1] = screenAddress[tempA1] & ~leftMask;
185 | let screenAddress[tempA2] = screenAddress[tempA2] & ~rightMask;
186 | }
187 |
188 | if ((tempA2 - tempA1) > 1) {
189 | let tempA1 = tempA1 + 1;
190 | while (tempA2 > tempA1) {
191 | let screenAddress[tempA1] = screenColor;
192 | let tempA1 = tempA1 + 1;
193 | }
194 | }
195 | }
196 | let address1 = address1 + 32;
197 | let address2 = address2 + 32;
198 | let y1 = y1 + 1;
199 | }
200 |
201 | return;
202 | }
203 |
204 | /** Draws a filled circle of radius r<=181 around (x,y), using the current color. */
205 | // Bresenham
206 | function void drawCircle(int x, int y, int r) {
207 | var int i, j;
208 | var int counter;
209 |
210 | let i = 0;
211 | let j = r;
212 | let counter = 3 - (r + r);
213 |
214 | do Screen.drawHorizontalLine(x - r, x + r, y);
215 |
216 | while (j > i) {
217 |
218 | if (counter < 0) {
219 | let counter = counter + 6 + i + i + i + i;
220 | let i = i + 1;
221 | } else {
222 | if ((counter > 0) & (j > i)) {
223 | let j = j - 1;
224 | let counter = (counter + 4) - (j + j + j + j);
225 | }
226 | }
227 |
228 | do Screen.drawHorizontalLine(x - i, x + i, y + j);
229 | do Screen.drawHorizontalLine(x - i, x + i, y - j);
230 | do Screen.drawHorizontalLine(x - j, x + j, y + i);
231 | do Screen.drawHorizontalLine(x - j, x + j, y - i);
232 |
233 | }
234 | return;
235 | }
236 |
237 | function void drawHorizontalLine(int x1, int x2, int y) {
238 | var int temp, address1, address2, leftMask, rightMask, mask, i;
239 |
240 | // exchange
241 | if (x1 > x2) {
242 | let temp = x1;
243 | let x1 = x2;
244 | let x2 = temp;
245 | }
246 |
247 | // x1 address
248 | let address1 = (y * 32) + (x1 / 16);
249 |
250 | // x2 address
251 | let address2 = (y * 32) + (x2 / 16);
252 |
253 | // 0 1 2 3 4 6 7
254 | // |..x1..|16bits|32bits|... |... |... |..x2..|
255 |
256 | // index
257 | let i = x1 & 15;
258 | let leftMask = ~(bitArray[i] - 1);
259 |
260 | let i = x2 & 15;
261 | let rightMask = bitArray[i + 1] - 1;
262 |
263 | if (address1 = address2) {
264 | let mask = leftMask & rightMask;
265 | if (screenColor) {
266 | let screenAddress[address1] = screenAddress[address1] | mask;
267 | } else {
268 | let screenAddress[address1] = screenAddress[address1] & ~mask;
269 | }
270 | return;
271 | } else {
272 | if (screenColor) {
273 | let screenAddress[address1] = screenAddress[address1] | leftMask;
274 | let screenAddress[address2] = screenAddress[address2] | rightMask;
275 | } else {
276 | let screenAddress[address1] = screenAddress[address1] & ~leftMask;
277 | let screenAddress[address2] = screenAddress[address2] & ~rightMask;
278 | }
279 | }
280 |
281 | if ((address2 - address1) > 1) {
282 | let address1 = address1 + 1;
283 | while (address2 > address1) {
284 | let screenAddress[address1] = screenColor;
285 | let address1 = address1 + 1;
286 | }
287 | }
288 |
289 | return;
290 | }
291 |
292 | function void drawVerticalLine(int x, int y1, int y2) {
293 | var int temp, address, mask;
294 |
295 | if (y1 > y2) {
296 | let temp = y1;
297 | let y1 = y2;
298 | let y2 = temp;
299 | }
300 |
301 | let address = (y1 * 32) + (x / 16);
302 | let mask = bitArray[x & 15];
303 |
304 | while (~(y1 > y2)) {
305 | if (screenColor) {
306 | let screenAddress[address] = screenAddress[address] | mask;
307 | } else {
308 | let screenAddress[address] = screenAddress[address] & ~mask;
309 | }
310 |
311 | let address = address + 32;
312 | let y1 = y1 + 1;
313 | }
314 |
315 | return;
316 | }
317 | }
318 |
--------------------------------------------------------------------------------
/12/String.jack:
--------------------------------------------------------------------------------
1 | /**
2 | * Represents character strings. In addition for constructing and disposing
3 | * strings, the class features methods for getting and setting individual
4 | * characters of the string, for erasing the string's last character,
5 | * for appending a character to the string's end, and more typical
6 | * string-oriented operations.
7 | */
8 | class String {
9 | field int len;
10 | field int maxLen;
11 | field Array chars;
12 | /** constructs a new empty string with a maximum length of maxLength
13 | * and initial length of 0. */
14 | constructor String new(int maxLength) {
15 | if (maxLength = 0) {
16 | let maxLength = 1;
17 | }
18 | let len = 0;
19 | let maxLen = maxLength;
20 | let chars = Array.new(maxLength);
21 | return this;
22 | }
23 |
24 | /** Disposes this string. */
25 | method void dispose() {
26 | do chars.dispose();
27 | return;
28 | }
29 |
30 | /** Returns the current length of this string. */
31 | method int length() {
32 | return len;
33 | }
34 |
35 | /** Returns the character at the j-th location of this string. */
36 | method char charAt(int j) {
37 | return chars[j];
38 | }
39 |
40 | /** Sets the character at the j-th location of this string to c. */
41 | method void setCharAt(int j, char c) {
42 | let chars[j] = c;
43 | return;
44 | }
45 |
46 | /** Appends c to this string's end and returns this string. */
47 | method String appendChar(char c) {
48 | if (maxLen > len) {
49 | let chars[len] = c;
50 | let len = len + 1;
51 | }
52 | return this;
53 | }
54 |
55 | /** Erases the last character from this string. */
56 | method void eraseLastChar() {
57 | if (len > 0) {
58 | let len = len - 1;
59 | }
60 | return;
61 | }
62 |
63 | /** Returns the integer value of this string,
64 | * until a non-digit character is detected. */
65 | method int intValue() {
66 | var int v, index, d, temp;
67 | var boolean isNeg;
68 | let v = 0;
69 | let index = 0;
70 |
71 | if (chars[index] = 45) {
72 | let index = index + 1;
73 | let isNeg = true;
74 | } else {
75 | let isNeg = false;
76 | }
77 | while (index < len) {
78 | let temp = chars[index];
79 | if ((temp < 48) | (temp > 57)) {
80 | if (isNeg) {
81 | return -v;
82 | } else {
83 | return v;
84 | }
85 | }
86 | let d = String.c2d(temp);
87 | let index = index + 1;
88 | let v = v * 10 + d;
89 | }
90 |
91 | if (isNeg) {
92 | return -v;
93 | } else {
94 | return v;
95 | }
96 | }
97 |
98 | /** Sets this string to hold a representation of the given value. */
99 | method void setInt(int n) {
100 | let len = 0;
101 | if (n < 0) {
102 | let n = -n;
103 | do appendChar(45);
104 | }
105 |
106 | do setInt2(n);
107 | return;
108 | }
109 |
110 | method void setInt2(int n) {
111 | var int nextN;
112 |
113 | if (n < 10) {
114 | do appendChar(String.d2c(n));
115 | } else {
116 | let nextN = n / 10;
117 | do setInt2(nextN);
118 | do appendChar(String.d2c(n - (nextN * 10)));
119 | }
120 |
121 | return;
122 | }
123 |
124 | /** Returns the new line character. */
125 | function char newLine() {
126 | return 128;
127 | }
128 |
129 | /** Returns the backspace character. */
130 | function char backSpace() {
131 | return 129;
132 | }
133 |
134 | /** Returns the double quote (") character. */
135 | function char doubleQuote() {
136 | return 34;
137 | }
138 |
139 | function int c2d(char c) {
140 | return c - 48;
141 | }
142 |
143 | function char d2c(int d) {
144 | return d + 48;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/12/Sys.jack:
--------------------------------------------------------------------------------
1 | /**
2 | * A library that supports various program execution services.
3 | */
4 | class Sys {
5 |
6 | /** Performs all the initializations required by the OS. */
7 | function void init() {
8 | do Memory.init();
9 | do Math.init();
10 | do Screen.init();
11 | do Output.init();
12 | do Keyboard.init();
13 | do Main.main();
14 | do Sys.halt();
15 | return;
16 | }
17 |
18 | /** Halts the program execution. */
19 | function void halt() {
20 | var int flag;
21 | let flag = 1;
22 | while (flag > 0) {
23 | let flag = 1;
24 | }
25 | return;
26 | }
27 |
28 | /** Waits approximately duration milliseconds and returns. */
29 | // 在我的电脑上times = 150 约为1毫秒 和电脑CPU有关
30 | function void wait(int duration) {
31 | var int times;
32 | while (duration > 0) {
33 | let duration = duration - 1;
34 | let times = 150;
35 | while (times > 0) {
36 | let times = times - 1;
37 | }
38 | }
39 | return;
40 | }
41 |
42 | /** Displays the given error code in the form "ERR",
43 | * and halts the program's execution. */
44 | function void error(int errorCode) {
45 | do Output.printString(errorCode);
46 | do Output.println();
47 | do Sys.halt();
48 | return;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 bin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 计算机系统要素-从零开始构建现代计算机(nand2tetris)
2 | 这本书主要讲解了计算机原理(1-5章)、编译原理(6-11章)、操作系统相关知识(12章)。不要看内容这么多,其实这本书的内容非常通俗易懂,翻译也很给力。每一章背后都有对应的练习,需要你手写代码去完成,堪称理论与实践结合的经典。
3 |
4 | 这里引用一下书里的内容简介,大家可以感受一下。
5 | >本书通过展现简单但功能强大的计算机系统之构建过程,为读者呈现了一幅完整、严格的计算机应用科学大图景。本书作者认为,理解计算机工作原理的最好方法就是亲自动手,从零开始构建计算机系统。
6 | 通过12个章节和项目来引领读者从头开始,本书逐步地构建一个基本的硬件平台和现代软件阶层体系。在这个过程中,读者能够获得关于硬件体系结构、操作系统、编程语言、编译器、数据结构、算法以及软件工程的详实知识。通过这种逐步构造的方法,本书揭示了计算机科学知识中的重要成分,并展示其它课程中所介绍的理论和应用技术如何融入这幅全局大图景当中去。
7 |
全书基于“先抽象再实现”的阐述模式,每一章都介绍一个关键的硬件或软件抽象,一种实现方式以及一个实际的项目。完成这些项目所必要的计算机科学知识在本书中都有涵盖,只要求读者具备程序设计经验。本书配套的支持网站提供了书中描述的用于构建所有硬件和软件系统所必需的工具和资料,以及用于12个项目的200个测试程序。
8 | 全书内容广泛、涉猎全面,适合计算机及相关专业本科生、研究生、技术开发人员、教师以及技术爱好者参考和学习。
9 |
10 | 而且,这本书的门槛非常低,只要你能熟练运用一门编程语言即可。
11 |
12 | 本书从与非门开始教你一步步构建一个完整的计算机(1-5章);从第 6 章开始一直到第 11 章,需要完成三个编译器(汇编编译器、VM 编译器、Jack 语言编译器);最后一章则需要完成操作系统部分功能。
13 |
14 | 如果你完成了本书所有的项目,则会获得以下成就:
15 | * 构建出一台计算机(在模拟器上运行)
16 | * 实现一门语言和相应的语言标准库
17 | * 实现一个简单的编译器
18 |
19 | 其他的编译原理书籍,在你学完后,可能还是不知道如何下手去实现一个编译器。
20 |
21 | 但这本书不一样,它是**手把手教你一步一步的实现一个编译器**。
22 |
23 | ## 其他语言版本答案
24 | 本书的第 6 - 11 章实验需要使用高级语言来实现,本仓库使用的是 JavaScript 语言。但仍然有许多开发者,他们熟悉的语言不是 JavaScript。因此,我添加了一些用其他语言实现的版本链接,方便大家学习。
25 | * [Java](https://github.com/AllenWrong/nand2tetris)
26 | * [C++](https://github.com/FusionBolt/The-Elements-of-Computer-Systems-Project)
27 | * [Python](https://github.com/xrahoo/nand2tetris-python)
28 |
29 | ## 配套资料
30 | * [全套工具下载](https://github.com/woai3c/teocs-exercises/blob/master/nand2tetris.zip)
31 | * [本书作者制作的教学视频课程](https://www.coursera.org/learn/build-a-computer/home/welcome)
32 | * [官网](https://www.nand2tetris.org/)
33 |
34 | 书籍请加 QQ 群 **39014053**,在群文件里下载。
35 | ### 注意
36 | 我上传的只有答案,测试用例和工具已经压缩放在仓库里,名字是 **nand2tetris.zip**,下载源码后请自行解压。
37 |
38 | 有问题欢迎提 [issues](https://github.com/woai3c/nand2tetris/issues),也可以选择加入 QQ 交流群 **39014053**,有问题随时提问。
39 |
40 | ## 问题
41 | * [windows 下无法打开 `.bat` 文件](https://github.com/woai3c/nand2tetris/tree/master/09)
42 |
43 | ## 内容简介
44 | 在完成所有项目后,可以跑一下软件提供的测试用例,感受一下计算机的奇妙之处:
45 |
46 | 
47 |
48 | 
49 |
50 | 
51 |
52 | ## 硬件平台
53 |
54 | ### 1.布尔逻辑
55 | 介绍了各种基础逻辑门,并且所有门都是基于nand门实现的
56 | * and and16
57 | * dmux dmux4way dmux8way
58 | * mux mux16 mux4way16 mux8way16
59 | * not not16
60 | * or or16 or8way
61 | * xor
62 |
63 | ### 2.布尔运算
64 | * 二进制数
65 | * 二进制加法
66 | * 半加器
67 | * 全加器
68 | * 加法器
69 | * 增量器
70 | * ALU
71 |
72 | ### 3.时序逻辑
73 | #### 组合芯片
74 | * 布尔芯片
75 | * 算术芯片
76 |
77 | #### 时序芯片
78 | 时序芯片基于大量的DFF门
79 | * 时钟
80 | * 触发器
81 | * 寄存器
82 | * 内存
83 | * 计数器
84 |
85 |
86 | ### 4.机器语言
87 | * A指令
88 | * C指令
89 | * 寻址方式:直接寻址、立即寻址、间接寻址
90 |
91 | ### 5.计算机体系结构
92 | * 内存
93 | * CPU
94 | * 寄存器
95 | * 输入输出
96 |
97 | ## 软件阶层体系
98 | 6. 汇编编译器
99 | 7. 虚拟机I:堆栈运算
100 | 8. 虚拟机II:程序控制
101 | 9. 高级语言
102 | 10. 编译器I:语法分析
103 | 11. 编译器II:代码生成
104 | 12. 操作系统
105 |
106 | ## License
107 | MIT
108 | ## 赞助
109 | 如果你觉得本项目对你的帮助很大,可以请作者喝一杯奶茶🎁😉。
110 |
111 | 
112 | 
113 |
--------------------------------------------------------------------------------
/img/wx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woai3c/nand2tetris/94c440196b1550a6b3cdbc4857480a4945bf2c1e/img/wx.jpg
--------------------------------------------------------------------------------
/img/zfb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woai3c/nand2tetris/94c440196b1550a6b3cdbc4857480a4945bf2c1e/img/zfb.jpg
--------------------------------------------------------------------------------
/nand2tetris.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woai3c/nand2tetris/94c440196b1550a6b3cdbc4857480a4945bf2c1e/nand2tetris.zip
--------------------------------------------------------------------------------