fez-lisp 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 }