ts: defmacro! doesn't modify existing functions
[jackhill/mal.git] / impls / ts / step2_eval.ts
1 import { readline } from "./node_readline";
2
3 import { Node, MalType, MalNumber, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types";
4 import { readStr } from "./reader";
5 import { prStr } from "./printer";
6
7 // READ
8 function read(str: string): MalType {
9 return readStr(str);
10 }
11
12 interface MalEnvironment {
13 [key: string]: MalFunction;
14 }
15
16 function evalAST(ast: MalType, env: MalEnvironment): MalType {
17 switch (ast.type) {
18 case Node.Symbol:
19 const f = env[ast.v];
20 if (!f) {
21 throw new Error(`unknown symbol: ${ast.v}`);
22 }
23 return f;
24 case Node.List:
25 return new MalList(ast.list.map(ast => evalMal(ast, env)));
26 case Node.Vector:
27 return new MalVector(ast.list.map(ast => evalMal(ast, env)));
28 case Node.HashMap:
29 const list: MalType[] = [];
30 for (const [key, value] of ast.entries()) {
31 list.push(key);
32 list.push(evalMal(value, env));
33 }
34 return new MalHashMap(list);
35 default:
36 return ast;
37 }
38 }
39
40 // EVAL
41 function evalMal(ast: MalType, env: MalEnvironment): MalType {
42 if (ast.type !== Node.List) {
43 return evalAST(ast, env);
44 }
45 if (ast.list.length === 0) {
46 return ast;
47 }
48 const result = evalAST(ast, env);
49 if (!isSeq(result)) {
50 throw new Error(`unexpected return type: ${result.type}, expected: list or vector`);
51 }
52 const [f, ...args] = result.list;
53 if (f.type !== Node.Function) {
54 throw new Error(`unexpected token: ${f.type}, expected: function`);
55 }
56 return f.func(...args);
57 }
58
59 // PRINT
60 function print(exp: MalType): string {
61 return prStr(exp);
62 }
63
64 const replEnv: MalEnvironment = {
65 "+": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v)),
66 "-": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v)),
67 "*": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v)),
68 "/": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v)),
69 };
70 function rep(str: string): string {
71 return print(evalMal(read(str), replEnv));
72 }
73
74 while (true) {
75 const line = readline("user> ");
76 if (line == null) {
77 break;
78 }
79 if (line === "") {
80 continue;
81 }
82 try {
83 console.log(rep(line));
84 } catch (e) {
85 const err: Error = e;
86 console.error(err.message);
87 }
88 }