555f7fc7 |
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 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 | loop: while (true) { |
39 | if (ast.type !== "list") { |
40 | return evalAST(ast, env); |
41 | } |
42 | if (ast.list.length === 0) { |
43 | return ast; |
44 | } |
45 | const first = ast.list[0]; |
46 | switch (first.type) { |
47 | case "symbol": |
48 | switch (first.v) { |
49 | case "def!": { |
50 | const [, key, value] = ast.list; |
51 | if (!MalSymbol.is(key)) { |
52 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
53 | } |
54 | if (!value) { |
55 | throw new Error(`unexpected syntax`); |
56 | } |
57 | return env.set(key, evalSexp(value, env)) |
58 | } |
59 | case "let*": { |
60 | env = new Env(env); |
61 | const pairs = ast.list[1]; |
62 | if (!MalList.is(pairs) && !MalVector.is(pairs)) { |
63 | throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); |
64 | } |
65 | for (let i = 0; i < pairs.list.length; i += 2) { |
66 | const key = pairs.list[i]; |
67 | const value = pairs.list[i + 1]; |
68 | if (!MalSymbol.is(key)) { |
69 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
70 | } |
71 | if (!key || !value) { |
72 | throw new Error(`unexpected syntax`); |
73 | } |
74 | |
75 | env.set(key, evalSexp(value, env)); |
76 | } |
77 | ast = ast.list[2]; |
78 | continue loop; |
79 | } |
80 | case "do": { |
81 | const [, ...list] = ast.list; |
82 | const ret = evalAST(new MalList(list), env); |
83 | if (!MalList.is(ret) && !MalVector.is(ret)) { |
84 | throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); |
85 | } |
86 | ast = ret.list[ret.list.length - 1]; |
87 | continue loop; |
88 | } |
89 | case "if": { |
90 | const [, cond, thenExpr, elseExrp] = ast.list; |
91 | const ret = evalSexp(cond, env); |
92 | let b = true; |
93 | if (MalBoolean.is(ret) && !ret.v) { |
94 | b = false; |
95 | } else if (MalNull.is(ret)) { |
96 | b = false; |
97 | } |
98 | if (b) { |
99 | ast = thenExpr; |
100 | } else if (elseExrp) { |
101 | ast = elseExrp; |
102 | } else { |
103 | ast = MalNull.instance; |
104 | } |
105 | continue loop; |
106 | } |
107 | case "fn*": { |
108 | const [, params, bodyAst] = ast.list; |
109 | if (!MalList.is(params) && !MalVector.is(params)) { |
110 | throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); |
111 | } |
112 | const symbols = params.list.map(param => { |
113 | if (!MalSymbol.is(param)) { |
114 | throw new Error(`unexpected return type: ${param.type}, expected: symbol`); |
115 | } |
116 | return param; |
117 | }); |
118 | return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); |
119 | } |
120 | } |
121 | } |
122 | const result = evalAST(ast, env); |
123 | if (!MalList.is(result) && !MalVector.is(result)) { |
124 | throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); |
125 | } |
126 | const [f, ...args] = result.list; |
127 | if (!MalFunction.is(f)) { |
128 | throw new Error(`unexpected token: ${f.type}, expected: function`); |
129 | } |
130 | if (f.ast) { |
131 | ast = f.ast; |
132 | env = f.newEnv(args); |
133 | continue loop; |
134 | } |
135 | |
136 | return f.func(...args); |
137 | } |
138 | } |
139 | |
140 | function print(exp: MalType): string { |
141 | return prStr(exp); |
142 | } |
143 | |
144 | const replEnv = new Env(); |
145 | for (const [key, value] of core.ns) { |
146 | replEnv.set(key, value); |
147 | } |
148 | replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { |
149 | if (!ast) { |
150 | throw new Error(`undefined argument`); |
151 | } |
152 | return evalSexp(ast, replEnv); |
153 | })); |
154 | |
155 | replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); |
156 | |
157 | // core.mal: defined using the language itself |
158 | rep("(def! not (fn* (a) (if a false true)))"); |
159 | rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`); |
160 | |
161 | if (typeof process !== "undefined" && 2 < process.argv.length) { |
162 | replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s)))); |
163 | rep(`(load-file "${process.argv[2]}")`); |
164 | process.exit(0); |
165 | } |
166 | |
167 | function rep(str: string): string { |
168 | return print(evalSexp(read(str), replEnv)); |
169 | } |
170 | |
171 | while (true) { |
172 | const line = readline("user> "); |
173 | if (line == null) { |
174 | break; |
175 | } |
176 | if (line === "") { |
177 | continue; |
178 | } |
179 | try { |
180 | console.log(rep(line)); |
181 | } catch (e) { |
182 | const err: Error = e; |
183 | console.error(err.message); |
184 | } |
185 | } |