fez-lisp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1036 @@
1
+ import { TYPE, VALUE, WORD, KEYWORDS } from './enums.js'
2
+ import { evaluate } from './interpreter.js'
3
+ import {
4
+ isAtom,
5
+ isEqual,
6
+ isEqualTypes,
7
+ isForbiddenVariableName,
8
+ lispify,
9
+ stringifyArgs,
10
+ } from './utils.js'
11
+
12
+ const keywords = {
13
+ [KEYWORDS.CONCATENATION]: (args, env) => {
14
+ if (args.length < 2)
15
+ throw new RangeError(
16
+ `Invalid number of arguments for (${
17
+ keywords.CONCATENATION
18
+ }), expected > 1 but got ${args.length}. (${
19
+ keywords.CONCATENATION
20
+ } ${stringifyArgs(args)}).`
21
+ )
22
+ const operands = args.map((x) => evaluate(x, env))
23
+ if (operands.some((x) => typeof x !== 'string'))
24
+ throw new TypeError(
25
+ `Not all arguments of (${KEYWORDS.CONCATENATION}) are (${
26
+ KEYWORDS.STRING_TYPE
27
+ }) (${KEYWORDS.CONCATENATION} ${stringifyArgs(args)}).`
28
+ )
29
+ return operands.reduce((a, b) => a + b, '')
30
+ },
31
+ [KEYWORDS.REMAINDER_OF_DIVISION]: (args, env) => {
32
+ if (args.length < 2)
33
+ throw new RangeError(
34
+ `Invalid number of arguments for (${
35
+ KEYWORDS.REMAINDER_OF_DIVISION
36
+ }), expected > 1 but got ${args.length}. (${
37
+ KEYWORDS.REMAINDER_OF_DIVISION
38
+ } ${stringifyArgs(args)}).`
39
+ )
40
+ const [a, b] = args.map((x) => evaluate(x, env))
41
+ if (typeof a !== 'number' || typeof b !== 'number')
42
+ throw new TypeError(
43
+ `Not all arguments of (${KEYWORDS.REMAINDER_OF_DIVISION}) are (${
44
+ KEYWORDS.NUMBER_TYPE
45
+ }) (${KEYWORDS.REMAINDER_OF_DIVISION} ${stringifyArgs(args)}).`
46
+ )
47
+ if (b === 0)
48
+ throw new TypeError(
49
+ `Second argument of (${
50
+ KEYWORDS.REMAINDER_OF_DIVISION
51
+ }) can't be a (0) (division by 0 is not allowed) (${
52
+ KEYWORDS.REMAINDER_OF_DIVISION
53
+ } ${stringifyArgs(args)}).`
54
+ )
55
+
56
+ return a % b
57
+ },
58
+ [KEYWORDS.DIVISION]: (args, env) => {
59
+ if (!args.length) return 0
60
+ if (args.length === 1) {
61
+ const number = evaluate(args[0], env)
62
+ if (typeof number !== 'number')
63
+ throw new TypeError(
64
+ `Arguments of (${KEYWORDS.DIVISION}) is not a (${
65
+ KEYWORDS.NUMBER_TYPE
66
+ }) (${KEYWORDS.DIVISION} ${stringifyArgs(args)}).`
67
+ )
68
+ if (number === 0)
69
+ throw new TypeError(
70
+ `Argument of (${
71
+ KEYWORDS.DIVISION
72
+ }) can't be a (0) (division by 0 is not allowed) (${
73
+ KEYWORDS.DIVISION
74
+ } ${stringifyArgs(args)}).`
75
+ )
76
+ return 1 / number
77
+ }
78
+ const operands = args.map((x) => evaluate(x, env))
79
+ if (operands.some((x) => typeof x !== 'number'))
80
+ throw new TypeError(
81
+ `Not all arguments of (${KEYWORDS.DIVISION}) are (${
82
+ KEYWORDS.NUMBER_TYPE
83
+ }) (${KEYWORDS.DIVISION} ${stringifyArgs(args)}).`
84
+ )
85
+ return operands.reduce((a, b) => a / b)
86
+ },
87
+ [KEYWORDS.ARRAY_OR_STRING_LENGTH]: (args, env) => {
88
+ if (args.length !== 1)
89
+ throw new RangeError(
90
+ `Invalid number of arguments for (${
91
+ KEYWORDS.ARRAY_OR_STRING_LENGTH
92
+ }) (1 required) (${KEYWORDS.ARRAY_OR_STRING_LENGTH} ${stringifyArgs(
93
+ args
94
+ )}).`
95
+ )
96
+ const array = evaluate(args[0], env)
97
+ if (!(Array.isArray(array) || typeof array === 'string'))
98
+ throw new TypeError(
99
+ `First argument of (${
100
+ KEYWORDS.ARRAY_OR_STRING_LENGTH
101
+ }) must be an (or ${KEYWORDS.ARRAY_TYPE} ${KEYWORDS.STRING_TYPE}) (${
102
+ KEYWORDS.ARRAY_OR_STRING_LENGTH
103
+ } ${stringifyArgs(args)}).`
104
+ )
105
+ return array.length
106
+ },
107
+ [KEYWORDS.IS_ARRAY]: (args, env) => {
108
+ if (args.length !== 1)
109
+ throw new RangeError(
110
+ `Invalid number of arguments for (${KEYWORDS.IS_ARRAY}) (1 required) (${
111
+ KEYWORDS.IS_ARRAY
112
+ } ${stringifyArgs(args)}).`
113
+ )
114
+ const array = evaluate(args[0], env)
115
+ return +Array.isArray(array)
116
+ },
117
+ [KEYWORDS.IS_NUMBER]: (args, env) => {
118
+ if (args.length !== 1)
119
+ throw new RangeError(
120
+ `Invalid number of arguments for (${
121
+ KEYWORDS.IS_NUMBER
122
+ }) (1 required) (${KEYWORDS.IS_NUMBER} ${stringifyArgs(args)}).`
123
+ )
124
+ return +(typeof evaluate(args[0], env) === 'number')
125
+ },
126
+ [KEYWORDS.IS_STRING]: (args, env) => {
127
+ if (args.length !== 1)
128
+ throw new RangeError(
129
+ `Invalid number of arguments for (${
130
+ KEYWORDS.IS_STRING
131
+ }) (1 required) (${KEYWORDS.IS_STRING} ${stringifyArgs(args)}).`
132
+ )
133
+ return +(typeof evaluate(args[0], env) === 'string')
134
+ },
135
+ [KEYWORDS.IS_FUNCTION]: (args, env) => {
136
+ if (args.length !== 1)
137
+ throw new RangeError(
138
+ `Invalid number of arguments for (${
139
+ KEYWORDS.IS_FUNCTION
140
+ }) (1 required) (${KEYWORDS.IS_FUNCTION} ${stringifyArgs(args)}).`
141
+ )
142
+ return +(typeof evaluate(args[0], env) === 'function')
143
+ },
144
+ [KEYWORDS.ADDITION]: (args, env) => {
145
+ if (args.length < 2)
146
+ throw new RangeError(
147
+ `Invalid number of arguments for (${
148
+ KEYWORDS.ADDITION
149
+ }), expected > 1 but got ${args.length}. (${
150
+ KEYWORDS.ADDITION
151
+ } ${stringifyArgs(args)}).`
152
+ )
153
+ const operands = args.map((x) => evaluate(x, env))
154
+ if (operands.some((x) => typeof x !== 'number'))
155
+ throw new TypeError(
156
+ `Not all arguments of (${KEYWORDS.ADDITION}) are (${
157
+ KEYWORDS.NUMBER_TYPE
158
+ }) (${KEYWORDS.ADDITION} ${stringifyArgs(args)}).`
159
+ )
160
+ return operands.reduce((a, b) => a + b)
161
+ },
162
+ [KEYWORDS.MULTIPLICATION]: (args, env) => {
163
+ if (!args.length) return 1
164
+ if (args.length < 2)
165
+ throw new RangeError(
166
+ `Invalid number of arguments for (${KEYWORDS.MULTIPLICATION}), expected (or (> 1) (= 0)) but got ${args.length}.`
167
+ )
168
+ const operands = args.map((x) => evaluate(x, env))
169
+ if (operands.some((x) => typeof x !== 'number'))
170
+ throw new TypeError(
171
+ `Not all arguments of (${KEYWORDS.MULTIPLICATION}) are (${
172
+ KEYWORDS.NUMBER_TYPE
173
+ }) (${KEYWORDS.MULTIPLICATION} ${stringifyArgs(args)}).`
174
+ )
175
+ return operands.reduce((a, b) => a * b)
176
+ },
177
+ [KEYWORDS.SUBTRACTION]: (args, env) => {
178
+ if (!args.length)
179
+ throw new RangeError(
180
+ `Invalid number of arguments for (${
181
+ KEYWORDS.SUBTRACTION
182
+ }), expected >= 1 but got ${args.length}. (${
183
+ KEYWORDS.SUBTRACTION
184
+ } ${stringifyArgs(args)}).`
185
+ )
186
+ const operands = args.map((x) => evaluate(x, env))
187
+ if (operands.some((x) => typeof x !== 'number'))
188
+ throw new TypeError(
189
+ `Not all arguments of (${KEYWORDS.SUBTRACTION}) are (${
190
+ KEYWORDS.NUMBER_TYPE
191
+ }) (${KEYWORDS.SUBTRACTION} ${stringifyArgs(args)}).`
192
+ )
193
+ return args.length === 1 ? -operands[0] : operands.reduce((a, b) => a - b)
194
+ },
195
+ [KEYWORDS.IF]: (args, env) => {
196
+ if (args.length !== 3)
197
+ throw new RangeError(
198
+ `Invalid number of arguments for (${
199
+ KEYWORDS.IF
200
+ }), expected (= 3) but got ${args.length} (${
201
+ KEYWORDS.IF
202
+ } ${stringifyArgs(args)}).`
203
+ )
204
+ return evaluate(args[0], env)
205
+ ? evaluate(args[1], env)
206
+ : evaluate(args[2], env)
207
+ },
208
+ [KEYWORDS.UNLESS]: (args, env) => {
209
+ if (args.length !== 3)
210
+ throw new RangeError(
211
+ `Invalid number of arguments for (${
212
+ KEYWORDS.UNLESS
213
+ }), expected (= 3) but got ${args.length} (${
214
+ KEYWORDS.UNLESS
215
+ } ${stringifyArgs(args)}).`
216
+ )
217
+ return evaluate(args[0], env)
218
+ ? evaluate(args[2], env)
219
+ : evaluate(args[1], env)
220
+ },
221
+ [KEYWORDS.WHEN]: (args, env) => {
222
+ if (args.length !== 2)
223
+ throw new RangeError(
224
+ `Invalid number of arguments for (${
225
+ KEYWORDS.WHEN
226
+ }), expected 2 but got ${args.length} (${KEYWORDS.WHEN} ${stringifyArgs(
227
+ args
228
+ )}).`
229
+ )
230
+ return evaluate(args[0], env) ? evaluate(args[1], env) : 0
231
+ },
232
+ [KEYWORDS.OTHERWISE]: (args, env) => {
233
+ if (args.length !== 2)
234
+ throw new RangeError(
235
+ `Invalid number of arguments for (${
236
+ KEYWORDS.OTHERWISE
237
+ }), expected 2 but got ${args.length} (${
238
+ KEYWORDS.OTHERWISE
239
+ } ${stringifyArgs(args)}).`
240
+ )
241
+ return evaluate(args[0], env) ? 0 : evaluate(args[1], env)
242
+ },
243
+ [KEYWORDS.CONDITION]: (args, env) => {
244
+ if (args.length < 2)
245
+ throw new RangeError(
246
+ `Invalid number of arguments for (${
247
+ KEYWORDS.CONDITION
248
+ }), expected (> 2 required) but got ${args.length} (${
249
+ KEYWORDS.CONDITION
250
+ } ${stringifyArgs(args)}).`
251
+ )
252
+ for (let i = 0; i < args.length; i += 2) {
253
+ if (evaluate(args[i], env)) return evaluate(args[i + 1], env)
254
+ }
255
+ return 0
256
+ },
257
+ [KEYWORDS.ARRAY_TYPE]: (args, env) => {
258
+ if (!args.length) return []
259
+ const isCapacity =
260
+ args.length === 2 && args[1][TYPE] === WORD && args[1][VALUE] === 'length'
261
+ if (isCapacity) {
262
+ if (args.length !== 2)
263
+ throw new RangeError(
264
+ `Invalid number of arguments for (${
265
+ KEYWORDS.ARRAY_TYPE
266
+ }) (= 2 required) (${KEYWORDS.ARRAY_TYPE} ${stringifyArgs(args)})`
267
+ )
268
+ const N = evaluate(args[0], env)
269
+ if (!Number.isInteger(N))
270
+ throw new TypeError(
271
+ `Size argument for (${KEYWORDS.ARRAY_TYPE}) has to be an (32 bit ${
272
+ KEYWORDS.NUMBER_TYPE
273
+ }) (${KEYWORDS.ARRAY_TYPE} ${stringifyArgs(args)})`
274
+ )
275
+ return new Array(N).fill(0)
276
+ }
277
+ return args.map((x) => evaluate(x, env))
278
+ },
279
+ [KEYWORDS.IS_ATOM]: (args, env) => {
280
+ if (args.length !== 1)
281
+ throw new RangeError(
282
+ `Invalid number of arguments for (${KEYWORDS.IS_ATOM}) (1 required) (${
283
+ KEYWORDS.IS_ATOM
284
+ } ${stringifyArgs(args)}).`
285
+ )
286
+ return isAtom(args[0], env)
287
+ },
288
+ [KEYWORDS.FIRST_ARRAY]: (args, env) => {
289
+ if (args.length !== 1)
290
+ throw new RangeError(
291
+ `Invalid number of arguments for (${
292
+ KEYWORDS.FIRST_ARRAY
293
+ }) (1 required) (${KEYWORDS.FIRST_ARRAY} ${stringifyArgs(args)}).`
294
+ )
295
+ const array = evaluate(args[0], env)
296
+ if (!Array.isArray(array))
297
+ throw new TypeError(
298
+ `Argument of (${KEYWORDS.FIRST_ARRAY}) must be an (${
299
+ KEYWORDS.ARRAY_TYPE
300
+ }) (${KEYWORDS.FIRST_ARRAY} ${stringifyArgs(args)}).`
301
+ )
302
+ if (array.length === 0)
303
+ throw new RangeError(
304
+ `Argument of (${KEYWORDS.FIRST_ARRAY}) is an empty (${
305
+ KEYWORDS.ARRAY_TYPE
306
+ }) (${KEYWORDS.FIRST_ARRAY} ${stringifyArgs(args)}).`
307
+ )
308
+ const value = array.at(0)
309
+ if (value == undefined)
310
+ throw new RangeError(
311
+ `Trying to get a null value in (${KEYWORDS.ARRAY_TYPE}) at (${
312
+ KEYWORDS.FIRST_ARRAY
313
+ }) (${KEYWORDS.FIRST_ARRAY} ${stringifyArgs(args)}).`
314
+ )
315
+ return value
316
+ },
317
+ [KEYWORDS.REST_ARRAY]: (args, env) => {
318
+ if (args.length !== 1)
319
+ throw new RangeError(
320
+ `Invalid number of arguments for (${
321
+ KEYWORDS.REST_ARRAY
322
+ }) (1 required) (${KEYWORDS.REST_ARRAY} ${stringifyArgs(args)})`
323
+ )
324
+ const array = evaluate(args[0], env)
325
+ if (!Array.isArray(array))
326
+ throw new TypeError(
327
+ `Argument of (${KEYWORDS.REST_ARRAY}) must be an (${
328
+ KEYWORDS.ARRAY_TYPE
329
+ }) (${KEYWORDS.REST_ARRAY} ${stringifyArgs(args)}).`
330
+ )
331
+ if (array.length === 0)
332
+ throw new RangeError(
333
+ `Argument of (${KEYWORDS.REST_ARRAY}) is an empty (${
334
+ KEYWORDS.ARRAY_TYPE
335
+ }) (${KEYWORDS.REST_ARRAY} ${stringifyArgs(args)}).`
336
+ )
337
+ return array.slice(1)
338
+ },
339
+ [KEYWORDS.GET_ARRAY]: (args, env) => {
340
+ if (args.length !== 2)
341
+ throw new RangeError(
342
+ `Invalid number of arguments for (${
343
+ KEYWORDS.GET_ARRAY
344
+ }) (2 required) (${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)})`
345
+ )
346
+ const array = evaluate(args[0], env)
347
+ if (!Array.isArray(array))
348
+ throw new TypeError(
349
+ `First argument of (${KEYWORDS.GET_ARRAY}) must be an (${
350
+ KEYWORDS.ARRAY_TYPE
351
+ })) (${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)}).`
352
+ )
353
+ if (array.length === 0)
354
+ throw new RangeError(
355
+ `First argument of (${KEYWORDS.GET_ARRAY}) is an empty (${
356
+ KEYWORDS.ARRAY_TYPE
357
+ })) (${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)})).`
358
+ )
359
+ const index = evaluate(args[1], env)
360
+ if (!Number.isInteger(index))
361
+ throw new TypeError(
362
+ `Second argument of (${KEYWORDS.GET_ARRAY}) must be an (32 bit ${
363
+ KEYWORDS.NUMBER_TYPE
364
+ }) (${index}) (${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)}).`
365
+ )
366
+ if (index > array.length - 1 || index * -1 > array.length)
367
+ throw new RangeError(
368
+ `Second argument of (${KEYWORDS.GET_ARRAY}) is outside of (${
369
+ KEYWORDS.ARRAY_TYPE
370
+ }) bounds (${index}) (${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)}).`
371
+ )
372
+ const value = array.at(index)
373
+ if (value == undefined)
374
+ throw new RangeError(
375
+ `Trying to get a null value in (${KEYWORDS.ARRAY_TYPE}) at (${
376
+ KEYWORDS.GET_ARRAY
377
+ }) (${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)}).`
378
+ )
379
+ return value
380
+ },
381
+ [KEYWORDS.LOG]: (args, env) => {
382
+ if (!args.length)
383
+ throw new RangeError(
384
+ `Invalid number of arguments to (${KEYWORDS.LOG}) (>= 1 required) (${
385
+ KEYWORDS.LOG
386
+ } ${stringifyArgs(args)})`
387
+ )
388
+ const expressions = args.map((x) => evaluate(x, env))
389
+ console.log(...expressions)
390
+ return expressions.at(-1)
391
+ },
392
+ [KEYWORDS.READ]: (args, env) => {
393
+ if (args.length)
394
+ throw new RangeError(
395
+ `Invalid number of arguments to (${KEYWORDS.READ}) (= 0 required) (${
396
+ KEYWORDS.READ
397
+ } ${stringifyArgs(args)})`
398
+ )
399
+ const inp = env[KEYWORDS.INPUT]
400
+ if (inp.length) return inp
401
+ else
402
+ throw new ReferenceError(
403
+ `${KEYWORDS.INPUT} is empty! at (${KEYWORDS.READ})`
404
+ )
405
+ },
406
+ [KEYWORDS.BLOCK]: (args, env) => {
407
+ if (!args.length)
408
+ throw new RangeError(
409
+ `Invalid number of arguments to (${KEYWORDS.BLOCK}) (>= 1 required) (${
410
+ KEYWORDS.BLOCK
411
+ } ${stringifyArgs(args)})`
412
+ )
413
+ return args.reduce((_, x) => evaluate(x, env), 0)
414
+ },
415
+ [KEYWORDS.ANONYMOUS_FUNCTION]: (args, env) => {
416
+ const params = args.slice(0, -1)
417
+ const body = args.at(-1)
418
+ return (props = [], scope) => {
419
+ if (props.length !== params.length)
420
+ throw new RangeError(
421
+ `Incorrect number of arguments for (${
422
+ KEYWORDS.ANONYMOUS_FUNCTION
423
+ } ${params.map((x) => x[VALUE]).join(' ')}) are provided. (expects ${
424
+ params.length
425
+ } but got ${props.length}) (${
426
+ KEYWORDS.ANONYMOUS_FUNCTION
427
+ } ${stringifyArgs(args)})`
428
+ )
429
+ const localEnv = Object.create(env)
430
+ for (let i = 0; i < props.length; ++i) {
431
+ Object.defineProperty(localEnv, params[i][VALUE], {
432
+ value: evaluate(props[i], scope),
433
+ writable: true,
434
+ })
435
+ }
436
+ return evaluate(body, localEnv)
437
+ }
438
+ },
439
+ [KEYWORDS.NOT]: (args, env) => {
440
+ if (args.length !== 1)
441
+ throw new RangeError(
442
+ `Invalid number of arguments for (${KEYWORDS.NOT}) (1 required) (${
443
+ KEYWORDS.NOT
444
+ } ${stringifyArgs(args)})`
445
+ )
446
+ return +!evaluate(args[0], env)
447
+ },
448
+ [KEYWORDS.EQUAL]: (args, env) => {
449
+ if (args.length !== 2)
450
+ throw new RangeError(
451
+ `Invalid number of arguments for (${KEYWORDS.EQUAL}) (2 required) (${
452
+ KEYWORDS.EQUAL
453
+ } ${stringifyArgs(args)})`
454
+ )
455
+ const a = evaluate(args[0], env)
456
+ const b = evaluate(args[1], env)
457
+ if (
458
+ Array.isArray(a) ||
459
+ Array.isArray(b) ||
460
+ typeof a === 'function' ||
461
+ typeof b === 'function'
462
+ )
463
+ throw new TypeError(
464
+ `Invalid use of (${KEYWORDS.EQUAL}), some arguments are not an ${
465
+ KEYWORDS.ATOM
466
+ } (${KEYWORDS.EQUAL} ${stringifyArgs(args)})`
467
+ )
468
+ return +(a === b)
469
+ },
470
+ [KEYWORDS.LESS_THAN]: (args, env) => {
471
+ if (args.length !== 2)
472
+ throw new RangeError(
473
+ `Invalid number of arguments for (${
474
+ KEYWORDS.LESS_THAN
475
+ }) (2 required) (${KEYWORDS.LESS_THAN} ${stringifyArgs(args)})`
476
+ )
477
+ const a = evaluate(args[0], env)
478
+ const b = evaluate(args[1], env)
479
+ if (
480
+ Array.isArray(a) ||
481
+ Array.isArray(b) ||
482
+ typeof a === 'function' ||
483
+ typeof b === 'function'
484
+ )
485
+ throw new TypeError(
486
+ `Invalid use of (${KEYWORDS.LESS_THAN}), some arguments are not an ${
487
+ KEYWORDS.ATOM
488
+ } (${KEYWORDS.LESS_THAN} ${stringifyArgs(args)})`
489
+ )
490
+ return +(a < b)
491
+ },
492
+ [KEYWORDS.GREATHER_THAN]: (args, env) => {
493
+ if (args.length !== 2)
494
+ throw new RangeError(
495
+ `Invalid number of arguments for (${
496
+ KEYWORDS.GREATHER_THAN
497
+ }) (2 required) (${KEYWORDS.GREATHER_THAN} ${stringifyArgs(args)})`
498
+ )
499
+ const a = evaluate(args[0], env)
500
+ const b = evaluate(args[1], env)
501
+ if (
502
+ Array.isArray(a) ||
503
+ Array.isArray(b) ||
504
+ typeof a === 'function' ||
505
+ typeof b === 'function'
506
+ )
507
+ throw new TypeError(
508
+ `Invalid use of (${
509
+ KEYWORDS.GREATHER_THAN
510
+ }), some arguments are not an ${KEYWORDS.ATOM} (${
511
+ KEYWORDS.GREATHER_THAN
512
+ } ${stringifyArgs(args)})`
513
+ )
514
+ return +(a > b)
515
+ },
516
+ [KEYWORDS.GREATHER_THAN_OR_EQUAL]: (args, env) => {
517
+ if (args.length !== 2)
518
+ throw new RangeError(
519
+ `Invalid number of arguments for (${
520
+ KEYWORDS.GREATHER_THAN_OR_EQUAL
521
+ }) (2 required) (${KEYWORDS.GREATHER_THAN_OR_EQUAL} ${stringifyArgs(
522
+ args
523
+ )})`
524
+ )
525
+ const a = evaluate(args[0], env)
526
+ const b = evaluate(args[1], env)
527
+ if (
528
+ Array.isArray(a) ||
529
+ Array.isArray(b) ||
530
+ typeof a === 'function' ||
531
+ typeof b === 'function'
532
+ )
533
+ throw new TypeError(
534
+ `Invalid use of (${
535
+ KEYWORDS.GREATHER_THAN_OR_EQUAL
536
+ }), some arguments are not an ${KEYWORDS.ATOM} (${
537
+ KEYWORDS.GREATHER_THAN_OR_EQUAL
538
+ } ${stringifyArgs(args)})`
539
+ )
540
+ return +(a >= b)
541
+ },
542
+ [KEYWORDS.LESS_THAN_OR_EQUAL]: (args, env) => {
543
+ if (args.length !== 2)
544
+ throw new RangeError(
545
+ `Invalid number of arguments for (${
546
+ KEYWORDS.LESS_THAN_OR_EQUAL
547
+ }) (2 required) (${KEYWORDS.LESS_THAN_OR_EQUAL} ${stringifyArgs(args)})`
548
+ )
549
+ const a = evaluate(args[0], env)
550
+ const b = evaluate(args[1], env)
551
+ if (
552
+ Array.isArray(a) ||
553
+ Array.isArray(b) ||
554
+ typeof a === 'function' ||
555
+ typeof b === 'function'
556
+ )
557
+ throw new TypeError(
558
+ `Invalid use of (${
559
+ KEYWORDS.LESS_THAN_OR_EQUAL
560
+ }), some arguments are not an ${KEYWORDS.ATOM} (${
561
+ KEYWORDS.LESS_THAN_OR_EQUAL
562
+ } ${stringifyArgs(args)})`
563
+ )
564
+ return +(a <= b)
565
+ },
566
+ [KEYWORDS.AND]: (args, env) => {
567
+ if (args.length < 2)
568
+ throw new RangeError(
569
+ `Invalid number of arguments for (${KEYWORDS.AND}) (>= 2 required) (${
570
+ KEYWORDS.AND
571
+ } ${stringifyArgs(args)})`
572
+ )
573
+ let circuit
574
+ for (let i = 0; i < args.length - 1; ++i) {
575
+ circuit = evaluate(args[i], env)
576
+ if (circuit) continue
577
+ else return circuit
578
+ }
579
+ return evaluate(args.at(-1), env)
580
+ },
581
+ [KEYWORDS.OR]: (args, env) => {
582
+ if (args.length < 2)
583
+ throw new RangeError(
584
+ `Invalid number of arguments for (${KEYWORDS.OR}) (>= 2 required) (${
585
+ KEYWORDS.OR
586
+ } ${stringifyArgs(args)})`
587
+ )
588
+ let circuit
589
+ for (let i = 0; i < args.length - 1; ++i) {
590
+ circuit = evaluate(args[i], env)
591
+ if (circuit) return circuit
592
+ else continue
593
+ }
594
+ return evaluate(args.at(-1), env)
595
+ },
596
+ [KEYWORDS.CALL_FUNCTION]: (args, env) => {
597
+ if (!args.length)
598
+ throw new RangeError(
599
+ `Invalid number of arguments to (${
600
+ KEYWORDS.CALL_FUNCTION
601
+ }) (>= 1 required) (${KEYWORDS.CALL_FUNCTION} ${stringifyArgs(args)})`
602
+ )
603
+ const [first, ...rest] = args
604
+ if (first[TYPE] === WORD && first[VALUE] in keywords)
605
+ throw new TypeError(
606
+ `Following argument of (${
607
+ KEYWORDS.CALL_FUNCTION
608
+ }) must not be an reserved word (${
609
+ KEYWORDS.CALL_FUNCTION
610
+ } ${stringifyArgs(args)})`
611
+ )
612
+ const apply = evaluate(first, env)
613
+ if (typeof apply !== 'function')
614
+ throw new TypeError(
615
+ `First argument of (${KEYWORDS.CALL_FUNCTION}) must be a (${
616
+ KEYWORDS.ANONYMOUS_FUNCTION
617
+ }) (${KEYWORDS.CALL_FUNCTION} ${stringifyArgs(args)})`
618
+ )
619
+
620
+ return apply(rest, env)
621
+ },
622
+ [KEYWORDS.DEFINE_VARIABLE]: (args, env) => {
623
+ if (args.length !== 2)
624
+ throw new RangeError(
625
+ `Invalid number of arguments to (${
626
+ KEYWORDS.DEFINE_VARIABLE
627
+ }) (= 2 required) (${KEYWORDS.DEFINE_VARIABLE} ${stringifyArgs(args)})`
628
+ )
629
+ let name
630
+ const word = args[0]
631
+ if (word[TYPE] !== WORD)
632
+ throw new SyntaxError(
633
+ `First argument of (${KEYWORDS.DEFINE_VARIABLE}) must be word but got ${
634
+ word[TYPE]
635
+ } (${KEYWORDS.DEFINE_VARIABLE} ${stringifyArgs(args)})`
636
+ )
637
+ else if (isForbiddenVariableName(word[VALUE]))
638
+ throw new ReferenceError(
639
+ `Variable name ${word[VALUE]} is forbidden at (${
640
+ KEYWORDS.DEFINE_VARIABLE
641
+ } ${stringifyArgs(args)})`
642
+ )
643
+ name = word[VALUE]
644
+ Object.defineProperty(env, name, {
645
+ value: evaluate(args[1], env),
646
+ writable: false,
647
+ })
648
+ return env[name]
649
+ },
650
+ [KEYWORDS.STRING_TYPE]: () => '',
651
+ [KEYWORDS.NUMBER_TYPE]: () => 0,
652
+ [KEYWORDS.BOOLEAN_TYPE]: () => 1,
653
+ [KEYWORDS.FUNCTION_TYPE]: () => () => {},
654
+ [KEYWORDS.CAST_TYPE]: (args, env) => {
655
+ if (args.length !== 2)
656
+ throw new RangeError(
657
+ `Invalid number of arguments for (${KEYWORDS.CAST_TYPE}) ${args.length}`
658
+ )
659
+ const type = args[1][VALUE]
660
+ const value = evaluate(args[0], env)
661
+ if (value == undefined)
662
+ throw ReferenceError(
663
+ `Trying to access undefined value at (${KEYWORDS.CAST_TYPE})`
664
+ )
665
+ if (args.length === 2) {
666
+ switch (type) {
667
+ case KEYWORDS.NUMBER_TYPE: {
668
+ const num = Number(value)
669
+ if (isNaN(num))
670
+ throw new TypeError(
671
+ `Attempting to convert Not a ${
672
+ KEYWORDS.NUMBER_TYPE
673
+ } ("${value}") to a ${KEYWORDS.NUMBER_TYPE} at (${
674
+ KEYWORDS.CAST_TYPE
675
+ }) (${KEYWORDS.CAST_TYPE} ${stringifyArgs(args)}).`
676
+ )
677
+ return num
678
+ }
679
+ case KEYWORDS.STRING_TYPE:
680
+ return value.toString()
681
+ case KEYWORDS.BIT_TYPE:
682
+ return parseInt(value, 2)
683
+ case KEYWORDS.BOOLEAN_TYPE:
684
+ return +!!value
685
+ case KEYWORDS.FUNCTION_TYPE:
686
+ return () => value
687
+ case KEYWORDS.ARRAY_TYPE: {
688
+ if (typeof value === 'number')
689
+ return [...Number(value).toString()].map(Number)
690
+ else if (typeof value[Symbol.iterator] !== 'function')
691
+ throw new TypeError(
692
+ `Arguments are not iterable for ${KEYWORDS.ARRAY_TYPE} at (${
693
+ KEYWORDS.CAST_TYPE
694
+ }) (${KEYWORDS.CAST_TYPE} ${stringifyArgs(args)}).`
695
+ )
696
+ return [...value]
697
+ }
698
+ case KEYWORDS.CHAR_TYPE: {
699
+ const index = evaluate(args[0], env)
700
+ if (!Number.isInteger(index) || index < 0)
701
+ throw new TypeError(
702
+ `Arguments are not (+ ${KEYWORDS.NUMBER_TYPE}) for ${
703
+ KEYWORDS.CHAR_TYPE
704
+ } at (${KEYWORDS.CAST_TYPE}) (${
705
+ KEYWORDS.CAST_TYPE
706
+ } ${stringifyArgs(args)}).`
707
+ )
708
+ return String.fromCharCode(index)
709
+ }
710
+ case KEYWORDS.CHAR_CODE_TYPE: {
711
+ const string = evaluate(args[0], env)
712
+ if (typeof string !== 'string')
713
+ throw new TypeError(
714
+ `Argument is not (${KEYWORDS.STRING_TYPE}) for ${
715
+ KEYWORDS.CHAR_CODE_TYPE
716
+ } at (${KEYWORDS.CAST_TYPE}) (${
717
+ KEYWORDS.CAST_TYPE
718
+ } ${stringifyArgs(args)}).`
719
+ )
720
+ if (string.length !== 1)
721
+ throw new RangeError(
722
+ `Argument is not of (= (length ${KEYWORDS.STRING_TYPE}) 1) for ${
723
+ KEYWORDS.CHAR_CODE_TYPE
724
+ } at (${KEYWORDS.CAST_TYPE}) (${
725
+ KEYWORDS.CAST_TYPE
726
+ } ${stringifyArgs(args)}).`
727
+ )
728
+ return string.charCodeAt(0)
729
+ }
730
+ default:
731
+ throw new TypeError(
732
+ `Can only cast (or ${KEYWORDS.NUMBER_TYPE} ${
733
+ KEYWORDS.STRING_TYPE
734
+ } ${KEYWORDS.ARRAY_TYPE} ${KEYWORDS.BIT_TYPE} ${
735
+ KEYWORDS.BOOLEAN_TYPE
736
+ } ${KEYWORDS.CHAR_TYPE} ${KEYWORDS.CHAR_CODE_TYPE}) at (${
737
+ KEYWORDS.CAST_TYPE
738
+ }) (${KEYWORDS.CAST_TYPE} ${stringifyArgs(args)}).`
739
+ )
740
+ }
741
+ }
742
+ },
743
+ [KEYWORDS.BIT_TYPE]: (args, env) => {
744
+ if (args.length !== 1)
745
+ throw new RangeError(
746
+ `Invalid number of arguments to (${KEYWORDS.BIT_TYPE}) (1 required). (${
747
+ KEYWORDS.BIT_TYPE
748
+ } ${stringifyArgs(args)})`
749
+ )
750
+ const operand = evaluate(args[0], env)
751
+ if (typeof operand !== 'number')
752
+ throw new TypeError(
753
+ `Argument of (${KEYWORDS.BIT_TYPE}) is not a (${
754
+ KEYWORDS.NUMBER_TYPE
755
+ }) (${KEYWORDS.BIT_TYPE} ${stringifyArgs(args)}).`
756
+ )
757
+ return (operand >>> 0).toString(2)
758
+ },
759
+ [KEYWORDS.BITWISE_AND]: (args, env) => {
760
+ if (args.length < 2)
761
+ throw new RangeError(
762
+ `Invalid number of arguments to (${
763
+ KEYWORDS.BITWISE_AND
764
+ }) (>= 2 required). (${KEYWORDS.BITWISE_AND} ${stringifyArgs(args)})`
765
+ )
766
+ const operands = args.map((a) => evaluate(a, env))
767
+ if (operands.some((x) => typeof x !== 'number'))
768
+ throw new TypeError(
769
+ `Not all arguments of (${KEYWORDS.BITWISE_AND}) are ${
770
+ KEYWORDS.NUMBER_TYPE
771
+ } (${KEYWORDS.BITWISE_AND} ${stringifyArgs(args)}).`
772
+ )
773
+ return operands.reduce((acc, x) => acc & x)
774
+ },
775
+ [KEYWORDS.BITWISE_NOT]: (args, env) => {
776
+ if (args.length !== 1)
777
+ throw new RangeError(
778
+ `Invalid number of arguments to (${
779
+ KEYWORDS.BITWISE_NOT
780
+ }) (1 required). (${KEYWORDS.BITWISE_NOT} ${stringifyArgs(args)})`
781
+ )
782
+ const operand = evaluate(args[0], env)
783
+ if (typeof operand !== 'number')
784
+ throw new TypeError(
785
+ `Argument of (${KEYWORDS.BITWISE_NOT}) is not a (${
786
+ KEYWORDS.NUMBER_TYPE
787
+ }) (${KEYWORDS.BITWISE_NOT} ${stringifyArgs(args)}).`
788
+ )
789
+ return ~operand
790
+ },
791
+ [KEYWORDS.BITWISE_OR]: (args, env) => {
792
+ if (args.length < 2)
793
+ throw new RangeError(
794
+ `Invalid number of arguments to (${
795
+ KEYWORDS.BITWISE_OR
796
+ }) (>= 2 required). (${KEYWORDS.BITWISE_OR} ${stringifyArgs(args)})`
797
+ )
798
+ const operands = args.map((a) => evaluate(a, env))
799
+ if (operands.some((x) => typeof x !== 'number'))
800
+ throw new TypeError(
801
+ `Not all arguments of (${KEYWORDS.BITWISE_OR}) are (${
802
+ KEYWORDS.NUMBER_TYPE
803
+ }) (${KEYWORDS.BITWISE_OR} ${stringifyArgs(args)}).`
804
+ )
805
+ return operands.reduce((acc, x) => acc | x)
806
+ },
807
+ [KEYWORDS.BITWISE_XOR]: (args, env) => {
808
+ if (args.length < 2)
809
+ throw new RangeError(
810
+ `Invalid number of arguments to (${
811
+ KEYWORDS.BITWISE_XOR
812
+ }) (>= 2 required). (${KEYWORDS.BITWISE_XOR} ${stringifyArgs(args)}).`
813
+ )
814
+ const operands = args.map((a) => evaluate(a, env))
815
+ if (operands.some((x) => typeof x !== 'number'))
816
+ throw new TypeError(
817
+ `Not all arguments of (${KEYWORDS.BITWISE_XOR}) are (${
818
+ KEYWORDS.NUMBER_TYPE
819
+ }) (${KEYWORDS.BITWISE_XOR} ${stringifyArgs(args)}).`
820
+ )
821
+ return operands.reduce((acc, x) => acc ^ x)
822
+ },
823
+ [KEYWORDS.BITWISE_LEFT_SHIFT]: (args, env) => {
824
+ if (args.length < 2)
825
+ throw new RangeError(
826
+ `Invalid number of arguments to (${
827
+ KEYWORDS.BITWISE_LEFT_SHIFT
828
+ }) (>= 2 required). (${KEYWORDS.BITWISE_LEFT_SHIFT} ${stringifyArgs(
829
+ args
830
+ )}).`
831
+ )
832
+ const operands = args.map((a) => evaluate(a, env))
833
+ if (operands.some((x) => typeof x !== 'number'))
834
+ throw new TypeError(
835
+ `Not all arguments of (${KEYWORDS.BITWISE_LEFT_SHIFT}) are (${
836
+ KEYWORDS.NUMBER_TYPE
837
+ }) (${KEYWORDS.BITWISE_LEFT_SHIFT} ${stringifyArgs(args)}).`
838
+ )
839
+ return operands.reduce((acc, x) => acc << x)
840
+ },
841
+ [KEYWORDS.BITWISE_RIGHT_SHIFT]: (args, env) => {
842
+ if (args.length < 2)
843
+ throw new RangeError(
844
+ `Invalid number of arguments to (${
845
+ KEYWORDS.BITWISE_RIGHT_SHIFT
846
+ }) (>= 2 required). (${KEYWORDS.BITWISE_RIGHT_SHIFT} ${stringifyArgs(
847
+ args
848
+ )}).`
849
+ )
850
+ const operands = args.map((a) => evaluate(a, env))
851
+ if (operands.some((x) => typeof x !== 'number'))
852
+ throw new TypeError(
853
+ `Not all arguments of (${KEYWORDS.BITWISE_RIGHT_SHIFT}) are (${
854
+ KEYWORDS.NUMBER_TYPE
855
+ }) (${KEYWORDS.BITWISE_RIGHT_SHIFT} ${stringifyArgs(args)}).`
856
+ )
857
+ return operands.reduce((acc, x) => acc >> x)
858
+ },
859
+ [KEYWORDS.BITWISE_UNSIGNED_RIGHT_SHIFT]: (args, env) => {
860
+ if (args.length < 2)
861
+ throw new RangeError(
862
+ `Invalid number of arguments to (${
863
+ KEYWORDS.BITWISE_UNSIGNED_RIGHT_SHIFT
864
+ }) (>= 2 required). (${
865
+ KEYWORDS.BITWISE_UNSIGNED_RIGHT_SHIFT
866
+ } ${stringifyArgs(args)}).`
867
+ )
868
+ const operands = args.map((a) => evaluate(a, env))
869
+ if (operands.some((x) => typeof x !== 'number'))
870
+ throw new TypeError(
871
+ `Not all arguments of (${KEYWORDS.BITWISE_UNSIGNED_RIGHT_SHIFT}) are (${
872
+ KEYWORDS.NUMBER_TYPE
873
+ }) (${KEYWORDS.BITWISE_UNSIGNED_RIGHT_SHIFT} ${stringifyArgs(args)}).`
874
+ )
875
+ return operands.reduce((acc, x) => acc >>> x)
876
+ },
877
+ [KEYWORDS.PIPE]: (args, env) => {
878
+ if (args.length < 1)
879
+ throw new RangeError(
880
+ `Invalid number of arguments to (${KEYWORDS.PIPE}) (>= 1 required). (${
881
+ KEYWORDS.PIPE
882
+ } ${stringifyArgs(args)})`
883
+ )
884
+ let inp = args[0]
885
+ for (let i = 1; i < args.length; ++i) {
886
+ if (!Array.isArray(args[i]))
887
+ throw new TypeError(
888
+ `Argument at position (${i}) of (${KEYWORDS.PIPE}) is not a (${
889
+ KEYWORDS.FUNCTION_TYPE
890
+ }). (${KEYWORDS.PIPE} ${stringifyArgs(args)})`
891
+ )
892
+ const [first, ...rest] = args[i]
893
+ const arr = [first, inp, ...rest]
894
+ inp = arr
895
+ }
896
+ return evaluate(inp, env)
897
+ },
898
+ [KEYWORDS.THROW_ERROR]: (args, env) => {
899
+ if (args.length !== 1)
900
+ throw new RangeError(
901
+ `Invalid number of arguments to (${
902
+ KEYWORDS.THROW_ERROR
903
+ }) (1 required). (${KEYWORDS.THROW_ERROR} ${stringifyArgs(args)}).`
904
+ )
905
+ const string = evaluate(args[0], env)
906
+ if (typeof string !== 'string')
907
+ throw new TypeError(
908
+ `First argument of (${KEYWORDS.THROW_ERROR}) must be an (${
909
+ KEYWORDS.STRING_TYPE
910
+ }) (${KEYWORDS.THROW_ERROR} ${stringifyArgs(args)}).`
911
+ )
912
+ throw new Error(string)
913
+ },
914
+ [KEYWORDS.MERGE]: (args, env) => {
915
+ if (args.length < 2)
916
+ throw new RangeError(
917
+ `Invalid number of arguments to (${KEYWORDS.MERGE}) (>= 2 required). (${
918
+ KEYWORDS.MERGE
919
+ } ${stringifyArgs(args)}).`
920
+ )
921
+ const arrays = args.map((arg) => evaluate(arg, env))
922
+ if (arrays.some((maybe) => !Array.isArray(maybe)))
923
+ throw new TypeError(
924
+ `Arguments of (${KEYWORDS.MERGE}) must be (${KEYWORDS.ARRAY_TYPE}) (${
925
+ KEYWORDS.MERGE
926
+ } ${stringifyArgs(args)}).`
927
+ )
928
+ return arrays.reduce((a, b) => a.concat(b), [])
929
+ },
930
+ [KEYWORDS.TAIL_CALLS_OPTIMISED_RECURSIVE_FUNCTION]: (args, env) => {
931
+ if (!args.length)
932
+ throw new RangeError(
933
+ `Invalid number of arguments to (${
934
+ KEYWORDS.TAIL_CALLS_OPTIMISED_RECURSIVE_FUNCTION
935
+ }) (>= 2 required). (${
936
+ KEYWORDS.TAIL_CALLS_OPTIMISED_RECURSIVE_FUNCTION
937
+ } ${stringifyArgs(args)}).`
938
+ )
939
+ // TODO: Add validation for TCO recursion
940
+ return keywords[KEYWORDS.DEFINE_VARIABLE](args, env)
941
+ },
942
+ [KEYWORDS.IMMUTABLE_FUNCTION]: (args, env) => {
943
+ if (!args.length)
944
+ throw new RangeError(
945
+ `Invalid number of arguments to (${
946
+ KEYWORDS.IMMUTABLE_FUNCTION
947
+ }) (>= 2 required). (${KEYWORDS.IMMUTABLE_FUNCTION} ${stringifyArgs(
948
+ args
949
+ )}).`
950
+ )
951
+ const [definition, ...functionArgs] = args
952
+ const token = definition[VALUE]
953
+ if (!(token in keywords))
954
+ throw new ReferenceError(
955
+ `There is no such keyword ${token} at (${
956
+ KEYWORDS.IMMUTABLE_FUNCTION
957
+ } ${stringifyArgs(args)})`
958
+ )
959
+
960
+ const params = functionArgs.slice(0, -1)
961
+ const body = functionArgs.at(-1)
962
+ return (props = [], scope) => {
963
+ if (props.length !== params.length)
964
+ throw new RangeError(
965
+ `Incorrect number of arguments for (${KEYWORDS.IMMUTABLE_FUNCTION} ${
966
+ KEYWORDS.ANONYMOUS_FUNCTION
967
+ } ${params.map((x) => x[VALUE]).join(' ')}) are provided. (expects ${
968
+ params.length
969
+ } but got ${props.length}) (${KEYWORDS.IMMUTABLE_FUNCTION} ${
970
+ KEYWORDS.ANONYMOUS_FUNCTION
971
+ } ${stringifyArgs(args)})`
972
+ )
973
+ const localEnv = Object.create({ ...keywords })
974
+ for (let i = 0; i < props.length; ++i) {
975
+ Object.defineProperty(localEnv, params[i][VALUE], {
976
+ value: evaluate(props[i], scope),
977
+ writable: true,
978
+ })
979
+ }
980
+ return evaluate(body, localEnv)
981
+ }
982
+ },
983
+ [KEYWORDS.TEST_CASE]: (args, env) => {
984
+ if (args.length !== 3)
985
+ throw new RangeError(
986
+ `Invalid number of arguments to (${
987
+ KEYWORDS.TEST_CASE
988
+ }) (= 3 required) (${KEYWORDS.TEST_CASE} ${stringifyArgs(args)})`
989
+ )
990
+ const description = evaluate(args[0], env)
991
+ const a = evaluate(args[1], env)
992
+ const b = evaluate(args[2], env)
993
+ return !isEqualTypes(a, b) || !isEqual(a, b)
994
+ ? [0, description, stringifyArgs([args[1]]), b, a]
995
+ : [1, description, stringifyArgs([args[1]]), a]
996
+ },
997
+ [KEYWORDS.TEST_BED]: (args, env) => {
998
+ let tests = []
999
+ try {
1000
+ tests = args.map((x) => evaluate(x, env))
1001
+ tests.forEach(([state, describe, ...rest]) =>
1002
+ !state
1003
+ ? console.log(
1004
+ '\x1b[31m',
1005
+ `${describe} Failed:\n`,
1006
+
1007
+ `${rest[0]} => ${lispify(rest[1])} != ${lispify(rest[2])}`,
1008
+ '\n',
1009
+ '\x1b[0m'
1010
+ )
1011
+ : console.log(
1012
+ '\x1b[32m',
1013
+ `${describe} Passed:\n`,
1014
+ `${rest[0]} => ${lispify(rest[1])}`,
1015
+ '\n',
1016
+ '\x1b[0m'
1017
+ )
1018
+ )
1019
+ } catch (err) {
1020
+ console.log('\x1b[31m', 'Tests failed: \n', err.toString())
1021
+ }
1022
+ !tests.length || tests.some(([t]) => !t)
1023
+ ? console.log('\x1b[31m', 'Some tests failed!', '\n', '\x1b[0m')
1024
+ : console.log('\x1b[32m', 'All tests passed!', '\n', '\x1b[0m')
1025
+ },
1026
+ [KEYWORDS.SERIALISE]: (args, env) => {
1027
+ if (!args.length)
1028
+ throw new RangeError(
1029
+ `Invalid number of arguments for (${KEYWORDS.SERIALISE})`
1030
+ )
1031
+ const data = evaluate(args[0], env)
1032
+ return lispify(data)
1033
+ },
1034
+ }
1035
+ keywords[KEYWORDS.NOT_COMPILED_BLOCK] = keywords[KEYWORDS.BLOCK]
1036
+ export { keywords }