Commit | Line | Data |
---|---|---|
555f7fc7 | 1 | import * as fs from "fs"; |
2 | ||
bbddf168 | 3 | import { readline } from "./node_readline"; |
4 | ||
6071876f | 5 | import { Node, MalType, MalSymbol, MalFunction, MalNil, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals, isSeq } from "./types"; |
555f7fc7 | 6 | import { readStr } from "./reader"; |
dfe70453 | 7 | import { prStr } from "./printer"; |
8 | ||
9 | export 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 | })(); |