fez-lisp 1.2.62 → 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,630 +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 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
-
366
- export const deSuggar = (ast) => {
367
- if (ast.length === 0) throw new SyntaxError(`No expressions to evaluate`)
368
- // for (const node of ast)
369
- // if (node[0] && node[0][TYPE] === APPLY && node[0][VALUE] === KEYWORDS.BLOCK)
370
- // throw new SyntaxError(`Top level (${KEYWORDS.BLOCK}) is not allowed`)
371
- let prev = undefined
372
- const evaluate = (exp) => {
373
- const [first, ...rest] = isLeaf(exp) ? [exp] : exp
374
- if (first != undefined) {
375
- switch (first[TYPE]) {
376
- case WORD:
377
- {
378
- switch (first[VALUE]) {
379
- case SUGGAR.POWER:
380
- throw new TypeError(
381
- `(${
382
- SUGGAR.POWER
383
- }), can't be used as a word (hint use math:power instead) (${
384
- SUGGAR.POWER
385
- } ${stringifyArgs(rest)})`
386
- )
387
- break
388
- case SUGGAR.INTEGER_DEVISION:
389
- exp.length = 0
390
- exp.push(
391
- ...[
392
- [APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
393
- [WORD, 'a'],
394
- [WORD, 'b'],
395
- [
396
- [APPLY, KEYWORDS.BITWISE_OR],
397
- [
398
- [APPLY, KEYWORDS.DIVISION],
399
- [WORD, 'a'],
400
- [WORD, 'b']
401
- ],
402
- [ATOM, 0]
403
- ]
404
- ]
405
- )
406
- break
407
- }
408
- }
409
- break
410
- case ATOM:
411
- break
412
- case APPLY:
413
- {
414
- switch (first[VALUE]) {
415
- case KEYWORDS.BLOCK:
416
- {
417
- if (
418
- prev == undefined ||
419
- (prev &&
420
- prev[TYPE] === APPLY &&
421
- prev[VALUE] !== KEYWORDS.ANONYMOUS_FUNCTION)
422
- ) {
423
- exp[0][VALUE] = KEYWORDS.CALL_FUNCTION
424
- exp[0][TYPE] = APPLY
425
- exp.length = 1
426
- exp[1] = [
427
- [APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
428
- [[APPLY, KEYWORDS.BLOCK], ...rest]
429
- ]
430
- deSuggar(exp)
431
- }
432
- }
433
- break
434
- case SUGGAR.PIPE:
435
- {
436
- if (rest.length < 1)
437
- throw new RangeError(
438
- `Invalid number of arguments to (${
439
- SUGGAR.PIPE
440
- }) (>= 1 required). (${SUGGAR.PIPE} ${stringifyArgs(
441
- rest
442
- )})`
443
- )
444
- let inp = rest[0]
445
- exp.length = 0
446
- for (let i = 1; i < rest.length; ++i) {
447
- if (!rest[i].length || rest[i][0][TYPE] !== APPLY)
448
- throw new TypeError(
449
- `Argument at position (${i}) of (${
450
- SUGGAR.PIPE
451
- }) is not an invoked (${
452
- KEYWORDS.ANONYMOUS_FUNCTION
453
- }). (${SUGGAR.PIPE} ${stringifyArgs(rest)})`
454
- )
455
- inp = [rest[i].shift(), inp, ...rest[i]]
456
- }
457
- for (let i = 0; i < inp.length; ++i) exp[i] = inp[i]
458
- deSuggar(exp)
459
- }
460
- break
461
- case SUGGAR.CONDITION:
462
- {
463
- if (rest.length < 2)
464
- throw new RangeError(
465
- `Invalid number of arguments for (${
466
- SUGGAR.CONDITION
467
- }), expected (> 2 required) but got ${rest.length} (${
468
- SUGGAR.CONDITION
469
- } ${stringifyArgs(rest)})`
470
- )
471
- if (rest.length % 2 !== 0)
472
- throw new RangeError(
473
- `Invalid number of arguments for (${
474
- SUGGAR.CONDITION
475
- }), expected even number of arguments but got ${
476
- rest.length
477
- } (${SUGGAR.CONDITION} ${stringifyArgs(rest)})`
478
- )
479
- exp.length = 0
480
- let temp = exp
481
- for (let i = 0; i < rest.length; i += 2) {
482
- if (i === rest.length - 2) {
483
- temp.push([APPLY, KEYWORDS.IF], rest[i], rest.at(-1))
484
- } else {
485
- temp.push([APPLY, KEYWORDS.IF], rest[i], rest[i + 1], [])
486
- temp = temp.at(-1)
487
- }
488
- }
489
- deSuggar(exp)
490
- }
491
- break
492
- case SUGGAR.LIST_TYPE:
493
- {
494
- exp.length = 0
495
- let temp = exp
496
- for (const item of rest) {
497
- temp.push([APPLY, KEYWORDS.ARRAY_TYPE], item, [])
498
- temp = temp.at(-1)
499
- }
500
- temp.push([APPLY, KEYWORDS.ARRAY_TYPE])
501
- deSuggar(exp)
502
- }
503
- break
504
- case SUGGAR.INTEGER_DEVISION:
505
- {
506
- if (rest.length !== 2)
507
- throw new RangeError(
508
- `Invalid number of arguments for (${
509
- SUGGAR.INTEGER_DEVISION
510
- }), expected (= 2) but got ${rest.length} (${
511
- SUGGAR.INTEGER_DEVISION
512
- } ${stringifyArgs(rest)})`
513
- )
514
- else if (rest.some((x) => x[TYPE] === APPLY))
515
- throw new TypeError(
516
- `Arguments of (${
517
- SUGGAR.INTEGER_DEVISION
518
- }), must be (or atom word) (hint use (math:floor (${
519
- KEYWORDS.DIVISION
520
- } a b)) instead) (${
521
- SUGGAR.INTEGER_DEVISION
522
- } ${stringifyArgs(rest)})`
523
- )
524
- else {
525
- exp.length = 1
526
- exp[0] = [APPLY, KEYWORDS.BITWISE_OR]
527
- exp.push([[APPLY, KEYWORDS.DIVISION], ...rest])
528
- exp.push([ATOM, 0])
529
- }
530
- }
531
- break
532
- case SUGGAR.POWER:
533
- {
534
- if (rest.length !== 2)
535
- throw new RangeError(
536
- `Invalid number of arguments for (${
537
- SUGGAR.POWER
538
- }), expected (= 2) but got ${rest.length} (${
539
- SUGGAR.POWER
540
- } ${stringifyArgs(rest)})`
541
- )
542
- const isExponentAtom = exp[1][TYPE] === ATOM
543
- const isPowerAtom = exp[2][TYPE] === ATOM
544
- const isExponentWord = exp[1][TYPE] === WORD
545
- if ((isExponentWord || isExponentAtom) && isPowerAtom) {
546
- exp[0][VALUE] = KEYWORDS.MULTIPLICATION
547
- const exponent = exp[1]
548
- const power = exp[2][VALUE]
549
- exp.length = 1
550
- if (isExponentAtom) {
551
- exp.push(exponent, [ATOM, exponent[VALUE] ** (power - 1)])
552
- } else if (isExponentWord) {
553
- exp.push(
554
- ...Array.from({ length: power })
555
- .fill(0)
556
- .map(() => [exponent[TYPE], exponent[VALUE]])
557
- )
558
- }
559
- } else
560
- throw new TypeError(
561
- `Second Arguments of (${
562
- SUGGAR.POWER
563
- }), must be (atom) (hint use math:power instead) (${
564
- SUGGAR.POWER
565
- } ${stringifyArgs(rest)})`
566
- )
567
- }
568
- break
569
- case KEYWORDS.MULTIPLICATION:
570
- if (!rest.length) {
571
- exp[0][TYPE] = ATOM
572
- exp[0][VALUE] = TRUE
573
- }
574
- break
575
- case KEYWORDS.ADDITION:
576
- case KEYWORDS.DIVISION:
577
- if (!rest.length) {
578
- exp[0][TYPE] = ATOM
579
- exp[0][VALUE] = FALSE
580
- }
581
- break
582
- case SUGGAR.UNLESS:
583
- {
584
- if (rest.length > 3 || rest.length < 2)
585
- throw new RangeError(
586
- `Invalid number of arguments for (${
587
- SUGGAR.UNLESS
588
- }), expected (or (= 3) (= 2)) but got ${rest.length} (${
589
- SUGGAR.UNLESS
590
- } ${stringifyArgs(rest)})`
591
- )
592
- exp[0][VALUE] = KEYWORDS.IF
593
- const temp = exp[2]
594
- exp[2] = exp[3] ?? [ATOM, FALSE]
595
- exp[3] = temp
596
- }
597
- deSuggar(exp)
598
- break
599
-
600
- case SUGGAR.NOT_EQUAL_1:
601
- case SUGGAR.NOT_EQUAL_2:
602
- {
603
- if (rest.length > 3 || rest.length < 2)
604
- throw new RangeError(
605
- `Invalid number of arguments for (${
606
- exp[0][1]
607
- }), expected (or (= 3) (= 2)) but got ${rest.length} (${
608
- exp[0][1]
609
- } ${stringifyArgs(rest)})`
610
- )
611
- exp[0][VALUE] = KEYWORDS.NOT
612
- exp[1] = [[APPLY, KEYWORDS.EQUAL], exp[1], exp[2]]
613
- exp.length = 2
614
- deSuggar(exp)
615
- }
616
- break
617
- }
618
- prev = first
619
- }
620
- break
621
- default:
622
- for (const e of exp) evaluate(e)
623
- break
624
- }
625
- for (const r of rest) evaluate(r)
626
- }
627
- }
628
- evaluate(ast)
629
- return ast
630
- }
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
+ }