fez-lisp 1.3.28 → 1.4.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 +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 +23 -11
- package/src/interpreter.js +166 -168
- package/src/keywords.js +8 -6
- package/src/macros.js +1 -6
- package/src/parser.js +3 -3
- package/src/utils.js +190 -39
package/src/keywords.js
CHANGED
@@ -39,12 +39,7 @@ export const KEYWORDS = {
|
|
39
39
|
CALL_FUNCTION: 'apply',
|
40
40
|
DEFINE_VARIABLE: 'let',
|
41
41
|
|
42
|
-
SET_ARRAY: 'set!'
|
43
|
-
|
44
|
-
LOG: 'log!',
|
45
|
-
LOG_STRING: 'log-string!',
|
46
|
-
LOG_CHAR: 'log-char!',
|
47
|
-
CLEAR_CONSOLE: 'clear!'
|
42
|
+
SET_ARRAY: 'set!'
|
48
43
|
}
|
49
44
|
|
50
45
|
export const TYPES = {
|
@@ -56,3 +51,10 @@ export const RUNTIME_TYPES = {
|
|
56
51
|
NUMBER: 'number',
|
57
52
|
ARRAY: 'array'
|
58
53
|
}
|
54
|
+
export const DEBUG = {
|
55
|
+
LOG: 'log',
|
56
|
+
ASSERT: 'assert',
|
57
|
+
ERROR: 'error'
|
58
|
+
}
|
59
|
+
|
60
|
+
export const SPECIAL_FORMS_SET = new Set(Object.values(KEYWORDS))
|
package/src/macros.js
CHANGED
@@ -31,7 +31,7 @@ export const SUGGAR = {
|
|
31
31
|
}
|
32
32
|
export const deSuggarAst = (ast, scope) => {
|
33
33
|
if (scope === undefined) scope = ast
|
34
|
-
if (ast.length === 0) throw new SyntaxError(`No expressions
|
34
|
+
if (ast.length === 0) throw new SyntaxError(`No expressions...`)
|
35
35
|
// for (const node of ast)
|
36
36
|
// if (node[0] && node[0][TYPE] === APPLY && node[0][VALUE] === KEYWORDS.BLOCK)
|
37
37
|
// throw new SyntaxError(`Top level (${KEYWORDS.BLOCK}) is not allowed`)
|
@@ -667,11 +667,6 @@ const iron2 = (scope, exp) => {
|
|
667
667
|
}
|
668
668
|
export const replaceQuotes = (source) =>
|
669
669
|
source
|
670
|
-
.replaceAll(/\'\(/g, `(${KEYWORDS.CREATE_ARRAY} `)
|
671
|
-
.replaceAll(/\`\(/g, `(${SUGGAR.CREATE_LIST} `)
|
672
|
-
.replaceAll(/\(\)/g, `(${KEYWORDS.CREATE_ARRAY})`)
|
673
|
-
.replaceAll(/\[\]/g, `(${KEYWORDS.CREATE_ARRAY})`)
|
674
|
-
.replaceAll(/\{\}/g, `(${SUGGAR.CREATE_LIST})`)
|
675
670
|
.replaceAll(/\[/g, `(${KEYWORDS.CREATE_ARRAY} `)
|
676
671
|
.replaceAll(/\]/g, ')')
|
677
672
|
.replaceAll(/\{/g, `(${SUGGAR.CREATE_LIST} `)
|
package/src/parser.js
CHANGED
@@ -30,6 +30,8 @@ export const LISP = {
|
|
30
30
|
},
|
31
31
|
stringify: (array) => {
|
32
32
|
if (array == undefined) return '()'
|
33
|
+
else if (typeof array === 'function') return '(lambda)'
|
34
|
+
else if (typeof array === 'boolean') return +array
|
33
35
|
else if (typeof array === 'object')
|
34
36
|
if (Array.isArray(array))
|
35
37
|
return array.length
|
@@ -39,8 +41,6 @@ export const LISP = {
|
|
39
41
|
return `(array ${array
|
40
42
|
.map(([key, value]) => `("${key}" ${LISP.stringify(value)})`)
|
41
43
|
.join(' ')})`
|
42
|
-
else if (typeof array === 'function') return '()'
|
43
|
-
else if (typeof array === 'boolean') return +array
|
44
44
|
else return array
|
45
45
|
},
|
46
46
|
source: (ast) => {
|
@@ -61,7 +61,7 @@ export const LISP = {
|
|
61
61
|
}
|
62
62
|
return out
|
63
63
|
}
|
64
|
-
return
|
64
|
+
return dfs(ast)
|
65
65
|
}
|
66
66
|
}
|
67
67
|
export const AST = {
|
package/src/utils.js
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
import std from '../lib/baked/std.js'
|
2
|
-
import {
|
3
|
-
import {
|
2
|
+
import { compile, OPTIMIZATIONS } from './compiler.js'
|
3
|
+
import {
|
4
|
+
APPLY,
|
5
|
+
ATOM,
|
6
|
+
DEBUG,
|
7
|
+
FALSE,
|
8
|
+
KEYWORDS,
|
9
|
+
RUNTIME_TYPES,
|
10
|
+
TRUE,
|
11
|
+
TYPE,
|
12
|
+
VALUE,
|
13
|
+
WORD
|
14
|
+
} from './keywords.js'
|
4
15
|
import { evaluate } from './evaluator.js'
|
5
16
|
import { AST, isLeaf, LISP } from './parser.js'
|
6
17
|
import {
|
@@ -12,8 +23,6 @@ import { keywords } from './interpreter.js'
|
|
12
23
|
export const logError = (error) =>
|
13
24
|
console.log('\x1b[31m', `\n${error}\n`, '\x1b[0m')
|
14
25
|
export const logSuccess = (output) => console.log('\x1b[32m', output, '\x1b[0m')
|
15
|
-
// export const replaceEmptyArrays = (source) =>
|
16
|
-
// source
|
17
26
|
export const removeNoCode = (source) =>
|
18
27
|
source
|
19
28
|
.replace(/;.+/g, '')
|
@@ -149,6 +158,7 @@ export const handleUnbalancedParens = (source) => {
|
|
149
158
|
export const removeMutation = (source) => source.replace(new RegExp(/!/g), 'ǃ')
|
150
159
|
const isDefinition = (x) =>
|
151
160
|
x[TYPE] === APPLY && x[VALUE] === KEYWORDS.DEFINE_VARIABLE
|
161
|
+
// [[, [, libs]]] is because std is wrapped in (apply (lambda (do ...)))
|
152
162
|
const toDeps = ([[, [, libs]]]) =>
|
153
163
|
libs.reduce(
|
154
164
|
(a, x, i) => a.set(x.at(1)[VALUE], { value: x, index: i }),
|
@@ -236,19 +246,18 @@ export const fez = (source, options = {}) => {
|
|
236
246
|
const scope = deSuggarAst(parsed)
|
237
247
|
const ast = wrapInBlock(shake(scope, std))
|
238
248
|
// if (options.check) typeCheck(ast)
|
239
|
-
if (options.compile) return
|
249
|
+
if (options.compile) return compile(ast)
|
240
250
|
return evaluate(ast, env)
|
241
251
|
} else if (Array.isArray(source)) {
|
242
252
|
const ast = !options.mutation
|
243
253
|
? AST.parse(AST.stringify(source).replace(new RegExp(/!/g), 'ǃ'))
|
244
254
|
: source
|
245
|
-
if (options.compile) return
|
255
|
+
if (options.compile) return compile(ast)
|
246
256
|
return evaluate(ast, env)
|
247
257
|
} else {
|
248
258
|
throw new Error('Source has to be either a lisp source code or an AST')
|
249
259
|
}
|
250
260
|
} catch (error) {
|
251
|
-
// console.log(error)
|
252
261
|
const err = error.message.replace("'[object Array]'", '(array)')
|
253
262
|
// .replace('object', '(array)')
|
254
263
|
logError(err)
|
@@ -256,38 +265,6 @@ export const fez = (source, options = {}) => {
|
|
256
265
|
return err
|
257
266
|
}
|
258
267
|
}
|
259
|
-
export const compress = (source) => {
|
260
|
-
let { result, occurance } = source.split('').reduce(
|
261
|
-
(acc, item) => {
|
262
|
-
if (item === ')') acc.occurance++
|
263
|
-
else {
|
264
|
-
if (acc.occurance < 3) {
|
265
|
-
acc.result += ')'.repeat(acc.occurance)
|
266
|
-
acc.occurance = 0
|
267
|
-
} else {
|
268
|
-
acc.result += '·' + acc.occurance
|
269
|
-
acc.occurance = 0
|
270
|
-
}
|
271
|
-
acc.result += item
|
272
|
-
}
|
273
|
-
return acc
|
274
|
-
},
|
275
|
-
{ result: '', occurance: 0 }
|
276
|
-
)
|
277
|
-
if (occurance > 0) result += '·' + occurance
|
278
|
-
return result
|
279
|
-
}
|
280
|
-
export const decompress = (raw) => {
|
281
|
-
const suffix = [...new Set(raw.match(/·+?\d+/g))]
|
282
|
-
const runes = suffix.reduce(
|
283
|
-
(acc, m) => acc.split(m).join(')'.repeat(parseInt(m.substring(1)))),
|
284
|
-
raw
|
285
|
-
)
|
286
|
-
let result = ''
|
287
|
-
for (const tok of runes) result += tok
|
288
|
-
return result
|
289
|
-
}
|
290
|
-
// shake(LISP.parse(removeNoCode(source)), std)
|
291
268
|
export const shake = (parsed, std) => treeShake(parsed, std).concat(parsed)
|
292
269
|
export const tree = (source, std) =>
|
293
270
|
std
|
@@ -313,4 +290,178 @@ export const ast = (source, deps) =>
|
|
313
290
|
deps.reduce((a, b) => a.concat(b), [])
|
314
291
|
)
|
315
292
|
)
|
293
|
+
|
316
294
|
export const astWithStd = (source) => wrapInBlock(shake(prep(source), std))
|
295
|
+
export const parse = (source) =>
|
296
|
+
wrapInBlock(
|
297
|
+
shake(
|
298
|
+
deSuggarAst(
|
299
|
+
LISP.parse(
|
300
|
+
removeNoCode(
|
301
|
+
handleUnbalancedQuotes(
|
302
|
+
handleUnbalancedParens(deSuggarSource(source))
|
303
|
+
)
|
304
|
+
)
|
305
|
+
)
|
306
|
+
),
|
307
|
+
std
|
308
|
+
)
|
309
|
+
)
|
310
|
+
|
311
|
+
const identity = (name) => [
|
312
|
+
[0, 'let'],
|
313
|
+
[1, name],
|
314
|
+
[
|
315
|
+
[0, 'lambda'],
|
316
|
+
[1, 'x'],
|
317
|
+
[1, 'x']
|
318
|
+
]
|
319
|
+
]
|
320
|
+
export const callStack = [KEYWORDS.CALL_FUNCTION]
|
321
|
+
export const debug = (ast) => {
|
322
|
+
callStack.length = 0
|
323
|
+
callStack.push(KEYWORDS.CALL_FUNCTION)
|
324
|
+
try {
|
325
|
+
const debugEnv = {
|
326
|
+
...keywords,
|
327
|
+
[DEBUG.LOG]: (args, env) => {
|
328
|
+
if (args.length !== 1 && args.length !== 2)
|
329
|
+
throw new RangeError(
|
330
|
+
`Invalid number of arguments to (${DEBUG.LOG}) (or (= 1) (= 2)) (${
|
331
|
+
DEBUG.LOG
|
332
|
+
} ${stringifyArgs(args)})`
|
333
|
+
)
|
334
|
+
const expression = evaluate(args[0], env)
|
335
|
+
if (args.length === 2) {
|
336
|
+
const option = evaluate(args[1], env)
|
337
|
+
if (!Array.isArray(option)) {
|
338
|
+
throw new TypeError(
|
339
|
+
`Second argument of (${DEBUG.LOG}) must be an (${
|
340
|
+
RUNTIME_TYPES.ARRAY
|
341
|
+
}) but got (${expression}) (${DEBUG.LOG} ${stringifyArgs(args)})`
|
342
|
+
)
|
343
|
+
}
|
344
|
+
const type = option.map((x) => String.fromCharCode(x)).join('')
|
345
|
+
switch (type) {
|
346
|
+
case 'string':
|
347
|
+
case 'str':
|
348
|
+
{
|
349
|
+
if (!Array.isArray(expression))
|
350
|
+
throw new TypeError(
|
351
|
+
`Argument of (${DEBUG.LOG}) must be an (${
|
352
|
+
RUNTIME_TYPES.ARRAY
|
353
|
+
}) in the case ${type} but got (${expression}) (${
|
354
|
+
DEBUG.LOG
|
355
|
+
} ${stringifyArgs(args)})`
|
356
|
+
)
|
357
|
+
console.log(
|
358
|
+
expression.map((x) => String.fromCharCode(x)).join('')
|
359
|
+
)
|
360
|
+
}
|
361
|
+
break
|
362
|
+
case 'char':
|
363
|
+
case 'ch':
|
364
|
+
{
|
365
|
+
if (typeof expression !== 'number')
|
366
|
+
throw new TypeError(
|
367
|
+
`Argument argument of (${DEBUG.LOG}) must be a (${
|
368
|
+
RUNTIME_TYPES.NUMBER
|
369
|
+
}) in the case ${type} but got (${expression}) (${
|
370
|
+
DEBUG.LOG
|
371
|
+
} ${stringifyArgs(args)})`
|
372
|
+
)
|
373
|
+
console.log(String.fromCharCode(expression))
|
374
|
+
}
|
375
|
+
|
376
|
+
break
|
377
|
+
case '*':
|
378
|
+
console.log(expression)
|
379
|
+
break
|
380
|
+
default:
|
381
|
+
throw new TypeError(
|
382
|
+
`Invalid number of option to (${
|
383
|
+
DEBUG.LOG
|
384
|
+
}) got ${option} ${stringifyArgs(args)})`
|
385
|
+
)
|
386
|
+
}
|
387
|
+
} else console.log(expression)
|
388
|
+
return expression
|
389
|
+
},
|
390
|
+
[DEBUG.ERROR]: (args, env) => {
|
391
|
+
if (args.length !== 1)
|
392
|
+
throw new RangeError(
|
393
|
+
`Invalid number of arguments to (${DEBUG.ERROR}) (= 1 required) (${
|
394
|
+
DEBUG.ERROR
|
395
|
+
} ${stringifyArgs(args)})`
|
396
|
+
)
|
397
|
+
const expression = evaluate(args[0], env)
|
398
|
+
if (!Array.isArray(expression))
|
399
|
+
throw new TypeError(
|
400
|
+
`Argument of (${DEBUG.ERROR}) must be an (${
|
401
|
+
DEBUG.ARRAY_TYPE
|
402
|
+
}) but got (${expression}) (${DEBUG.ERROR} ${stringifyArgs(args)})`
|
403
|
+
)
|
404
|
+
throw new Error(expression.map((x) => String.fromCharCode(x)).join(''))
|
405
|
+
},
|
406
|
+
[DEBUG.ASSERT]: (args, env) => {
|
407
|
+
if (args.length < 2)
|
408
|
+
throw new RangeError(
|
409
|
+
`Invalid number of arguments for (${
|
410
|
+
DEBUG.ASSERT
|
411
|
+
}), expected (> 2 required) but got ${args.length} (${
|
412
|
+
DEBUG.ASSERT
|
413
|
+
} ${stringifyArgs(args)})`
|
414
|
+
)
|
415
|
+
if (args.length % 2 !== 0)
|
416
|
+
throw new RangeError(
|
417
|
+
`Invalid number of arguments for (${
|
418
|
+
DEBUG.ASSERT
|
419
|
+
}), expected even number of arguments but got ${args.length} (${
|
420
|
+
DEBUG.ASSERT
|
421
|
+
} ${stringifyArgs(args)})`
|
422
|
+
)
|
423
|
+
for (let i = 0; i < args.length; i += 2) {
|
424
|
+
const condition = evaluate(args[i], env)
|
425
|
+
if (condition !== FALSE && condition !== TRUE)
|
426
|
+
throw new TypeError(
|
427
|
+
`Condition of (${
|
428
|
+
DEBUG.ASSERT
|
429
|
+
}) must be ${TRUE} or ${FALSE} but got (${
|
430
|
+
DEBUG.ASSERT
|
431
|
+
} ${stringifyArgs(args)})`
|
432
|
+
)
|
433
|
+
if (condition) {
|
434
|
+
const error = args[i + 1]
|
435
|
+
if (error[0][TYPE] === APPLY && error[0][VALUE] === DEBUG.ERROR)
|
436
|
+
return evaluate(error, env)
|
437
|
+
else
|
438
|
+
throw new TypeError(
|
439
|
+
`Concequence of (${DEBUG.ASSERT}) must be (${
|
440
|
+
DEBUG.ERROR
|
441
|
+
}) but got (${DEBUG.ASSERT} ${stringifyArgs(args)})`
|
442
|
+
)
|
443
|
+
}
|
444
|
+
}
|
445
|
+
return 0
|
446
|
+
}
|
447
|
+
}
|
448
|
+
evaluate(ast, debugEnv)
|
449
|
+
} catch (error) {
|
450
|
+
const isMaxCallStack =
|
451
|
+
error.message.includes('Maximum call stack size exceeded') ||
|
452
|
+
error.message.includes('too much recursion')
|
453
|
+
if (!isMaxCallStack) {
|
454
|
+
error.message += `\n\nscope:\n(${callStack.at(-1)})`
|
455
|
+
throw error
|
456
|
+
} else logError(error.message)
|
457
|
+
}
|
458
|
+
const block = ast[1][1]
|
459
|
+
const temp = block.shift()
|
460
|
+
block.unshift(
|
461
|
+
temp,
|
462
|
+
identity(DEBUG.LOG),
|
463
|
+
identity(DEBUG.ERROR),
|
464
|
+
identity(DEBUG.ASSERT)
|
465
|
+
)
|
466
|
+
return compile(ast)
|
467
|
+
}
|