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