ada.2: typo
[jackhill/mal.git] / impls / ts / step8_macros.ts
CommitLineData
e21a85a3 1import { readline } from "./node_readline";
2
dd7a4f55 3import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types";
e21a85a3 4import { Env } from "./env";
5import * as core from "./core";
6import { readStr } from "./reader";
7import { prStr } from "./printer";
8
9c92462f 9// READ
e21a85a3 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)) {
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 54function 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
75function 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
94function 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
119function 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// PRINT
e21a85a3 254function print(exp: MalType): string {
255 return prStr(exp);
256}
257
258const replEnv = new Env();
9c92462f 259function rep(str: string): string {
260 return print(evalMal(read(str), replEnv));
261}
262
263// core.EXT: defined using Racket
eb7a2bbd 264core.ns.forEach((value, key) => {
e21a85a3 265 replEnv.set(key, value);
eb7a2bbd 266});
e21a85a3 267replEnv.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 273replEnv.set(MalSymbol.get("*ARGV*"), new MalList([]));
274
275// core.mal: defined using the language itself
276rep("(def! not (fn* (a) (if a false true)))");
e6d41de4 277rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))`);
e21a85a3 278rep(`(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
280if (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 286while (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}