Merge pull request #378 from asarhaddon/test-macro-not-changing-function
[jackhill/mal.git] / ts / step4_if_fn_do.ts
CommitLineData
dfe70453 1import { readline } from "./node_readline";
2
dd7a4f55 3import { Node, MalType, MalNil, MalList, MalVector, MalHashMap, MalFunction, isAST, isSeq } from "./types";
dfe70453 4import { Env } from "./env";
5import * as core from "./core";
6import { readStr } from "./reader";
7import { prStr } from "./printer";
8
9c92462f 9// READ
dfe70453 10function read(str: string): MalType {
11 return readStr(str);
12}
13
14function evalAST(ast: MalType, env: Env): MalType {
15 switch (ast.type) {
5bb7479d 16 case Node.Symbol:
dfe70453 17 const f = env.get(ast);
18 if (!f) {
19 throw new Error(`unknown symbol: ${ast.v}`);
20 }
21 return f;
5bb7479d 22 case Node.List:
9c92462f 23 return new MalList(ast.list.map(ast => evalMal(ast, env)));
5bb7479d 24 case Node.Vector:
9c92462f 25 return new MalVector(ast.list.map(ast => evalMal(ast, env)));
5bb7479d 26 case Node.HashMap:
dfe70453 27 const list: MalType[] = [];
10f8aa84 28 for (const [key, value] of ast.entries()) {
dfe70453 29 list.push(key);
9c92462f 30 list.push(evalMal(value, env));
dfe70453 31 }
32 return new MalHashMap(list);
33 default:
34 return ast;
35 }
36}
37
9c92462f 38// EVAL
39function evalMal(ast: MalType, env: Env): MalType {
5bb7479d 40 if (ast.type !== Node.List) {
dfe70453 41 return evalAST(ast, env);
42 }
43 if (ast.list.length === 0) {
44 return ast;
45 }
46 const first = ast.list[0];
47 switch (first.type) {
5bb7479d 48 case Node.Symbol:
dfe70453 49 switch (first.v) {
50 case "def!": {
51 const [, key, value] = ast.list;
5bb7479d 52 if (key.type !== Node.Symbol) {
dfe70453 53 throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
54 }
55 if (!value) {
56 throw new Error(`unexpected syntax`);
57 }
677a1c9d 58 return env.set(key, evalMal(value, env));
dfe70453 59 }
60 case "let*": {
61 let letEnv = new Env(env);
62 const pairs = ast.list[1];
92bf0530 63 if (!isSeq(pairs)) {
dfe70453 64 throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`);
65 }
66 for (let i = 0; i < pairs.list.length; i += 2) {
67 const key = pairs.list[i];
68 const value = pairs.list[i + 1];
5bb7479d 69 if (key.type !== Node.Symbol) {
dfe70453 70 throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
71 }
72 if (!key || !value) {
73 throw new Error(`unexpected syntax`);
74 }
75
9c92462f 76 letEnv.set(key, evalMal(value, letEnv));
dfe70453 77 }
9c92462f 78 return evalMal(ast.list[2], letEnv);
dfe70453 79 }
80 case "do": {
81 const [, ...list] = ast.list;
82 const ret = evalAST(new MalList(list), env);
92bf0530 83 if (!isSeq(ret)) {
dfe70453 84 throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`);
85 }
86 return ret.list[ret.list.length - 1];
87 }
88 case "if": {
89 const [, cond, thenExpr, elseExrp] = ast.list;
9c92462f 90 const ret = evalMal(cond, env);
dfe70453 91 let b = true;
5bb7479d 92 if (ret.type === Node.Boolean && !ret.v) {
dfe70453 93 b = false;
6071876f 94 } else if (ret.type === Node.Nil) {
dfe70453 95 b = false;
96 }
97 if (b) {
9c92462f 98 return evalMal(thenExpr, env);
dfe70453 99 } else if (elseExrp) {
9c92462f 100 return evalMal(elseExrp, env);
dfe70453 101 } else {
6071876f 102 return MalNil.instance;
dfe70453 103 }
104 }
105 case "fn*": {
106 const [, args, binds] = ast.list;
92bf0530 107 if (!isSeq(args)) {
dfe70453 108 throw new Error(`unexpected return type: ${args.type}, expected: list or vector`);
109 }
5bb7479d 110 const symbols = args.list.map(param => {
111 if (param.type !== Node.Symbol) {
112 throw new Error(`unexpected return type: ${param.type}, expected: symbol`);
dfe70453 113 }
5bb7479d 114 return param;
dfe70453 115 });
79a10a6e 116 return MalFunction.fromBootstrap((...fnArgs: MalType[]) => {
9c92462f 117 return evalMal(binds, new Env(env, symbols, fnArgs));
dfe70453 118 });
119 }
120 }
121 }
122 const result = evalAST(ast, env);
92bf0530 123 if (!isSeq(result)) {
dfe70453 124 throw new Error(`unexpected return type: ${result.type}, expected: list or vector`);
125 }
79a10a6e 126 const [f, ...args] = result.list;
5bb7479d 127 if (f.type !== Node.Function) {
dfe70453 128 throw new Error(`unexpected token: ${f.type}, expected: function`);
129 }
79a10a6e 130 return f.func(...args);
dfe70453 131}
132
9c92462f 133// PRINT
dfe70453 134function print(exp: MalType): string {
135 return prStr(exp);
136}
137
138const replEnv = new Env();
9c92462f 139function rep(str: string): string {
140 return print(evalMal(read(str), replEnv));
141}
142
143// core.EXT: defined using Racket
eb7a2bbd 144core.ns.forEach((value, key) => {
dfe70453 145 replEnv.set(key, value);
eb7a2bbd 146});
dfe70453 147
148// core.mal: defined using the language itself
149rep("(def! not (fn* (a) (if a false true)))");
150
dfe70453 151while (true) {
152 const line = readline("user> ");
153 if (line == null) {
154 break;
155 }
156 if (line === "") {
157 continue;
158 }
159 try {
160 console.log(rep(line));
161 } catch (e) {
dd7a4f55
JM
162 if (isAST(e)) {
163 console.error("Error:", prStr(e));
164 } else {
165 const err: Error = e;
166 console.error("Error:", err.message);
167 }
dfe70453 168 }
169}