fez-lisp 1.2.61 → 1.3.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.
package/src/utils.js CHANGED
@@ -1,637 +1,832 @@
1
- import std from '../lib/baked/std.js'
2
- import { comp } from './compiler.js'
3
- import {
4
- APPLY,
5
- ATOM,
6
- FALSE,
7
- KEYWORDS,
8
- SUGGAR,
9
- TRUE,
10
- TYPE,
11
- VALUE,
12
- WORD
13
- } from './keywords.js'
14
- import { evaluate, run } from './evaluator.js'
15
- import { AST, isLeaf, LISP } from './parser.js'
16
- export const logError = (error) =>
17
- console.log('\x1b[31m', `\n${error}\n`, '\x1b[0m')
18
- export const logSuccess = (output) => console.log(output, '\x1b[0m')
19
- export const replaceStrings = (source) => {
20
- // const quotes = source.match(/"(.*?)"/g)
21
- const quotes = source.match(/"(?:.*?(\n|\r))*?.*?"/g)
22
- // TODO handle escaping
23
- if (quotes)
24
- for (const q of quotes)
25
- source = source.replaceAll(
26
- q,
27
- `(array ${[...q.replaceAll('\r', '')]
28
- .slice(1, -1)
29
- .map((x) => x.charCodeAt(0))
30
- .join(' ')})`
31
- )
32
- return source
33
- }
34
- export const replaceQuotes = (source) =>
35
- source
36
- .replaceAll(/\'\(/g, '(array ')
37
- .replaceAll(/\`\(/g, '(list ')
38
- .replaceAll(/\(\)/g, '(array)')
39
- // export const replaceEmptyArrays = (source) =>
40
- // source
41
- export const removeNoCode = (source) =>
42
- source
43
- .replace(/;.+/g, '')
44
- .replace(/[\s\s]/g, ' ')
45
- .trim()
46
- export const isBalancedParenthesis = (sourceCode) => {
47
- let count = 0
48
- const stack = []
49
- const str = sourceCode.match(/[/\(|\)]/g) ?? []
50
- for (let i = 0; i < str.length; ++i) {
51
- const current = str[i]
52
- if (current === '(') stack.push(current)
53
- else if (current === ')') if (stack.pop() !== '(') ++count
54
- }
55
- return count - stack.length
56
- }
57
- export const escape = (Char) => {
58
- switch (Char) {
59
- case '\\':
60
- return '\\'
61
- case 'n':
62
- return '\n'
63
- case 'r':
64
- return '\r'
65
- case 't':
66
- return '\t'
67
- case 's':
68
- return ' '
69
- case '"':
70
- return '"'
71
- default:
72
- return ''
73
- }
74
- }
75
- export const stringifyArrayTypes = (type) =>
76
- Array.isArray(type)
77
- ? `(array${type.length ? ' ' : ''}${type
78
- .map((x) => stringifyArrayTypes(x))
79
- .join(' ')
80
- .trim()})`
81
- : 'number'
82
- export const stringifyType = (type) => {
83
- if (!isLeaf(type)) {
84
- const [car] = type
85
- if (car == undefined) return '(array)'
86
- else if (car[TYPE] === APPLY && car[VALUE] === KEYWORDS.ARRAY_TYPE)
87
- return `(array ${type
88
- .map((t) => stringifyType(t))
89
- .join(' ')
90
- .trim()})`
91
- return type
92
- .map((t) => stringifyType(t))
93
- .join(' ')
94
- .trim()
95
- } else if (type[TYPE] === ATOM) return 'number'
96
- }
97
- export const stringifyArgs = (args) =>
98
- args
99
- .map((x) =>
100
- !isLeaf(x)
101
- ? `(${stringifyArgs(x)})`
102
- : x[TYPE] === APPLY || x[TYPE] === WORD
103
- ? x[VALUE]
104
- : JSON.stringify(x[VALUE])
105
- .replace(new RegExp(/\[/g), '(')
106
- .replace(new RegExp(/\]/g), ')')
107
- .replace(new RegExp(/\,/g), ' ')
108
- .replace(new RegExp(/"/g), '')
109
- )
110
- .join(' ')
111
- export const isForbiddenVariableName = (name) => {
112
- switch (name) {
113
- case '_':
114
- case KEYWORDS.DEFINE_VARIABLE:
115
- case SUGGAR.RECURSION:
116
- case SUGGAR.CACHE:
117
- return true
118
- default:
119
- return !isNaN(name[0])
120
- }
121
- }
122
- export const isEqual = (a, b) =>
123
- +(
124
- (Array.isArray(a) &&
125
- a.length === b.length &&
126
- !a.some((_, i) => !isEqual(a.at(i), b.at(i)))) ||
127
- a === b ||
128
- 0
129
- )
130
- export const isEqualTypes = (a, b) =>
131
- (typeof a !== 'object' && typeof b !== 'object' && typeof a === typeof b) ||
132
- (Array.isArray(a) &&
133
- Array.isArray(b) &&
134
- (!a.length ||
135
- !b.length ||
136
- !(a.length > b.length ? a : b).some(
137
- (_, i, bigger) =>
138
- !isEqualTypes(
139
- bigger.at(i),
140
- (a.length > b.length ? b : a).at(
141
- i % (a.length > b.length ? b : a).length
142
- )
143
- )
144
- ))) ||
145
- false
146
- export const isPartialTypes = (a, b) =>
147
- (typeof a !== 'object' && typeof b !== 'object' && typeof a === typeof b) ||
148
- (Array.isArray(a) &&
149
- Array.isArray(b) &&
150
- (!a.length ||
151
- !b.length ||
152
- !(a.length < b.length ? a : b).some(
153
- (_, i, smaller) =>
154
- !isEqualTypes(
155
- smaller.at(i),
156
- (a.length < b.length ? b : a).at(
157
- i % (a.length < b.length ? b : a).length
158
- )
159
- )
160
- ))) ||
161
- false
162
- export const handleUnbalancedParens = (source) => {
163
- const diff = isBalancedParenthesis(removeNoCode(source))
164
- if (diff !== 0)
165
- throw new SyntaxError(
166
- `Parenthesis are unbalanced by ${diff > 0 ? '+' : ''}${diff}`
167
- )
168
- return source
169
- }
170
- export const handleUnbalancedQuotes = (source) => {
171
- const diff = (source.match(/\"/g) ?? []).length % 2
172
- if (diff !== 0) throw new SyntaxError(`Quotes are unbalanced "`)
173
- return source
174
- }
175
- export const removeMutation = (source) => source.replace(new RegExp(/!/g), 'ǃ')
176
- const isDefinition = (x) =>
177
- x[TYPE] === APPLY && x[VALUE] === KEYWORDS.DEFINE_VARIABLE
178
- const toDeps = (libs) =>
179
- libs.reduce(
180
- (a, x, i) => a.set(x.at(1)[VALUE], { value: x, index: i }),
181
- new Map()
182
- )
183
- const deepShake = (tree, deps, visited = new Set(), ignored = new Set()) => {
184
- const type = tree[TYPE]
185
- const value = tree[VALUE]
186
- if (!isLeaf(tree)) {
187
- const [car, ...rest] = tree
188
- if (car == undefined) return
189
- if (isDefinition(car)) {
190
- if (
191
- !isLeaf(rest.at(-1)) &&
192
- rest
193
- .at(-1)
194
- .some(
195
- (x) => x[TYPE] === APPLY && x[VALUE] === KEYWORDS.ANONYMOUS_FUNCTION
196
- )
197
- ) {
198
- const args = rest.at(-1).filter((x) => !isDefinition(x))
199
- const body = args.pop()
200
- // const params = new Set(args.map((x) => x[VALUE])
201
- for (const arg of args) ignored.add(arg[VALUE])
202
- deepShake(body, deps, visited, ignored)
203
- } else rest.forEach((x) => deepShake(x, deps, visited, ignored))
204
- } else tree.forEach((x) => deepShake(x, deps, visited, ignored))
205
- } else if (
206
- (type === APPLY || type === WORD) &&
207
- deps.has(value) &&
208
- !visited.has(value) &&
209
- !ignored.has(value)
210
- ) {
211
- visited.add(value)
212
- deepShake(deps.get(value).value, deps, visited, ignored)
213
- }
214
- }
215
- const extractDeps = (visited, deps) =>
216
- [...visited]
217
- .map((x) => deps.get(x))
218
- .sort((a, b) => a.index - b.index)
219
- .map((x) => x.value)
220
- const toIgnore = (ast) =>
221
- ast.filter(([x]) => isDefinition(x)).map(([_, x]) => x[VALUE])
222
- export const treeShake = (ast, libs) => {
223
- const deps = toDeps(libs)
224
- const visited = new Set()
225
- const ignored = new Set(toIgnore(ast))
226
- deepShake(ast, deps, visited, ignored)
227
- return extractDeps(visited, deps)
228
- }
229
- export const shakedList = (ast, libs) => {
230
- const deps = toDeps(libs)
231
- const visited = new Set()
232
- const ignored = new Set(toIgnore(ast))
233
- deepShake(ast, deps, visited, ignored)
234
- const out = []
235
- for (const [key] of deps) if (visited.has(key)) out.push(key)
236
- return out
237
- }
238
- export const dfs = (tree, callback) => {
239
- if (!isLeaf(tree)) for (const leaf of tree) dfs(leaf)
240
- else callback(tree)
241
- }
242
- export const deepClone = (ast) => AST.parse(AST.stringify(ast))
243
- export const interpret = (ast, keywords) =>
244
- ast.reduce((_, x) => evaluate(x, keywords), 0)
245
- export const fez = (source, options = {}) => {
246
- const env = Object.create(null)
247
- try {
248
- if (typeof source === 'string') {
249
- source = replaceQuotes(replaceStrings(source))
250
- const valid = handleUnbalancedQuotes(
251
- handleUnbalancedParens(removeNoCode(source))
252
- )
253
- const code = !options.mutation ? removeMutation(valid) : valid
254
- if (!code.length && options.throw) throw new Error('Nothing to parse!')
255
- const parsed = deSuggar(LISP.parse(code))
256
- const ast = [...treeShake(parsed, std), ...parsed]
257
- // if (options.check) typeCheck(ast)
258
- if (options.compile) {
259
- const js = Object.values(comp(deepClone(ast))).join('')
260
- return js
261
- }
262
- return run(ast, env)
263
- } else if (Array.isArray(source)) {
264
- const ast = !options.mutation
265
- ? AST.parse(AST.stringify(source).replace(new RegExp(/!/g), 'ǃ'))
266
- : source
267
- if (options.compile) {
268
- const js = Object.values(comp(deepClone(ast))).join('')
269
- return js
270
- }
271
- return run(ast, env)
272
- } else {
273
- throw new Error('Source has to be either a lisp source code or an AST')
274
- }
275
- } catch (error) {
276
- // console.log(error)
277
- const err = error.message.replace("'[object Array]'", '(array)')
278
- // .replace('object', '(array)')
279
- logError(err)
280
- if (options.throw) throw err
281
- return err
282
- }
283
- }
284
- export const compress = (source) => {
285
- let { result, occurance } = source.split('').reduce(
286
- (acc, item) => {
287
- if (item === ')') acc.occurance++
288
- else {
289
- if (acc.occurance < 3) {
290
- acc.result += ')'.repeat(acc.occurance)
291
- acc.occurance = 0
292
- } else {
293
- acc.result += '·' + acc.occurance
294
- acc.occurance = 0
295
- }
296
- acc.result += item
297
- }
298
- return acc
299
- },
300
- { result: '', occurance: 0 }
301
- )
302
- if (occurance > 0) result += '·' + occurance
303
- return result
304
- }
305
- export const decompress = (raw) => {
306
- const suffix = [...new Set(raw.match(/·+?\d+/g))]
307
- const runes = suffix.reduce(
308
- (acc, m) => acc.split(m).join(')'.repeat(parseInt(m.substring(1)))),
309
- raw
310
- )
311
- let result = ''
312
- for (const tok of runes) result += tok
313
- return result
314
- }
315
- // shake(LISP.parse(removeNoCode(source)), std)
316
- export const shake = (parsed, std) => [...treeShake(parsed, std), ...parsed]
317
- export const tree = (source, std) =>
318
- std
319
- ? shake(
320
- LISP.parse(replaceQuotes(replaceStrings(removeNoCode(source)))),
321
- std
322
- )
323
- : LISP.parse(replaceQuotes(replaceStrings(removeNoCode(source))))
324
- export const minify = (source) =>
325
- LISP.source(
326
- deSuggar(LISP.parse(replaceQuotes(replaceStrings(removeNoCode(source)))))
327
- )
328
- export const prep = (source) =>
329
- deSuggar(LISP.parse(removeNoCode(replaceQuotes(replaceStrings(source)))))
330
- export const src = (source, deps) => {
331
- source = prep(source)
332
- return LISP.source([
333
- ...treeShake(
334
- source,
335
- deps.reduce((a, b) => a.concat(b), [])
336
- ),
337
- ...source
338
- ])
339
- }
340
- export const ast = (source, deps) => {
341
- source = prep(source)
342
- return [
343
- ...treeShake(
344
- source,
345
- deps.reduce((a, b) => a.concat(b), [])
346
- ),
347
- ...source
348
- ]
349
- }
350
- export const astWithStd = (source) => {
351
- const parsed = prep(source)
352
- return [...treeShake(parsed, std), ...parsed]
353
- }
354
- export const dependencies = (source, deps) => {
355
- source = prep(source)
356
- return shakedList(
357
- source,
358
- deps.reduce((a, b) => a.concat(b), [])
359
- )
360
- }
361
- export const js = (source, deps) => {
362
- source = prep(source)
363
- const { top, program } = comp([
364
- ...treeShake(
365
- source,
366
- deps.reduce((a, b) => a.concat(b), [])
367
- ),
368
- ...source
369
- ])
370
- return `${top}${program}`
371
- }
372
-
373
- export const deSuggar = (ast) => {
374
- if (ast.length === 0) throw new SyntaxError(`No expressions to evaluate`)
375
- // for (const node of ast)
376
- // if (node[0] && node[0][TYPE] === APPLY && node[0][VALUE] === KEYWORDS.BLOCK)
377
- // throw new SyntaxError(`Top level (${KEYWORDS.BLOCK}) is not allowed`)
378
- let prev = undefined
379
- const evaluate = (exp) => {
380
- const [first, ...rest] = isLeaf(exp) ? [exp] : exp
381
- if (first != undefined) {
382
- switch (first[TYPE]) {
383
- case WORD:
384
- {
385
- switch (first[VALUE]) {
386
- case SUGGAR.POWER:
387
- throw new TypeError(
388
- `(${
389
- SUGGAR.POWER
390
- }), can't be used as a word (hint use math:power instead) (${
391
- SUGGAR.POWER
392
- } ${stringifyArgs(rest)})`
393
- )
394
- break
395
- case SUGGAR.INTEGER_DEVISION:
396
- exp.length = 0
397
- exp.push(
398
- ...[
399
- [APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
400
- [WORD, 'a'],
401
- [WORD, 'b'],
402
- [
403
- [APPLY, KEYWORDS.BITWISE_OR],
404
- [
405
- [APPLY, KEYWORDS.DIVISION],
406
- [WORD, 'a'],
407
- [WORD, 'b']
408
- ],
409
- [ATOM, 0]
410
- ]
411
- ]
412
- )
413
- break
414
- }
415
- }
416
- break
417
- case ATOM:
418
- break
419
- case APPLY:
420
- {
421
- switch (first[VALUE]) {
422
- case KEYWORDS.BLOCK:
423
- {
424
- if (
425
- prev == undefined ||
426
- (prev &&
427
- prev[TYPE] === APPLY &&
428
- prev[VALUE] !== KEYWORDS.ANONYMOUS_FUNCTION)
429
- ) {
430
- exp[0][VALUE] = KEYWORDS.CALL_FUNCTION
431
- exp[0][TYPE] = APPLY
432
- exp.length = 1
433
- exp[1] = [
434
- [APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
435
- [[APPLY, KEYWORDS.BLOCK], ...rest]
436
- ]
437
- deSuggar(exp)
438
- }
439
- }
440
- break
441
- case SUGGAR.PIPE:
442
- {
443
- if (rest.length < 1)
444
- throw new RangeError(
445
- `Invalid number of arguments to (${
446
- SUGGAR.PIPE
447
- }) (>= 1 required). (${SUGGAR.PIPE} ${stringifyArgs(
448
- rest
449
- )})`
450
- )
451
- let inp = rest[0]
452
- exp.length = 0
453
- for (let i = 1; i < rest.length; ++i) {
454
- if (!rest[i].length || rest[i][0][TYPE] !== APPLY)
455
- throw new TypeError(
456
- `Argument at position (${i}) of (${
457
- SUGGAR.PIPE
458
- }) is not an invoked (${
459
- KEYWORDS.ANONYMOUS_FUNCTION
460
- }). (${SUGGAR.PIPE} ${stringifyArgs(rest)})`
461
- )
462
- inp = [rest[i].shift(), inp, ...rest[i]]
463
- }
464
- for (let i = 0; i < inp.length; ++i) exp[i] = inp[i]
465
- deSuggar(exp)
466
- }
467
- break
468
- case SUGGAR.CONDITION:
469
- {
470
- if (rest.length < 2)
471
- throw new RangeError(
472
- `Invalid number of arguments for (${
473
- SUGGAR.CONDITION
474
- }), expected (> 2 required) but got ${rest.length} (${
475
- SUGGAR.CONDITION
476
- } ${stringifyArgs(rest)})`
477
- )
478
- if (rest.length % 2 !== 0)
479
- throw new RangeError(
480
- `Invalid number of arguments for (${
481
- SUGGAR.CONDITION
482
- }), expected even number of arguments but got ${
483
- rest.length
484
- } (${SUGGAR.CONDITION} ${stringifyArgs(rest)})`
485
- )
486
- exp.length = 0
487
- let temp = exp
488
- for (let i = 0; i < rest.length; i += 2) {
489
- if (i === rest.length - 2) {
490
- temp.push([APPLY, KEYWORDS.IF], rest[i], rest.at(-1))
491
- } else {
492
- temp.push([APPLY, KEYWORDS.IF], rest[i], rest[i + 1], [])
493
- temp = temp.at(-1)
494
- }
495
- }
496
- deSuggar(exp)
497
- }
498
- break
499
- case SUGGAR.LIST_TYPE:
500
- {
501
- exp.length = 0
502
- let temp = exp
503
- for (const item of rest) {
504
- temp.push([APPLY, KEYWORDS.ARRAY_TYPE], item, [])
505
- temp = temp.at(-1)
506
- }
507
- temp.push([APPLY, KEYWORDS.ARRAY_TYPE])
508
- deSuggar(exp)
509
- }
510
- break
511
- case SUGGAR.INTEGER_DEVISION:
512
- {
513
- if (rest.length !== 2)
514
- throw new RangeError(
515
- `Invalid number of arguments for (${
516
- SUGGAR.INTEGER_DEVISION
517
- }), expected (= 2) but got ${rest.length} (${
518
- SUGGAR.INTEGER_DEVISION
519
- } ${stringifyArgs(rest)})`
520
- )
521
- else if (rest.some((x) => x[TYPE] === APPLY))
522
- throw new TypeError(
523
- `Arguments of (${
524
- SUGGAR.INTEGER_DEVISION
525
- }), must be (or atom word) (hint use (math:floor (${
526
- KEYWORDS.DIVISION
527
- } a b)) instead) (${
528
- SUGGAR.INTEGER_DEVISION
529
- } ${stringifyArgs(rest)})`
530
- )
531
- else {
532
- exp.length = 1
533
- exp[0] = [APPLY, KEYWORDS.BITWISE_OR]
534
- exp.push([[APPLY, KEYWORDS.DIVISION], ...rest])
535
- exp.push([ATOM, 0])
536
- }
537
- }
538
- break
539
- case SUGGAR.POWER:
540
- {
541
- if (rest.length !== 2)
542
- throw new RangeError(
543
- `Invalid number of arguments for (${
544
- SUGGAR.POWER
545
- }), expected (= 2) but got ${rest.length} (${
546
- SUGGAR.POWER
547
- } ${stringifyArgs(rest)})`
548
- )
549
- const isExponentAtom = exp[1][TYPE] === ATOM
550
- const isPowerAtom = exp[2][TYPE] === ATOM
551
- const isExponentWord = exp[1][TYPE] === WORD
552
- if ((isExponentWord || isExponentAtom) && isPowerAtom) {
553
- exp[0][VALUE] = KEYWORDS.MULTIPLICATION
554
- const exponent = exp[1]
555
- const power = exp[2][VALUE]
556
- exp.length = 1
557
- if (isExponentAtom) {
558
- exp.push(exponent, [ATOM, exponent[VALUE] ** (power - 1)])
559
- } else if (isExponentWord) {
560
- exp.push(
561
- ...Array.from({ length: power })
562
- .fill(0)
563
- .map(() => [exponent[TYPE], exponent[VALUE]])
564
- )
565
- }
566
- } else
567
- throw new TypeError(
568
- `Second Arguments of (${
569
- SUGGAR.POWER
570
- }), must be (atom) (hint use math:power instead) (${
571
- SUGGAR.POWER
572
- } ${stringifyArgs(rest)})`
573
- )
574
- }
575
- break
576
- case KEYWORDS.MULTIPLICATION:
577
- if (!rest.length) {
578
- exp[0][TYPE] = ATOM
579
- exp[0][VALUE] = TRUE
580
- }
581
- break
582
- case KEYWORDS.ADDITION:
583
- case KEYWORDS.DIVISION:
584
- if (!rest.length) {
585
- exp[0][TYPE] = ATOM
586
- exp[0][VALUE] = FALSE
587
- }
588
- break
589
- case SUGGAR.UNLESS:
590
- {
591
- if (rest.length > 3 || rest.length < 2)
592
- throw new RangeError(
593
- `Invalid number of arguments for (${
594
- SUGGAR.UNLESS
595
- }), expected (or (= 3) (= 2)) but got ${rest.length} (${
596
- SUGGAR.UNLESS
597
- } ${stringifyArgs(rest)})`
598
- )
599
- exp[0][VALUE] = KEYWORDS.IF
600
- const temp = exp[2]
601
- exp[2] = exp[3] ?? [ATOM, FALSE]
602
- exp[3] = temp
603
- }
604
- deSuggar(exp)
605
- break
606
-
607
- case SUGGAR.NOT_EQUAL_1:
608
- case SUGGAR.NOT_EQUAL_2:
609
- {
610
- if (rest.length > 3 || rest.length < 2)
611
- throw new RangeError(
612
- `Invalid number of arguments for (${
613
- exp[0][1]
614
- }), expected (or (= 3) (= 2)) but got ${rest.length} (${
615
- exp[0][1]
616
- } ${stringifyArgs(rest)})`
617
- )
618
- exp[0][VALUE] = KEYWORDS.NOT
619
- exp[1] = [[APPLY, KEYWORDS.EQUAL], exp[1], exp[2]]
620
- exp.length = 2
621
- deSuggar(exp)
622
- }
623
- break
624
- }
625
- prev = first
626
- }
627
- break
628
- default:
629
- for (const e of exp) evaluate(e)
630
- break
631
- }
632
- for (const r of rest) evaluate(r)
633
- }
634
- }
635
- evaluate(ast)
636
- return ast
637
- }
1
+ import std from '../lib/baked/std.js'
2
+ import { comp } from './compiler.js'
3
+ import {
4
+ APPLY,
5
+ ATOM,
6
+ FALSE,
7
+ KEYWORDS,
8
+ SUGGAR,
9
+ TRUE,
10
+ TYPE,
11
+ VALUE,
12
+ WORD
13
+ } from './keywords.js'
14
+ import { evaluate, run } from './evaluator.js'
15
+ import { AST, isLeaf, LISP } from './parser.js'
16
+ export const logError = (error) =>
17
+ console.log('\x1b[31m', `\n${error}\n`, '\x1b[0m')
18
+ export const logSuccess = (output) => console.log(output, '\x1b[0m')
19
+ export const replaceStrings = (source) => {
20
+ // const quotes = source.match(/"(.*?)"/g)
21
+ const quotes = source.match(/"(?:.*?(\n|\r))*?.*?"/g)
22
+ // TODO handle escaping
23
+ if (quotes)
24
+ for (const q of quotes)
25
+ source = source.replaceAll(
26
+ q,
27
+ `(array ${[...q.replaceAll('\r', '')]
28
+ .slice(1, -1)
29
+ .map((x) => x.charCodeAt(0))
30
+ .join(' ')})`
31
+ )
32
+ return source
33
+ }
34
+ export const replaceQuotes = (source) =>
35
+ source
36
+ .replaceAll(/\'\(/g, '(array ')
37
+ .replaceAll(/\`\(/g, '(list ')
38
+ .replaceAll(/\(\)/g, '(array)')
39
+ // export const replaceEmptyArrays = (source) =>
40
+ // source
41
+ export const removeNoCode = (source) =>
42
+ source
43
+ .replace(/;.+/g, '')
44
+ .replace(/[\s\s]/g, ' ')
45
+ .trim()
46
+ export const isBalancedParenthesis = (sourceCode) => {
47
+ let count = 0
48
+ const stack = []
49
+ const str = sourceCode.match(/[/\(|\)]/g) ?? []
50
+ for (let i = 0; i < str.length; ++i) {
51
+ const current = str[i]
52
+ if (current === '(') stack.push(current)
53
+ else if (current === ')') if (stack.pop() !== '(') ++count
54
+ }
55
+ return count - stack.length
56
+ }
57
+ export const escape = (Char) => {
58
+ switch (Char) {
59
+ case '\\':
60
+ return '\\'
61
+ case 'n':
62
+ return '\n'
63
+ case 'r':
64
+ return '\r'
65
+ case 't':
66
+ return '\t'
67
+ case 's':
68
+ return ' '
69
+ case '"':
70
+ return '"'
71
+ default:
72
+ return ''
73
+ }
74
+ }
75
+ export const stringifyArrayTypes = (type) =>
76
+ Array.isArray(type)
77
+ ? `(array${type.length ? ' ' : ''}${type
78
+ .map((x) => stringifyArrayTypes(x))
79
+ .join(' ')
80
+ .trim()})`
81
+ : 'number'
82
+ export const stringifyType = (type) => {
83
+ if (!isLeaf(type)) {
84
+ const [car] = type
85
+ if (car == undefined) return '(array)'
86
+ else if (car[TYPE] === APPLY && car[VALUE] === KEYWORDS.ARRAY_TYPE)
87
+ return `(array ${type
88
+ .map((t) => stringifyType(t))
89
+ .join(' ')
90
+ .trim()})`
91
+ return type
92
+ .map((t) => stringifyType(t))
93
+ .join(' ')
94
+ .trim()
95
+ } else if (type[TYPE] === ATOM) return 'number'
96
+ }
97
+ export const stringifyArgs = (args) =>
98
+ args
99
+ .map((x) =>
100
+ !isLeaf(x)
101
+ ? `(${stringifyArgs(x)})`
102
+ : x[TYPE] === APPLY || x[TYPE] === WORD
103
+ ? x[VALUE]
104
+ : JSON.stringify(x[VALUE])
105
+ .replace(new RegExp(/\[/g), '(')
106
+ .replace(new RegExp(/\]/g), ')')
107
+ .replace(new RegExp(/\,/g), ' ')
108
+ .replace(new RegExp(/"/g), '')
109
+ )
110
+ .join(' ')
111
+ export const isForbiddenVariableName = (name) => {
112
+ switch (name) {
113
+ case '_':
114
+ case KEYWORDS.DEFINE_VARIABLE:
115
+ case SUGGAR.RECURSION:
116
+ case SUGGAR.CACHE:
117
+ return true
118
+ default:
119
+ return !isNaN(name[0])
120
+ }
121
+ }
122
+ export const isEqual = (a, b) =>
123
+ +(
124
+ (Array.isArray(a) &&
125
+ a.length === b.length &&
126
+ !a.some((_, i) => !isEqual(a.at(i), b.at(i)))) ||
127
+ a === b ||
128
+ 0
129
+ )
130
+ export const isEqualTypes = (a, b) =>
131
+ (typeof a !== 'object' && typeof b !== 'object' && typeof a === typeof b) ||
132
+ (Array.isArray(a) &&
133
+ Array.isArray(b) &&
134
+ (!a.length ||
135
+ !b.length ||
136
+ !(a.length > b.length ? a : b).some(
137
+ (_, i, bigger) =>
138
+ !isEqualTypes(
139
+ bigger.at(i),
140
+ (a.length > b.length ? b : a).at(
141
+ i % (a.length > b.length ? b : a).length
142
+ )
143
+ )
144
+ ))) ||
145
+ false
146
+ export const isPartialTypes = (a, b) =>
147
+ (typeof a !== 'object' && typeof b !== 'object' && typeof a === typeof b) ||
148
+ (Array.isArray(a) &&
149
+ Array.isArray(b) &&
150
+ (!a.length ||
151
+ !b.length ||
152
+ !(a.length < b.length ? a : b).some(
153
+ (_, i, smaller) =>
154
+ !isEqualTypes(
155
+ smaller.at(i),
156
+ (a.length < b.length ? b : a).at(
157
+ i % (a.length < b.length ? b : a).length
158
+ )
159
+ )
160
+ ))) ||
161
+ false
162
+ export const handleUnbalancedParens = (source) => {
163
+ const diff = isBalancedParenthesis(removeNoCode(source))
164
+ if (diff !== 0)
165
+ throw new SyntaxError(
166
+ `Parenthesis are unbalanced by ${diff > 0 ? '+' : ''}${diff}`
167
+ )
168
+ return source
169
+ }
170
+ export const handleUnbalancedQuotes = (source) => {
171
+ const diff = (source.match(/\"/g) ?? []).length % 2
172
+ if (diff !== 0) throw new SyntaxError(`Quotes are unbalanced "`)
173
+ return source
174
+ }
175
+ export const removeMutation = (source) => source.replace(new RegExp(/!/g), 'ǃ')
176
+ const isDefinition = (x) =>
177
+ x[TYPE] === APPLY && x[VALUE] === KEYWORDS.DEFINE_VARIABLE
178
+ const toDeps = (libs) =>
179
+ libs.reduce(
180
+ (a, x, i) => a.set(x.at(1)[VALUE], { value: x, index: i }),
181
+ new Map()
182
+ )
183
+ const deepShake = (tree, deps, visited = new Set(), ignored = new Set()) => {
184
+ const type = tree[TYPE]
185
+ const value = tree[VALUE]
186
+ if (!isLeaf(tree)) {
187
+ const [car, ...rest] = tree
188
+ if (car == undefined) return
189
+ if (isDefinition(car)) {
190
+ if (
191
+ !isLeaf(rest.at(-1)) &&
192
+ rest
193
+ .at(-1)
194
+ .some(
195
+ (x) => x[TYPE] === APPLY && x[VALUE] === KEYWORDS.ANONYMOUS_FUNCTION
196
+ )
197
+ ) {
198
+ const args = rest.at(-1).filter((x) => !isDefinition(x))
199
+ const body = args.pop()
200
+ // const params = new Set(args.map((x) => x[VALUE])
201
+ for (const arg of args) ignored.add(arg[VALUE])
202
+ deepShake(body, deps, visited, ignored)
203
+ } else rest.forEach((x) => deepShake(x, deps, visited, ignored))
204
+ } else tree.forEach((x) => deepShake(x, deps, visited, ignored))
205
+ } else if (
206
+ (type === APPLY || type === WORD) &&
207
+ deps.has(value) &&
208
+ !visited.has(value) &&
209
+ !ignored.has(value)
210
+ ) {
211
+ visited.add(value)
212
+ deepShake(deps.get(value).value, deps, visited, ignored)
213
+ }
214
+ }
215
+ const extractDeps = (visited, deps) =>
216
+ [...visited]
217
+ .map((x) => deps.get(x))
218
+ .sort((a, b) => a.index - b.index)
219
+ .map((x) => x.value)
220
+ const toIgnore = (ast) =>
221
+ ast.filter(([x]) => isDefinition(x)).map(([_, x]) => x[VALUE])
222
+ export const treeShake = (ast, libs) => {
223
+ const deps = toDeps(libs)
224
+ const visited = new Set()
225
+ const ignored = new Set(toIgnore(ast))
226
+ deepShake(ast, deps, visited, ignored)
227
+ return extractDeps(visited, deps)
228
+ }
229
+ export const shakedList = (ast, libs) => {
230
+ const deps = toDeps(libs)
231
+ const visited = new Set()
232
+ const ignored = new Set(toIgnore(ast))
233
+ deepShake(ast, deps, visited, ignored)
234
+ const out = []
235
+ for (const [key] of deps) if (visited.has(key)) out.push(key)
236
+ return out
237
+ }
238
+ export const dfs = (tree, callback) => {
239
+ if (!isLeaf(tree)) for (const leaf of tree) dfs(leaf)
240
+ else callback(tree)
241
+ }
242
+ export const interpret = (ast, keywords) =>
243
+ ast.reduce((_, x) => evaluate(x, keywords), 0)
244
+ export const fez = (source, options = {}) => {
245
+ const env = Object.create(null)
246
+ try {
247
+ if (typeof source === 'string') {
248
+ source = replaceQuotes(replaceStrings(source))
249
+ const valid = handleUnbalancedQuotes(
250
+ handleUnbalancedParens(removeNoCode(source))
251
+ )
252
+ const code = !options.mutation ? removeMutation(valid) : valid
253
+ if (!code.length && options.throw) throw new Error('Nothing to parse!')
254
+ const parsed = deSuggar(LISP.parse(code))
255
+ const ast = [...treeShake(parsed, std), ...parsed]
256
+ // if (options.check) typeCheck(ast)
257
+ if (options.compile) return comp(ast)
258
+ return run(ast, env)
259
+ } else if (Array.isArray(source)) {
260
+ const ast = !options.mutation
261
+ ? AST.parse(AST.stringify(source).replace(new RegExp(/!/g), 'ǃ'))
262
+ : source
263
+ if (options.compile) return comp(ast)
264
+ return run(ast, env)
265
+ } else {
266
+ throw new Error('Source has to be either a lisp source code or an AST')
267
+ }
268
+ } catch (error) {
269
+ // console.log(error)
270
+ const err = error.message.replace("'[object Array]'", '(array)')
271
+ // .replace('object', '(array)')
272
+ logError(err)
273
+ if (options.throw) throw err
274
+ return err
275
+ }
276
+ }
277
+ export const compress = (source) => {
278
+ let { result, occurance } = source.split('').reduce(
279
+ (acc, item) => {
280
+ if (item === ')') acc.occurance++
281
+ else {
282
+ if (acc.occurance < 3) {
283
+ acc.result += ')'.repeat(acc.occurance)
284
+ acc.occurance = 0
285
+ } else {
286
+ acc.result += '·' + acc.occurance
287
+ acc.occurance = 0
288
+ }
289
+ acc.result += item
290
+ }
291
+ return acc
292
+ },
293
+ { result: '', occurance: 0 }
294
+ )
295
+ if (occurance > 0) result += '·' + occurance
296
+ return result
297
+ }
298
+ export const decompress = (raw) => {
299
+ const suffix = [...new Set(raw.match(/·+?\d+/g))]
300
+ const runes = suffix.reduce(
301
+ (acc, m) => acc.split(m).join(')'.repeat(parseInt(m.substring(1)))),
302
+ raw
303
+ )
304
+ let result = ''
305
+ for (const tok of runes) result += tok
306
+ return result
307
+ }
308
+ // shake(LISP.parse(removeNoCode(source)), std)
309
+ export const shake = (parsed, std) => [...treeShake(parsed, std), ...parsed]
310
+ export const tree = (source, std) =>
311
+ std
312
+ ? shake(
313
+ LISP.parse(replaceQuotes(replaceStrings(removeNoCode(source)))),
314
+ std
315
+ )
316
+ : LISP.parse(replaceQuotes(replaceStrings(removeNoCode(source))))
317
+ export const minify = (source) =>
318
+ LISP.source(
319
+ deSuggar(LISP.parse(replaceQuotes(replaceStrings(removeNoCode(source)))))
320
+ )
321
+ export const prep = (source) =>
322
+ deSuggar(LISP.parse(removeNoCode(replaceQuotes(replaceStrings(source)))))
323
+ export const src = (source, deps) => {
324
+ source = prep(source)
325
+ return LISP.source([
326
+ ...treeShake(
327
+ source,
328
+ deps.reduce((a, b) => a.concat(b), [])
329
+ ),
330
+ ...source
331
+ ])
332
+ }
333
+ export const ast = (source, deps) => {
334
+ source = prep(source)
335
+ return [
336
+ ...treeShake(
337
+ source,
338
+ deps.reduce((a, b) => a.concat(b), [])
339
+ ),
340
+ ...source
341
+ ]
342
+ }
343
+ export const astWithStd = (source) => {
344
+ const parsed = prep(source)
345
+ return [...treeShake(parsed, std), ...parsed]
346
+ }
347
+ export const dependencies = (source, deps) => {
348
+ source = prep(source)
349
+ return shakedList(
350
+ source,
351
+ deps.reduce((a, b) => a.concat(b), [])
352
+ )
353
+ }
354
+ export const js = (source, deps) => {
355
+ source = prep(source)
356
+ const { top, program } = comp([
357
+ ...treeShake(
358
+ source,
359
+ deps.reduce((a, b) => a.concat(b), [])
360
+ ),
361
+ ...source
362
+ ])
363
+ return `${top}${program}`
364
+ }
365
+ const EXPONENTIATION = [
366
+ [0, 'lambda'],
367
+ [1, 'base'],
368
+ [1, 'exp'],
369
+ [
370
+ [0, 'do'],
371
+ [
372
+ [0, 'let'],
373
+ [1, 'power'],
374
+ [
375
+ [0, 'lambda'],
376
+ [1, 'base'],
377
+ [1, 'exp'],
378
+ [
379
+ [0, 'if'],
380
+ [
381
+ [0, '<'],
382
+ [1, 'exp'],
383
+ [2, 0]
384
+ ],
385
+ [
386
+ [0, 'if'],
387
+ [
388
+ [0, '='],
389
+ [1, 'base'],
390
+ [2, 0]
391
+ ],
392
+ [
393
+ [0, 'throw'],
394
+ [
395
+ [0, 'array'],
396
+ [2, 66],
397
+ [2, 97],
398
+ [2, 115],
399
+ [2, 101],
400
+ [2, 32],
401
+ [2, 99],
402
+ [2, 97],
403
+ [2, 110],
404
+ [2, 39],
405
+ [2, 116],
406
+ [2, 32],
407
+ [2, 98],
408
+ [2, 101],
409
+ [2, 32],
410
+ [2, 48],
411
+ [2, 32],
412
+ [2, 105],
413
+ [2, 102],
414
+ [2, 32],
415
+ [2, 101],
416
+ [2, 120],
417
+ [2, 112],
418
+ [2, 111],
419
+ [2, 110],
420
+ [2, 101],
421
+ [2, 110],
422
+ [2, 116],
423
+ [2, 32],
424
+ [2, 105],
425
+ [2, 115],
426
+ [2, 32],
427
+ [2, 60],
428
+ [2, 32],
429
+ [2, 48]
430
+ ]
431
+ ],
432
+ [
433
+ [0, '/'],
434
+ [
435
+ [0, '*'],
436
+ [1, 'base'],
437
+ [
438
+ [0, 'power'],
439
+ [1, 'base'],
440
+ [
441
+ [0, '-'],
442
+ [
443
+ [0, '*'],
444
+ [1, 'exp'],
445
+ [2, -1]
446
+ ],
447
+ [2, 1]
448
+ ]
449
+ ]
450
+ ]
451
+ ]
452
+ ],
453
+ [
454
+ [0, 'if'],
455
+ [
456
+ [0, '='],
457
+ [1, 'exp'],
458
+ [2, 0]
459
+ ],
460
+ [2, 1],
461
+ [
462
+ [0, 'if'],
463
+ [
464
+ [0, '='],
465
+ [1, 'exp'],
466
+ [2, 1]
467
+ ],
468
+ [1, 'base'],
469
+ [
470
+ [0, 'if'],
471
+ [[2, 1]],
472
+ [
473
+ [0, '*'],
474
+ [1, 'base'],
475
+ [
476
+ [0, 'power'],
477
+ [1, 'base'],
478
+ [
479
+ [0, '-'],
480
+ [1, 'exp'],
481
+ [2, 1]
482
+ ]
483
+ ]
484
+ ]
485
+ ]
486
+ ]
487
+ ]
488
+ ]
489
+ ]
490
+ ],
491
+ [
492
+ [0, 'power'],
493
+ [1, 'base'],
494
+ [1, 'exp']
495
+ ]
496
+ ]
497
+ ]
498
+ export const deSuggar = (ast) => {
499
+ if (ast.length === 0) throw new SyntaxError(`No expressions to evaluate`)
500
+ // for (const node of ast)
501
+ // if (node[0] && node[0][TYPE] === APPLY && node[0][VALUE] === KEYWORDS.BLOCK)
502
+ // throw new SyntaxError(`Top level (${KEYWORDS.BLOCK}) is not allowed`)
503
+ let prev = undefined
504
+ const evaluate = (exp) => {
505
+ const [first, ...rest] = isLeaf(exp) ? [exp] : exp
506
+ if (first != undefined) {
507
+ switch (first[TYPE]) {
508
+ case WORD:
509
+ {
510
+ switch (first[VALUE]) {
511
+ case SUGGAR.POWER:
512
+ exp.length = 0
513
+ exp.push(...EXPONENTIATION)
514
+ break
515
+ case SUGGAR.INTEGER_DEVISION:
516
+ exp.length = 0
517
+ exp.push(
518
+ ...[
519
+ [APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
520
+ [WORD, 'a'],
521
+ [WORD, 'b'],
522
+ [
523
+ [APPLY, KEYWORDS.BITWISE_OR],
524
+ [
525
+ [APPLY, KEYWORDS.DIVISION],
526
+ [WORD, 'a'],
527
+ [WORD, 'b']
528
+ ],
529
+ [ATOM, 0]
530
+ ]
531
+ ]
532
+ )
533
+ break
534
+ }
535
+ }
536
+ break
537
+ case ATOM:
538
+ break
539
+ case APPLY:
540
+ {
541
+ switch (first[VALUE]) {
542
+ case KEYWORDS.BLOCK:
543
+ {
544
+ if (
545
+ prev == undefined ||
546
+ (prev &&
547
+ prev[TYPE] === APPLY &&
548
+ prev[VALUE] !== KEYWORDS.ANONYMOUS_FUNCTION)
549
+ ) {
550
+ exp[0][VALUE] = KEYWORDS.CALL_FUNCTION
551
+ exp[0][TYPE] = APPLY
552
+ exp.length = 1
553
+ exp[1] = [
554
+ [APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
555
+ [[APPLY, KEYWORDS.BLOCK], ...rest]
556
+ ]
557
+ deSuggar(exp)
558
+ }
559
+ }
560
+ break
561
+ case SUGGAR.PIPE:
562
+ {
563
+ if (rest.length < 1)
564
+ throw new RangeError(
565
+ `Invalid number of arguments to (${
566
+ SUGGAR.PIPE
567
+ }) (>= 1 required). (${SUGGAR.PIPE} ${stringifyArgs(
568
+ rest
569
+ )})`
570
+ )
571
+ let inp = rest[0]
572
+ exp.length = 0
573
+ for (let i = 1; i < rest.length; ++i) {
574
+ if (!rest[i].length || rest[i][0][TYPE] !== APPLY)
575
+ throw new TypeError(
576
+ `Argument at position (${i}) of (${
577
+ SUGGAR.PIPE
578
+ }) is not an invoked (${
579
+ KEYWORDS.ANONYMOUS_FUNCTION
580
+ }). (${SUGGAR.PIPE} ${stringifyArgs(rest)})`
581
+ )
582
+ inp = [rest[i].shift(), inp, ...rest[i]]
583
+ }
584
+ for (let i = 0; i < inp.length; ++i) exp[i] = inp[i]
585
+ deSuggar(exp)
586
+ }
587
+ break
588
+ case SUGGAR.CONDITION:
589
+ {
590
+ if (rest.length < 2)
591
+ throw new RangeError(
592
+ `Invalid number of arguments for (${
593
+ SUGGAR.CONDITION
594
+ }), expected (> 2 required) but got ${rest.length} (${
595
+ SUGGAR.CONDITION
596
+ } ${stringifyArgs(rest)})`
597
+ )
598
+ if (rest.length % 2 !== 0)
599
+ throw new RangeError(
600
+ `Invalid number of arguments for (${
601
+ SUGGAR.CONDITION
602
+ }), expected even number of arguments but got ${
603
+ rest.length
604
+ } (${SUGGAR.CONDITION} ${stringifyArgs(rest)})`
605
+ )
606
+ exp.length = 0
607
+ let temp = exp
608
+ for (let i = 0; i < rest.length; i += 2) {
609
+ if (i === rest.length - 2) {
610
+ temp.push([APPLY, KEYWORDS.IF], rest[i], rest.at(-1))
611
+ } else {
612
+ temp.push([APPLY, KEYWORDS.IF], rest[i], rest[i + 1], [])
613
+ temp = temp.at(-1)
614
+ }
615
+ }
616
+ deSuggar(exp)
617
+ }
618
+ break
619
+ case SUGGAR.LIST_TYPE:
620
+ {
621
+ exp.length = 0
622
+ let temp = exp
623
+ for (const item of rest) {
624
+ temp.push([APPLY, KEYWORDS.ARRAY_TYPE], item, [])
625
+ temp = temp.at(-1)
626
+ }
627
+ temp.push([APPLY, KEYWORDS.ARRAY_TYPE])
628
+ }
629
+ deSuggar(exp)
630
+ break
631
+ case SUGGAR.INTEGER_DEVISION:
632
+ {
633
+ if (rest.length !== 2)
634
+ throw new RangeError(
635
+ `Invalid number of arguments for (${
636
+ SUGGAR.INTEGER_DEVISION
637
+ }), expected (= 2) but got ${rest.length} (${
638
+ SUGGAR.INTEGER_DEVISION
639
+ } ${stringifyArgs(rest)})`
640
+ )
641
+ else if (rest.some((x) => x[TYPE] === APPLY))
642
+ throw new TypeError(
643
+ `Arguments of (${
644
+ SUGGAR.INTEGER_DEVISION
645
+ }), must be (or atom word) (hint use (math:floor (${
646
+ KEYWORDS.DIVISION
647
+ } a b)) instead) (${
648
+ SUGGAR.INTEGER_DEVISION
649
+ } ${stringifyArgs(rest)})`
650
+ )
651
+ else {
652
+ exp.length = 1
653
+ exp[0] = [APPLY, KEYWORDS.BITWISE_OR]
654
+ exp.push([[APPLY, KEYWORDS.DIVISION], ...rest])
655
+ exp.push([ATOM, 0])
656
+ }
657
+ }
658
+ break
659
+ case SUGGAR.POWER:
660
+ {
661
+ if (rest.length !== 2)
662
+ throw new RangeError(
663
+ `Invalid number of arguments for (${
664
+ SUGGAR.POWER
665
+ }), expected (= 2) but got ${rest.length} (${
666
+ SUGGAR.POWER
667
+ } ${stringifyArgs(rest)})`
668
+ )
669
+ const isExponentAtom = exp[1][TYPE] === ATOM
670
+ const isPowerAtom = exp[2][TYPE] === ATOM
671
+ const isExponentWord = exp[1][TYPE] === WORD
672
+ if ((isExponentWord || isExponentAtom) && isPowerAtom) {
673
+ if (isExponentAtom) {
674
+ exp[0][VALUE] = KEYWORDS.MULTIPLICATION
675
+ const exponent = exp[1]
676
+ const power = exp[2][VALUE]
677
+ exp.length = 1
678
+ exp.push(exponent, [ATOM, exponent[VALUE] ** (power - 1)])
679
+ } else if (isExponentWord) {
680
+ const exponent = exp[1]
681
+ const power = exp[2]
682
+ exp.length = 0
683
+ exp.push([0, 'apply'], EXPONENTIATION, exponent, power)
684
+ }
685
+ } else {
686
+ const exponent = exp[1]
687
+ const power = exp[2]
688
+ exp.length = 0
689
+ exp.push([0, 'apply'], EXPONENTIATION, exponent, power)
690
+ }
691
+ deSuggar(exp)
692
+ }
693
+ break
694
+ case KEYWORDS.MULTIPLICATION:
695
+ if (!rest.length) {
696
+ exp[0][TYPE] = ATOM
697
+ exp[0][VALUE] = TRUE
698
+ } else if (rest.length > 2) {
699
+ exp.length = 0
700
+ let temp = exp
701
+ for (let i = 0; i < rest.length; i += 1) {
702
+ if (i < rest.length - 1) {
703
+ temp.push([APPLY, KEYWORDS.MULTIPLICATION], rest[i], [])
704
+ temp = temp.at(-1)
705
+ } else {
706
+ temp.push(...rest[i])
707
+ }
708
+ }
709
+ deSuggar(exp)
710
+ }
711
+ break
712
+ case KEYWORDS.ADDITION:
713
+ if (!rest.length) {
714
+ exp[0][TYPE] = ATOM
715
+ exp[0][VALUE] = FALSE
716
+ } else if (rest.length > 2) {
717
+ exp.length = 0
718
+ let temp = exp
719
+ for (let i = 0; i < rest.length; i += 1) {
720
+ if (i < rest.length - 1) {
721
+ temp.push([APPLY, KEYWORDS.ADDITION], rest[i], [])
722
+ temp = temp.at(-1)
723
+ } else {
724
+ temp.push(...rest[i])
725
+ }
726
+ }
727
+ deSuggar(exp)
728
+ }
729
+ break
730
+ case KEYWORDS.DIVISION:
731
+ if (!rest.length) {
732
+ exp[0][TYPE] = ATOM
733
+ exp[0][VALUE] = FALSE
734
+ } else if (rest.length > 2) {
735
+ exp.length = 0
736
+ let temp = exp
737
+ for (let i = 0; i < rest.length; i += 1) {
738
+ if (i < rest.length - 1) {
739
+ temp.push([APPLY, KEYWORDS.DIVISION], rest[i], [])
740
+ temp = temp.at(-1)
741
+ } else {
742
+ temp.push(...rest[i])
743
+ }
744
+ }
745
+ deSuggar(exp)
746
+ }
747
+ break
748
+ case KEYWORDS.AND:
749
+ if (!rest.length) {
750
+ exp[0][TYPE] = ATOM
751
+ exp[0][VALUE] = FALSE
752
+ } else if (rest.length > 2) {
753
+ exp.length = 0
754
+ let temp = exp
755
+ for (let i = 0; i < rest.length; i += 1) {
756
+ if (i < rest.length - 1) {
757
+ temp.push([APPLY, KEYWORDS.AND], rest[i], [])
758
+ temp = temp.at(-1)
759
+ } else {
760
+ temp.push(...rest[i])
761
+ }
762
+ }
763
+ deSuggar(exp)
764
+ }
765
+ break
766
+ case KEYWORDS.OR:
767
+ if (!rest.length) {
768
+ exp[0][TYPE] = ATOM
769
+ exp[0][VALUE] = FALSE
770
+ } else if (rest.length > 2) {
771
+ exp.length = 0
772
+ let temp = exp
773
+ for (let i = 0; i < rest.length; i += 1) {
774
+ if (i < rest.length - 1) {
775
+ temp.push([APPLY, KEYWORDS.OR], rest[i], [])
776
+ temp = temp.at(-1)
777
+ } else {
778
+ temp.push(...rest[i])
779
+ }
780
+ }
781
+ deSuggar(exp)
782
+ }
783
+ break
784
+ case SUGGAR.UNLESS:
785
+ {
786
+ if (rest.length > 3 || rest.length < 2)
787
+ throw new RangeError(
788
+ `Invalid number of arguments for (${
789
+ SUGGAR.UNLESS
790
+ }), expected (or (= 3) (= 2)) but got ${rest.length} (${
791
+ SUGGAR.UNLESS
792
+ } ${stringifyArgs(rest)})`
793
+ )
794
+ exp[0][VALUE] = KEYWORDS.IF
795
+ const temp = exp[2]
796
+ exp[2] = exp[3] ?? [ATOM, FALSE]
797
+ exp[3] = temp
798
+ }
799
+ deSuggar(exp)
800
+ break
801
+
802
+ case SUGGAR.NOT_EQUAL_1:
803
+ case SUGGAR.NOT_EQUAL_2:
804
+ {
805
+ if (rest.length > 3 || rest.length < 2)
806
+ throw new RangeError(
807
+ `Invalid number of arguments for (${
808
+ exp[0][1]
809
+ }), expected (or (= 3) (= 2)) but got ${rest.length} (${
810
+ exp[0][1]
811
+ } ${stringifyArgs(rest)})`
812
+ )
813
+ exp[0][VALUE] = KEYWORDS.NOT
814
+ exp[1] = [[APPLY, KEYWORDS.EQUAL], exp[1], exp[2]]
815
+ exp.length = 2
816
+ deSuggar(exp)
817
+ }
818
+ break
819
+ }
820
+ prev = first
821
+ }
822
+ break
823
+ default:
824
+ for (const e of exp) evaluate(e)
825
+ break
826
+ }
827
+ for (const r of rest) evaluate(r)
828
+ }
829
+ }
830
+ evaluate(ast)
831
+ return ast
832
+ }