fez-lisp 1.2.62 → 1.3.1

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