TypeScript: step 5
[jackhill/mal.git] / ts / step3_env.ts
1 import { readline } from "./node_readline";
2
3 import { MalType, MalNumber, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types";
4 import { Env } from "./env";
5 import { readStr } from "./reader";
6 import { prStr } from "./printer";
7
8 function read(str: string): MalType {
9 return readStr(str);
10 }
11
12 function evalAST(ast: MalType, env: Env): MalType {
13 switch (ast.type) {
14 case "symbol":
15 const f = env.get(ast);
16 if (!f) {
17 throw new Error(`unknown symbol: ${ast.v}`);
18 }
19 return f;
20 case "list":
21 return new MalList(ast.list.map(ast => evalSexp(ast, env)));
22 case "vector":
23 return new MalVector(ast.list.map(ast => evalSexp(ast, env)));
24 case "hash-map":
25 const list: MalType[] = [];
26 for (const [key, value] of ast.map) {
27 list.push(key);
28 list.push(evalSexp(value, env));
29 }
30 return new MalHashMap(list);
31 default:
32 return ast;
33 }
34 }
35
36 function evalSexp(ast: MalType, env: Env): MalType {
37 if (ast.type !== "list") {
38 return evalAST(ast, env);
39 }
40 if (ast.list.length === 0) {
41 return ast;
42 }
43 const first = ast.list[0];
44 switch (first.type) {
45 case "symbol":
46 switch (first.v) {
47 case "def!": {
48 const [, key, value] = ast.list;
49 if (key instanceof MalSymbol === false) {
50 throw new Error(`unexpected toke type: ${key.type}, expected: symbol`);
51 }
52 if (!value) {
53 throw new Error(`unexpected syntax`);
54 }
55 return env.set(key as MalSymbol, evalSexp(value, env))
56 }
57 case "let*": {
58 let letEnv = new Env(env);
59 const pairs = ast.list[1];
60 if (pairs instanceof MalList === false && pairs instanceof MalVector === false) {
61 throw new Error(`unexpected toke type: ${pairs.type}, expected: list or vector`);
62 }
63 const list = (pairs as (MalList | MalVector)).list;
64 for (let i = 0; i < list.length; i += 2) {
65 const key = list[i];
66 const value = list[i + 1];
67 if (!key || !value) {
68 throw new Error(`unexpected syntax`);
69 }
70
71 letEnv.set(key as MalSymbol, evalSexp(value, letEnv));
72 }
73 return evalSexp(ast.list[2], letEnv);
74 }
75 }
76 }
77 const result = evalAST(ast, env) as MalList;
78 const [f, ...args] = result.list;
79 if (!MalFunction.is(f)) {
80 throw new Error(`unexpected token: ${f.type}, expected: function`);
81 }
82 return f.func(...args);
83 }
84
85 function print(exp: MalType): string {
86 return prStr(exp);
87 }
88
89 const replEnv = new Env();
90 replEnv.set(MalSymbol.get("+"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v)));
91 replEnv.set(MalSymbol.get("-"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v)));
92 replEnv.set(MalSymbol.get("*"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v)));
93 replEnv.set(MalSymbol.get("/"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v)));
94
95 function rep(str: string): string {
96 return print(evalSexp(read(str), replEnv));
97 }
98
99 while (true) {
100 const line = readline("user> ");
101 if (line == null) {
102 break;
103 }
104 if (line === "") {
105 continue;
106 }
107 try {
108 console.log(rep(line));
109 } catch (e) {
110 const err: Error = e;
111 console.error(err.message);
112 }
113 }