fez-lisp 1.3.27 → 1.4.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/README.md +29 -107
- package/index.js +3 -4
- package/lib/baked/std.js +1 -1
- package/package.json +1 -1
- package/src/compiler.js +25 -33
- package/src/evaluator.js +34 -10
- package/src/interpreter.js +164 -168
- package/src/keywords.js +8 -6
- package/src/macros.js +1 -6
- package/src/parser.js +1 -1
- package/src/utils.js +187 -6
package/package.json
CHANGED
package/src/compiler.js
CHANGED
@@ -112,11 +112,7 @@ const Helpers = {
|
|
112
112
|
not: 'not=(a)=>+!a',
|
113
113
|
and: `and=(a, b)=>+(a&&b)`,
|
114
114
|
or: `or=(a, b)=>+(a||b)`,
|
115
|
-
|
116
|
-
log_char_effect: `log_char_effect=(msg)=>{console.log(String.fromCharCode(msg));return msg}`,
|
117
|
-
log_string_effect: `log_string_effect=(msg)=>{console.log(msg.map(x=>String.fromCharCode(x)).join(''));return msg}`,
|
118
|
-
clear_effect: `clear_effect=()=>{console.clear();return 0}`,
|
119
|
-
get: 'get=(arr,i)=>arr.at(i)',
|
115
|
+
get: 'get=(arr,i)=>arr[i]',
|
120
116
|
length: 'length=(arr)=>arr.length',
|
121
117
|
__tco: `__tco=fn=>(...args)=>{let result=fn(...args);while(typeof result==='function')result=result();return result}`,
|
122
118
|
atom_predicate: `atom_predicate=(number)=>+(typeof number==='number')`,
|
@@ -144,10 +140,10 @@ const semiColumnEdgeCases = new Set([
|
|
144
140
|
';]',
|
145
141
|
';^'
|
146
142
|
])
|
147
|
-
const parse = (Arguments, Drill) => Arguments.map((x) =>
|
143
|
+
const parse = (Arguments, Drill) => Arguments.map((x) => comp(x, Drill))
|
148
144
|
const parseArgs = (Arguments, Drill, separator = ',') =>
|
149
145
|
parse(Arguments, Drill).join(separator)
|
150
|
-
const
|
146
|
+
const comp = (tree, Drill) => {
|
151
147
|
if (!tree) return ''
|
152
148
|
const [first, ...Arguments] = !isLeaf(tree) ? tree : [tree]
|
153
149
|
if (first == undefined) return '[];'
|
@@ -157,19 +153,19 @@ const compile = (tree, Drill) => {
|
|
157
153
|
case KEYWORDS.BLOCK: {
|
158
154
|
if (Arguments.length > 1) {
|
159
155
|
return `(${Arguments.map((x) =>
|
160
|
-
(
|
156
|
+
(comp(x, Drill) ?? '').toString().trim()
|
161
157
|
)
|
162
158
|
.filter((x) => x !== undefined)
|
163
159
|
.join(',')});`
|
164
160
|
} else {
|
165
|
-
const res =
|
161
|
+
const res = comp(Arguments[0], Drill)
|
166
162
|
return res !== undefined ? res.toString().trim() : ''
|
167
163
|
}
|
168
164
|
}
|
169
165
|
case KEYWORDS.CALL_FUNCTION: {
|
170
166
|
const first = Arguments.pop()
|
171
167
|
const rest = Arguments
|
172
|
-
const apply =
|
168
|
+
const apply = comp(first, Drill)
|
173
169
|
return `${
|
174
170
|
apply[apply.length - 1] === ';'
|
175
171
|
? apply.substring(0, apply.length - 1)
|
@@ -192,7 +188,7 @@ const compile = (tree, Drill) => {
|
|
192
188
|
const body = functionArgs.pop()
|
193
189
|
const FunctionDrill = { Variables: new Set(), Helpers: Drill.Helpers }
|
194
190
|
deepRename(n, `()=>${newName}`, body)
|
195
|
-
const evaluatedBody =
|
191
|
+
const evaluatedBody = comp(body, FunctionDrill)
|
196
192
|
const vars = FunctionDrill.Variables.size
|
197
193
|
? `var ${[...FunctionDrill.Variables].join(',')};`
|
198
194
|
: ''
|
@@ -211,7 +207,7 @@ const compile = (tree, Drill) => {
|
|
211
207
|
const body = functionArgs.pop()
|
212
208
|
deepRename(n, newName, body)
|
213
209
|
const FunctionDrill = { Variables: new Set(), Helpers: Drill.Helpers }
|
214
|
-
const evaluatedBody =
|
210
|
+
const evaluatedBody = comp(body, FunctionDrill)
|
215
211
|
const vars = FunctionDrill.Variables.size
|
216
212
|
? `var ${[...FunctionDrill.Variables].join(',')};`
|
217
213
|
: ''
|
@@ -224,23 +220,23 @@ const compile = (tree, Drill) => {
|
|
224
220
|
} else {
|
225
221
|
const name = lispToJavaScriptVariableName(n)
|
226
222
|
Drill.Variables.add(name)
|
227
|
-
return `${name}=${
|
223
|
+
return `${name}=${comp(Arguments[1], Drill)};`
|
228
224
|
}
|
229
225
|
}
|
230
226
|
case KEYWORDS.IS_ATOM:
|
231
227
|
Drill.Helpers.add('atom_predicate')
|
232
|
-
return `atom_predicate(${
|
228
|
+
return `atom_predicate(${comp(Arguments[0], Drill)});`
|
233
229
|
case KEYWORDS.IS_LAMBDA:
|
234
230
|
Drill.Helpers.add('lambda_predicate')
|
235
|
-
return `lambda_predicate(${
|
231
|
+
return `lambda_predicate(${comp(Arguments[0], Drill)});`
|
236
232
|
case KEYWORDS.CREATE_ARRAY:
|
237
233
|
return `[${parseArgs(Arguments, Drill)}];`
|
238
234
|
case KEYWORDS.ARRAY_LENGTH:
|
239
235
|
Drill.Helpers.add('length')
|
240
|
-
return `length(${
|
236
|
+
return `length(${comp(Arguments[0], Drill)})`
|
241
237
|
case KEYWORDS.GET_ARRAY:
|
242
238
|
Drill.Helpers.add('get')
|
243
|
-
return `get(${
|
239
|
+
return `get(${comp(Arguments[0], Drill)}, ${comp(
|
244
240
|
Arguments[1],
|
245
241
|
Drill
|
246
242
|
)});`
|
@@ -248,7 +244,7 @@ const compile = (tree, Drill) => {
|
|
248
244
|
const functionArgs = Arguments
|
249
245
|
const body = Arguments.pop()
|
250
246
|
const InnerDrills = { Variables: new Set(), Helpers: Drill.Helpers }
|
251
|
-
const evaluatedBody =
|
247
|
+
const evaluatedBody = comp(body, InnerDrills)
|
252
248
|
const vars = InnerDrills.Variables.size
|
253
249
|
? `var ${[...InnerDrills.Variables].join(',')};`
|
254
250
|
: ''
|
@@ -278,7 +274,7 @@ const compile = (tree, Drill) => {
|
|
278
274
|
return `+(${parseArgs(Arguments, Drill, token)});`
|
279
275
|
case KEYWORDS.SUBTRACTION:
|
280
276
|
return Arguments.length === 1
|
281
|
-
? `(-${
|
277
|
+
? `(-${comp(Arguments[0], Drill)});`
|
282
278
|
: `(${parse(Arguments, Drill)
|
283
279
|
// Add space so it doesn't consider it 2--1 but 2- -1
|
284
280
|
.map((x) => (typeof x === 'number' && x < 0 ? ` ${x}` : x))
|
@@ -297,21 +293,17 @@ const compile = (tree, Drill) => {
|
|
297
293
|
case KEYWORDS.BITWISE_UNSIGNED_RIGHT_SHIFT:
|
298
294
|
return `(${parseArgs(Arguments, Drill, token)});`
|
299
295
|
case KEYWORDS.REMAINDER_OF_DIVISION:
|
300
|
-
return `(${
|
301
|
-
Arguments[1],
|
302
|
-
Drill
|
303
|
-
)});`
|
296
|
+
return `(${comp(Arguments[0], Drill)}%${comp(Arguments[1], Drill)});`
|
304
297
|
case KEYWORDS.BIT_TYPE:
|
305
|
-
return `(${
|
298
|
+
return `(${comp(Arguments[0], Drill)}>>>0).toString(2)`
|
306
299
|
case KEYWORDS.BITWISE_NOT:
|
307
|
-
return `~(${
|
300
|
+
return `~(${comp(Arguments[0], Drill)})`
|
308
301
|
case KEYWORDS.NOT:
|
309
|
-
return `(+!${
|
302
|
+
return `(+!${comp(Arguments[0], Drill)})`
|
310
303
|
case KEYWORDS.IF: {
|
311
|
-
return `(${
|
312
|
-
Arguments[
|
313
|
-
|
314
|
-
)}:${Arguments.length === 3 ? compile(Arguments[2], Drill) : 0});`
|
304
|
+
return `(${comp(Arguments[0], Drill)}?${comp(Arguments[1], Drill)}:${
|
305
|
+
Arguments.length === 3 ? comp(Arguments[2], Drill) : 0
|
306
|
+
});`
|
315
307
|
}
|
316
308
|
default: {
|
317
309
|
const camelCased = lispToJavaScriptVariableName(token)
|
@@ -327,10 +319,10 @@ const compile = (tree, Drill) => {
|
|
327
319
|
}
|
328
320
|
}
|
329
321
|
const HelpersEntries = new Map(Object.entries(Helpers))
|
330
|
-
export const
|
322
|
+
export const compile = (ast) => {
|
331
323
|
const Drill = { Variables: new Set(), Helpers: new Set() }
|
332
324
|
const raw = AST.parse(AST.stringify(ast)) // cloning for fn renames mutations
|
333
|
-
.map((tree) =>
|
325
|
+
.map((tree) => comp(tree, Drill))
|
334
326
|
.filter((x) => x !== undefined)
|
335
327
|
.join('\n')
|
336
328
|
let program = ''
|
@@ -348,5 +340,5 @@ export const comp = (ast) => {
|
|
348
340
|
? `var ${[...Drill.Variables].join(',')};`
|
349
341
|
: ''
|
350
342
|
const top = `${help}${vars}`
|
351
|
-
return
|
343
|
+
return `(()=>{${top};return${program}})()`
|
352
344
|
}
|
package/src/evaluator.js
CHANGED
@@ -1,34 +1,58 @@
|
|
1
|
-
import {
|
1
|
+
import { keywords } from './interpreter.js'
|
2
|
+
import {
|
3
|
+
APPLY,
|
4
|
+
ATOM,
|
5
|
+
KEYWORDS,
|
6
|
+
SPECIAL_FORMS_SET,
|
7
|
+
TYPE,
|
8
|
+
VALUE,
|
9
|
+
WORD
|
10
|
+
} from './keywords.js'
|
2
11
|
import { isLeaf } from './parser.js'
|
3
12
|
import { stringifyArgs } from './utils.js'
|
4
|
-
|
5
|
-
export const
|
13
|
+
export const DEFAULT_MAXIMUM_FUNCTION_CALLS = 262144
|
14
|
+
export const MAXIMUM_FUNCTION_CALLS = process.env.FEZ_MAXIMUM_FUNCTION_CALLS
|
15
|
+
? +process.env.FEZ_MAXIMUM_FUNCTION_CALLS
|
16
|
+
: DEFAULT_MAXIMUM_FUNCTION_CALLS
|
17
|
+
export const evaluate = (exp, env = keywords) => {
|
6
18
|
const [first, ...rest] = isLeaf(exp) ? [exp] : exp
|
7
19
|
if (first == undefined) return []
|
20
|
+
const value = first[VALUE]
|
8
21
|
switch (first[TYPE]) {
|
9
22
|
case WORD: {
|
10
|
-
const word = env[
|
23
|
+
const word = env[value]
|
11
24
|
if (word == undefined)
|
12
|
-
throw new ReferenceError(`Undefined variable ${
|
25
|
+
throw new ReferenceError(`Undefined variable ${value}`)
|
13
26
|
return word
|
14
27
|
}
|
15
28
|
case APPLY: {
|
16
|
-
const apply = env[
|
29
|
+
const apply = env[value]
|
17
30
|
if (apply == undefined)
|
18
31
|
throw new ReferenceError(
|
19
|
-
`Undefined (${KEYWORDS.ANONYMOUS_FUNCTION}) ${
|
32
|
+
`Undefined (${KEYWORDS.ANONYMOUS_FUNCTION}) (${value})`
|
20
33
|
)
|
21
34
|
if (typeof apply !== 'function')
|
22
35
|
throw new TypeError(
|
23
|
-
`${
|
36
|
+
`${value} is not a (${KEYWORDS.ANONYMOUS_FUNCTION})`
|
24
37
|
)
|
25
|
-
|
38
|
+
const isSpecial = SPECIAL_FORMS_SET.has(value)
|
39
|
+
if (!isSpecial) {
|
40
|
+
evaluate.count = (evaluate.count || 0) + 1
|
41
|
+
if (evaluate.count > MAXIMUM_FUNCTION_CALLS) {
|
42
|
+
evaluate.count = 0
|
43
|
+
throw new RangeError('Maximum function invocation limit exceeded')
|
44
|
+
}
|
45
|
+
}
|
46
|
+
const result = apply(rest, env, value)
|
47
|
+
if (!isSpecial) evaluate.stack.pop()
|
48
|
+
return result
|
26
49
|
}
|
27
50
|
case ATOM:
|
28
|
-
return
|
51
|
+
return value
|
29
52
|
default:
|
30
53
|
throw new ReferenceError(
|
31
54
|
`Attempting to access Undefined near ${stringifyArgs(exp)}`
|
32
55
|
)
|
33
56
|
}
|
34
57
|
}
|
58
|
+
evaluate.stack = [KEYWORDS.CALL_FUNCTION]
|