fez-lisp 1.0.54 → 1.1.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/README.md +66 -30
- package/lib/baked/std.js +1 -1
- package/package.json +1 -1
- package/src/compiler.js +6 -16
- package/src/evaluator.js +1 -1
- package/src/interpreter.js +31 -120
- package/src/keywords.js +2 -9
- package/src/parser.js +2 -18
- package/src/utils.js +20 -15
package/package.json
CHANGED
package/src/compiler.js
CHANGED
@@ -10,6 +10,7 @@ import {
|
|
10
10
|
import { leaf, isLeaf } from './parser.js'
|
11
11
|
import { deepRename, lispToJavaScriptVariableName } from './utils.js'
|
12
12
|
const Helpers = {
|
13
|
+
__string: `__string=(...args)=>{const str=args.flat();str.isString=true;return str}`,
|
13
14
|
__add: `__add=(...numbers)=>{return numbers.reduce((a,b)=>a+b,0)}`,
|
14
15
|
__sub: `__sub=(...numbers)=>{return numbers.reduce((a,b)=>a-b,0)}`,
|
15
16
|
__mult: `__mult=(...numbers)=>{return numbers.reduce((a,b)=>a*b,1)}`,
|
@@ -46,13 +47,10 @@ const Helpers = {
|
|
46
47
|
length: 'length=(arr)=>arr.length',
|
47
48
|
__tco: `__tco=fn=>(...args)=>{let result=fn(...args);while(typeof result==='function')result=result();return result}`,
|
48
49
|
numberPredicate: `numberPredicate=(number)=>+(typeof number==='number')`,
|
49
|
-
stringPredicate: `stringPredicate=(string)=>+(typeof string==='string')`,
|
50
50
|
lambdaPredicate: `lambdaPredicate=(lambda)=>+(typeof lambda==='function')`,
|
51
51
|
arrayPredicate: `arrayPredicate=(array)=>+Array.isArray(array)`,
|
52
|
-
atomPredicate: `atomPredicate=(value)=>+(typeof value==='number'||typeof value==='string')`,
|
53
52
|
error: `error=(error)=>{throw new Error(error)}`,
|
54
|
-
array_setEffect: `array_setEffect=(array,index,value)=>{if(index<0){const target=array.length+index;while(array.length!==target)array.pop()}else array[index] = value;return array}
|
55
|
-
cast: `cast=(type,value)=>{switch(type){case '${KEYWORDS.NUMBER_TYPE}':return Number(value);case '${KEYWORDS.STRING_TYPE}':return value.toString();case '${KEYWORDS.ARRAY_TYPE}':return typeof value==='number'?[...Number(value).toString()].map(Number):[...value];case '${KEYWORDS.BOOLEAN_TYPE}':return +!!value;case '${KEYWORDS.ANONYMOUS_FUNCTION}':return ()=>value;case '${KEYWORDS.CHAR_CODE_TYPE}':return value.charCodeAt(0);case '${KEYWORDS.CHAR_TYPE}':return String.fromCharCode(value);default:return 0}}`
|
53
|
+
array_setEffect: `array_setEffect=(array,index,value)=>{if(index<0){const target=array.length+index;while(array.length!==target)array.pop()}else array[index] = value;return array}`
|
56
54
|
}
|
57
55
|
const semiColumnEdgeCases = new Set([
|
58
56
|
';)',
|
@@ -116,9 +114,6 @@ const compile = (tree, Drill) => {
|
|
116
114
|
out += `),${name});`
|
117
115
|
return out
|
118
116
|
}
|
119
|
-
case KEYWORDS.IS_STRING:
|
120
|
-
Drill.Helpers.add('stringPredicate')
|
121
|
-
return `stringPredicate(${compile(Arguments[0], Drill)});`
|
122
117
|
case KEYWORDS.IS_NUMBER:
|
123
118
|
Drill.Helpers.add('numberPredicate')
|
124
119
|
return `numberPredicate(${compile(Arguments[0], Drill)});`
|
@@ -133,19 +128,17 @@ const compile = (tree, Drill) => {
|
|
133
128
|
case KEYWORDS.BOOLEAN_TYPE:
|
134
129
|
return '1'
|
135
130
|
case KEYWORDS.STRING_TYPE:
|
136
|
-
|
131
|
+
Drill.Helpers.add('__string')
|
132
|
+
return `__string(${parseArgs(Arguments, Drill)});`
|
137
133
|
case KEYWORDS.ARRAY_TYPE:
|
138
134
|
return Arguments.length === 2 &&
|
139
135
|
Arguments[1][TYPE] === WORD &&
|
140
136
|
Arguments[1][VALUE] === 'length'
|
141
|
-
? `(new Array(${compile(Arguments[0], Drill)}).fill(0))
|
137
|
+
? `(new Array(${compile(Arguments[0], Drill)}).fill(0));`
|
142
138
|
: `[${parseArgs(Arguments, Drill)}];`
|
143
|
-
case KEYWORDS.
|
139
|
+
case KEYWORDS.ARRAY_LENGTH:
|
144
140
|
Drill.Helpers.add('length')
|
145
141
|
return `length(${compile(Arguments[0], Drill)})`
|
146
|
-
case KEYWORDS.IS_ATOM:
|
147
|
-
Drill.Helpers.add('atomPredicate')
|
148
|
-
return `atomPredicate(${compile(Arguments[0], Drill)});`
|
149
142
|
case KEYWORDS.FIRST_ARRAY:
|
150
143
|
Drill.Helpers.add('car')
|
151
144
|
return `car(${compile(Arguments[0], Drill)});`
|
@@ -295,9 +288,6 @@ const compile = (tree, Drill) => {
|
|
295
288
|
out += '0);'
|
296
289
|
return out
|
297
290
|
}
|
298
|
-
case KEYWORDS.CAST_TYPE:
|
299
|
-
Drill.Helpers.add('cast')
|
300
|
-
return `cast("${Arguments[1][VALUE]}", ${compile(Arguments[0], Drill)})`
|
301
291
|
case KEYWORDS.PIPE: {
|
302
292
|
let inp = Arguments[0]
|
303
293
|
for (let i = 1; i < Arguments.length; ++i)
|
package/src/evaluator.js
CHANGED
@@ -36,7 +36,7 @@ export const isAtom = (arg, env) => {
|
|
36
36
|
if (arg[TYPE] === ATOM) return 1
|
37
37
|
else {
|
38
38
|
const atom = evaluate(arg, env)
|
39
|
-
return +(typeof atom === 'number'
|
39
|
+
return +(typeof atom === 'number')
|
40
40
|
}
|
41
41
|
}
|
42
42
|
export const run = (tree, env = {}) =>
|
package/src/interpreter.js
CHANGED
@@ -8,26 +8,7 @@ import {
|
|
8
8
|
isForbiddenVariableName,
|
9
9
|
stringifyArgs
|
10
10
|
} from './utils.js'
|
11
|
-
|
12
11
|
const keywords = {
|
13
|
-
[KEYWORDS.CONCATENATION]: (args, env) => {
|
14
|
-
if (args.length < 2)
|
15
|
-
throw new RangeError(
|
16
|
-
`Invalid number of arguments for (${
|
17
|
-
KEYWORDS.CONCATENATION
|
18
|
-
}), expected > 1 but got ${args.length}. (${
|
19
|
-
KEYWORDS.CONCATENATION
|
20
|
-
} ${stringifyArgs(args)})`
|
21
|
-
)
|
22
|
-
const operands = args.map((x) => evaluate(x, env))
|
23
|
-
if (operands.some((x) => typeof x !== 'string'))
|
24
|
-
throw new TypeError(
|
25
|
-
`Not all arguments of (${KEYWORDS.CONCATENATION}) are (${
|
26
|
-
KEYWORDS.STRING_TYPE
|
27
|
-
}) (${KEYWORDS.CONCATENATION} ${stringifyArgs(args)})`
|
28
|
-
)
|
29
|
-
return operands.reduce((a, b) => a + b, '')
|
30
|
-
},
|
31
12
|
[KEYWORDS.REMAINDER_OF_DIVISION]: (args, env) => {
|
32
13
|
if (args.length < 2)
|
33
14
|
throw new RangeError(
|
@@ -92,23 +73,19 @@ const keywords = {
|
|
92
73
|
)
|
93
74
|
return operands.reduce((a, b) => a / b)
|
94
75
|
},
|
95
|
-
[KEYWORDS.
|
76
|
+
[KEYWORDS.ARRAY_LENGTH]: (args, env) => {
|
96
77
|
if (args.length !== 1)
|
97
78
|
throw new RangeError(
|
98
79
|
`Invalid number of arguments for (${
|
99
|
-
KEYWORDS.
|
100
|
-
}) (= 1 required) (${KEYWORDS.
|
101
|
-
args
|
102
|
-
)})`
|
80
|
+
KEYWORDS.ARRAY_LENGTH
|
81
|
+
}) (= 1 required) (${KEYWORDS.ARRAY_LENGTH} ${stringifyArgs(args)})`
|
103
82
|
)
|
104
83
|
const array = evaluate(args[0], env)
|
105
|
-
if (!
|
84
|
+
if (!Array.isArray(array))
|
106
85
|
throw new TypeError(
|
107
|
-
`First argument of (${
|
108
|
-
KEYWORDS.
|
109
|
-
}
|
110
|
-
KEYWORDS.ARRAY_OR_STRING_LENGTH
|
111
|
-
} ${stringifyArgs(args)})`
|
86
|
+
`First argument of (${KEYWORDS.ARRAY_LENGTH}) must be a ${
|
87
|
+
KEYWORDS.ARRAY_TYPE
|
88
|
+
} (${KEYWORDS.ARRAY_LENGTH} ${stringifyArgs(args)})`
|
112
89
|
)
|
113
90
|
return array.length
|
114
91
|
},
|
@@ -131,15 +108,6 @@ const keywords = {
|
|
131
108
|
)
|
132
109
|
return +(typeof evaluate(args[0], env) === 'number')
|
133
110
|
},
|
134
|
-
[KEYWORDS.IS_STRING]: (args, env) => {
|
135
|
-
if (args.length !== 1)
|
136
|
-
throw new RangeError(
|
137
|
-
`Invalid number of arguments for (${
|
138
|
-
KEYWORDS.IS_STRING
|
139
|
-
}) (= 1 required) (${KEYWORDS.IS_STRING} ${stringifyArgs(args)})`
|
140
|
-
)
|
141
|
-
return +(typeof evaluate(args[0], env) === 'string')
|
142
|
-
},
|
143
111
|
[KEYWORDS.IS_FUNCTION]: (args, env) => {
|
144
112
|
if (args.length !== 1)
|
145
113
|
throw new RangeError(
|
@@ -262,6 +230,11 @@ const keywords = {
|
|
262
230
|
if (evaluate(args[i], env)) return evaluate(args[i + 1], env)
|
263
231
|
return 0
|
264
232
|
},
|
233
|
+
[KEYWORDS.STRING_TYPE]: (args, env) => {
|
234
|
+
const str = args.flatMap((x) => evaluate(x, env))
|
235
|
+
str.isString = true
|
236
|
+
return str
|
237
|
+
},
|
265
238
|
[KEYWORDS.ARRAY_TYPE]: (args, env) => {
|
266
239
|
if (!args.length) return []
|
267
240
|
const isCapacity =
|
@@ -284,15 +257,6 @@ const keywords = {
|
|
284
257
|
}
|
285
258
|
return args.map((x) => evaluate(x, env))
|
286
259
|
},
|
287
|
-
[KEYWORDS.IS_ATOM]: (args, env) => {
|
288
|
-
if (args.length !== 1)
|
289
|
-
throw new RangeError(
|
290
|
-
`Invalid number of arguments for (${
|
291
|
-
KEYWORDS.IS_ATOM
|
292
|
-
}) (= 1 required) (${KEYWORDS.IS_ATOM} ${stringifyArgs(args)})`
|
293
|
-
)
|
294
|
-
return isAtom(args[0], env)
|
295
|
-
},
|
296
260
|
[KEYWORDS.FIRST_ARRAY]: (args, env) => {
|
297
261
|
if (args.length !== 1)
|
298
262
|
throw new RangeError(
|
@@ -439,7 +403,7 @@ const keywords = {
|
|
439
403
|
const b = evaluate(args[1], env)
|
440
404
|
if (typeof a !== 'number')
|
441
405
|
throw new TypeError(
|
442
|
-
`Invalid use of (${KEYWORDS.EQUAL}), first
|
406
|
+
`Invalid use of (${KEYWORDS.EQUAL}), first argument is not an ${
|
443
407
|
KEYWORDS.NUMBER_TYPE
|
444
408
|
} (${KEYWORDS.EQUAL} ${stringifyArgs(args)})`
|
445
409
|
)
|
@@ -462,7 +426,7 @@ const keywords = {
|
|
462
426
|
const b = evaluate(args[1], env)
|
463
427
|
if (typeof a !== 'number')
|
464
428
|
throw new TypeError(
|
465
|
-
`Invalid use of (${KEYWORDS.LESS_THAN}), first
|
429
|
+
`Invalid use of (${KEYWORDS.LESS_THAN}), first argument is not an ${
|
466
430
|
KEYWORDS.NUMBER_TYPE
|
467
431
|
} (${KEYWORDS.LESS_THAN} ${stringifyArgs(args)})`
|
468
432
|
)
|
@@ -485,11 +449,9 @@ const keywords = {
|
|
485
449
|
const b = evaluate(args[1], env)
|
486
450
|
if (typeof a !== 'number')
|
487
451
|
throw new TypeError(
|
488
|
-
`Invalid use of (${
|
489
|
-
KEYWORDS.
|
490
|
-
}
|
491
|
-
KEYWORDS.GREATHER_THAN
|
492
|
-
} ${stringifyArgs(args)})`
|
452
|
+
`Invalid use of (${KEYWORDS.GREATHER_THAN}), first argument is not an ${
|
453
|
+
KEYWORDS.NUMBER_TYPE
|
454
|
+
} (${KEYWORDS.GREATHER_THAN} ${stringifyArgs(args)})`
|
493
455
|
)
|
494
456
|
if (typeof b !== 'number')
|
495
457
|
throw new TypeError(
|
@@ -516,7 +478,7 @@ const keywords = {
|
|
516
478
|
throw new TypeError(
|
517
479
|
`Invalid use of (${
|
518
480
|
KEYWORDS.GREATHER_THAN_OR_EQUAL
|
519
|
-
}), first
|
481
|
+
}), first argument is not an ${KEYWORDS.NUMBER_TYPE} (${
|
520
482
|
KEYWORDS.GREATHER_THAN_OR_EQUAL
|
521
483
|
} ${stringifyArgs(args)})`
|
522
484
|
)
|
@@ -545,7 +507,7 @@ const keywords = {
|
|
545
507
|
throw new TypeError(
|
546
508
|
`Invalid use of (${
|
547
509
|
KEYWORDS.LESS_THAN_OR_EQUAL
|
548
|
-
}), first
|
510
|
+
}), first argument is not an ${KEYWORDS.NUMBER_TYPE} (${
|
549
511
|
KEYWORDS.LESS_THAN_OR_EQUAL
|
550
512
|
} ${stringifyArgs(args)})`
|
551
513
|
)
|
@@ -643,7 +605,6 @@ const keywords = {
|
|
643
605
|
})
|
644
606
|
return env[name]
|
645
607
|
},
|
646
|
-
[KEYWORDS.STRING_TYPE]: () => '',
|
647
608
|
[KEYWORDS.NUMBER_TYPE]: () => 0,
|
648
609
|
[KEYWORDS.BOOLEAN_TYPE]: () => 1,
|
649
610
|
[KEYWORDS.CAST_TYPE]: (args, env) => {
|
@@ -671,60 +632,13 @@ const keywords = {
|
|
671
632
|
)
|
672
633
|
return num
|
673
634
|
}
|
674
|
-
case KEYWORDS.STRING_TYPE:
|
675
|
-
return value.toString()
|
676
635
|
case KEYWORDS.BOOLEAN_TYPE:
|
677
636
|
return +!!value
|
678
|
-
case KEYWORDS.ARRAY_TYPE: {
|
679
|
-
if (typeof value === 'number')
|
680
|
-
return [...Number(value).toString()].map(Number)
|
681
|
-
else if (typeof value[Symbol.iterator] !== 'function')
|
682
|
-
throw new TypeError(
|
683
|
-
`Arguments are not iterable for ${KEYWORDS.ARRAY_TYPE} at (${
|
684
|
-
KEYWORDS.CAST_TYPE
|
685
|
-
}) (${KEYWORDS.CAST_TYPE} ${stringifyArgs(args)})`
|
686
|
-
)
|
687
|
-
return [...value]
|
688
|
-
}
|
689
|
-
case KEYWORDS.CHAR_TYPE: {
|
690
|
-
const index = evaluate(args[0], env)
|
691
|
-
if (!Number.isInteger(index) || index < 0)
|
692
|
-
throw new TypeError(
|
693
|
-
`Arguments are not (+ ${KEYWORDS.NUMBER_TYPE}) for ${
|
694
|
-
KEYWORDS.CHAR_TYPE
|
695
|
-
} at (${KEYWORDS.CAST_TYPE}) (${
|
696
|
-
KEYWORDS.CAST_TYPE
|
697
|
-
} ${stringifyArgs(args)})`
|
698
|
-
)
|
699
|
-
return String.fromCharCode(index)
|
700
|
-
}
|
701
|
-
case KEYWORDS.CHAR_CODE_TYPE: {
|
702
|
-
const string = evaluate(args[0], env)
|
703
|
-
if (typeof string !== 'string')
|
704
|
-
throw new TypeError(
|
705
|
-
`Argument is not (${KEYWORDS.STRING_TYPE}) for ${
|
706
|
-
KEYWORDS.CHAR_CODE_TYPE
|
707
|
-
} at (${KEYWORDS.CAST_TYPE}) (${
|
708
|
-
KEYWORDS.CAST_TYPE
|
709
|
-
} ${stringifyArgs(args)})`
|
710
|
-
)
|
711
|
-
if (string.length !== 1)
|
712
|
-
throw new RangeError(
|
713
|
-
`Argument is not of (= (length ${KEYWORDS.STRING_TYPE}) 1) for ${
|
714
|
-
KEYWORDS.CHAR_CODE_TYPE
|
715
|
-
} at (${KEYWORDS.CAST_TYPE}) (${
|
716
|
-
KEYWORDS.CAST_TYPE
|
717
|
-
} ${stringifyArgs(args)})`
|
718
|
-
)
|
719
|
-
return string.charCodeAt(0)
|
720
|
-
}
|
721
637
|
default:
|
722
638
|
throw new TypeError(
|
723
|
-
`Can only cast (or ${KEYWORDS.NUMBER_TYPE} ${
|
724
|
-
KEYWORDS.
|
725
|
-
}
|
726
|
-
KEYWORDS.CHAR_TYPE
|
727
|
-
} ${KEYWORDS.CHAR_CODE_TYPE}) at (${KEYWORDS.CAST_TYPE}) (${
|
639
|
+
`Can only cast (or ${KEYWORDS.NUMBER_TYPE} ${KEYWORDS.ARRAY_TYPE} ${
|
640
|
+
KEYWORDS.BOOLEAN_TYPE
|
641
|
+
}) at (${KEYWORDS.CAST_TYPE}) (${
|
728
642
|
KEYWORDS.CAST_TYPE
|
729
643
|
} ${stringifyArgs(args)})`
|
730
644
|
)
|
@@ -965,18 +879,17 @@ const keywords = {
|
|
965
879
|
return keywords[KEYWORDS.DEFINE_VARIABLE](args, env)
|
966
880
|
},
|
967
881
|
[KEYWORDS.TEST_CASE]: (args, env) => {
|
968
|
-
if (args.length !==
|
882
|
+
if (args.length !== 2)
|
969
883
|
throw new RangeError(
|
970
884
|
`Invalid number of arguments to (${
|
971
885
|
KEYWORDS.TEST_CASE
|
972
|
-
}) (=
|
886
|
+
}) (= 2 required) (${KEYWORDS.TEST_CASE} ${stringifyArgs(args)})`
|
973
887
|
)
|
974
|
-
const
|
975
|
-
const
|
976
|
-
const b = evaluate(args[2], env)
|
888
|
+
const a = evaluate(args[0], env)
|
889
|
+
const b = evaluate(args[1], env)
|
977
890
|
return !isEqualTypes(a, b) || !isEqual(a, b)
|
978
|
-
? [0,
|
979
|
-
: [1,
|
891
|
+
? [0, stringifyArgs([args[0]]), b, a]
|
892
|
+
: [1, stringifyArgs([args[0]]), a]
|
980
893
|
},
|
981
894
|
[KEYWORDS.TEST_BED]: (args, env) => {
|
982
895
|
let tests = []
|
@@ -994,15 +907,13 @@ const keywords = {
|
|
994
907
|
)
|
995
908
|
tests = args.map((x) => evaluate(x, env))
|
996
909
|
res = tests.reduce(
|
997
|
-
(acc, [state,
|
910
|
+
(acc, [state, ...rest]) =>
|
998
911
|
`${acc}${
|
999
912
|
!state
|
1000
|
-
? `x ${
|
913
|
+
? `x ${rest[0]}\n + ${LISP.stringify(
|
1001
914
|
rest[1]
|
1002
915
|
)}\n - ${LISP.stringify(rest[2])}\n`
|
1003
|
-
: `✓ ${
|
1004
|
-
rest[1]
|
1005
|
-
)}\n`
|
916
|
+
: `✓ ${rest[0]}\n + ${LISP.stringify(rest[1])}\n`
|
1006
917
|
}`,
|
1007
918
|
''
|
1008
919
|
)
|
package/src/keywords.js
CHANGED
@@ -9,20 +9,13 @@ export const ATOM = 2
|
|
9
9
|
export const PLACEHOLDER = '.'
|
10
10
|
// keywords aliases
|
11
11
|
export const KEYWORDS = {
|
12
|
-
STRING_TYPE: 'string',
|
13
12
|
NUMBER_TYPE: 'number',
|
14
|
-
|
13
|
+
STRING_TYPE: 'string',
|
15
14
|
ARRAY_TYPE: 'array',
|
16
|
-
|
17
|
-
CHAR_TYPE: 'char',
|
18
|
-
CAST_TYPE: 'type',
|
19
|
-
CONCATENATION: 'string:merge',
|
20
|
-
ARRAY_OR_STRING_LENGTH: 'length',
|
15
|
+
ARRAY_LENGTH: 'length',
|
21
16
|
IS_ARRAY: 'array?',
|
22
17
|
IS_NUMBER: 'number?',
|
23
|
-
IS_STRING: 'string?',
|
24
18
|
IS_FUNCTION: 'lambda?',
|
25
|
-
IS_ATOM: 'atom?',
|
26
19
|
ADDITION: '+',
|
27
20
|
SUBTRACTION: '-',
|
28
21
|
MULTIPLICATION: '*',
|
package/src/parser.js
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import { APPLY, ATOM, TYPE, WORD, VALUE } from './keywords.js'
|
2
|
-
import { escape, preserveEscape } from './utils.js'
|
3
2
|
export const leaf = (type, value) => [type, value]
|
4
3
|
export const isLeaf = ([car]) => car === APPLY || car === ATOM || car === WORD
|
5
4
|
export const LISP = {
|
@@ -10,15 +9,6 @@ export const LISP = {
|
|
10
9
|
acc = ''
|
11
10
|
for (let i = 0; i < source.length; ++i) {
|
12
11
|
const cursor = source[i]
|
13
|
-
if (cursor === '"') {
|
14
|
-
acc += '"'
|
15
|
-
++i
|
16
|
-
while (source[i] !== '"') {
|
17
|
-
if (source[i] === '\\') acc += escape(source[++i])
|
18
|
-
else acc += source[i]
|
19
|
-
++i
|
20
|
-
}
|
21
|
-
}
|
22
12
|
if (cursor === '(') {
|
23
13
|
head.push([])
|
24
14
|
stack.push(head)
|
@@ -28,8 +18,6 @@ export const LISP = {
|
|
28
18
|
acc = ''
|
29
19
|
if (token) {
|
30
20
|
if (!head.length) head.push(leaf(APPLY, token))
|
31
|
-
else if (token.match(/^"([^"]*)"/))
|
32
|
-
head.push(leaf(ATOM, token.substring(1, token.length - 1)))
|
33
21
|
else if (token.match(/^-?[0-9]\d*(\.\d+)?$/))
|
34
22
|
head.push(leaf(ATOM, Number(token)))
|
35
23
|
else head.push(leaf(WORD, token))
|
@@ -50,7 +38,6 @@ export const LISP = {
|
|
50
38
|
return `(array ${array
|
51
39
|
.map(([key, value]) => `("${key}" ${LISP.stringify(value)})`)
|
52
40
|
.join(' ')})`
|
53
|
-
else if (typeof array === 'string') return `"${array}"`
|
54
41
|
else if (typeof array === 'function') return '()'
|
55
42
|
else if (typeof array === 'boolean') return +array
|
56
43
|
else return array
|
@@ -65,10 +52,7 @@ export const LISP = {
|
|
65
52
|
out += first[VALUE]
|
66
53
|
break
|
67
54
|
case ATOM:
|
68
|
-
out +=
|
69
|
-
typeof first[VALUE] === 'string'
|
70
|
-
? `"${preserveEscape(first[VALUE])}"`
|
71
|
-
: first[VALUE]
|
55
|
+
out += first[VALUE]
|
72
56
|
break
|
73
57
|
case APPLY:
|
74
58
|
out += `(${first[VALUE]} ${rest.map(dfs).join(' ')})`
|
@@ -117,6 +101,6 @@ export const AST = {
|
|
117
101
|
typeof ast === 'object'
|
118
102
|
? `[${ast.map(AST.stringify).join(',')}]`
|
119
103
|
: typeof ast === 'string'
|
120
|
-
? `"${
|
104
|
+
? `"${ast}"`
|
121
105
|
: ast
|
122
106
|
}
|
package/src/utils.js
CHANGED
@@ -5,16 +5,29 @@ import { run } from './evaluator.js'
|
|
5
5
|
import { AST, isLeaf, LISP } from './parser.js'
|
6
6
|
export const logError = (error) => console.log('\x1b[31m', error, '\x1b[0m')
|
7
7
|
export const logSuccess = (output) => console.log(output, '\x1b[0m')
|
8
|
+
export const replaceStrings = (source) => {
|
9
|
+
const quotes = source.match(/"(.*?)"/g)
|
10
|
+
if (quotes)
|
11
|
+
for (const q of quotes)
|
12
|
+
source = source.replaceAll(
|
13
|
+
q,
|
14
|
+
`(string ${[...q]
|
15
|
+
.slice(1, -1)
|
16
|
+
.map((x) => x.charCodeAt(0))
|
17
|
+
.join(' ')})`
|
18
|
+
)
|
19
|
+
return source
|
20
|
+
}
|
8
21
|
export const removeNoCode = (source) =>
|
9
22
|
source
|
10
|
-
|
11
|
-
.replace(
|
12
|
-
.replace(/[\s\s]+(?=[^"]*(?:"[^"]*"[^"]*)*$)/g, ' ')
|
23
|
+
.replace(/;.+/g, '')
|
24
|
+
.replace(/[\s\s]/g, ' ')
|
13
25
|
.trim()
|
26
|
+
|
14
27
|
export const isBalancedParenthesis = (sourceCode) => {
|
15
28
|
let count = 0
|
16
29
|
const stack = []
|
17
|
-
const str = sourceCode.match(/[/\(|\)]
|
30
|
+
const str = sourceCode.match(/[/\(|\)]/g) ?? []
|
18
31
|
for (let i = 0; i < str.length; ++i)
|
19
32
|
if (str[i] === '(') stack.push(str[i])
|
20
33
|
else if (str[i] === ')') if (stack.pop() !== '(') ++count
|
@@ -38,15 +51,6 @@ export const escape = (Char) => {
|
|
38
51
|
return ''
|
39
52
|
}
|
40
53
|
}
|
41
|
-
const escapeChars = {
|
42
|
-
'\n': '\\n',
|
43
|
-
'\r': '\\r',
|
44
|
-
'\t': '\\t',
|
45
|
-
s: '\\s',
|
46
|
-
'"': '\\"'
|
47
|
-
}
|
48
|
-
export const preserveEscape = (str) =>
|
49
|
-
str.replace(/[\n\r\t\s\"]/g, (match) => escapeChars[match] || match)
|
50
54
|
export const stringifyType = (type) =>
|
51
55
|
!isLeaf(type)
|
52
56
|
? `(array ${type.map((t) => stringifyType(t)).join(' ')})`
|
@@ -156,9 +160,10 @@ export const dfs = (tree, callback) => {
|
|
156
160
|
}
|
157
161
|
export const deepClone = (ast) => AST.parse(AST.stringify(ast))
|
158
162
|
export const fez = (source, options = {}) => {
|
159
|
-
const env =
|
163
|
+
const env = Object.create(null)
|
160
164
|
try {
|
161
165
|
if (typeof source === 'string') {
|
166
|
+
if (options.strings) source = replaceStrings(source)
|
162
167
|
let code
|
163
168
|
if (!options.compile)
|
164
169
|
code = handleUnbalancedQuotes(
|
@@ -172,7 +177,7 @@ export const fez = (source, options = {}) => {
|
|
172
177
|
throw new Error(
|
173
178
|
'Top level expressions need to be wrapped in a (do) block'
|
174
179
|
)
|
175
|
-
const ast = [...
|
180
|
+
const ast = [...treeShake(parsed, std), ...parsed]
|
176
181
|
if (options.compile) {
|
177
182
|
const js = Object.values(comp(deepClone(ast))).join('')
|
178
183
|
return options.eval ? eval(js) : js
|