dfe70453 |
1 | import { readline } from "./node_readline"; |
2 | |
3 | import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; |
4 | import { Env } from "./env"; |
5 | import * as core from "./core"; |
6 | import { readStr } from "./reader"; |
7 | import { prStr } from "./printer"; |
8 | |
9 | function read(str: string): MalType { |
10 | return readStr(str); |
11 | } |
12 | |
13 | function evalAST(ast: MalType, env: Env): MalType { |
14 | switch (ast.type) { |
15 | case "symbol": |
16 | const f = env.get(ast); |
17 | if (!f) { |
18 | throw new Error(`unknown symbol: ${ast.v}`); |
19 | } |
20 | return f; |
21 | case "list": |
22 | return new MalList(ast.list.map(ast => evalSexp(ast, env))); |
23 | case "vector": |
24 | return new MalVector(ast.list.map(ast => evalSexp(ast, env))); |
25 | case "hash-map": |
26 | const list: MalType[] = []; |
27 | for (const [key, value] of ast.map) { |
28 | list.push(key); |
29 | list.push(evalSexp(value, env)); |
30 | } |
31 | return new MalHashMap(list); |
32 | default: |
33 | return ast; |
34 | } |
35 | } |
36 | |
37 | function evalSexp(ast: MalType, env: Env): MalType { |
38 | if (ast.type !== "list") { |
39 | return evalAST(ast, env); |
40 | } |
41 | if (ast.list.length === 0) { |
42 | return ast; |
43 | } |
44 | const first = ast.list[0]; |
45 | switch (first.type) { |
46 | case "symbol": |
47 | switch (first.v) { |
48 | case "def!": { |
49 | const [, key, value] = ast.list; |
50 | if (!MalSymbol.is(key)) { |
51 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
52 | } |
53 | if (!value) { |
54 | throw new Error(`unexpected syntax`); |
55 | } |
56 | return env.set(key, evalSexp(value, env)) |
57 | } |
58 | case "let*": { |
59 | let letEnv = new Env(env); |
60 | const pairs = ast.list[1]; |
61 | if (!MalList.is(pairs) && !MalVector.is(pairs)) { |
62 | throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); |
63 | } |
64 | for (let i = 0; i < pairs.list.length; i += 2) { |
65 | const key = pairs.list[i]; |
66 | const value = pairs.list[i + 1]; |
67 | if (!MalSymbol.is(key)) { |
68 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
69 | } |
70 | if (!key || !value) { |
71 | throw new Error(`unexpected syntax`); |
72 | } |
73 | |
74 | letEnv.set(key, evalSexp(value, letEnv)); |
75 | } |
76 | return evalSexp(ast.list[2], letEnv); |
77 | } |
78 | case "do": { |
79 | const [, ...list] = ast.list; |
80 | const ret = evalAST(new MalList(list), env); |
81 | if (!MalList.is(ret) && !MalVector.is(ret)) { |
82 | throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); |
83 | } |
84 | return ret.list[ret.list.length - 1]; |
85 | } |
86 | case "if": { |
87 | const [, cond, thenExpr, elseExrp] = ast.list; |
88 | const ret = evalSexp(cond, env); |
89 | let b = true; |
90 | if (MalBoolean.is(ret) && !ret.v) { |
91 | b = false; |
92 | } else if (MalNull.is(ret)) { |
93 | b = false; |
94 | } |
95 | if (b) { |
96 | return evalSexp(thenExpr, env); |
97 | } else if (elseExrp) { |
98 | return evalSexp(elseExrp, env); |
99 | } else { |
100 | return MalNull.instance; |
101 | } |
102 | } |
103 | case "fn*": { |
104 | const [, args, binds] = ast.list; |
105 | if (!MalList.is(args) && !MalVector.is(args)) { |
106 | throw new Error(`unexpected return type: ${args.type}, expected: list or vector`); |
107 | } |
108 | const symbols = args.list.map(arg => { |
109 | if (!MalSymbol.is(arg)) { |
110 | throw new Error(`unexpected return type: ${arg.type}, expected: symbol`); |
111 | } |
112 | return arg; |
113 | }); |
79a10a6e |
114 | return MalFunction.fromBootstrap((...fnArgs: MalType[]) => { |
dfe70453 |
115 | return evalSexp(binds, new Env(env, symbols, fnArgs)); |
116 | }); |
117 | } |
118 | } |
119 | } |
120 | const result = evalAST(ast, env); |
121 | if (!MalList.is(result) && !MalVector.is(result)) { |
122 | throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); |
123 | } |
79a10a6e |
124 | const [f, ...args] = result.list; |
dfe70453 |
125 | if (!MalFunction.is(f)) { |
126 | throw new Error(`unexpected token: ${f.type}, expected: function`); |
127 | } |
79a10a6e |
128 | return f.func(...args); |
dfe70453 |
129 | } |
130 | |
131 | function print(exp: MalType): string { |
132 | return prStr(exp); |
133 | } |
134 | |
135 | const replEnv = new Env(); |
136 | for (const [key, value] of core.ns) { |
137 | replEnv.set(key, value); |
138 | } |
139 | |
140 | // core.mal: defined using the language itself |
141 | rep("(def! not (fn* (a) (if a false true)))"); |
142 | |
143 | function rep(str: string): string { |
144 | return print(evalSexp(read(str), replEnv)); |
145 | } |
146 | |
147 | while (true) { |
148 | const line = readline("user> "); |
149 | if (line == null) { |
150 | break; |
151 | } |
152 | if (line === "") { |
153 | continue; |
154 | } |
155 | try { |
156 | console.log(rep(line)); |
157 | } catch (e) { |
158 | const err: Error = e; |
159 | console.error(err.message); |
160 | } |
161 | } |