switch to tail -f circular pipes
[jackhill/mal.git] / ts / step6_file.ts
CommitLineData
555f7fc7 1import { readline } from "./node_readline";
2
dd7a4f55 3import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types";
555f7fc7 4import { Env } from "./env";
5import * as core from "./core";
6import { readStr } from "./reader";
7import { prStr } from "./printer";
8
9c92462f 9// READ
555f7fc7 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:
555f7fc7 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:
555f7fc7 27 const list: MalType[] = [];
10f8aa84 28 for (const [key, value] of ast.entries()) {
555f7fc7 29 list.push(key);
9c92462f 30 list.push(evalMal(value, env));
555f7fc7 31 }
32 return new MalHashMap(list);
33 default:
34 return ast;
35 }
36}
37
9c92462f 38// EVAL
39function evalMal(ast: MalType, env: Env): MalType {
555f7fc7 40 loop: while (true) {
5bb7479d 41 if (ast.type !== Node.List) {
555f7fc7 42 return evalAST(ast, env);
43 }
44 if (ast.list.length === 0) {
45 return ast;
46 }
47 const first = ast.list[0];
48 switch (first.type) {
5bb7479d 49 case Node.Symbol:
555f7fc7 50 switch (first.v) {
51 case "def!": {
52 const [, key, value] = ast.list;
5bb7479d 53 if (key.type !== Node.Symbol) {
555f7fc7 54 throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
55 }
56 if (!value) {
57 throw new Error(`unexpected syntax`);
58 }
677a1c9d 59 return env.set(key, evalMal(value, env));
555f7fc7 60 }
61 case "let*": {
62 env = new Env(env);
63 const pairs = ast.list[1];
92bf0530 64 if (!isSeq(pairs)) {
555f7fc7 65 throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`);
66 }
67 for (let i = 0; i < pairs.list.length; i += 2) {
68 const key = pairs.list[i];
69 const value = pairs.list[i + 1];
5bb7479d 70 if (key.type !== Node.Symbol) {
555f7fc7 71 throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
72 }
73 if (!key || !value) {
74 throw new Error(`unexpected syntax`);
75 }
76
9c92462f 77 env.set(key, evalMal(value, env));
555f7fc7 78 }
79 ast = ast.list[2];
80 continue loop;
81 }
82 case "do": {
bbddf168 83 const list = ast.list.slice(1, -1);
84 evalAST(new MalList(list), env);
85 ast = ast.list[ast.list.length - 1];
555f7fc7 86 continue loop;
87 }
88 case "if": {
89 const [, cond, thenExpr, elseExrp] = ast.list;
9c92462f 90 const ret = evalMal(cond, env);
555f7fc7 91 let b = true;
5bb7479d 92 if (ret.type === Node.Boolean && !ret.v) {
555f7fc7 93 b = false;
6071876f 94 } else if (ret.type === Node.Nil) {
555f7fc7 95 b = false;
96 }
97 if (b) {
98 ast = thenExpr;
99 } else if (elseExrp) {
100 ast = elseExrp;
101 } else {
6071876f 102 ast = MalNil.instance;
555f7fc7 103 }
104 continue loop;
105 }
106 case "fn*": {
107 const [, params, bodyAst] = ast.list;
92bf0530 108 if (!isSeq(params)) {
555f7fc7 109 throw new Error(`unexpected return type: ${params.type}, expected: list or vector`);
110 }
111 const symbols = params.list.map(param => {
5bb7479d 112 if (param.type !== Node.Symbol) {
555f7fc7 113 throw new Error(`unexpected return type: ${param.type}, expected: symbol`);
114 }
115 return param;
116 });
9c92462f 117 return MalFunction.fromLisp(evalMal, env, symbols, bodyAst);
555f7fc7 118 }
119 }
120 }
121 const result = evalAST(ast, env);
92bf0530 122 if (!isSeq(result)) {
555f7fc7 123 throw new Error(`unexpected return type: ${result.type}, expected: list or vector`);
124 }
125 const [f, ...args] = result.list;
5bb7479d 126 if (f.type !== Node.Function) {
555f7fc7 127 throw new Error(`unexpected token: ${f.type}, expected: function`);
128 }
129 if (f.ast) {
130 ast = f.ast;
131 env = f.newEnv(args);
132 continue loop;
133 }
134
135 return f.func(...args);
136 }
137}
138
9c92462f 139// PRINT
555f7fc7 140function print(exp: MalType): string {
141 return prStr(exp);
142}
143
144const replEnv = new Env();
9c92462f 145function rep(str: string): string {
146 return print(evalMal(read(str), replEnv));
147}
148
149// core.EXT: defined using Racket
eb7a2bbd 150core.ns.forEach((value, key) => {
555f7fc7 151 replEnv.set(key, value);
eb7a2bbd 152});
555f7fc7 153replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => {
154 if (!ast) {
155 throw new Error(`undefined argument`);
156 }
9c92462f 157 return evalMal(ast, replEnv);
555f7fc7 158}));
159
160replEnv.set(MalSymbol.get("*ARGV*"), new MalList([]));
161
162// core.mal: defined using the language itself
163rep("(def! not (fn* (a) (if a false true)))");
e6d41de4 164rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))`);
555f7fc7 165
166if (typeof process !== "undefined" && 2 < process.argv.length) {
167 replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s))));
168 rep(`(load-file "${process.argv[2]}")`);
169 process.exit(0);
170}
171
555f7fc7 172while (true) {
173 const line = readline("user> ");
174 if (line == null) {
175 break;
176 }
177 if (line === "") {
178 continue;
179 }
180 try {
181 console.log(rep(line));
182 } catch (e) {
dd7a4f55
JM
183 if (isAST(e)) {
184 console.error("Error:", prStr(e));
185 } else {
186 const err: Error = e;
187 console.error("Error:", err.message);
188 }
555f7fc7 189 }
190}