small fix
[jackhill/mal.git] / ts / step8_macros.ts
1 import { readline } from "./node_readline";
2
3 import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } 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 // READ
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 }
18 if (!isSeq(ast)) {
19 throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`);
20 }
21 const [arg1, arg2] = ast.list;
22 if (arg1.type === Node.Symbol && arg1.v === "unquote") {
23 return arg2;
24 }
25 if (isPair(arg1)) {
26 if (!isSeq(arg1)) {
27 throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`);
28 }
29 const [arg11, arg12] = arg1.list;
30 if (arg11.type === Node.Symbol && arg11.v === "splice-unquote") {
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) {
46 if (!isSeq(ast)) {
47 return false;
48 }
49
50 return 0 < ast.list.length;
51 }
52 }
53
54 function isMacro(ast: MalType, env: Env): boolean {
55 if (!isSeq(ast)) {
56 return false;
57 }
58 const s = ast.list[0];
59 if (s.type !== Node.Symbol) {
60 return false;
61 }
62 const foundEnv = env.find(s);
63 if (!foundEnv) {
64 return false;
65 }
66
67 const f = foundEnv.get(s);
68 if (f.type !== Node.Function) {
69 return false;
70 }
71
72 return f.isMacro;
73 }
74
75 function macroexpand(ast: MalType, env: Env): MalType {
76 while (isMacro(ast, env)) {
77 if (!isSeq(ast)) {
78 throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`);
79 }
80 const s = ast.list[0];
81 if (s.type !== Node.Symbol) {
82 throw new Error(`unexpected token type: ${s.type}, expected: symbol`);
83 }
84 const f = env.get(s);
85 if (f.type !== Node.Function) {
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) {
96 case Node.Symbol:
97 const f = env.get(ast);
98 if (!f) {
99 throw new Error(`unknown symbol: ${ast.v}`);
100 }
101 return f;
102 case Node.List:
103 return new MalList(ast.list.map(ast => evalMal(ast, env)));
104 case Node.Vector:
105 return new MalVector(ast.list.map(ast => evalMal(ast, env)));
106 case Node.HashMap:
107 const list: MalType[] = [];
108 for (const [key, value] of ast.entries()) {
109 list.push(key);
110 list.push(evalMal(value, env));
111 }
112 return new MalHashMap(list);
113 default:
114 return ast;
115 }
116 }
117
118 // EVAL
119 function evalMal(ast: MalType, env: Env): MalType {
120 loop: while (true) {
121 if (ast.type !== Node.List) {
122 return evalAST(ast, env);
123 }
124
125 ast = macroexpand(ast, env);
126 if (!isSeq(ast)) {
127 return evalAST(ast, env);
128 }
129
130 if (ast.list.length === 0) {
131 return ast;
132 }
133 const first = ast.list[0];
134 switch (first.type) {
135 case Node.Symbol:
136 switch (first.v) {
137 case "def!": {
138 const [, key, value] = ast.list;
139 if (key.type !== Node.Symbol) {
140 throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
141 }
142 if (!value) {
143 throw new Error(`unexpected syntax`);
144 }
145 return env.set(key, evalMal(value, env));
146 }
147 case "let*": {
148 env = new Env(env);
149 const pairs = ast.list[1];
150 if (!isSeq(pairs)) {
151 throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`);
152 }
153 for (let i = 0; i < pairs.list.length; i += 2) {
154 const key = pairs.list[i];
155 const value = pairs.list[i + 1];
156 if (key.type !== Node.Symbol) {
157 throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
158 }
159 if (!key || !value) {
160 throw new Error(`unexpected syntax`);
161 }
162
163 env.set(key, evalMal(value, env));
164 }
165 ast = ast.list[2];
166 continue loop;
167 }
168 case "quote": {
169 return ast.list[1];
170 }
171 case "quasiquote": {
172 ast = quasiquote(ast.list[1]);
173 continue loop;
174 }
175 case "defmacro!": {
176 const [, key, value] = ast.list;
177 if (key.type !== Node.Symbol) {
178 throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
179 }
180 if (!value) {
181 throw new Error(`unexpected syntax`);
182 }
183 const f = evalMal(value, env);
184 if (f.type !== Node.Function) {
185 throw new Error(`unexpected token type: ${f.type}, expected: function`);
186 }
187 f.isMacro = true;
188 return env.set(key, f);
189 }
190 case "macroexpand": {
191 return macroexpand(ast.list[1], env);
192 }
193 case "do": {
194 const list = ast.list.slice(1, -1);
195 evalAST(new MalList(list), env);
196 ast = ast.list[ast.list.length - 1];
197 continue loop;
198 }
199 case "if": {
200 const [, cond, thenExpr, elseExrp] = ast.list;
201 const ret = evalMal(cond, env);
202 let b = true;
203 if (ret.type === Node.Boolean && !ret.v) {
204 b = false;
205 } else if (ret.type === Node.Nil) {
206 b = false;
207 }
208 if (b) {
209 ast = thenExpr;
210 } else if (elseExrp) {
211 ast = elseExrp;
212 } else {
213 ast = MalNil.instance;
214 }
215 continue loop;
216 }
217 case "fn*": {
218 const [, params, bodyAst] = ast.list;
219 if (!isSeq(params)) {
220 throw new Error(`unexpected return type: ${params.type}, expected: list or vector`);
221 }
222 const symbols = params.list.map(param => {
223 if (param.type !== Node.Symbol) {
224 throw new Error(`unexpected return type: ${param.type}, expected: symbol`);
225 }
226 return param;
227 });
228 return MalFunction.fromLisp(evalMal, env, symbols, bodyAst);
229 }
230 }
231 }
232 const result = evalAST(ast, env);
233 if (!isSeq(result)) {
234 throw new Error(`unexpected return type: ${result.type}, expected: list or vector`);
235 }
236 const [f, ...args] = result.list;
237 if (f.type !== Node.Function) {
238 throw new Error(`unexpected token: ${f.type}, expected: function`);
239 }
240 if (f.ast) {
241 ast = f.ast;
242 env = f.newEnv(args);
243 continue loop;
244 }
245
246 return f.func(...args);
247 }
248 }
249
250 // PRINT
251 function print(exp: MalType): string {
252 return prStr(exp);
253 }
254
255 const replEnv = new Env();
256 function rep(str: string): string {
257 return print(evalMal(read(str), replEnv));
258 }
259
260 // core.EXT: defined using Racket
261 core.ns.forEach((value, key) => {
262 replEnv.set(key, value);
263 });
264 replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => {
265 if (!ast) {
266 throw new Error(`undefined argument`);
267 }
268 return evalMal(ast, replEnv);
269 }));
270 replEnv.set(MalSymbol.get("*ARGV*"), new MalList([]));
271
272 // core.mal: defined using the language itself
273 rep("(def! not (fn* (a) (if a false true)))");
274 rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`);
275 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)))))))`);
276 rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))");
277
278 if (typeof process !== "undefined" && 2 < process.argv.length) {
279 replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s))));
280 rep(`(load-file "${process.argv[2]}")`);
281 process.exit(0);
282 }
283
284 while (true) {
285 const line = readline("user> ");
286 if (line == null) {
287 break;
288 }
289 if (line === "") {
290 continue;
291 }
292 try {
293 console.log(rep(line));
294 } catch (e) {
295 const err: Error = e;
296 console.error(err.message);
297 }
298 }