fez-lisp 1.0.54 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|