add Dockerfile
[jackhill/mal.git] / ts / stepA_mal.ts
CommitLineData
bbddf168 1import { readline } from "./node_readline";
2
3import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST } from "./types";
4import { Env } from "./env";
5import * as core from "./core";
6import { readStr } from "./reader";
7import { prStr } from "./printer";
8
9function read(str: string): MalType {
10 return readStr(str);
11}
12
13function quasiquote(ast: MalType): MalType {
14 if (!isPair(ast)) {
15 return new MalList([MalSymbol.get("quote"), ast]);
16 }
17 if (!MalList.is(ast) && !MalVector.is(ast)) {
18 throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`);
19 }
20 const [arg1, arg2] = ast.list;
21 if (MalSymbol.is(arg1) && arg1.v === "unquote") {
22 return arg2;
23 }
24 if (isPair(arg1)) {
25 if (!MalList.is(arg1) && !MalVector.is(arg1)) {
26 throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`);
27 }
28 const [arg11, arg12] = arg1.list;
29 if (MalSymbol.is(arg11) && arg11.v === "splice-unquote") {
30 return new MalList([
31 MalSymbol.get("concat"),
32 arg12,
33 quasiquote(new MalList(ast.list.slice(1))),
34 ]);
35 }
36 }
37
38 return new MalList([
39 MalSymbol.get("cons"),
40 quasiquote(arg1),
41 quasiquote(new MalList(ast.list.slice(1))),
42 ]);
43
44 function isPair(ast: MalType) {
45 if (!MalList.is(ast) && !MalVector.is(ast)) {
46 return false;
47 }
48
49 return 0 < ast.list.length;
50 }
51}
52
53function isMacroCall(ast: MalType, env: Env): boolean {
54 if (!MalList.is(ast) && !MalVector.is(ast)) {
55 return false;
56 }
57 const s = ast.list[0];
58 if (!MalSymbol.is(s)) {
59 return false;
60 }
61 const foundEnv = env.find(s);
62 if (!foundEnv) {
63 return false;
64 }
65
66 const f = foundEnv.get(s);
67 if (!MalFunction.is(f)) {
68 return false;
69 }
70
71 return f.isMacro;
72}
73
74function macroexpand(ast: MalType, env: Env): MalType {
75 while (isMacroCall(ast, env)) {
76 if (!MalList.is(ast) && !MalVector.is(ast)) {
77 throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`);
78 }
79 const s = ast.list[0];
80 if (!MalSymbol.is(s)) {
81 throw new Error(`unexpected token type: ${s.type}, expected: symbol`);
82 }
83 const f = env.get(s);
84 if (!MalFunction.is(f)) {
85 throw new Error(`unexpected token type: ${f.type}, expected: function`);
86 }
87 ast = f.func(...ast.list.slice(1));
88 }
89
90 return ast;
91}
92
93function evalAST(ast: MalType, env: Env): MalType {
94 switch (ast.type) {
95 case "symbol":
96 const f = env.get(ast);
97 if (!f) {
98 throw new Error(`unknown symbol: ${ast.v}`);
99 }
100 return f;
101 case "list":
102 return new MalList(ast.list.map(ast => evalSexp(ast, env)));
103 case "vector":
104 return new MalVector(ast.list.map(ast => evalSexp(ast, env)));
105 case "hash-map":
106 const list: MalType[] = [];
107 for (const [key, value] of ast.entries()) {
108 list.push(key);
109 list.push(evalSexp(value, env));
110 }
111 return new MalHashMap(list);
112 default:
113 return ast;
114 }
115}
116
117function evalSexp(ast: MalType, env: Env): MalType {
118 loop: while (true) {
119 if (ast.type !== "list") {
120 return evalAST(ast, env);
121 }
122
123 ast = macroexpand(ast, env);
124 if (ast.type !== "list" && ast.type !== "vector") {
125 return evalAST(ast, env);
126 }
127
128 if (ast.list.length === 0) {
129 return ast;
130 }
131 const first = ast.list[0];
132 switch (first.type) {
133 case "symbol":
134 switch (first.v) {
135 case "def!": {
136 const [, key, value] = ast.list;
137 if (!MalSymbol.is(key)) {
138 throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
139 }
140 if (!value) {
141 throw new Error(`unexpected syntax`);
142 }
143 return env.set(key, evalSexp(value, env))
144 }
145 case "let*": {
146 env = new Env(env);
147 const pairs = ast.list[1];
148 if (!MalList.is(pairs) && !MalVector.is(pairs)) {
149 throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`);
150 }
151 for (let i = 0; i < pairs.list.length; i += 2) {
152 const key = pairs.list[i];
153 const value = pairs.list[i + 1];
154 if (!MalSymbol.is(key)) {
155 throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
156 }
157 if (!key || !value) {
158 throw new Error(`unexpected syntax`);
159 }
160
161 env.set(key, evalSexp(value, env));
162 }
163 ast = ast.list[2];
164 continue loop;
165 }
166 case "quote": {
167 return ast.list[1];
168 }
169 case "quasiquote": {
170 ast = quasiquote(ast.list[1]);
171 continue loop;
172 }
173 case "defmacro!": {
174 const [, key, value] = ast.list;
175 if (!MalSymbol.is(key)) {
176 throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
177 }
178 if (!value) {
179 throw new Error(`unexpected syntax`);
180 }
181 const f = evalSexp(value, env);
182 if (!MalFunction.is(f)) {
183 throw new Error(`unexpected token type: ${f.type}, expected: function`);
184 }
185 f.isMacro = true;
186 return env.set(key, f);
187 }
188 case "macroexpand": {
189 return macroexpand(ast.list[1], env);
190 }
191 case "try*": {
192 try {
193 return evalSexp(ast.list[1], env);
194 } catch (e) {
195 const catchBody = ast.list[2];
196 if (!MalList.is(catchBody) && !MalVector.is(catchBody)) {
197 throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`);
198 }
199 const catchSymbol = catchBody.list[0];
200 if (MalSymbol.is(catchSymbol) && catchSymbol.v === "catch*") {
201 const errorSymbol = catchBody.list[1];
202 if (!MalSymbol.is(errorSymbol)) {
203 throw new Error(`unexpected return type: ${errorSymbol.type}, expected: symbol`);
204 }
205 if (!isAST(e)) {
206 e = new MalString((e as Error).message);
207 }
208 return evalSexp(catchBody.list[2], new Env(env, [errorSymbol], [e]));
209 }
210 throw e;
211 }
212 }
213 case "do": {
214 const list = ast.list.slice(1, -1);
215 evalAST(new MalList(list), env);
216 ast = ast.list[ast.list.length - 1];
217 continue loop;
218 }
219 case "if": {
220 const [, cond, thenExpr, elseExrp] = ast.list;
221 const ret = evalSexp(cond, env);
222 let b = true;
223 if (MalBoolean.is(ret) && !ret.v) {
224 b = false;
225 } else if (MalNull.is(ret)) {
226 b = false;
227 }
228 if (b) {
229 ast = thenExpr;
230 } else if (elseExrp) {
231 ast = elseExrp;
232 } else {
233 ast = MalNull.instance;
234 }
235 continue loop;
236 }
237 case "fn*": {
238 const [, params, bodyAst] = ast.list;
239 if (!MalList.is(params) && !MalVector.is(params)) {
240 throw new Error(`unexpected return type: ${params.type}, expected: list or vector`);
241 }
242 const symbols = params.list.map(param => {
243 if (!MalSymbol.is(param)) {
244 throw new Error(`unexpected return type: ${param.type}, expected: symbol`);
245 }
246 return param;
247 });
248 return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst);
249 }
250 }
251 }
252 const result = evalAST(ast, env);
253 if (!MalList.is(result) && !MalVector.is(result)) {
254 throw new Error(`unexpected return type: ${result.type}, expected: list or vector`);
255 }
256 const [f, ...args] = result.list;
257 if (!MalFunction.is(f)) {
258 throw new Error(`unexpected token: ${f.type}, expected: function`);
259 }
260 if (f.ast) {
261 ast = f.ast;
262 env = f.newEnv(args);
263 continue loop;
264 }
265
266 return f.func(...args);
267 }
268}
269
270function print(exp: MalType): string {
271 return prStr(exp);
272}
273
274const replEnv = new Env();
eb7a2bbd 275core.ns.forEach((value, key) => {
bbddf168 276 replEnv.set(key, value);
eb7a2bbd 277});
bbddf168 278replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => {
279 if (!ast) {
280 throw new Error(`undefined argument`);
281 }
282 return evalSexp(ast, replEnv);
283}));
284
285replEnv.set(MalSymbol.get("*ARGV*"), new MalList([]));
286
287// core.mal: defined using the language itself
288rep(`(def! *host-language* "TypeScript")`);
289rep("(def! not (fn* (a) (if a false true)))");
290rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`);
291rep(`(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)))))))`);
292rep("(def! *gensym-counter* (atom 0))");
293rep("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))");
294rep("(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)))))))))");
295
296if (typeof process !== "undefined" && 2 < process.argv.length) {
297 replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s))));
298 rep(`(load-file "${process.argv[2]}")`);
299 process.exit(0);
300}
301
302function rep(str: string): string {
303 return print(evalSexp(read(str), replEnv));
304}
305
306rep(`(println (str "Mal [" *host-language* "]"))`);
307while (true) {
308 const line = readline("user> ");
309 if (line == null) {
310 break;
311 }
312 if (line === "") {
313 continue;
314 }
315 try {
316 console.log(rep(line));
317 } catch (e) {
318 const err: Error = e;
319 console.error(err.message);
320 }
321}