fez-lisp 1.0.22 → 1.0.24
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/lib/baked/std.js +1 -1
- package/package.json +1 -1
- package/src/compiler.js +6 -18
- package/src/enums.js +2 -3
- package/src/interpreter.js +2 -1
- package/src/parser.js +9 -14
- package/src/tokeniser.js +9 -87
- package/src/utils.js +29 -19
package/package.json
CHANGED
package/src/compiler.js
CHANGED
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
VALUE,
|
8
8
|
WORD,
|
9
9
|
} from './enums.js'
|
10
|
+
import { leaf, isLeaf } from './parser.js'
|
10
11
|
import { deepRename, lispToJavaScriptVariableName } from './utils.js'
|
11
12
|
const Helpers = {
|
12
13
|
log: `var logEffect=(msg)=>{console.log(msg);return msg}`,
|
@@ -28,14 +29,6 @@ return result
|
|
28
29
|
isAtom: `atomPredicate=(value)=>typeof value==='number'||typeof value==='string'`,
|
29
30
|
error: `_error=(error)=>{
|
30
31
|
throw new Error(error)
|
31
|
-
}`,
|
32
|
-
arraySet: `arraySet=(array,index,value)=>{
|
33
|
-
array=[...array]
|
34
|
-
if (index < 0) {
|
35
|
-
const target = array.length + index
|
36
|
-
while (array.length !== target) array.pop()
|
37
|
-
} else array[index] = value;
|
38
|
-
return array
|
39
32
|
}`,
|
40
33
|
arraySetEffect: `arraySetEffect=(array,index,value)=>{
|
41
34
|
if (index < 0) {
|
@@ -45,7 +38,7 @@ return result
|
|
45
38
|
return array
|
46
39
|
}`,
|
47
40
|
serialise:
|
48
|
-
"serialise=(ast)=>{\n if(ast==undefined) return '()'\n else if(
|
41
|
+
"serialise=(ast)=>{\n if(ast==undefined) return '()'\n else if(typeof ast==='object')\n if(Array.isArray(ast)) return `(array ${ast.map(stringify).join(' ')})`\n else\n return `(array ${ast.map(([key, value]) => `(\"${key}\" ${stringify(value)})`).join(' ')})`\n else if(typeof ast==='string') return `\"${ast}\"`\n else if(typeof ast==='function') return '()'\n else return ast\n}",
|
49
42
|
cast: `_cast=(type,value)=>{
|
50
43
|
switch (type) {
|
51
44
|
case '${KEYWORDS.NUMBER_TYPE}':
|
@@ -95,7 +88,7 @@ const parseArgs = (Arguments, Variables, separator = ',') =>
|
|
95
88
|
parse(Arguments, Variables).join(separator)
|
96
89
|
const compile = (tree, Variables) => {
|
97
90
|
if (!tree) return ''
|
98
|
-
const [first, ...Arguments] =
|
91
|
+
const [first, ...Arguments] = !isLeaf(tree) ? tree : [tree]
|
99
92
|
if (first == undefined) return '[];'
|
100
93
|
const token = first[VALUE]
|
101
94
|
if (first[TYPE] === APPLY) {
|
@@ -186,8 +179,8 @@ const compile = (tree, Variables) => {
|
|
186
179
|
return `((${parseArgs(
|
187
180
|
functionArgs.map((node, index) =>
|
188
181
|
node[VALUE] === PLACEHOLDER
|
189
|
-
?
|
190
|
-
:
|
182
|
+
? leaf(node[TYPE], `_${index}`)
|
183
|
+
: leaf(node[TYPE], node[VALUE])
|
191
184
|
),
|
192
185
|
Variables
|
193
186
|
)})=>{${vars}return ${evaluatedBody.toString().trimStart()}});`
|
@@ -318,15 +311,10 @@ const compile = (tree, Variables) => {
|
|
318
311
|
}
|
319
312
|
case KEYWORDS.IMMUTABLE_FUNCTION: {
|
320
313
|
const [first, ...rest] = Arguments
|
321
|
-
return compile(
|
322
|
-
[{ [TYPE]: APPLY, [VALUE]: first[VALUE] }, ...rest],
|
323
|
-
Variables
|
324
|
-
)
|
314
|
+
return compile([leaf(APPLY, first[VALUE]), ...rest], Variables)
|
325
315
|
}
|
326
316
|
case KEYWORDS.SERIALISE:
|
327
317
|
return `serialise(${compile(Arguments[0], Variables)});`
|
328
|
-
case KEYWORDS.SET_IMMUTABLE_ARRAY:
|
329
|
-
return `arraySet(${parseArgs(Arguments, Variables)});`
|
330
318
|
case KEYWORDS.SET_ARRAY:
|
331
319
|
return `arraySetEffect(${parseArgs(Arguments, Variables)});`
|
332
320
|
case KEYWORDS.NOT_COMPILED_BLOCK:
|
package/src/enums.js
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
// AST enums
|
2
|
+
export const TYPE = 0
|
3
|
+
export const VALUE = 1
|
2
4
|
export const WORD = 'w'
|
3
5
|
export const APPLY = 'f'
|
4
|
-
export const VALUE = 'v'
|
5
|
-
export const TYPE = 't'
|
6
6
|
export const ATOM = 'a'
|
7
7
|
// tokeniser enums
|
8
8
|
export const PLACEHOLDER = '.'
|
@@ -77,5 +77,4 @@ export const KEYWORDS = {
|
|
77
77
|
SERIALISE: 'serialise',
|
78
78
|
|
79
79
|
SET_ARRAY: 'array:set!',
|
80
|
-
SET_IMMUTABLE_ARRAY: 'array:set',
|
81
80
|
}
|
package/src/interpreter.js
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
import { APPLY, ATOM, KEYWORDS, TYPE, VALUE, WORD } from './enums.js'
|
2
|
+
import { isLeaf } from './parser.js'
|
2
3
|
import { keywords } from './tokeniser.js'
|
3
4
|
import { stringifyArgs } from './utils.js'
|
4
5
|
|
5
6
|
export const evaluate = (exp, env) => {
|
6
|
-
const [first, ...rest] =
|
7
|
+
const [first, ...rest] = !isLeaf(exp) ? exp : [exp]
|
7
8
|
if (first == undefined) return []
|
8
9
|
switch (first[TYPE]) {
|
9
10
|
case WORD: {
|
package/src/parser.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
import { APPLY, ATOM,
|
1
|
+
import { APPLY, ATOM, WORD } from './enums.js'
|
2
2
|
import { escape } from './utils.js'
|
3
|
-
|
3
|
+
export const leaf = (type, value) => [type, value]
|
4
|
+
export const isLeaf = (tree) => typeof tree[0] === 'string'
|
4
5
|
export const parse = (source) => {
|
5
6
|
const tree = []
|
6
7
|
let head = tree,
|
@@ -25,18 +26,12 @@ export const parse = (source) => {
|
|
25
26
|
let token = acc
|
26
27
|
acc = ''
|
27
28
|
if (token) {
|
28
|
-
if (!head.length) head.push(
|
29
|
+
if (!head.length) head.push(leaf(APPLY, token))
|
29
30
|
else if (token.match(/^"([^"]*)"/))
|
30
|
-
head.push(
|
31
|
-
[TYPE]: ATOM,
|
32
|
-
[VALUE]: token.substring(1, token.length - 1),
|
33
|
-
})
|
31
|
+
head.push(leaf(ATOM, token.substring(1, token.length - 1)))
|
34
32
|
else if (token.match(/^-?[0-9]\d*(\.\d+)?$/))
|
35
|
-
head.push(
|
36
|
-
|
37
|
-
[VALUE]: Number(token),
|
38
|
-
})
|
39
|
-
else head.push({ [TYPE]: WORD, [VALUE]: token })
|
33
|
+
head.push(leaf(ATOM, Number(token)))
|
34
|
+
else head.push(leaf(WORD, token))
|
40
35
|
}
|
41
36
|
if (cursor === ')') head = stack.pop()
|
42
37
|
} else acc += cursor
|
@@ -46,10 +41,10 @@ export const parse = (source) => {
|
|
46
41
|
export const stringify = (ast) => {
|
47
42
|
if (ast == undefined) return '()'
|
48
43
|
else if (typeof ast === 'object')
|
49
|
-
if (
|
44
|
+
if (!isLeaf(ast))
|
50
45
|
return ast.length ? `(array ${ast.map(stringify).join(' ')})` : '()'
|
51
46
|
else
|
52
|
-
return `(array ${
|
47
|
+
return `(array ${ast
|
53
48
|
.map(([key, value]) => `("${key}" ${stringify(value)})`)
|
54
49
|
.join(' ')})`
|
55
50
|
else if (typeof ast === 'string') return `"${ast}"`
|
package/src/tokeniser.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { TYPE, VALUE, WORD, KEYWORDS } from './enums.js'
|
1
|
+
import { TYPE, VALUE, WORD, KEYWORDS, APPLY } from './enums.js'
|
2
2
|
import { evaluate } from './interpreter.js'
|
3
3
|
import { stringify } from './parser.js'
|
4
4
|
import {
|
@@ -14,9 +14,9 @@ const keywords = {
|
|
14
14
|
if (args.length < 2)
|
15
15
|
throw new RangeError(
|
16
16
|
`Invalid number of arguments for (${
|
17
|
-
|
17
|
+
KEYWORDS.CONCATENATION
|
18
18
|
}), expected > 1 but got ${args.length}. (${
|
19
|
-
|
19
|
+
KEYWORDS.CONCATENATION
|
20
20
|
} ${stringifyArgs(args)})`
|
21
21
|
)
|
22
22
|
const operands = args.map((x) => evaluate(x, env))
|
@@ -870,11 +870,13 @@ const keywords = {
|
|
870
870
|
)
|
871
871
|
let inp = args[0]
|
872
872
|
for (let i = 1; i < args.length; ++i) {
|
873
|
-
if (!
|
873
|
+
if (!args[i].length || args[i][0][TYPE] !== APPLY)
|
874
874
|
throw new TypeError(
|
875
|
-
`Argument at position (${i}) of (${
|
876
|
-
KEYWORDS.
|
877
|
-
})
|
875
|
+
`Argument at position (${i}) of (${
|
876
|
+
KEYWORDS.PIPE
|
877
|
+
}) is not an invoked (${KEYWORDS.ANONYMOUS_FUNCTION}). (${
|
878
|
+
KEYWORDS.PIPE
|
879
|
+
} ${stringifyArgs(args)})`
|
878
880
|
)
|
879
881
|
const [first, ...rest] = args[i]
|
880
882
|
const arr = [first, inp, ...rest]
|
@@ -1088,86 +1090,6 @@ const keywords = {
|
|
1088
1090
|
}
|
1089
1091
|
return array
|
1090
1092
|
},
|
1091
|
-
|
1092
|
-
[KEYWORDS.SET_IMMUTABLE_ARRAY]: (args, env) => {
|
1093
|
-
if (args.length !== 2 && args.length !== 3)
|
1094
|
-
throw new RangeError(
|
1095
|
-
`Invalid number of arguments for (${
|
1096
|
-
KEYWORDS.SET_IMMUTABLE_ARRAY
|
1097
|
-
}) (or 2 3) required (${KEYWORDS.SET_IMMUTABLE_ARRAY} ${stringifyArgs(
|
1098
|
-
args
|
1099
|
-
)})`
|
1100
|
-
)
|
1101
|
-
let array = evaluate(args[0], env)
|
1102
|
-
if (!Array.isArray(array))
|
1103
|
-
throw new TypeError(
|
1104
|
-
`First argument of (${KEYWORDS.SET_IMMUTABLE_ARRAY}) must be an (${
|
1105
|
-
KEYWORDS.ARRAY_TYPE
|
1106
|
-
}) but got (${array}) (${KEYWORDS.SET_IMMUTABLE_ARRAY} ${stringifyArgs(
|
1107
|
-
args
|
1108
|
-
)})`
|
1109
|
-
)
|
1110
|
-
array = [...array]
|
1111
|
-
const index = evaluate(args[1], env)
|
1112
|
-
if (!Number.isInteger(index))
|
1113
|
-
throw new TypeError(
|
1114
|
-
`Second argument of (${KEYWORDS.SET_IMMUTABLE_ARRAY}) must be an (${
|
1115
|
-
KEYWORDS.NUMBER_TYPE
|
1116
|
-
} integer) (${index}) (${KEYWORDS.SET_IMMUTABLE_ARRAY} ${stringifyArgs(
|
1117
|
-
args
|
1118
|
-
)})`
|
1119
|
-
)
|
1120
|
-
if (index > array.length)
|
1121
|
-
throw new RangeError(
|
1122
|
-
`Second argument of (${
|
1123
|
-
KEYWORDS.SET_IMMUTABLE_ARRAY
|
1124
|
-
}) is outside of the (${
|
1125
|
-
KEYWORDS.ARRAY_TYPE
|
1126
|
-
}) bounds (index ${index} bounds ${array.length}) (${
|
1127
|
-
KEYWORDS.SET_IMMUTABLE_ARRAY
|
1128
|
-
} ${stringifyArgs(args)})`
|
1129
|
-
)
|
1130
|
-
if (index < 0) {
|
1131
|
-
if (args.length !== 2)
|
1132
|
-
throw new RangeError(
|
1133
|
-
`Invalid number of arguments for (${
|
1134
|
-
KEYWORDS.SET_IMMUTABLE_ARRAY
|
1135
|
-
}) (if (< index 0) then 2 required) (${
|
1136
|
-
KEYWORDS.SET_IMMUTABLE_ARRAY
|
1137
|
-
} ${stringifyArgs(args)})`
|
1138
|
-
)
|
1139
|
-
if (index * -1 > array.length)
|
1140
|
-
throw new RangeError(
|
1141
|
-
`Second argument of (${
|
1142
|
-
KEYWORDS.SET_IMMUTABLE_ARRAY
|
1143
|
-
}) is outside of the (${
|
1144
|
-
KEYWORDS.ARRAY_TYPE
|
1145
|
-
}) bounds (index ${index} bounds ${array.length}) (${
|
1146
|
-
KEYWORDS.SET_IMMUTABLE_ARRAY
|
1147
|
-
} ${stringifyArgs(args)})`
|
1148
|
-
)
|
1149
|
-
const target = array.length + index
|
1150
|
-
while (array.length !== target) array.pop()
|
1151
|
-
} else {
|
1152
|
-
if (args.length !== 3)
|
1153
|
-
throw new RangeError(
|
1154
|
-
`Invalid number of arguments for (${
|
1155
|
-
KEYWORDS.SET_IMMUTABLE_ARRAY
|
1156
|
-
}) (if (>= index 0) then 3 required) (${
|
1157
|
-
KEYWORDS.SET_IMMUTABLE_ARRAY
|
1158
|
-
} ${stringifyArgs(args)})`
|
1159
|
-
)
|
1160
|
-
const value = evaluate(args[2], env)
|
1161
|
-
if (value == undefined)
|
1162
|
-
throw new RangeError(
|
1163
|
-
`Trying to set a null value in (${KEYWORDS.ARRAY_TYPE}) at (${
|
1164
|
-
KEYWORDS.SET_IMMUTABLE_ARRAY
|
1165
|
-
}). (${KEYWORDS.SET_IMMUTABLE_ARRAY} ${stringifyArgs(args)})`
|
1166
|
-
)
|
1167
|
-
array[index] = value
|
1168
|
-
}
|
1169
|
-
return array
|
1170
|
-
},
|
1171
1093
|
}
|
1172
1094
|
keywords[KEYWORDS.NOT_COMPILED_BLOCK] = keywords[KEYWORDS.BLOCK]
|
1173
1095
|
export { keywords }
|
package/src/utils.js
CHANGED
@@ -1,8 +1,16 @@
|
|
1
1
|
import std from '../lib/baked/std.js'
|
2
2
|
import { comp } from './compiler.js'
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
APPLY,
|
5
|
+
ATOM,
|
6
|
+
KEYWORDS,
|
7
|
+
PLACEHOLDER,
|
8
|
+
TYPE,
|
9
|
+
VALUE,
|
10
|
+
WORD,
|
11
|
+
} from './enums.js'
|
4
12
|
import { evaluate, run } from './interpreter.js'
|
5
|
-
import { parse } from './parser.js'
|
13
|
+
import { isLeaf, parse } from './parser.js'
|
6
14
|
export const logError = (error) => console.log('\x1b[31m', error, '\x1b[0m')
|
7
15
|
export const logSuccess = (output) => console.log(output, '\x1b[0m')
|
8
16
|
export const removeNoCode = (source) =>
|
@@ -39,13 +47,13 @@ export const escape = (Char) => {
|
|
39
47
|
}
|
40
48
|
}
|
41
49
|
export const stringifyType = (type) =>
|
42
|
-
|
50
|
+
!isLeaf(type)
|
43
51
|
? `(array ${type.map((t) => stringifyType(t)).join(' ')})`
|
44
52
|
: typeof type
|
45
53
|
export const stringifyArgs = (args) =>
|
46
54
|
args
|
47
55
|
.map((x) =>
|
48
|
-
|
56
|
+
!isLeaf(x)
|
49
57
|
? `(${stringifyArgs(x)})`
|
50
58
|
: x[TYPE] === APPLY || x[TYPE] === WORD
|
51
59
|
? x[VALUE]
|
@@ -132,7 +140,7 @@ export const treeShake = (ast, libs) => {
|
|
132
140
|
const deps = libs.reduce((a, x) => a.add(x.at(1)[VALUE]), new Set())
|
133
141
|
const visited = new Set()
|
134
142
|
const dfs = (tree) => {
|
135
|
-
if (
|
143
|
+
if (!isLeaf(tree)) tree.forEach(dfs)
|
136
144
|
else if (
|
137
145
|
(tree[TYPE] === APPLY || tree[TYPE] === WORD) &&
|
138
146
|
deps.has(tree[VALUE]) &&
|
@@ -153,20 +161,18 @@ export const runFromCompiled = (source) => {
|
|
153
161
|
const tree = parse(
|
154
162
|
handleUnbalancedQuotes(handleUnbalancedParens(removeNoCode(source)))
|
155
163
|
)
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
return eval(JavaScript)
|
160
|
-
}
|
164
|
+
const compiled = comp(tree)
|
165
|
+
const JavaScript = `${compiled.top}${compiled.program}`
|
166
|
+
return eval(JavaScript)
|
161
167
|
}
|
162
168
|
export const runFromInterpreted = (source, env = {}) => {
|
163
169
|
const tree = parse(
|
164
170
|
handleUnbalancedQuotes(handleUnbalancedParens(removeNoCode(source)))
|
165
171
|
)
|
166
|
-
|
172
|
+
run(tree, env)
|
167
173
|
}
|
168
174
|
export const dfs = (tree, callback) => {
|
169
|
-
if (
|
175
|
+
if (!isLeaf(tree)) for (const leaf of tree) dfs(leaf)
|
170
176
|
else callback(tree)
|
171
177
|
}
|
172
178
|
export const deepClone = (ast) => JSON.parse(JSON.stringify(ast))
|
@@ -180,7 +186,12 @@ export const fez = (source, options = {}) => {
|
|
180
186
|
)
|
181
187
|
else code = removeNoCode(source)
|
182
188
|
if (!options.mutation) code = removeMutation(code)
|
189
|
+
if (!code.length && options.throw) throw new Error('Nothing to parse!')
|
183
190
|
const parsed = parse(code)
|
191
|
+
if (parsed.length === 0 && options.throw)
|
192
|
+
throw new Error(
|
193
|
+
'Top level expressions need to be wrapped in a (do) block'
|
194
|
+
)
|
184
195
|
const standard = options.std
|
185
196
|
? options.shake
|
186
197
|
? treeShake(parsed, std)
|
@@ -196,9 +207,8 @@ export const fez = (source, options = {}) => {
|
|
196
207
|
const err = error.message
|
197
208
|
.replace("'[object Array]'", '(array)')
|
198
209
|
.replace('object', '(array)')
|
199
|
-
if (options.errors)
|
200
|
-
|
201
|
-
}
|
210
|
+
if (options.errors) logError(err)
|
211
|
+
if (options.throw) throw err
|
202
212
|
return err
|
203
213
|
}
|
204
214
|
}
|
@@ -227,12 +237,12 @@ export const toCamelCase = (name) => {
|
|
227
237
|
return out
|
228
238
|
}
|
229
239
|
export const deepRename = (name, newName, tree) => {
|
230
|
-
if (
|
231
|
-
for (const
|
240
|
+
if (!isLeaf(tree))
|
241
|
+
for (const leaf of tree) {
|
232
242
|
// Figure out a non mutable solution so
|
233
243
|
// I can get rid of deep copy
|
234
|
-
if (
|
235
|
-
deepRename(name, newName,
|
244
|
+
if (leaf[VALUE] === name) leaf[VALUE] = `()=>${newName}`
|
245
|
+
deepRename(name, newName, leaf)
|
236
246
|
}
|
237
247
|
}
|
238
248
|
export const lispToJavaScriptVariableName = (name) =>
|