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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "fez-lisp",
3
3
  "description": "Lisp interpreted & compiled to JavaScript",
4
4
  "author": "AT290690",
5
- "version": "1.3.27",
5
+ "version": "1.4.0",
6
6
  "type": "module",
7
7
  "main": "index.js",
8
8
  "keywords": [
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
- log_effect: `log_effect=(msg)=>{console.log(msg);return msg}`,
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) => compile(x, Drill))
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 compile = (tree, Drill) => {
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
- (compile(x, Drill) ?? '').toString().trim()
156
+ (comp(x, Drill) ?? '').toString().trim()
161
157
  )
162
158
  .filter((x) => x !== undefined)
163
159
  .join(',')});`
164
160
  } else {
165
- const res = compile(Arguments[0], Drill)
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 = compile(first, Drill)
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 = compile(body, FunctionDrill)
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 = compile(body, FunctionDrill)
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}=${compile(Arguments[1], Drill)};`
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(${compile(Arguments[0], Drill)});`
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(${compile(Arguments[0], Drill)});`
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(${compile(Arguments[0], Drill)})`
236
+ return `length(${comp(Arguments[0], Drill)})`
241
237
  case KEYWORDS.GET_ARRAY:
242
238
  Drill.Helpers.add('get')
243
- return `get(${compile(Arguments[0], Drill)}, ${compile(
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 = compile(body, InnerDrills)
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
- ? `(-${compile(Arguments[0], Drill)});`
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 `(${compile(Arguments[0], Drill)}%${compile(
301
- Arguments[1],
302
- Drill
303
- )});`
296
+ return `(${comp(Arguments[0], Drill)}%${comp(Arguments[1], Drill)});`
304
297
  case KEYWORDS.BIT_TYPE:
305
- return `(${compile(Arguments[0], Drill)}>>>0).toString(2)`
298
+ return `(${comp(Arguments[0], Drill)}>>>0).toString(2)`
306
299
  case KEYWORDS.BITWISE_NOT:
307
- return `~(${compile(Arguments[0], Drill)})`
300
+ return `~(${comp(Arguments[0], Drill)})`
308
301
  case KEYWORDS.NOT:
309
- return `(+!${compile(Arguments[0], Drill)})`
302
+ return `(+!${comp(Arguments[0], Drill)})`
310
303
  case KEYWORDS.IF: {
311
- return `(${compile(Arguments[0], Drill)}?${compile(
312
- Arguments[1],
313
- Drill
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 comp = (ast) => {
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) => compile(tree, Drill))
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 `${top}${program}`
343
+ return `(()=>{${top};return${program}})()`
352
344
  }
package/src/evaluator.js CHANGED
@@ -1,34 +1,58 @@
1
- import { APPLY, ATOM, KEYWORDS, TYPE, VALUE, WORD } from './keywords.js'
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 evaluate = (exp, env) => {
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[first[VALUE]]
23
+ const word = env[value]
11
24
  if (word == undefined)
12
- throw new ReferenceError(`Undefined variable ${first[VALUE]}`)
25
+ throw new ReferenceError(`Undefined variable ${value}`)
13
26
  return word
14
27
  }
15
28
  case APPLY: {
16
- const apply = env[first[VALUE]]
29
+ const apply = env[value]
17
30
  if (apply == undefined)
18
31
  throw new ReferenceError(
19
- `Undefined (${KEYWORDS.ANONYMOUS_FUNCTION}) ${first[VALUE]}`
32
+ `Undefined (${KEYWORDS.ANONYMOUS_FUNCTION}) (${value})`
20
33
  )
21
34
  if (typeof apply !== 'function')
22
35
  throw new TypeError(
23
- `${first[VALUE]} is not a (${KEYWORDS.ANONYMOUS_FUNCTION})`
36
+ `${value} is not a (${KEYWORDS.ANONYMOUS_FUNCTION})`
24
37
  )
25
- return apply(rest, env)
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 first[VALUE]
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]