├── 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} \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} \r\n` 44 | 45 | temp = this._getNextToken() 46 | key = temp[0] 47 | val = temp[1] 48 | 49 | if (val == '{') { 50 | this.output += `<${key}> ${val} \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} \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} \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} \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} \r\n` 114 | 115 | temp = this._getNextToken() 116 | key = temp[0] 117 | val = temp[1] 118 | 119 | if (val == ';') { 120 | this.output += `<${key}> ${val} \r\n` 121 | break 122 | } else if (val == ',') { 123 | this.output += `<${key}> ${val} \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} \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} \r\n` 153 | temp = this._getNextToken() 154 | key = temp[0] 155 | val = temp[1] 156 | } 157 | 158 | if (val == '(') { 159 | this.output += `<${key}> ${val} \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} \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} \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} \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} \r\n` 246 | 247 | temp = this._getNextToken() 248 | key = temp[0] 249 | val = temp[1] 250 | if (key == 'identifier') { 251 | this.output += `<${key}> ${val} \r\n` 252 | 253 | temp = this._getNextToken() 254 | key = temp[0] 255 | val = temp[1] 256 | if (val == ',') { 257 | this.output += `<${key}> ${val} \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} \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} \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} \r\n` 294 | temp = this._getNextToken() 295 | key = temp[0] 296 | val = temp[1] 297 | 298 | if (val == ',') { 299 | this.output += `<${key}> ${val} \r\n` 300 | temp = this._getNextToken() 301 | key = temp[0] 302 | val = temp[1] 303 | } else if (val == ';') { 304 | this.output += `<${key}> ${val} \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} \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} \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} \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} \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} \r\n` 405 | 406 | temp = this._getNextToken() 407 | key = temp[0] 408 | val = temp[1] 409 | 410 | if (val == '[') { 411 | this.output += `<${key}> ${val} \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} \r\n` 419 | temp = this._getNextToken() 420 | key = temp[0] 421 | val = temp[1] 422 | 423 | if (val == '=') { 424 | this.output += `<${key}> ${val} \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} \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} \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} \r\n` 459 | 460 | temp = this._getNextToken() 461 | key = temp[0] 462 | val = temp[1] 463 | 464 | if (val == '(') { 465 | this.output += `<${key}> ${val} \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} \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} \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} \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} \r\n` 514 | 515 | temp = this._getNextToken() 516 | key = temp[0] 517 | val = temp[1] 518 | 519 | if (val == '(') { 520 | this.output += `<${key}> ${val} \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} \r\n` 529 | 530 | temp = this._getNextToken() 531 | key = temp[0] 532 | val = temp[1] 533 | 534 | if (val == '{') { 535 | this.output += `<${key}> ${val} \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} \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} \r\n` 579 | 580 | temp = this._getNextToken() 581 | key = temp[0] 582 | val = temp[1] 583 | 584 | if (val == '{') { 585 | this.output += `<${key}> ${val} \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} \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} \r\n` 611 | 612 | temp = this._getNextToken() 613 | key = temp[0] 614 | val = temp[1] 615 | 616 | if (val == ';') { 617 | this.output += `<${key}> ${val} \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} \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} \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} \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} \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} \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} \r\n` 696 | } else if (key == 'stringConstant') { 697 | this.output += `<${key}> ${val} \r\n` 698 | } else { 699 | switch (val) { 700 | case '(': 701 | this.output += `<${key}> ${val} \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} \r\n` 710 | } else { 711 | this._error(key, val, ')') 712 | } 713 | break 714 | case '-': 715 | this.output += `<${key}> ${val} \r\n` 716 | this._compileTerm() 717 | break 718 | case '~': 719 | this.output += `<${key}> ${val} \r\n` 720 | this._compileTerm() 721 | break 722 | case 'null': 723 | case 'false': 724 | this.output += `<${key}> ${val} \r\n` 725 | break 726 | case 'true': 727 | this.output += `<${key}> ${val} \r\n` 728 | break 729 | case 'this': 730 | this.output += `<${key}> ${val} \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} \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} \r\n` 751 | } else { 752 | this._error(key, val, ')') 753 | } 754 | } else if (val == '.') { 755 | this.output += `<${key}> ${val} \r\n` 756 | temp = this._getNextToken() 757 | key = temp[0] 758 | val = temp[1] 759 | 760 | if (key == 'identifier') { 761 | this.output += `<${key}> ${val} \r\n` 762 | temp = this._getNextToken() 763 | key = temp[0] 764 | val = temp[1] 765 | if (val == '(') { 766 | this.output += `<${key}> ${val} \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} \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} \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} \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} \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 | ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d040649df3bb4cd28b7b90a9f857fe4e~tplv-k3u1fbpfcp-zoom-1.image) 47 | 48 | ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e5820185fae84ac389f457d5df08f2c8~tplv-k3u1fbpfcp-zoom-1.image) 49 | 50 | ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ae42f59db6714190a9a2b8c13741d0c2~tplv-k3u1fbpfcp-zoom-1.image) 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 | ![](https://github.com/woai3c/nand2tetris/blob/master/img/wx.jpg) 112 | ![](https://github.com/woai3c/nand2tetris/blob/master/img/zfb.jpg) 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 --------------------------------------------------------------------------------