fez-lisp 1.0.22 → 1.0.24
Sign up to get free protection for your applications and to get access to all the features.
- 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) =>
|