refactor to using const enum
[jackhill/mal.git] / ts / core.ts
1 import * as fs from "fs";
2
3 import { readline } from "./node_readline";
4
5 import { Node, MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals, isSeq } from "./types";
6 import { readStr } from "./reader";
7 import { prStr } from "./printer";
8
9 export const ns: Map<MalSymbol, MalFunction> = (() => {
10 const ns: { [symbol: string]: typeof MalFunction.prototype.func; } = {
11 "="(a: MalType, b: MalType): MalBoolean {
12 return new MalBoolean(equals(a, b));
13 },
14 throw(v: MalType): MalType {
15 throw v;
16 },
17
18 "nil?"(v: MalType) {
19 return new MalBoolean(v.type === Node.Null);
20 },
21 "true?"(v: MalType) {
22 return new MalBoolean(v.type === Node.Boolean && v.v);
23 },
24 "false?"(v: MalType) {
25 return new MalBoolean(v.type === Node.Boolean && !v.v);
26 },
27 "string?"(v: MalType) {
28 return new MalBoolean(v.type === Node.String);
29 },
30 symbol(v: MalType) {
31 if (v.type !== Node.String) {
32 throw new Error(`unexpected symbol: ${v.type}, expected: string`);
33 }
34 return MalSymbol.get(v.v);
35 },
36 "symbol?"(v: MalType) {
37 return new MalBoolean(v.type === Node.Symbol);
38 },
39 keyword(v: MalType) {
40 if (v.type !== Node.String) {
41 throw new Error(`unexpected symbol: ${v.type}, expected: string`);
42 }
43 return MalKeyword.get(v.v);
44 },
45 "keyword?"(v: MalType) {
46 return new MalBoolean(v.type === Node.Keyword);
47 },
48
49 "pr-str"(...args: MalType[]): MalString {
50 return new MalString(args.map(v => prStr(v, true)).join(" "));
51 },
52 "str"(...args: MalType[]): MalString {
53 return new MalString(args.map(v => prStr(v, false)).join(""));
54 },
55 prn(...args: MalType[]): MalNull {
56 const str = args.map(v => prStr(v, true)).join(" ");
57 console.log(str);
58 return MalNull.instance;
59 },
60 println(...args: MalType[]): MalNull {
61 const str = args.map(v => prStr(v, false)).join(" ");
62 console.log(str);
63 return MalNull.instance;
64 },
65 "read-string"(v: MalType) {
66 if (v.type !== Node.String) {
67 throw new Error(`unexpected symbol: ${v.type}, expected: string`);
68 }
69 return readStr(v.v);
70 },
71 readline(v: MalType) {
72 if (v.type !== Node.String) {
73 throw new Error(`unexpected symbol: ${v.type}, expected: string`);
74 }
75
76 const ret = readline(v.v);
77 if (ret == null) {
78 return MalNull.instance;
79 }
80
81 return new MalString(ret);
82 },
83 slurp(v: MalType) {
84 if (v.type !== Node.String) {
85 throw new Error(`unexpected symbol: ${v.type}, expected: string`);
86 }
87 const content = fs.readFileSync(v.v, "UTF-8");
88 return new MalString(content);
89 },
90
91 "<"(a: MalType, b: MalType): MalBoolean {
92 if (a.type !== Node.Number) {
93 throw new Error(`unexpected symbol: ${a.type}, expected: number`);
94 }
95 if (b.type !== Node.Number) {
96 throw new Error(`unexpected symbol: ${b.type}, expected: number`);
97 }
98
99 return new MalBoolean(a.v < b.v);
100 },
101 "<="(a: MalType, b: MalType): MalBoolean {
102 if (a.type !== Node.Number) {
103 throw new Error(`unexpected symbol: ${a.type}, expected: number`);
104 }
105 if (b.type !== Node.Number) {
106 throw new Error(`unexpected symbol: ${b.type}, expected: number`);
107 }
108
109 return new MalBoolean(a.v <= b.v);
110 },
111 ">"(a: MalType, b: MalType): MalBoolean {
112 if (a.type !== Node.Number) {
113 throw new Error(`unexpected symbol: ${a.type}, expected: number`);
114 }
115 if (b.type !== Node.Number) {
116 throw new Error(`unexpected symbol: ${b.type}, expected: number`);
117 }
118
119 return new MalBoolean(a.v > b.v);
120 },
121 ">="(a: MalType, b: MalType): MalBoolean {
122 if (a.type !== Node.Number) {
123 throw new Error(`unexpected symbol: ${a.type}, expected: number`);
124 }
125 if (b.type !== Node.Number) {
126 throw new Error(`unexpected symbol: ${b.type}, expected: number`);
127 }
128
129 return new MalBoolean(a.v >= b.v);
130 },
131 "+"(a: MalType, b: MalType): MalNumber {
132 if (a.type !== Node.Number) {
133 throw new Error(`unexpected symbol: ${a.type}, expected: number`);
134 }
135 if (b.type !== Node.Number) {
136 throw new Error(`unexpected symbol: ${b.type}, expected: number`);
137 }
138
139 return new MalNumber(a.v + b.v);
140 },
141 "-"(a: MalType, b: MalType): MalNumber {
142 if (a.type !== Node.Number) {
143 throw new Error(`unexpected symbol: ${a.type}, expected: number`);
144 }
145 if (b.type !== Node.Number) {
146 throw new Error(`unexpected symbol: ${b.type}, expected: number`);
147 }
148
149 return new MalNumber(a.v - b.v);
150 },
151 "*"(a: MalType, b: MalType): MalNumber {
152 if (a.type !== Node.Number) {
153 throw new Error(`unexpected symbol: ${a.type}, expected: number`);
154 }
155 if (b.type !== Node.Number) {
156 throw new Error(`unexpected symbol: ${b.type}, expected: number`);
157 }
158
159 return new MalNumber(a.v * b.v);
160 },
161 "/"(a: MalType, b: MalType): MalNumber {
162 if (a.type !== Node.Number) {
163 throw new Error(`unexpected symbol: ${a.type}, expected: number`);
164 }
165 if (b.type !== Node.Number) {
166 throw new Error(`unexpected symbol: ${b.type}, expected: number`);
167 }
168
169 return new MalNumber(a.v / b.v);
170 },
171 "time-ms"() {
172 return new MalNumber(Date.now());
173 },
174
175 list(...args: MalType[]): MalList {
176 return new MalList(args);
177 },
178 "list?"(v: MalType): MalBoolean {
179 return new MalBoolean(v instanceof MalList);
180 },
181 vector(...args: MalType[]): MalVector {
182 return new MalVector(args);
183 },
184 "vector?"(v: MalType): MalBoolean {
185 return new MalBoolean(v.type === Node.Vector);
186 },
187 "hash-map"(...args: MalType[]) {
188 return new MalHashMap(args);
189 },
190 "map?"(v: MalType): MalBoolean {
191 return new MalBoolean(v.type === Node.HashMap);
192 },
193 assoc(v: MalType, ...args: MalType[]) {
194 if (v.type !== Node.HashMap) {
195 throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`);
196 }
197 return v.assoc(args);
198 },
199 dissoc(v: MalType, ...args: MalType[]) {
200 if (v.type !== Node.HashMap) {
201 throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`);
202 }
203 return v.dissoc(args);
204 },
205 get(v: MalType, key: MalType) {
206 if (v.type === Node.Null) {
207 return MalNull.instance;
208 }
209 if (v.type !== Node.HashMap) {
210 throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`);
211 }
212 if (key.type !== Node.String && key.type !== Node.Keyword) {
213 throw new Error(`unexpected symbol: ${key.type}, expected: string or keyword`);
214 }
215
216 return v.get(key) || MalNull.instance;
217 },
218 "contains?"(v: MalType, key: MalType) {
219 if (v.type === Node.Null) {
220 return MalNull.instance;
221 }
222 if (v.type !== Node.HashMap) {
223 throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`);
224 }
225 if (key.type !== Node.String && key.type !== Node.Keyword) {
226 throw new Error(`unexpected symbol: ${key.type}, expected: string or keyword`);
227 }
228
229 return new MalBoolean(v.has(key));
230 },
231 keys(v: MalType) {
232 if (v.type !== Node.HashMap) {
233 throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`);
234 }
235
236 return new MalList([...v.keys()]);
237 },
238 vals(v: MalType) {
239 if (v.type !== Node.HashMap) {
240 throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`);
241 }
242
243 return new MalList([...v.vals()]);
244 },
245
246 "sequential?"(v: MalType) {
247 return new MalBoolean(isSeq(v));
248 },
249 cons(a: MalType, b: MalType) {
250 if (!isSeq(b)) {
251 throw new Error(`unexpected symbol: ${b.type}, expected: list or vector`);
252 }
253
254 return new MalList([a].concat(b.list));
255 },
256 concat(...args: MalType[]) {
257 const list = args
258 .map(arg => {
259 if (!isSeq(arg)) {
260 throw new Error(`unexpected symbol: ${arg.type}, expected: list or vector`);
261 }
262 return arg;
263 })
264 .reduce((p, c) => p.concat(c.list), [] as MalType[]);
265
266 return new MalList(list);
267 },
268 nth(list: MalType, idx: MalType) {
269 if (!isSeq(list)) {
270 throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`);
271 }
272 if (idx.type !== Node.Number) {
273 throw new Error(`unexpected symbol: ${idx.type}, expected: number`);
274 }
275
276 const v = list.list[idx.v];
277 if (!v) {
278 throw new Error("nth: index out of range");
279 }
280
281 return v;
282 },
283 first(v: MalType) {
284 if (v.type === Node.Null) {
285 return MalNull.instance;
286 }
287 if (!isSeq(v)) {
288 throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`);
289 }
290
291 return v.list[0] || MalNull.instance;
292 },
293 rest(v: MalType) {
294 if (v.type === Node.Null) {
295 return new MalList([]);
296 }
297 if (!isSeq(v)) {
298 throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`);
299 }
300
301 return new MalList(v.list.slice(1));
302 },
303 "empty?"(v: MalType): MalBoolean {
304 if (!isSeq(v)) {
305 return new MalBoolean(false);
306 }
307 return new MalBoolean(v.list.length === 0);
308 },
309 count(v: MalType): MalNumber {
310 if (isSeq(v)) {
311 return new MalNumber(v.list.length);
312 }
313 if (v.type === Node.Null) {
314 return new MalNumber(0);
315 }
316 throw new Error(`unexpected symbol: ${v.type}`);
317 },
318 apply(f: MalType, ...list: MalType[]) {
319 if (f.type !== Node.Function) {
320 throw new Error(`unexpected symbol: ${f.type}, expected: function`);
321 }
322
323 const tail = list[list.length - 1];
324 if (!isSeq(tail)) {
325 throw new Error(`unexpected symbol: ${tail.type}, expected: list or vector`);
326 }
327 const args = list.slice(0, -1).concat(tail.list);
328 return f.func(...args);
329 },
330 map(f: MalType, list: MalType) {
331 if (f.type !== Node.Function) {
332 throw new Error(`unexpected symbol: ${f.type}, expected: function`);
333 }
334 if (!isSeq(list)) {
335 throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`);
336 }
337
338 return new MalList(list.list.map(v => f.func(v)));
339 },
340
341 conj(list: MalType, ...args: MalType[]) {
342 switch (list.type) {
343 case Node.List:
344 const newList = new MalList(list.list);
345 args.forEach(arg => newList.list.unshift(arg));
346 return newList;
347 case Node.Vector:
348 return new MalVector([...list.list, ...args]);
349 }
350
351 throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`);
352 },
353 seq(v: MalType) {
354 if (v.type === Node.List) {
355 if (v.list.length === 0) {
356 return MalNull.instance;
357 }
358 return v;
359 }
360 if (v.type === Node.Vector) {
361 if (v.list.length === 0) {
362 return MalNull.instance;
363 }
364 return new MalList(v.list);
365 }
366 if (v.type === Node.String) {
367 if (v.v.length === 0) {
368 return MalNull.instance;
369 }
370 return new MalList(v.v.split("").map(s => new MalString(s)));
371 }
372 if (v.type === Node.Null) {
373 return MalNull.instance;
374 }
375
376 throw new Error(`unexpected symbol: ${v.type}, expected: list or vector or string`);
377 },
378
379 meta(v: MalType) {
380 return v.meta || MalNull.instance;
381 },
382 "with-meta"(v: MalType, m: MalType) {
383 return v.withMeta(m);
384 },
385 atom(v: MalType): MalAtom {
386 return new MalAtom(v);
387 },
388 "atom?"(v: MalType): MalBoolean {
389 return new MalBoolean(v.type === Node.Atom);
390 },
391 deref(v: MalType): MalType {
392 if (v.type !== Node.Atom) {
393 throw new Error(`unexpected symbol: ${v.type}, expected: atom`);
394 }
395 return v.v;
396 },
397 "reset!"(atom: MalType, v: MalType): MalType {
398 if (atom.type !== Node.Atom) {
399 throw new Error(`unexpected symbol: ${atom.type}, expected: atom`);
400 }
401 atom.v = v;
402 return v;
403 },
404 "swap!"(atom: MalType, f: MalType, ...args: MalType[]): MalType {
405 if (atom.type !== Node.Atom) {
406 throw new Error(`unexpected symbol: ${atom.type}, expected: atom`);
407 }
408 if (f.type !== Node.Function) {
409 throw new Error(`unexpected symbol: ${f.type}, expected: function`);
410 }
411 atom.v = f.func(...[atom.v].concat(args));
412 return atom.v;
413 },
414 };
415
416 const map = new Map<MalSymbol, MalFunction>();
417 Object.keys(ns).forEach(key => map.set(MalSymbol.get(key), MalFunction.fromBootstrap(ns[key])));
418 return map;
419 })();