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