fez-lisp 1.0.54 → 1.1.2
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 +64 -21
- 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 -76
package/package.json
CHANGED
package/src/compiler.js
CHANGED
@@ -8,8 +8,65 @@ import {
|
|
8
8
|
WORD
|
9
9
|
} from './keywords.js'
|
10
10
|
import { leaf, isLeaf } from './parser.js'
|
11
|
-
import { deepRename
|
11
|
+
import { deepRename } from './utils.js'
|
12
|
+
const earMuffsToLodashes = (name) => name.replace(new RegExp(/\*/g), '_')
|
13
|
+
const dotNamesToEmpty = (name) => name.replace(new RegExp(/\./g), '')
|
14
|
+
const commaToLodash = (name) => name.replace(new RegExp(/\,/g), '_')
|
15
|
+
const arrowFromTo = (name) => name.replace(new RegExp(/->/g), '-to-')
|
16
|
+
const moduleNameToLodashes = (name) => name.replace(new RegExp(/:/g), '_')
|
17
|
+
const questionMarkToPredicate = (name) =>
|
18
|
+
name.replace(new RegExp(/\?/g), 'Predicate')
|
19
|
+
const exclamationMarkMarkToEffect = (name) =>
|
20
|
+
name.replace(new RegExp(/\!/g), 'Effect')
|
21
|
+
const toCamelCase = (name) => {
|
22
|
+
let out = name[0]
|
23
|
+
for (let i = 1; i < name.length; ++i) {
|
24
|
+
const current = name[i],
|
25
|
+
prev = name[i - 1]
|
26
|
+
if (current === '-') continue
|
27
|
+
else if (prev === '-') out += current.toUpperCase()
|
28
|
+
else out += current
|
29
|
+
}
|
30
|
+
return out
|
31
|
+
}
|
32
|
+
const keywordToHelper = (name) => {
|
33
|
+
switch (name) {
|
34
|
+
case KEYWORDS.ADDITION:
|
35
|
+
return '__add'
|
36
|
+
case KEYWORDS.MULTIPLICATION:
|
37
|
+
return '__mult'
|
38
|
+
case KEYWORDS.SUBTRACTION:
|
39
|
+
return '__sub'
|
40
|
+
case KEYWORDS.GREATHER_THAN:
|
41
|
+
return '__gt'
|
42
|
+
case KEYWORDS.EQUAL:
|
43
|
+
return '__eq'
|
44
|
+
case KEYWORDS.GREATHER_THAN_OR_EQUAL:
|
45
|
+
return '__gteq'
|
46
|
+
case KEYWORDS.LESS_THAN:
|
47
|
+
return '__lt'
|
48
|
+
case KEYWORDS.LESS_THAN_OR_EQUAL:
|
49
|
+
return '__lteq'
|
50
|
+
default:
|
51
|
+
return name
|
52
|
+
}
|
53
|
+
}
|
54
|
+
const lispToJavaScriptVariableName = (name) =>
|
55
|
+
toCamelCase(
|
56
|
+
arrowFromTo(
|
57
|
+
dotNamesToEmpty(
|
58
|
+
exclamationMarkMarkToEffect(
|
59
|
+
questionMarkToPredicate(
|
60
|
+
commaToLodash(
|
61
|
+
moduleNameToLodashes(earMuffsToLodashes(keywordToHelper(name)))
|
62
|
+
)
|
63
|
+
)
|
64
|
+
)
|
65
|
+
)
|
66
|
+
)
|
67
|
+
)
|
12
68
|
const Helpers = {
|
69
|
+
__string: `__string=(...args)=>{const str=args.flat();str.isString=true;return str}`,
|
13
70
|
__add: `__add=(...numbers)=>{return numbers.reduce((a,b)=>a+b,0)}`,
|
14
71
|
__sub: `__sub=(...numbers)=>{return numbers.reduce((a,b)=>a-b,0)}`,
|
15
72
|
__mult: `__mult=(...numbers)=>{return numbers.reduce((a,b)=>a*b,1)}`,
|
@@ -46,13 +103,10 @@ const Helpers = {
|
|
46
103
|
length: 'length=(arr)=>arr.length',
|
47
104
|
__tco: `__tco=fn=>(...args)=>{let result=fn(...args);while(typeof result==='function')result=result();return result}`,
|
48
105
|
numberPredicate: `numberPredicate=(number)=>+(typeof number==='number')`,
|
49
|
-
stringPredicate: `stringPredicate=(string)=>+(typeof string==='string')`,
|
50
106
|
lambdaPredicate: `lambdaPredicate=(lambda)=>+(typeof lambda==='function')`,
|
51
107
|
arrayPredicate: `arrayPredicate=(array)=>+Array.isArray(array)`,
|
52
|
-
atomPredicate: `atomPredicate=(value)=>+(typeof value==='number'||typeof value==='string')`,
|
53
108
|
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}}`
|
109
|
+
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
110
|
}
|
57
111
|
const semiColumnEdgeCases = new Set([
|
58
112
|
';)',
|
@@ -116,9 +170,6 @@ const compile = (tree, Drill) => {
|
|
116
170
|
out += `),${name});`
|
117
171
|
return out
|
118
172
|
}
|
119
|
-
case KEYWORDS.IS_STRING:
|
120
|
-
Drill.Helpers.add('stringPredicate')
|
121
|
-
return `stringPredicate(${compile(Arguments[0], Drill)});`
|
122
173
|
case KEYWORDS.IS_NUMBER:
|
123
174
|
Drill.Helpers.add('numberPredicate')
|
124
175
|
return `numberPredicate(${compile(Arguments[0], Drill)});`
|
@@ -133,19 +184,17 @@ const compile = (tree, Drill) => {
|
|
133
184
|
case KEYWORDS.BOOLEAN_TYPE:
|
134
185
|
return '1'
|
135
186
|
case KEYWORDS.STRING_TYPE:
|
136
|
-
|
187
|
+
Drill.Helpers.add('__string')
|
188
|
+
return `__string(${parseArgs(Arguments, Drill)});`
|
137
189
|
case KEYWORDS.ARRAY_TYPE:
|
138
190
|
return Arguments.length === 2 &&
|
139
191
|
Arguments[1][TYPE] === WORD &&
|
140
192
|
Arguments[1][VALUE] === 'length'
|
141
|
-
? `(new Array(${compile(Arguments[0], Drill)}).fill(0))
|
193
|
+
? `(new Array(${compile(Arguments[0], Drill)}).fill(0));`
|
142
194
|
: `[${parseArgs(Arguments, Drill)}];`
|
143
|
-
case KEYWORDS.
|
195
|
+
case KEYWORDS.ARRAY_LENGTH:
|
144
196
|
Drill.Helpers.add('length')
|
145
197
|
return `length(${compile(Arguments[0], Drill)})`
|
146
|
-
case KEYWORDS.IS_ATOM:
|
147
|
-
Drill.Helpers.add('atomPredicate')
|
148
|
-
return `atomPredicate(${compile(Arguments[0], Drill)});`
|
149
198
|
case KEYWORDS.FIRST_ARRAY:
|
150
199
|
Drill.Helpers.add('car')
|
151
200
|
return `car(${compile(Arguments[0], Drill)});`
|
@@ -295,9 +344,6 @@ const compile = (tree, Drill) => {
|
|
295
344
|
out += '0);'
|
296
345
|
return out
|
297
346
|
}
|
298
|
-
case KEYWORDS.CAST_TYPE:
|
299
|
-
Drill.Helpers.add('cast')
|
300
|
-
return `cast("${Arguments[1][VALUE]}", ${compile(Arguments[0], Drill)})`
|
301
347
|
case KEYWORDS.PIPE: {
|
302
348
|
let inp = Arguments[0]
|
303
349
|
for (let i = 1; i < Arguments.length; ++i)
|
@@ -319,10 +365,7 @@ const compile = (tree, Drill) => {
|
|
319
365
|
return `${camelCased}(${parseArgs(Arguments, Drill)});`
|
320
366
|
}
|
321
367
|
}
|
322
|
-
} else if (first[TYPE] === ATOM)
|
323
|
-
return typeof first[VALUE] === 'string'
|
324
|
-
? `\`${first[VALUE]}\``
|
325
|
-
: first[VALUE]
|
368
|
+
} else if (first[TYPE] === ATOM) return first[VALUE]
|
326
369
|
else if (first[TYPE] === WORD) {
|
327
370
|
const camelCased = lispToJavaScriptVariableName(token)
|
328
371
|
if (camelCased in Helpers) Drill.Helpers.add(camelCased)
|
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
|
@@ -199,51 +204,6 @@ export const fez = (source, options = {}) => {
|
|
199
204
|
return err
|
200
205
|
}
|
201
206
|
}
|
202
|
-
|
203
|
-
export const earMuffsToLodashes = (name) => name.replace(new RegExp(/\*/g), '_')
|
204
|
-
export const dotNamesToEmpty = (name) => name.replace(new RegExp(/\./g), '')
|
205
|
-
export const colonNamesTo$ = (name) => name.replace(new RegExp(/\:/g), '$')
|
206
|
-
export const commaToLodash = (name) => name.replace(new RegExp(/\,/g), '_')
|
207
|
-
export const arrowFromTo = (name) => name.replace(new RegExp(/->/g), '-to-')
|
208
|
-
export const moduleNameToLodashes = (name) =>
|
209
|
-
name.replace(new RegExp(/:/g), '_')
|
210
|
-
export const questionMarkToLodash = (name) =>
|
211
|
-
name.replace(new RegExp(/\?/g), 'Predicate')
|
212
|
-
export const exclamationMarkMarkToLodash = (name) =>
|
213
|
-
name.replace(new RegExp(/\!/g), 'Effect')
|
214
|
-
export const toCamelCase = (name) => {
|
215
|
-
let out = name[0]
|
216
|
-
for (let i = 1; i < name.length; ++i) {
|
217
|
-
const current = name[i],
|
218
|
-
prev = name[i - 1]
|
219
|
-
if (current === '-') continue
|
220
|
-
else if (prev === '-') out += current.toUpperCase()
|
221
|
-
else out += current
|
222
|
-
}
|
223
|
-
return out
|
224
|
-
}
|
225
|
-
export const keywordToHelper = (name) => {
|
226
|
-
switch (name) {
|
227
|
-
case KEYWORDS.ADDITION:
|
228
|
-
return '__add'
|
229
|
-
case KEYWORDS.MULTIPLICATION:
|
230
|
-
return '__mult'
|
231
|
-
case KEYWORDS.SUBTRACTION:
|
232
|
-
return '__sub'
|
233
|
-
case KEYWORDS.GREATHER_THAN:
|
234
|
-
return '__gt'
|
235
|
-
case KEYWORDS.EQUAL:
|
236
|
-
return '__eq'
|
237
|
-
case KEYWORDS.GREATHER_THAN_OR_EQUAL:
|
238
|
-
return '__gteq'
|
239
|
-
case KEYWORDS.LESS_THAN:
|
240
|
-
return '__lt'
|
241
|
-
case KEYWORDS.LESS_THAN_OR_EQUAL:
|
242
|
-
return '__lteq'
|
243
|
-
default:
|
244
|
-
return name
|
245
|
-
}
|
246
|
-
}
|
247
207
|
export const deepRename = (name, newName, tree) => {
|
248
208
|
if (!isLeaf(tree))
|
249
209
|
for (const leaf of tree) {
|
@@ -291,19 +251,3 @@ export const tree = (source, std) =>
|
|
291
251
|
std
|
292
252
|
? shake(LISP.parse(removeNoCode(source)), std)
|
293
253
|
: LISP.parse(removeNoCode(source))
|
294
|
-
export const lispToJavaScriptVariableName = (name) =>
|
295
|
-
toCamelCase(
|
296
|
-
arrowFromTo(
|
297
|
-
dotNamesToEmpty(
|
298
|
-
colonNamesTo$(
|
299
|
-
exclamationMarkMarkToLodash(
|
300
|
-
questionMarkToLodash(
|
301
|
-
commaToLodash(
|
302
|
-
moduleNameToLodashes(earMuffsToLodashes(keywordToHelper(name)))
|
303
|
-
)
|
304
|
-
)
|
305
|
-
)
|
306
|
-
)
|
307
|
-
)
|
308
|
-
)
|
309
|
-
)
|