Commit | Line | Data |
---|---|---|
e21a85a3 | 1 | import { readline } from "./node_readline"; |
2 | ||
dd7a4f55 | 3 | import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, 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 | } | |
90ca8485 JM |
124 | if (ast.list.length === 0) { |
125 | return ast; | |
126 | } | |
e21a85a3 | 127 | |
128 | ast = macroexpand(ast, env); | |
5bb7479d | 129 | if (!isSeq(ast)) { |
e21a85a3 | 130 | return evalAST(ast, env); |
131 | } | |
132 | ||
133 | if (ast.list.length === 0) { | |
134 | return ast; | |
135 | } | |
136 | const first = ast.list[0]; | |
137 | switch (first.type) { | |
5bb7479d | 138 | case Node.Symbol: |
e21a85a3 | 139 | switch (first.v) { |
140 | case "def!": { | |
141 | const [, key, value] = ast.list; | |
5bb7479d | 142 | if (key.type !== Node.Symbol) { |
e21a85a3 | 143 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
144 | } | |
145 | if (!value) { | |
146 | throw new Error(`unexpected syntax`); | |
147 | } | |
677a1c9d | 148 | return env.set(key, evalMal(value, env)); |
e21a85a3 | 149 | } |
150 | case "let*": { | |
151 | env = new Env(env); | |
152 | const pairs = ast.list[1]; | |
92bf0530 | 153 | if (!isSeq(pairs)) { |
e21a85a3 | 154 | throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); |
155 | } | |
156 | for (let i = 0; i < pairs.list.length; i += 2) { | |
157 | const key = pairs.list[i]; | |
158 | const value = pairs.list[i + 1]; | |
5bb7479d | 159 | if (key.type !== Node.Symbol) { |
e21a85a3 | 160 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
161 | } | |
162 | if (!key || !value) { | |
163 | throw new Error(`unexpected syntax`); | |
164 | } | |
165 | ||
9c92462f | 166 | env.set(key, evalMal(value, env)); |
e21a85a3 | 167 | } |
168 | ast = ast.list[2]; | |
169 | continue loop; | |
170 | } | |
171 | case "quote": { | |
172 | return ast.list[1]; | |
173 | } | |
174 | case "quasiquote": { | |
175 | ast = quasiquote(ast.list[1]); | |
176 | continue loop; | |
177 | } | |
178 | case "defmacro!": { | |
179 | const [, key, value] = ast.list; | |
5bb7479d | 180 | if (key.type !== Node.Symbol) { |
e21a85a3 | 181 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
182 | } | |
183 | if (!value) { | |
184 | throw new Error(`unexpected syntax`); | |
185 | } | |
9c92462f | 186 | const f = evalMal(value, env); |
5bb7479d | 187 | if (f.type !== Node.Function) { |
e21a85a3 | 188 | throw new Error(`unexpected token type: ${f.type}, expected: function`); |
189 | } | |
190 | f.isMacro = true; | |
191 | return env.set(key, f); | |
192 | } | |
193 | case "macroexpand": { | |
194 | return macroexpand(ast.list[1], env); | |
195 | } | |
196 | case "do": { | |
bbddf168 | 197 | const list = ast.list.slice(1, -1); |
198 | evalAST(new MalList(list), env); | |
199 | ast = ast.list[ast.list.length - 1]; | |
e21a85a3 | 200 | continue loop; |
201 | } | |
202 | case "if": { | |
203 | const [, cond, thenExpr, elseExrp] = ast.list; | |
9c92462f | 204 | const ret = evalMal(cond, env); |
e21a85a3 | 205 | let b = true; |
5bb7479d | 206 | if (ret.type === Node.Boolean && !ret.v) { |
e21a85a3 | 207 | b = false; |
6071876f | 208 | } else if (ret.type === Node.Nil) { |
e21a85a3 | 209 | b = false; |
210 | } | |
211 | if (b) { | |
212 | ast = thenExpr; | |
213 | } else if (elseExrp) { | |
214 | ast = elseExrp; | |
215 | } else { | |
6071876f | 216 | ast = MalNil.instance; |
e21a85a3 | 217 | } |
218 | continue loop; | |
219 | } | |
220 | case "fn*": { | |
221 | const [, params, bodyAst] = ast.list; | |
92bf0530 | 222 | if (!isSeq(params)) { |
e21a85a3 | 223 | throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); |
224 | } | |
225 | const symbols = params.list.map(param => { | |
5bb7479d | 226 | if (param.type !== Node.Symbol) { |
e21a85a3 | 227 | throw new Error(`unexpected return type: ${param.type}, expected: symbol`); |
228 | } | |
229 | return param; | |
230 | }); | |
9c92462f | 231 | return MalFunction.fromLisp(evalMal, env, symbols, bodyAst); |
e21a85a3 | 232 | } |
233 | } | |
234 | } | |
235 | const result = evalAST(ast, env); | |
92bf0530 | 236 | if (!isSeq(result)) { |
e21a85a3 | 237 | throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); |
238 | } | |
239 | const [f, ...args] = result.list; | |
5bb7479d | 240 | if (f.type !== Node.Function) { |
e21a85a3 | 241 | throw new Error(`unexpected token: ${f.type}, expected: function`); |
242 | } | |
243 | if (f.ast) { | |
244 | ast = f.ast; | |
245 | env = f.newEnv(args); | |
246 | continue loop; | |
247 | } | |
248 | ||
249 | return f.func(...args); | |
250 | } | |
251 | } | |
252 | ||
9c92462f | 253 | |
e21a85a3 | 254 | function print(exp: MalType): string { |
255 | return prStr(exp); | |
256 | } | |
257 | ||
258 | const replEnv = new Env(); | |
9c92462f | 259 | function rep(str: string): string { |
260 | return print(evalMal(read(str), replEnv)); | |
261 | } | |
262 | ||
263 | // core.EXT: defined using Racket | |
eb7a2bbd | 264 | core.ns.forEach((value, key) => { |
e21a85a3 | 265 | replEnv.set(key, value); |
eb7a2bbd | 266 | }); |
e21a85a3 | 267 | replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { |
268 | if (!ast) { | |
269 | throw new Error(`undefined argument`); | |
270 | } | |
9c92462f | 271 | return evalMal(ast, replEnv); |
e21a85a3 | 272 | })); |
e21a85a3 | 273 | replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); |
274 | ||
275 | // core.mal: defined using the language itself | |
276 | rep("(def! not (fn* (a) (if a false true)))"); | |
e6d41de4 | 277 | rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))`); |
e21a85a3 | 278 | 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)))))))`); |
e21a85a3 | 279 | |
280 | if (typeof process !== "undefined" && 2 < process.argv.length) { | |
281 | replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s)))); | |
282 | rep(`(load-file "${process.argv[2]}")`); | |
283 | process.exit(0); | |
284 | } | |
285 | ||
e21a85a3 | 286 | while (true) { |
287 | const line = readline("user> "); | |
288 | if (line == null) { | |
289 | break; | |
290 | } | |
291 | if (line === "") { | |
292 | continue; | |
293 | } | |
294 | try { | |
295 | console.log(rep(line)); | |
296 | } catch (e) { | |
dd7a4f55 JM |
297 | if (isAST(e)) { |
298 | console.error("Error:", prStr(e)); | |
299 | } else { | |
300 | const err: Error = e; | |
301 | console.error("Error:", err.message); | |
302 | } | |
e21a85a3 | 303 | } |
304 | } |