86fa8806 |
1 | import { readline } from "./node_readline"; |
2 | |
677a1c9d |
3 | import { Node, MalType, MalNumber, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; |
86fa8806 |
4 | import { Env } from "./env"; |
5 | import { readStr } from "./reader"; |
6 | import { prStr } from "./printer"; |
7 | |
9c92462f |
8 | // READ |
86fa8806 |
9 | function read(str: string): MalType { |
10 | return readStr(str); |
11 | } |
12 | |
13 | function evalAST(ast: MalType, env: Env): MalType { |
14 | switch (ast.type) { |
5bb7479d |
15 | case Node.Symbol: |
86fa8806 |
16 | const f = env.get(ast); |
17 | if (!f) { |
18 | throw new Error(`unknown symbol: ${ast.v}`); |
19 | } |
20 | return f; |
5bb7479d |
21 | case Node.List: |
9c92462f |
22 | return new MalList(ast.list.map(ast => evalMal(ast, env))); |
5bb7479d |
23 | case Node.Vector: |
9c92462f |
24 | return new MalVector(ast.list.map(ast => evalMal(ast, env))); |
5bb7479d |
25 | case Node.HashMap: |
86fa8806 |
26 | const list: MalType[] = []; |
10f8aa84 |
27 | for (const [key, value] of ast.entries()) { |
86fa8806 |
28 | list.push(key); |
9c92462f |
29 | list.push(evalMal(value, env)); |
86fa8806 |
30 | } |
31 | return new MalHashMap(list); |
32 | default: |
33 | return ast; |
34 | } |
35 | } |
36 | |
9c92462f |
37 | // EVAL |
38 | function evalMal(ast: MalType, env: Env): MalType { |
5bb7479d |
39 | if (ast.type !== Node.List) { |
86fa8806 |
40 | return evalAST(ast, env); |
41 | } |
42 | if (ast.list.length === 0) { |
43 | return ast; |
44 | } |
45 | const first = ast.list[0]; |
46 | switch (first.type) { |
5bb7479d |
47 | case Node.Symbol: |
86fa8806 |
48 | switch (first.v) { |
49 | case "def!": { |
50 | const [, key, value] = ast.list; |
677a1c9d |
51 | if (key.type !== Node.Symbol) { |
86fa8806 |
52 | throw new Error(`unexpected toke type: ${key.type}, expected: symbol`); |
53 | } |
54 | if (!value) { |
55 | throw new Error(`unexpected syntax`); |
56 | } |
677a1c9d |
57 | return env.set(key, evalMal(value, env)); |
86fa8806 |
58 | } |
59 | case "let*": { |
60 | let letEnv = new Env(env); |
61 | const pairs = ast.list[1]; |
677a1c9d |
62 | if (!isSeq(pairs)) { |
86fa8806 |
63 | throw new Error(`unexpected toke type: ${pairs.type}, expected: list or vector`); |
64 | } |
677a1c9d |
65 | const list = pairs.list; |
86fa8806 |
66 | for (let i = 0; i < list.length; i += 2) { |
67 | const key = list[i]; |
68 | const value = list[i + 1]; |
677a1c9d |
69 | if (key.type !== Node.Symbol) { |
70 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
71 | } |
86fa8806 |
72 | if (!key || !value) { |
73 | throw new Error(`unexpected syntax`); |
74 | } |
75 | |
677a1c9d |
76 | letEnv.set(key, evalMal(value, letEnv)); |
86fa8806 |
77 | } |
9c92462f |
78 | return evalMal(ast.list[2], letEnv); |
86fa8806 |
79 | } |
80 | } |
81 | } |
677a1c9d |
82 | const result = evalAST(ast, env); |
83 | if (!isSeq(result)) { |
84 | throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); |
85 | } |
79a10a6e |
86 | const [f, ...args] = result.list; |
5bb7479d |
87 | if (f.type !== Node.Function) { |
86fa8806 |
88 | throw new Error(`unexpected token: ${f.type}, expected: function`); |
89 | } |
79a10a6e |
90 | return f.func(...args); |
86fa8806 |
91 | } |
92 | |
9c92462f |
93 | // PRINT |
86fa8806 |
94 | function print(exp: MalType): string { |
95 | return prStr(exp); |
96 | } |
97 | |
98 | const replEnv = new Env(); |
9c92462f |
99 | function rep(str: string): string { |
100 | return print(evalMal(read(str), replEnv)); |
101 | } |
102 | |
79a10a6e |
103 | replEnv.set(MalSymbol.get("+"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v))); |
104 | replEnv.set(MalSymbol.get("-"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v))); |
105 | replEnv.set(MalSymbol.get("*"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v))); |
106 | replEnv.set(MalSymbol.get("/"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v))); |
86fa8806 |
107 | |
86fa8806 |
108 | while (true) { |
109 | const line = readline("user> "); |
110 | if (line == null) { |
111 | break; |
112 | } |
113 | if (line === "") { |
114 | continue; |
115 | } |
116 | try { |
117 | console.log(rep(line)); |
118 | } catch (e) { |
119 | const err: Error = e; |
120 | console.error(err.message); |
121 | } |
122 | } |