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/lib/baked/std.js +1 -1
- package/package.json +1 -1
- package/src/compiler.js +350 -350
- package/src/interpreter.js +783 -743
- package/src/utils.js +832 -630
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
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
exp
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
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
|
+
}
|