Commit | Line | Data |
---|---|---|
555f7fc7 | 1 | import { readline } from "./node_readline"; |
2 | ||
dd7a4f55 | 3 | import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; |
555f7fc7 | 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 |
555f7fc7 | 10 | function read(str: string): MalType { |
11 | return readStr(str); | |
12 | } | |
13 | ||
14 | function evalAST(ast: MalType, env: Env): MalType { | |
15 | switch (ast.type) { | |
5bb7479d | 16 | case Node.Symbol: |
555f7fc7 | 17 | const f = env.get(ast); |
18 | if (!f) { | |
19 | throw new Error(`unknown symbol: ${ast.v}`); | |
20 | } | |
21 | return f; | |
5bb7479d | 22 | case Node.List: |
9c92462f | 23 | return new MalList(ast.list.map(ast => evalMal(ast, env))); |
5bb7479d | 24 | case Node.Vector: |
9c92462f | 25 | return new MalVector(ast.list.map(ast => evalMal(ast, env))); |
5bb7479d | 26 | case Node.HashMap: |
555f7fc7 | 27 | const list: MalType[] = []; |
10f8aa84 | 28 | for (const [key, value] of ast.entries()) { |
555f7fc7 | 29 | list.push(key); |
9c92462f | 30 | list.push(evalMal(value, env)); |
555f7fc7 | 31 | } |
32 | return new MalHashMap(list); | |
33 | default: | |
34 | return ast; | |
35 | } | |
36 | } | |
37 | ||
9c92462f | 38 | // EVAL |
39 | function evalMal(ast: MalType, env: Env): MalType { | |
555f7fc7 | 40 | loop: while (true) { |
5bb7479d | 41 | if (ast.type !== Node.List) { |
555f7fc7 | 42 | return evalAST(ast, env); |
43 | } | |
44 | if (ast.list.length === 0) { | |
45 | return ast; | |
46 | } | |
47 | const first = ast.list[0]; | |
48 | switch (first.type) { | |
5bb7479d | 49 | case Node.Symbol: |
555f7fc7 | 50 | switch (first.v) { |
51 | case "def!": { | |
52 | const [, key, value] = ast.list; | |
5bb7479d | 53 | if (key.type !== Node.Symbol) { |
555f7fc7 | 54 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
55 | } | |
56 | if (!value) { | |
57 | throw new Error(`unexpected syntax`); | |
58 | } | |
677a1c9d | 59 | return env.set(key, evalMal(value, env)); |
555f7fc7 | 60 | } |
61 | case "let*": { | |
62 | env = new Env(env); | |
63 | const pairs = ast.list[1]; | |
92bf0530 | 64 | if (!isSeq(pairs)) { |
555f7fc7 | 65 | throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); |
66 | } | |
67 | for (let i = 0; i < pairs.list.length; i += 2) { | |
68 | const key = pairs.list[i]; | |
69 | const value = pairs.list[i + 1]; | |
5bb7479d | 70 | if (key.type !== Node.Symbol) { |
555f7fc7 | 71 | throw new Error(`unexpected token type: ${key.type}, expected: symbol`); |
72 | } | |
73 | if (!key || !value) { | |
74 | throw new Error(`unexpected syntax`); | |
75 | } | |
76 | ||
9c92462f | 77 | env.set(key, evalMal(value, env)); |
555f7fc7 | 78 | } |
79 | ast = ast.list[2]; | |
80 | continue loop; | |
81 | } | |
82 | case "do": { | |
bbddf168 | 83 | const list = ast.list.slice(1, -1); |
84 | evalAST(new MalList(list), env); | |
85 | ast = ast.list[ast.list.length - 1]; | |
555f7fc7 | 86 | continue loop; |
87 | } | |
88 | case "if": { | |
89 | const [, cond, thenExpr, elseExrp] = ast.list; | |
9c92462f | 90 | const ret = evalMal(cond, env); |
555f7fc7 | 91 | let b = true; |
5bb7479d | 92 | if (ret.type === Node.Boolean && !ret.v) { |
555f7fc7 | 93 | b = false; |
6071876f | 94 | } else if (ret.type === Node.Nil) { |
555f7fc7 | 95 | b = false; |
96 | } | |
97 | if (b) { | |
98 | ast = thenExpr; | |
99 | } else if (elseExrp) { | |
100 | ast = elseExrp; | |
101 | } else { | |
6071876f | 102 | ast = MalNil.instance; |
555f7fc7 | 103 | } |
104 | continue loop; | |
105 | } | |
106 | case "fn*": { | |
107 | const [, params, bodyAst] = ast.list; | |
92bf0530 | 108 | if (!isSeq(params)) { |
555f7fc7 | 109 | throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); |
110 | } | |
111 | const symbols = params.list.map(param => { | |
5bb7479d | 112 | if (param.type !== Node.Symbol) { |
555f7fc7 | 113 | throw new Error(`unexpected return type: ${param.type}, expected: symbol`); |
114 | } | |
115 | return param; | |
116 | }); | |
9c92462f | 117 | return MalFunction.fromLisp(evalMal, env, symbols, bodyAst); |
555f7fc7 | 118 | } |
119 | } | |
120 | } | |
121 | const result = evalAST(ast, env); | |
92bf0530 | 122 | if (!isSeq(result)) { |
555f7fc7 | 123 | throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); |
124 | } | |
125 | const [f, ...args] = result.list; | |
5bb7479d | 126 | if (f.type !== Node.Function) { |
555f7fc7 | 127 | throw new Error(`unexpected token: ${f.type}, expected: function`); |
128 | } | |
129 | if (f.ast) { | |
130 | ast = f.ast; | |
131 | env = f.newEnv(args); | |
132 | continue loop; | |
133 | } | |
134 | ||
135 | return f.func(...args); | |
136 | } | |
137 | } | |
138 | ||
9c92462f | 139 | |
555f7fc7 | 140 | function print(exp: MalType): string { |
141 | return prStr(exp); | |
142 | } | |
143 | ||
144 | const replEnv = new Env(); | |
9c92462f | 145 | function rep(str: string): string { |
146 | return print(evalMal(read(str), replEnv)); | |
147 | } | |
148 | ||
149 | // core.EXT: defined using Racket | |
eb7a2bbd | 150 | core.ns.forEach((value, key) => { |
555f7fc7 | 151 | replEnv.set(key, value); |
eb7a2bbd | 152 | }); |
555f7fc7 | 153 | replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { |
154 | if (!ast) { | |
155 | throw new Error(`undefined argument`); | |
156 | } | |
9c92462f | 157 | return evalMal(ast, replEnv); |
555f7fc7 | 158 | })); |
159 | ||
160 | replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); | |
161 | ||
162 | // core.mal: defined using the language itself | |
163 | rep("(def! not (fn* (a) (if a false true)))"); | |
e6d41de4 | 164 | rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))`); |
555f7fc7 | 165 | |
166 | if (typeof process !== "undefined" && 2 < process.argv.length) { | |
167 | replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s)))); | |
168 | rep(`(load-file "${process.argv[2]}")`); | |
169 | process.exit(0); | |
170 | } | |
171 | ||
555f7fc7 | 172 | while (true) { |
173 | const line = readline("user> "); | |
174 | if (line == null) { | |
175 | break; | |
176 | } | |
177 | if (line === "") { | |
178 | continue; | |
179 | } | |
180 | try { | |
181 | console.log(rep(line)); | |
182 | } catch (e) { | |
dd7a4f55 JM |
183 | if (isAST(e)) { |
184 | console.error("Error:", prStr(e)); | |
185 | } else { | |
186 | const err: Error = e; | |
187 | console.error("Error:", err.message); | |
188 | } | |
555f7fc7 | 189 | } |
190 | } |