ts: defmacro! doesn't modify existing functions
[jackhill/mal.git] / impls / ts / step2_eval.ts
CommitLineData
83aaf848 1import { readline } from "./node_readline";
2
677a1c9d 3import { Node, MalType, MalNumber, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types";
83aaf848 4import { readStr } from "./reader";
5import { prStr } from "./printer";
6
9c92462f 7// READ
83aaf848 8function read(str: string): MalType {
9 return readStr(str);
10}
11
12interface MalEnvironment {
13 [key: string]: MalFunction;
14}
15
16function evalAST(ast: MalType, env: MalEnvironment): MalType {
17 switch (ast.type) {
5bb7479d 18 case Node.Symbol:
83aaf848 19 const f = env[ast.v];
20 if (!f) {
21 throw new Error(`unknown symbol: ${ast.v}`);
22 }
23 return f;
5bb7479d 24 case Node.List:
9c92462f 25 return new MalList(ast.list.map(ast => evalMal(ast, env)));
5bb7479d 26 case Node.Vector:
9c92462f 27 return new MalVector(ast.list.map(ast => evalMal(ast, env)));
5bb7479d 28 case Node.HashMap:
83aaf848 29 const list: MalType[] = [];
10f8aa84 30 for (const [key, value] of ast.entries()) {
83aaf848 31 list.push(key);
9c92462f 32 list.push(evalMal(value, env));
83aaf848 33 }
34 return new MalHashMap(list);
35 default:
36 return ast;
37 }
38}
39
9c92462f 40// EVAL
41function evalMal(ast: MalType, env: MalEnvironment): MalType {
5bb7479d 42 if (ast.type !== Node.List) {
83aaf848 43 return evalAST(ast, env);
44 }
45 if (ast.list.length === 0) {
46 return ast;
47 }
677a1c9d 48 const result = evalAST(ast, env);
49 if (!isSeq(result)) {
50 throw new Error(`unexpected return type: ${result.type}, expected: list or vector`);
51 }
79a10a6e 52 const [f, ...args] = result.list;
5bb7479d 53 if (f.type !== Node.Function) {
83aaf848 54 throw new Error(`unexpected token: ${f.type}, expected: function`);
55 }
79a10a6e 56 return f.func(...args);
83aaf848 57}
58
9c92462f 59// PRINT
83aaf848 60function print(exp: MalType): string {
61 return prStr(exp);
62}
63
64const replEnv: MalEnvironment = {
79a10a6e 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)),
83aaf848 69};
70function rep(str: string): string {
9c92462f 71 return print(evalMal(read(str), replEnv));
83aaf848 72}
73
74while (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}