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