fez-lisp 1.4.11 → 1.4.13
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/macros.js +2 -2
- package/lib/baked/std.js +1 -1
- package/package.json +1 -1
- package/src/compiler.js +55 -51
- package/src/interpreter.js +6 -0
- package/src/keywords.js +2 -2
- package/src/macros.js +191 -27
- package/src/utils.js +3 -2
package/package.json
CHANGED
package/src/compiler.js
CHANGED
@@ -7,18 +7,15 @@ import {
|
|
7
7
|
VALUE,
|
8
8
|
WORD
|
9
9
|
} from './keywords.js'
|
10
|
+
import { OPTIMIZATIONS } from './macros.js'
|
10
11
|
import { leaf, isLeaf, AST } from './parser.js'
|
11
|
-
export const OPTIMIZATIONS = {
|
12
|
-
RECURSION: 'recursive',
|
13
|
-
CACHE: 'memoized'
|
14
|
-
}
|
15
12
|
const deepRename = (name, newName, tree) => {
|
16
13
|
if (!isLeaf(tree))
|
17
14
|
for (const leaf of tree) {
|
18
15
|
// Figure out a non mutable solution so
|
19
16
|
// I can get rid of deep clone AST.parse(AST.stringify(ast))
|
20
17
|
if (leaf[VALUE] === name) leaf[VALUE] = newName
|
21
|
-
deepRename(name, newName, leaf)
|
18
|
+
else deepRename(name, newName, leaf)
|
22
19
|
}
|
23
20
|
}
|
24
21
|
const earMuffsToLodashes = (name) => name.replace(new RegExp(/\*/g), '_')
|
@@ -180,52 +177,53 @@ const comp = (tree, Drill) => {
|
|
180
177
|
}
|
181
178
|
case KEYWORDS.DEFINE_VARIABLE: {
|
182
179
|
const n = tail[0][VALUE]
|
183
|
-
const prefix = n.split(':')[0]
|
184
|
-
if (prefix === OPTIMIZATIONS.RECURSION) {
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
} else
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
180
|
+
// const prefix = n.split(':')[0]
|
181
|
+
// if (prefix === OPTIMIZATIONS.RECURSION) {
|
182
|
+
// const name = lispToJavaScriptVariableName(n)
|
183
|
+
// const newName = `${OPTIMIZATIONS.RECURSION}_${performance
|
184
|
+
// .now()
|
185
|
+
// .toString()
|
186
|
+
// .replace('.', 7)}`
|
187
|
+
// Drill.Variables.add(name)
|
188
|
+
// Drill.Variables.add(newName)
|
189
|
+
// Drill.Helpers.add('__tco')
|
190
|
+
// const functionArgs = tail.at(-1).slice(1)
|
191
|
+
// const body = functionArgs.pop()
|
192
|
+
// const FunctionDrill = { Variables: new Set(), Helpers: Drill.Helpers }
|
193
|
+
// deepRename(n, `()=>${newName}`, body)
|
194
|
+
// const evaluatedBody = comp(body, FunctionDrill)
|
195
|
+
// const vars = FunctionDrill.Variables.size
|
196
|
+
// ? `var ${[...FunctionDrill.Variables].join(',')};`
|
197
|
+
// : ''
|
198
|
+
// return `(${name}=(__tco(${newName}=(${parseArgs(
|
199
|
+
// functionArgs,
|
200
|
+
// Drill
|
201
|
+
// )})=>{${vars}return ${evaluatedBody.toString().trim()}})));`
|
202
|
+
// } else
|
203
|
+
|
204
|
+
// if (prefix === OPTIMIZATIONS.CACHE) {
|
205
|
+
// // memoization here
|
206
|
+
// const name = lispToJavaScriptVariableName(n)
|
207
|
+
// const newName = name.substring(OPTIMIZATIONS.CACHE.length + 1)
|
208
|
+
// Drill.Variables.add(name)
|
209
|
+
// const functionArgs = tail.at(-1).slice(1)
|
210
|
+
// const body = functionArgs.pop()
|
211
|
+
// deepRename(n, newName, body)
|
212
|
+
// const FunctionDrill = { Variables: new Set(), Helpers: Drill.Helpers }
|
213
|
+
// const evaluatedBody = comp(body, FunctionDrill)
|
214
|
+
// const vars = FunctionDrill.Variables.size
|
215
|
+
// ? `var ${[...FunctionDrill.Variables].join(',')};`
|
216
|
+
// : ''
|
217
|
+
// return `(${name}=function(){var __${newName}_map = new Map();var ${newName}=(function(${parseArgs(
|
218
|
+
// functionArgs,
|
219
|
+
// Drill
|
220
|
+
// )}){${vars};var __key=[...arguments].join(',');if(__${newName}_map.has(__key)){return __${newName}_map.get(__key)}else{var __res = ${evaluatedBody
|
221
|
+
// .toString()
|
222
|
+
// .trim()};__${newName}_map.set(__key, __res);return __res}});return ${newName}(...arguments)});`
|
223
|
+
// }
|
224
|
+
const name = lispToJavaScriptVariableName(n)
|
225
|
+
Drill.Variables.add(name)
|
226
|
+
return `${name}=${comp(tail[1], Drill)};`
|
229
227
|
}
|
230
228
|
case KEYWORDS.IS_ATOM:
|
231
229
|
Drill.Helpers.add('atom_predicate')
|
@@ -306,6 +304,12 @@ const comp = (tree, Drill) => {
|
|
306
304
|
tail.length === 3 ? comp(tail[2], Drill) : 0
|
307
305
|
});`
|
308
306
|
}
|
307
|
+
case KEYWORDS.LOOP: {
|
308
|
+
return `(()=>{while(${comp(tail[0], Drill)}){${comp(
|
309
|
+
tail[1],
|
310
|
+
Drill
|
311
|
+
)}}return 0})();`
|
312
|
+
}
|
309
313
|
case KEYWORDS.ERROR: {
|
310
314
|
Drill.Helpers.add('__error')
|
311
315
|
return `__error(${compile(tail[0], Drill)})`
|
package/src/interpreter.js
CHANGED
@@ -13,6 +13,12 @@ import { evaluate } from './evaluator.js'
|
|
13
13
|
import { isForbiddenVariableName, stringifyArgs } from './utils.js'
|
14
14
|
import { LISP } from './parser.js'
|
15
15
|
export const keywords = {
|
16
|
+
[KEYWORDS.LOOP]: (args, env) => {
|
17
|
+
if (args.length != 2)
|
18
|
+
throw new RangeError(`Wrong number of args to ${KEYWORDS.LOOP}`)
|
19
|
+
while (evaluate(args[0], env) !== FALSE) evaluate(args[1], env)
|
20
|
+
return FALSE
|
21
|
+
},
|
16
22
|
[KEYWORDS.ADDITION]: (args, env) => {
|
17
23
|
if (args.length !== 2)
|
18
24
|
throw new RangeError(
|
package/src/keywords.js
CHANGED
@@ -7,6 +7,7 @@ export const TRUE = 1
|
|
7
7
|
export const FALSE = 0
|
8
8
|
export const PLACEHOLDER = '.'
|
9
9
|
export const KEYWORDS = {
|
10
|
+
LOOP: 'loop',
|
10
11
|
CREATE_ARRAY: 'array',
|
11
12
|
ARRAY_LENGTH: 'length',
|
12
13
|
IS_ATOM: 'atom?',
|
@@ -40,7 +41,7 @@ export const KEYWORDS = {
|
|
40
41
|
DEFINE_VARIABLE: 'let',
|
41
42
|
|
42
43
|
SET_ARRAY: 'set!',
|
43
|
-
ERROR: 'throw'
|
44
|
+
ERROR: 'throw'
|
44
45
|
}
|
45
46
|
|
46
47
|
export const TYPES = {
|
@@ -56,7 +57,6 @@ export const DEBUG = {
|
|
56
57
|
LOG: 'log',
|
57
58
|
ASSERT: 'assert',
|
58
59
|
CALLSTACK: '(CALLSTACK)' // so that you can't use it in the code
|
59
|
-
|
60
60
|
}
|
61
61
|
|
62
62
|
export const SPECIAL_FORMS_SET = new Set(Object.values(KEYWORDS))
|
package/src/macros.js
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
import {
|
1
|
+
import { isLeaf } from './parser.js'
|
2
2
|
import {
|
3
|
-
|
3
|
+
EXPONENTIATION_RAW,
|
4
4
|
INTEGER_DIVISION,
|
5
|
-
NOT_EQUAL
|
6
|
-
SLICE
|
5
|
+
NOT_EQUAL
|
7
6
|
} from '../lib/baked/macros.js'
|
8
7
|
import {
|
9
8
|
APPLY,
|
@@ -29,6 +28,19 @@ export const SUGGAR = {
|
|
29
28
|
INTEGER_DEVISION: '//',
|
30
29
|
CONDITION: 'cond'
|
31
30
|
}
|
31
|
+
export const OPTIMIZATIONS = {
|
32
|
+
RECURSION: 'recursive',
|
33
|
+
CACHE: 'memoized'
|
34
|
+
}
|
35
|
+
const deepTransform = (predicate, transform, tree) => {
|
36
|
+
if (!isLeaf(tree))
|
37
|
+
for (const leaf of tree) {
|
38
|
+
// Figure out a non mutable solution so
|
39
|
+
// I can get rid of deep clone AST.parse(AST.stringify(ast))
|
40
|
+
if (predicate(leaf)) transform(leaf)
|
41
|
+
else deepTransform(predicate, transform, leaf)
|
42
|
+
}
|
43
|
+
}
|
32
44
|
export const deSuggarAst = (ast, scope) => {
|
33
45
|
if (scope === undefined) scope = ast
|
34
46
|
if (ast.length === 0) throw new SyntaxError(`No expressions...`)
|
@@ -53,7 +65,7 @@ export const deSuggarAst = (ast, scope) => {
|
|
53
65
|
break
|
54
66
|
case SUGGAR.POWER:
|
55
67
|
exp.length = 0
|
56
|
-
exp.push(...
|
68
|
+
exp.push(...EXPONENTIATION_RAW)
|
57
69
|
break
|
58
70
|
case SUGGAR.INTEGER_DEVISION:
|
59
71
|
exp.length = 0
|
@@ -214,23 +226,13 @@ export const deSuggarAst = (ast, scope) => {
|
|
214
226
|
const exponent = exp[1]
|
215
227
|
const power = exp[2]
|
216
228
|
exp.length = 0
|
217
|
-
exp.push(
|
218
|
-
[0, KEYWORDS.CALL_FUNCTION],
|
219
|
-
exponent,
|
220
|
-
power,
|
221
|
-
EXPONENTIATION
|
222
|
-
)
|
229
|
+
exp.push([APPLY, 'math:power'], exponent, power)
|
223
230
|
}
|
224
231
|
} else {
|
225
232
|
const exponent = exp[1]
|
226
233
|
const power = exp[2]
|
227
234
|
exp.length = 0
|
228
|
-
exp.push(
|
229
|
-
[0, KEYWORDS.CALL_FUNCTION],
|
230
|
-
exponent,
|
231
|
-
power,
|
232
|
-
EXPONENTIATION
|
233
|
-
)
|
235
|
+
exp.push([APPLY, 'math:power'], exponent, power)
|
234
236
|
}
|
235
237
|
deSuggarAst(exp, scope)
|
236
238
|
}
|
@@ -393,9 +395,10 @@ export const deSuggarAst = (ast, scope) => {
|
|
393
395
|
break
|
394
396
|
case KEYWORDS.DEFINE_VARIABLE:
|
395
397
|
{
|
398
|
+
const last = exp.at(-1)
|
396
399
|
if (!isLeaf(exp[1]) && exp[1][0][TYPE] === APPLY) {
|
397
400
|
const left = exp[1].slice(1)
|
398
|
-
const right =
|
401
|
+
const right = last
|
399
402
|
let newScope
|
400
403
|
if (exp[1][0][VALUE] === SUGGAR.CREATE_LIST) {
|
401
404
|
const lastLeft = left.pop()
|
@@ -478,10 +481,9 @@ export const deSuggarAst = (ast, scope) => {
|
|
478
481
|
[APPLY, KEYWORDS.DEFINE_VARIABLE],
|
479
482
|
lastLeft,
|
480
483
|
[
|
481
|
-
[APPLY,
|
484
|
+
[APPLY, 'array:drop'],
|
482
485
|
right,
|
483
|
-
[ATOM, indexes.at(-1)[0] + 1]
|
484
|
-
SLICE
|
486
|
+
[ATOM, indexes.at(-1)[0] + 1]
|
485
487
|
]
|
486
488
|
])
|
487
489
|
}
|
@@ -489,6 +491,172 @@ export const deSuggarAst = (ast, scope) => {
|
|
489
491
|
exp.iron = true
|
490
492
|
exp.push(newScope)
|
491
493
|
deSuggarAst(scope)
|
494
|
+
} else if (
|
495
|
+
!isLeaf(last) &&
|
496
|
+
Array.isArray(last) &&
|
497
|
+
last[0] &&
|
498
|
+
last[0][TYPE] === APPLY &&
|
499
|
+
last[0][VALUE] === KEYWORDS.ANONYMOUS_FUNCTION
|
500
|
+
) {
|
501
|
+
const name = exp[1][VALUE]
|
502
|
+
const prefix = name.split(':')[0]
|
503
|
+
if (prefix === OPTIMIZATIONS.RECURSION) {
|
504
|
+
const args = last.slice(1, -1)
|
505
|
+
const newName = `*${performance
|
506
|
+
.now()
|
507
|
+
.toString()
|
508
|
+
.replace('.', 0)}*`
|
509
|
+
deepTransform(
|
510
|
+
(leaf) =>
|
511
|
+
Array.isArray(leaf) &&
|
512
|
+
leaf[0] &&
|
513
|
+
leaf[0][TYPE] === APPLY &&
|
514
|
+
leaf[0][VALUE] === name,
|
515
|
+
(leaf) => {
|
516
|
+
const copy = [...leaf]
|
517
|
+
leaf.length = 0
|
518
|
+
copy[0][VALUE] = newName
|
519
|
+
leaf.push([APPLY, KEYWORDS.ANONYMOUS_FUNCTION], copy)
|
520
|
+
},
|
521
|
+
last
|
522
|
+
)
|
523
|
+
exp[exp.length - 1] = [
|
524
|
+
[APPLY, KEYWORDS.CALL_FUNCTION],
|
525
|
+
[
|
526
|
+
[APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
|
527
|
+
[
|
528
|
+
[APPLY, KEYWORDS.BLOCK],
|
529
|
+
[
|
530
|
+
[APPLY, KEYWORDS.DEFINE_VARIABLE],
|
531
|
+
[WORD, newName],
|
532
|
+
last
|
533
|
+
],
|
534
|
+
[
|
535
|
+
[APPLY, KEYWORDS.CALL_FUNCTION],
|
536
|
+
[WORD, newName],
|
537
|
+
[
|
538
|
+
[APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
|
539
|
+
[WORD, '*fn*'],
|
540
|
+
[
|
541
|
+
[APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
|
542
|
+
...args,
|
543
|
+
[
|
544
|
+
[APPLY, KEYWORDS.BLOCK],
|
545
|
+
[
|
546
|
+
[APPLY, KEYWORDS.DEFINE_VARIABLE],
|
547
|
+
[WORD, '*res*'],
|
548
|
+
[
|
549
|
+
[APPLY, KEYWORDS.CREATE_ARRAY],
|
550
|
+
[[APPLY, '*fn*'], ...args]
|
551
|
+
]
|
552
|
+
],
|
553
|
+
[
|
554
|
+
[APPLY, KEYWORDS.LOOP],
|
555
|
+
[
|
556
|
+
[APPLY, KEYWORDS.IS_LAMBDA],
|
557
|
+
[
|
558
|
+
[APPLY, KEYWORDS.GET_ARRAY],
|
559
|
+
[WORD, '*res*'],
|
560
|
+
[ATOM, 0]
|
561
|
+
]
|
562
|
+
],
|
563
|
+
[
|
564
|
+
[APPLY, KEYWORDS.SET_ARRAY],
|
565
|
+
[WORD, '*res*'],
|
566
|
+
[ATOM, 0],
|
567
|
+
[
|
568
|
+
[APPLY, KEYWORDS.CALL_FUNCTION],
|
569
|
+
[
|
570
|
+
[APPLY, KEYWORDS.GET_ARRAY],
|
571
|
+
[WORD, '*res*'],
|
572
|
+
[ATOM, 0]
|
573
|
+
]
|
574
|
+
]
|
575
|
+
]
|
576
|
+
],
|
577
|
+
[
|
578
|
+
[APPLY, KEYWORDS.GET_ARRAY],
|
579
|
+
[WORD, '*res*'],
|
580
|
+
[ATOM, 0]
|
581
|
+
]
|
582
|
+
]
|
583
|
+
]
|
584
|
+
]
|
585
|
+
]
|
586
|
+
]
|
587
|
+
]
|
588
|
+
]
|
589
|
+
deSuggarAst(exp[exp.length - 1])
|
590
|
+
} else if (prefix === OPTIMIZATIONS.CACHE) {
|
591
|
+
// TODO: Make this
|
592
|
+
const args = last.slice(1, -1)
|
593
|
+
const newName = `*${performance
|
594
|
+
.now()
|
595
|
+
.toString()
|
596
|
+
.replace('.', 0)}*`
|
597
|
+
deepTransform(
|
598
|
+
(leaf) =>
|
599
|
+
Array.isArray(leaf) &&
|
600
|
+
leaf[0] &&
|
601
|
+
leaf[0][TYPE] === APPLY &&
|
602
|
+
leaf[0][VALUE] === name,
|
603
|
+
(leaf) => (leaf[0][VALUE] = newName),
|
604
|
+
last
|
605
|
+
)
|
606
|
+
const memoName = newName + ':memo'
|
607
|
+
const keyName = newName + ':key'
|
608
|
+
|
609
|
+
exp[exp.length - 1] = [
|
610
|
+
[APPLY, KEYWORDS.CALL_FUNCTION],
|
611
|
+
[
|
612
|
+
[APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
|
613
|
+
[
|
614
|
+
[APPLY, KEYWORDS.BLOCK],
|
615
|
+
[
|
616
|
+
[APPLY, KEYWORDS.DEFINE_VARIABLE],
|
617
|
+
[WORD, memoName],
|
618
|
+
[[APPLY, 'new:map64']]
|
619
|
+
],
|
620
|
+
[
|
621
|
+
[APPLY, KEYWORDS.DEFINE_VARIABLE],
|
622
|
+
[WORD, newName],
|
623
|
+
[
|
624
|
+
[APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
|
625
|
+
...args,
|
626
|
+
[
|
627
|
+
[APPLY, KEYWORDS.BLOCK],
|
628
|
+
[
|
629
|
+
[APPLY, KEYWORDS.DEFINE_VARIABLE],
|
630
|
+
[WORD, keyName],
|
631
|
+
[[APPLY, KEYWORDS.CREATE_ARRAY], ...args]
|
632
|
+
],
|
633
|
+
[
|
634
|
+
[APPLY, KEYWORDS.IF],
|
635
|
+
[
|
636
|
+
[APPLY, 'map:has?'],
|
637
|
+
[WORD, memoName],
|
638
|
+
[WORD, keyName]
|
639
|
+
],
|
640
|
+
[
|
641
|
+
[APPLY, 'map:get'],
|
642
|
+
[WORD, memoName],
|
643
|
+
[WORD, keyName]
|
644
|
+
],
|
645
|
+
[
|
646
|
+
[APPLY, 'map:set-and-get!'],
|
647
|
+
[WORD, memoName],
|
648
|
+
[WORD, keyName],
|
649
|
+
last.at(-1)
|
650
|
+
]
|
651
|
+
]
|
652
|
+
]
|
653
|
+
]
|
654
|
+
],
|
655
|
+
[WORD, newName]
|
656
|
+
]
|
657
|
+
]
|
658
|
+
]
|
659
|
+
}
|
492
660
|
}
|
493
661
|
}
|
494
662
|
break
|
@@ -507,12 +675,10 @@ export const deSuggarAst = (ast, scope) => {
|
|
507
675
|
const left = exp[i].slice(1)
|
508
676
|
const right = [WORD, `_arg${i}`]
|
509
677
|
const lastLeft = left.pop()
|
510
|
-
|
511
678
|
const vars = left
|
512
679
|
const indexes = vars
|
513
680
|
.map((x, i) => [x, i])
|
514
681
|
.filter((x) => x[0][VALUE] !== PLACEHOLDER)
|
515
|
-
|
516
682
|
newBlock.push(
|
517
683
|
...indexes.map(([name, n]) => {
|
518
684
|
let wrap = right
|
@@ -545,7 +711,6 @@ export const deSuggarAst = (ast, scope) => {
|
|
545
711
|
wrap
|
546
712
|
])
|
547
713
|
}
|
548
|
-
|
549
714
|
left[0][TYPE] = WORD
|
550
715
|
exp[i] = right
|
551
716
|
exp[i].length = 2
|
@@ -572,10 +737,9 @@ export const deSuggarAst = (ast, scope) => {
|
|
572
737
|
[APPLY, KEYWORDS.DEFINE_VARIABLE],
|
573
738
|
lastLeft,
|
574
739
|
[
|
575
|
-
[APPLY,
|
740
|
+
[APPLY, 'array:drop'],
|
576
741
|
right,
|
577
|
-
[ATOM, indexes.at(-1)[0] + 1]
|
578
|
-
SLICE
|
742
|
+
[ATOM, indexes.at(-1)[0] + 1]
|
579
743
|
]
|
580
744
|
])
|
581
745
|
exp[i] = right
|
package/src/utils.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import std from '../lib/baked/std.js'
|
2
|
-
import { compile
|
2
|
+
import { compile } from './compiler.js'
|
3
3
|
import {
|
4
4
|
APPLY,
|
5
5
|
ATOM,
|
@@ -17,7 +17,8 @@ import { isLeaf, LISP } from './parser.js'
|
|
17
17
|
import {
|
18
18
|
deSuggarAst,
|
19
19
|
deSuggarSource,
|
20
|
-
handleUnbalancedQuotes
|
20
|
+
handleUnbalancedQuotes,
|
21
|
+
OPTIMIZATIONS
|
21
22
|
} from './macros.js'
|
22
23
|
import { keywords } from './interpreter.js'
|
23
24
|
export const logError = (error) =>
|