Merge pull request #378 from asarhaddon/test-macro-not-changing-function
[jackhill/mal.git] / ts / stepA_mal.ts
CommitLineData
bbddf168 1import { readline } from "./node_readline";
2
6071876f 3import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types";
bbddf168 4import { Env } from "./env";
5import * as core from "./core";
6import { readStr } from "./reader";
7import { prStr } from "./printer";
8
9c92462f 9// READ
bbddf168 10function read(str: string): MalType {
11 return readStr(str);
12}
13
14function quasiquote(ast: MalType): MalType {
15 if (!isPair(ast)) {
16 return new MalList([MalSymbol.get("quote"), ast]);
17 }
92bf0530 18 if (!isSeq(ast)) {
bbddf168 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") {
bbddf168 23 return arg2;
24 }
25 if (isPair(arg1)) {
92bf0530 26 if (!isSeq(arg1)) {
bbddf168 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") {
bbddf168 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)) {
bbddf168 47 return false;
48 }
49
50 return 0 < ast.list.length;
51 }
52}
53
9c92462f 54function isMacro(ast: MalType, env: Env): boolean {
92bf0530 55 if (!isSeq(ast)) {
bbddf168 56 return false;
57 }
58 const s = ast.list[0];
5bb7479d 59 if (s.type !== Node.Symbol) {
bbddf168 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) {
bbddf168 69 return false;
70 }
71
72 return f.isMacro;
73}
74
75function macroexpand(ast: MalType, env: Env): MalType {
9c92462f 76 while (isMacro(ast, env)) {
92bf0530 77 if (!isSeq(ast)) {
bbddf168 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) {
bbddf168 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) {
bbddf168 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
94function evalAST(ast: MalType, env: Env): MalType {
95 switch (ast.type) {
5bb7479d 96 case Node.Symbol:
bbddf168 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:
bbddf168 107 const list: MalType[] = [];
108 for (const [key, value] of ast.entries()) {
109 list.push(key);
9c92462f 110 list.push(evalMal(value, env));
bbddf168 111 }
112 return new MalHashMap(list);
113 default:
114 return ast;
115 }
116}
117
9c92462f 118// EVAL
119function evalMal(ast: MalType, env: Env): MalType {
bbddf168 120 loop: while (true) {
5bb7479d 121 if (ast.type !== Node.List) {
bbddf168 122 return evalAST(ast, env);
123 }
90ca8485
JM
124 if (ast.list.length === 0) {
125 return ast;
126 }
bbddf168 127
128 ast = macroexpand(ast, env);
5bb7479d 129 if (!isSeq(ast)) {
bbddf168 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:
bbddf168 139 switch (first.v) {
140 case "def!": {
141 const [, key, value] = ast.list;
5bb7479d 142 if (key.type !== Node.Symbol) {
bbddf168 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));
bbddf168 149 }
150 case "let*": {
151 env = new Env(env);
152 const pairs = ast.list[1];
92bf0530 153 if (!isSeq(pairs)) {
bbddf168 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) {
bbddf168 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));
bbddf168 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) {
bbddf168 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) {
bbddf168 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 "try*": {
197 try {
9c92462f 198 return evalMal(ast.list[1], env);
bbddf168 199 } catch (e) {
dd7a4f55
JM
200 if (ast.list.length < 3) {
201 throw e;
202 }
bbddf168 203 const catchBody = ast.list[2];
92bf0530 204 if (!isSeq(catchBody)) {
bbddf168 205 throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`);
206 }
207 const catchSymbol = catchBody.list[0];
5bb7479d 208 if (catchSymbol.type === Node.Symbol && catchSymbol.v === "catch*") {
bbddf168 209 const errorSymbol = catchBody.list[1];
5bb7479d 210 if (errorSymbol.type !== Node.Symbol) {
bbddf168 211 throw new Error(`unexpected return type: ${errorSymbol.type}, expected: symbol`);
212 }
213 if (!isAST(e)) {
214 e = new MalString((e as Error).message);
215 }
9c92462f 216 return evalMal(catchBody.list[2], new Env(env, [errorSymbol], [e]));
bbddf168 217 }
218 throw e;
219 }
220 }
221 case "do": {
222 const list = ast.list.slice(1, -1);
223 evalAST(new MalList(list), env);
224 ast = ast.list[ast.list.length - 1];
225 continue loop;
226 }
227 case "if": {
228 const [, cond, thenExpr, elseExrp] = ast.list;
9c92462f 229 const ret = evalMal(cond, env);
bbddf168 230 let b = true;
5bb7479d 231 if (ret.type === Node.Boolean && !ret.v) {
bbddf168 232 b = false;
6071876f 233 } else if (ret.type === Node.Nil) {
bbddf168 234 b = false;
235 }
236 if (b) {
237 ast = thenExpr;
238 } else if (elseExrp) {
239 ast = elseExrp;
240 } else {
6071876f 241 ast = MalNil.instance;
bbddf168 242 }
243 continue loop;
244 }
245 case "fn*": {
246 const [, params, bodyAst] = ast.list;
92bf0530 247 if (!isSeq(params)) {
bbddf168 248 throw new Error(`unexpected return type: ${params.type}, expected: list or vector`);
249 }
250 const symbols = params.list.map(param => {
5bb7479d 251 if (param.type !== Node.Symbol) {
bbddf168 252 throw new Error(`unexpected return type: ${param.type}, expected: symbol`);
253 }
254 return param;
255 });
9c92462f 256 return MalFunction.fromLisp(evalMal, env, symbols, bodyAst);
bbddf168 257 }
258 }
259 }
260 const result = evalAST(ast, env);
92bf0530 261 if (!isSeq(result)) {
bbddf168 262 throw new Error(`unexpected return type: ${result.type}, expected: list or vector`);
263 }
264 const [f, ...args] = result.list;
5bb7479d 265 if (f.type !== Node.Function) {
bbddf168 266 throw new Error(`unexpected token: ${f.type}, expected: function`);
267 }
268 if (f.ast) {
269 ast = f.ast;
270 env = f.newEnv(args);
271 continue loop;
272 }
273
274 return f.func(...args);
275 }
276}
277
9c92462f 278// PRINT
bbddf168 279function print(exp: MalType): string {
280 return prStr(exp);
281}
282
283const replEnv = new Env();
9c92462f 284function rep(str: string): string {
285 return print(evalMal(read(str), replEnv));
286}
287
288// core.EXT: defined using Racket
eb7a2bbd 289core.ns.forEach((value, key) => {
bbddf168 290 replEnv.set(key, value);
eb7a2bbd 291});
bbddf168 292replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => {
293 if (!ast) {
294 throw new Error(`undefined argument`);
295 }
9c92462f 296 return evalMal(ast, replEnv);
bbddf168 297}));
bbddf168 298replEnv.set(MalSymbol.get("*ARGV*"), new MalList([]));
299
300// core.mal: defined using the language itself
301rep(`(def! *host-language* "TypeScript")`);
302rep("(def! not (fn* (a) (if a false true)))");
303rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`);
304rep(`(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)))))))`);
14ab099c
NB
305rep("(def! inc (fn* [x] (+ x 1)))");
306rep("(def! gensym (let* [counter (atom 0)] (fn* [] (symbol (str \"G__\" (swap! counter inc))))))");
bbddf168 307rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))");
308
309if (typeof process !== "undefined" && 2 < process.argv.length) {
310 replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s))));
311 rep(`(load-file "${process.argv[2]}")`);
312 process.exit(0);
313}
314
bbddf168 315rep(`(println (str "Mal [" *host-language* "]"))`);
316while (true) {
317 const line = readline("user> ");
318 if (line == null) {
319 break;
320 }
321 if (line === "") {
322 continue;
323 }
324 try {
325 console.log(rep(line));
326 } catch (e) {
dd7a4f55
JM
327 if (isAST(e)) {
328 console.error("Error:", prStr(e));
329 } else {
330 const err: Error = e;
331 console.error("Error:", err.message);
332 }
bbddf168 333 }
334}