83aaf848 |
1 | import { readline } from "./node_readline"; |
2 | |
677a1c9d |
3 | import { Node, MalType, MalNumber, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types"; |
83aaf848 |
4 | import { readStr } from "./reader"; |
5 | import { prStr } from "./printer"; |
6 | |
9c92462f |
7 | // READ |
83aaf848 |
8 | function read(str: string): MalType { |
9 | return readStr(str); |
10 | } |
11 | |
12 | interface MalEnvironment { |
13 | [key: string]: MalFunction; |
14 | } |
15 | |
16 | function evalAST(ast: MalType, env: MalEnvironment): MalType { |
17 | switch (ast.type) { |
5bb7479d |
18 | case Node.Symbol: |
83aaf848 |
19 | const f = env[ast.v]; |
20 | if (!f) { |
21 | throw new Error(`unknown symbol: ${ast.v}`); |
22 | } |
23 | return f; |
5bb7479d |
24 | case Node.List: |
9c92462f |
25 | return new MalList(ast.list.map(ast => evalMal(ast, env))); |
5bb7479d |
26 | case Node.Vector: |
9c92462f |
27 | return new MalVector(ast.list.map(ast => evalMal(ast, env))); |
5bb7479d |
28 | case Node.HashMap: |
83aaf848 |
29 | const list: MalType[] = []; |
10f8aa84 |
30 | for (const [key, value] of ast.entries()) { |
83aaf848 |
31 | list.push(key); |
9c92462f |
32 | list.push(evalMal(value, env)); |
83aaf848 |
33 | } |
34 | return new MalHashMap(list); |
35 | default: |
36 | return ast; |
37 | } |
38 | } |
39 | |
9c92462f |
40 | // EVAL |
41 | function evalMal(ast: MalType, env: MalEnvironment): MalType { |
5bb7479d |
42 | if (ast.type !== Node.List) { |
83aaf848 |
43 | return evalAST(ast, env); |
44 | } |
45 | if (ast.list.length === 0) { |
46 | return ast; |
47 | } |
677a1c9d |
48 | const result = evalAST(ast, env); |
49 | if (!isSeq(result)) { |
50 | throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); |
51 | } |
79a10a6e |
52 | const [f, ...args] = result.list; |
5bb7479d |
53 | if (f.type !== Node.Function) { |
83aaf848 |
54 | throw new Error(`unexpected token: ${f.type}, expected: function`); |
55 | } |
79a10a6e |
56 | return f.func(...args); |
83aaf848 |
57 | } |
58 | |
9c92462f |
59 | // PRINT |
83aaf848 |
60 | function print(exp: MalType): string { |
61 | return prStr(exp); |
62 | } |
63 | |
64 | const replEnv: MalEnvironment = { |
79a10a6e |
65 | "+": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v)), |
66 | "-": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v)), |
67 | "*": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v)), |
68 | "/": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v)), |
83aaf848 |
69 | }; |
70 | function rep(str: string): string { |
9c92462f |
71 | return print(evalMal(read(str), replEnv)); |
83aaf848 |
72 | } |
73 | |
74 | while (true) { |
75 | const line = readline("user> "); |
76 | if (line == null) { |
77 | break; |
78 | } |
79 | if (line === "") { |
80 | continue; |
81 | } |
82 | try { |
83 | console.log(rep(line)); |
84 | } catch (e) { |
85 | const err: Error = e; |
86 | console.error(err.message); |
87 | } |
88 | } |