e21a85a3 |
1 | import { readline } from "./node_readline"; |
2 | |
3 | import { MalType, MalString, 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 quasiquote(ast: MalType): MalType { |
14 | if (!isPair(ast)) { |
15 | return new MalList([MalSymbol.get("quote"), ast]); |
16 | } |
17 | if (!MalList.is(ast) && !MalVector.is(ast)) { |
18 | throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); |
19 | } |
20 | const [arg1, arg2] = ast.list; |
21 | if (MalSymbol.is(arg1) && arg1.v === "unquote") { |
22 | return arg2; |
23 | } |
24 | if (isPair(arg1)) { |
25 | if (!MalList.is(arg1) && !MalVector.is(arg1)) { |
26 | throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); |
27 | } |
28 | const [arg11, arg12] = arg1.list; |
29 | if (MalSymbol.is(arg11) && arg11.v === "splice-unquote") { |
30 | return new MalList([ |
31 | MalSymbol.get("concat"), |
32 | arg12, |
33 | quasiquote(new MalList(ast.list.slice(1))), |
34 | ]); |
35 | } |
36 | } |
37 | |
38 | return new MalList([ |
39 | MalSymbol.get("cons"), |
40 | quasiquote(arg1), |
41 | quasiquote(new MalList(ast.list.slice(1))), |
42 | ]); |
43 | |
44 | function isPair(ast: MalType) { |
45 | if (!MalList.is(ast) && !MalVector.is(ast)) { |
46 | return false; |
47 | } |
48 | |
49 | return 0 < ast.list.length; |
50 | } |
51 | } |
52 | |
53 | function isMacroCall(ast: MalType, env: Env): boolean { |
54 | if (!MalList.is(ast) && !MalVector.is(ast)) { |
55 | return false; |
56 | } |
57 | const s = ast.list[0]; |
58 | if (!MalSymbol.is(s)) { |
59 | return false; |
60 | } |
61 | const foundEnv = env.find(s); |
62 | if (!foundEnv) { |
63 | return false; |
64 | } |
65 | |
66 | const f = foundEnv.get(s); |
67 | if (!MalFunction.is(f)) { |
68 | return false; |
69 | } |
70 | |
71 | return f.isMacro; |
72 | } |
73 | |
74 | function macroexpand(ast: MalType, env: Env): MalType { |
75 | while (isMacroCall(ast, env)) { |
76 | if (!MalList.is(ast) && !MalVector.is(ast)) { |
77 | throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); |
78 | } |
79 | const s = ast.list[0]; |
80 | if (!MalSymbol.is(s)) { |
81 | throw new Error(`unexpected token type: ${s.type}, expected: symbol`); |
82 | } |
83 | const f = env.get(s); |
84 | if (!MalFunction.is(f)) { |
85 | throw new Error(`unexpected token type: ${f.type}, expected: function`); |
86 | } |
87 | ast = f.func(...ast.list.slice(1)); |
88 | } |
89 | |
90 | return ast; |
91 | } |
92 | |
93 | function evalAST(ast: MalType, env: Env): MalType { |
94 | switch (ast.type) { |
95 | case "symbol": |
96 | const f = env.get(ast); |
97 | if (!f) { |
98 | throw new Error(`unknown symbol: ${ast.v}`); |
99 | } |
100 | return f; |
101 | case "list": |
102 | return new MalList(ast.list.map(ast => evalSexp(ast, env))); |
103 | case "vector": |
104 | return new MalVector(ast.list.map(ast => evalSexp(ast, env))); |
105 | case "hash-map": |
106 | const list: MalType[] = []; |
107 | for (const [key, value] of ast.map) { |
108 | list.push(key); |
109 | list.push(evalSexp(value, env)); |
110 | } |
111 | return new MalHashMap(list); |
112 | default: |
113 | return ast; |
114 | } |
115 | } |
116 | |
117 | function evalSexp(ast: MalType, env: Env): MalType { |
118 | loop: while (true) { |
119 | if (ast.type !== "list") { |
120 | return evalAST(ast, env); |
121 | } |
122 | |
123 | ast = macroexpand(ast, env); |
124 | if (ast.type !== "list" && ast.type !== "vector") { |
125 | return evalAST(ast, env); |
126 | } |
127 | |
128 | if (ast.list.length === 0) { |
129 | return ast; |
130 | } |
131 | const first = ast.list[0]; |
132 | switch (first.type) { |
133 | case "symbol": |
134 | switch (first.v) { |
135 | case "def!": { |
136 | const [, key, value] = ast.list; |
137 | if (!MalSymbol.is(key)) { |
138 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
139 | } |
140 | if (!value) { |
141 | throw new Error(`unexpected syntax`); |
142 | } |
143 | return env.set(key, evalSexp(value, env)) |
144 | } |
145 | case "let*": { |
146 | env = new Env(env); |
147 | const pairs = ast.list[1]; |
148 | if (!MalList.is(pairs) && !MalVector.is(pairs)) { |
149 | throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); |
150 | } |
151 | for (let i = 0; i < pairs.list.length; i += 2) { |
152 | const key = pairs.list[i]; |
153 | const value = pairs.list[i + 1]; |
154 | if (!MalSymbol.is(key)) { |
155 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
156 | } |
157 | if (!key || !value) { |
158 | throw new Error(`unexpected syntax`); |
159 | } |
160 | |
161 | env.set(key, evalSexp(value, env)); |
162 | } |
163 | ast = ast.list[2]; |
164 | continue loop; |
165 | } |
166 | case "quote": { |
167 | return ast.list[1]; |
168 | } |
169 | case "quasiquote": { |
170 | ast = quasiquote(ast.list[1]); |
171 | continue loop; |
172 | } |
173 | case "defmacro!": { |
174 | const [, key, value] = ast.list; |
175 | if (!MalSymbol.is(key)) { |
176 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
177 | } |
178 | if (!value) { |
179 | throw new Error(`unexpected syntax`); |
180 | } |
181 | const f = evalSexp(value, env); |
182 | if (!MalFunction.is(f)) { |
183 | throw new Error(`unexpected token type: ${f.type}, expected: function`); |
184 | } |
185 | f.isMacro = true; |
186 | return env.set(key, f); |
187 | } |
188 | case "macroexpand": { |
189 | return macroexpand(ast.list[1], env); |
190 | } |
191 | case "do": { |
192 | const [, ...list] = ast.list; |
193 | const ret = evalAST(new MalList(list), env); |
194 | if (!MalList.is(ret) && !MalVector.is(ret)) { |
195 | throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); |
196 | } |
197 | ast = ret.list[ret.list.length - 1]; |
198 | continue loop; |
199 | } |
200 | case "if": { |
201 | const [, cond, thenExpr, elseExrp] = ast.list; |
202 | const ret = evalSexp(cond, env); |
203 | let b = true; |
204 | if (MalBoolean.is(ret) && !ret.v) { |
205 | b = false; |
206 | } else if (MalNull.is(ret)) { |
207 | b = false; |
208 | } |
209 | if (b) { |
210 | ast = thenExpr; |
211 | } else if (elseExrp) { |
212 | ast = elseExrp; |
213 | } else { |
214 | ast = MalNull.instance; |
215 | } |
216 | continue loop; |
217 | } |
218 | case "fn*": { |
219 | const [, params, bodyAst] = ast.list; |
220 | if (!MalList.is(params) && !MalVector.is(params)) { |
221 | throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); |
222 | } |
223 | const symbols = params.list.map(param => { |
224 | if (!MalSymbol.is(param)) { |
225 | throw new Error(`unexpected return type: ${param.type}, expected: symbol`); |
226 | } |
227 | return param; |
228 | }); |
229 | return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); |
230 | } |
231 | } |
232 | } |
233 | const result = evalAST(ast, env); |
234 | if (!MalList.is(result) && !MalVector.is(result)) { |
235 | throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); |
236 | } |
237 | const [f, ...args] = result.list; |
238 | if (!MalFunction.is(f)) { |
239 | throw new Error(`unexpected token: ${f.type}, expected: function`); |
240 | } |
241 | if (f.ast) { |
242 | ast = f.ast; |
243 | env = f.newEnv(args); |
244 | continue loop; |
245 | } |
246 | |
247 | return f.func(...args); |
248 | } |
249 | } |
250 | |
251 | function print(exp: MalType): string { |
252 | return prStr(exp); |
253 | } |
254 | |
255 | const replEnv = new Env(); |
256 | for (const [key, value] of core.ns) { |
257 | replEnv.set(key, value); |
258 | } |
259 | replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { |
260 | if (!ast) { |
261 | throw new Error(`undefined argument`); |
262 | } |
263 | return evalSexp(ast, replEnv); |
264 | })); |
265 | |
266 | replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); |
267 | |
268 | // core.mal: defined using the language itself |
269 | rep("(def! not (fn* (a) (if a false true)))"); |
270 | rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`); |
271 | rep(`(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))`); |
272 | rep('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))'); |
273 | |
274 | if (typeof process !== "undefined" && 2 < process.argv.length) { |
275 | replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s)))); |
276 | rep(`(load-file "${process.argv[2]}")`); |
277 | process.exit(0); |
278 | } |
279 | |
280 | function rep(str: string): string { |
281 | return print(evalSexp(read(str), replEnv)); |
282 | } |
283 | |
284 | while (true) { |
285 | const line = readline("user> "); |
286 | if (line == null) { |
287 | break; |
288 | } |
289 | if (line === "") { |
290 | continue; |
291 | } |
292 | try { |
293 | console.log(rep(line)); |
294 | } catch (e) { |
295 | const err: Error = e; |
296 | console.error(err.message); |
297 | } |
298 | } |