simplex-lang 0.3.0 → 1.0.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 +529 -12
- package/build/parser/index.js +1161 -479
- package/build/parser/index.js.map +1 -1
- package/build/src/compiler.d.ts +26 -18
- package/build/src/compiler.js +197 -463
- package/build/src/compiler.js.map +1 -1
- package/build/src/constants.d.ts +25 -0
- package/build/src/constants.js +29 -0
- package/build/src/constants.js.map +1 -0
- package/build/src/error-mapping.d.ts +31 -0
- package/build/src/error-mapping.js +72 -0
- package/build/src/error-mapping.js.map +1 -0
- package/build/src/errors.d.ts +8 -5
- package/build/src/errors.js +8 -10
- package/build/src/errors.js.map +1 -1
- package/build/src/index.d.ts +1 -0
- package/build/src/index.js +1 -0
- package/build/src/index.js.map +1 -1
- package/build/src/simplex-tree.d.ts +25 -3
- package/build/src/simplex.peggy +120 -3
- package/build/src/tools/index.d.ts +17 -7
- package/build/src/tools/index.js +81 -17
- package/build/src/tools/index.js.map +1 -1
- package/build/src/version.js +1 -1
- package/build/src/visitors.d.ts +15 -0
- package/build/src/visitors.js +330 -0
- package/build/src/visitors.js.map +1 -0
- package/package.json +6 -5
- package/parser/index.js +1161 -479
- package/parser/index.js.map +1 -1
- package/src/compiler.ts +308 -610
- package/src/constants.ts +30 -0
- package/src/error-mapping.ts +112 -0
- package/src/errors.ts +8 -12
- package/src/index.ts +1 -0
- package/src/simplex-tree.ts +30 -2
- package/src/simplex.peggy +120 -3
- package/src/tools/index.ts +107 -22
- package/src/visitors.ts +491 -0
- package/build/src/tools/cast.d.ts +0 -2
- package/build/src/tools/cast.js +0 -20
- package/build/src/tools/cast.js.map +0 -1
- package/build/src/tools/ensure.d.ts +0 -3
- package/build/src/tools/ensure.js +0 -30
- package/build/src/tools/ensure.js.map +0 -1
- package/build/src/tools/guards.d.ts +0 -2
- package/build/src/tools/guards.js +0 -24
- package/build/src/tools/guards.js.map +0 -1
- package/src/tools/cast.ts +0 -26
- package/src/tools/ensure.ts +0 -41
- package/src/tools/guards.ts +0 -34
package/build/src/compiler.js
CHANGED
|
@@ -1,118 +1,144 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
2
2
|
// eslint-disable-next-line n/no-missing-import
|
|
3
3
|
import { parse } from '../parser/index.js';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import { castToBoolean } from './tools/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
4
|
+
import { ExpressionError, UnexpectedTypeError } from './errors.js';
|
|
5
|
+
import { getActiveErrorMapper, getExpressionErrorLocation } from './error-mapping.js';
|
|
6
|
+
import { castToBoolean, castToString, ensureFunction, ensureNumber, ensureArray, ensureObject, ensureRelationalComparable, isSimpleValue, objToStringAlias, typeOf } from './tools/index.js';
|
|
7
|
+
import { traverse } from './visitors.js';
|
|
8
|
+
import { GEN, SCOPE_NAMES, SCOPE_VALUES, SCOPE_PARENT } from './constants.js';
|
|
9
|
+
export { traverse, getExpressionErrorLocation };
|
|
10
10
|
var hasOwn = Object.hasOwn;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (typeofObj === 'string' && typeof key === 'number') {
|
|
36
|
-
return obj[key];
|
|
37
|
-
}
|
|
38
|
-
if (typeofObj !== 'object') {
|
|
39
|
-
throw new UnexpectedTypeError(['object'], obj);
|
|
40
|
-
}
|
|
41
|
-
if (isSimpleValue(key) === false) {
|
|
42
|
-
throw new UnexpectedTypeError(['simple type object key'], key);
|
|
43
|
-
}
|
|
44
|
-
if (hasOwn(obj, key)) {
|
|
45
|
-
// @ts-expect-error Type cannot be used as an index type
|
|
46
|
-
return obj[key];
|
|
47
|
-
}
|
|
48
|
-
if (obj instanceof Map) {
|
|
49
|
-
return obj.get(key);
|
|
11
|
+
/** Look up an identifier in globals first, then data; throw on miss. */
|
|
12
|
+
function defaultGetIdentifierValue(identifierName, globals, data) {
|
|
13
|
+
if (identifierName === 'undefined')
|
|
14
|
+
return undefined;
|
|
15
|
+
if (globals != null && Object.hasOwn(globals, identifierName)) {
|
|
16
|
+
return globals[identifierName];
|
|
17
|
+
}
|
|
18
|
+
if (data != null && Object.hasOwn(data, identifierName)) {
|
|
19
|
+
return data[identifierName];
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`Unknown identifier - ${identifierName}`);
|
|
22
|
+
}
|
|
23
|
+
/** Look up an extension method for the given object type and bind obj as first argument. */
|
|
24
|
+
function getExtensionMethod(obj, key, extensionMap, classesKeys, classesValues) {
|
|
25
|
+
var typeofObj = typeof obj;
|
|
26
|
+
var methods;
|
|
27
|
+
if (typeofObj === 'object') {
|
|
28
|
+
for (var i = 0; i < classesKeys.length; i++) {
|
|
29
|
+
// @ts-expect-error supports objects with Symbol.hasInstance
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
31
|
+
if (obj instanceof classesKeys[i]) {
|
|
32
|
+
methods = classesValues[i];
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
50
35
|
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
methods = extensionMap.get(typeofObj);
|
|
39
|
+
}
|
|
40
|
+
if (methods === undefined) {
|
|
41
|
+
throw new TypeError(`No extension methods defined for type "${typeofObj}"`);
|
|
42
|
+
}
|
|
43
|
+
var method = methods[key];
|
|
44
|
+
if (method === undefined) {
|
|
45
|
+
throw new TypeError(`Extension method "${String(key)}" is not defined for type "${typeofObj}"`);
|
|
46
|
+
}
|
|
47
|
+
return method.bind(null, obj);
|
|
48
|
+
}
|
|
49
|
+
/** Resolve property access on an object, Map, or string (null-safe). */
|
|
50
|
+
function defaultGetProperty(obj, key, extension) {
|
|
51
|
+
if (obj == null)
|
|
51
52
|
return undefined;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
if (extension) {
|
|
54
|
+
throw new ExpressionError('Extension member expression (::) is reserved and not implemented', '', null);
|
|
55
|
+
}
|
|
56
|
+
var typeofObj = typeof obj;
|
|
57
|
+
if (typeofObj === 'string' && typeof key === 'number') {
|
|
58
|
+
return obj[key];
|
|
59
|
+
}
|
|
60
|
+
if (typeofObj !== 'object') {
|
|
61
|
+
throw new UnexpectedTypeError(['object'], obj);
|
|
62
|
+
}
|
|
63
|
+
if (isSimpleValue(key) === false) {
|
|
64
|
+
throw new UnexpectedTypeError(['simple type object key'], key);
|
|
65
|
+
}
|
|
66
|
+
if (hasOwn(obj, key)) {
|
|
67
|
+
// @ts-expect-error Type cannot be used as an index type
|
|
68
|
+
return obj[key];
|
|
69
|
+
}
|
|
70
|
+
if (obj instanceof Map) {
|
|
71
|
+
return obj.get(key);
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
/** Call a function value; null/undefined silently returns undefined. */
|
|
76
|
+
function defaultCallFunction(fn, args) {
|
|
77
|
+
return fn == null
|
|
78
|
+
? undefined
|
|
79
|
+
: (args === null
|
|
80
|
+
? ensureFunction(fn)()
|
|
81
|
+
: ensureFunction(fn).apply(null, args));
|
|
82
|
+
}
|
|
83
|
+
/** Assert that a value is not null or undefined; throw on null/undefined. */
|
|
84
|
+
function defaultNonNullAssert(val) {
|
|
85
|
+
if (val == null) {
|
|
86
|
+
throw new ExpressionError('Non-null assertion failed: value is ' +
|
|
87
|
+
(val === null ? 'null' : 'undefined'), '', null);
|
|
88
|
+
}
|
|
89
|
+
return val;
|
|
90
|
+
}
|
|
91
|
+
/** Execute a pipe sequence, threading each result through the next step. */
|
|
92
|
+
function defaultPipe(head, tail) {
|
|
93
|
+
var result = head;
|
|
94
|
+
for (const it of tail) {
|
|
95
|
+
if (it.fwd) {
|
|
96
|
+
throw new ExpressionError('Pipe forward operator (|>) is reserved and not implemented', '', null);
|
|
66
97
|
}
|
|
67
|
-
|
|
98
|
+
if (it.opt && result == null)
|
|
99
|
+
return result;
|
|
100
|
+
result = it.next(result);
|
|
68
101
|
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
const defaultContextHelpers = {
|
|
105
|
+
castToBoolean,
|
|
106
|
+
castToString,
|
|
107
|
+
ensureFunction,
|
|
108
|
+
ensureObject,
|
|
109
|
+
ensureArray,
|
|
110
|
+
nonNullAssert: defaultNonNullAssert,
|
|
111
|
+
getIdentifierValue: defaultGetIdentifierValue,
|
|
112
|
+
getProperty: defaultGetProperty,
|
|
113
|
+
callFunction: defaultCallFunction,
|
|
114
|
+
pipe: defaultPipe
|
|
69
115
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
116
|
+
/** Create the default unary operator map (+, -, not, typeof). */
|
|
117
|
+
export function createDefaultUnaryOperators(bool) {
|
|
118
|
+
return {
|
|
119
|
+
'+': val => ensureNumber(val),
|
|
120
|
+
'-': val => -ensureNumber(val),
|
|
121
|
+
'not': val => !bool(val),
|
|
122
|
+
'typeof': val => typeof val
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export const defaultUnaryOperators = createDefaultUnaryOperators(castToBoolean);
|
|
126
|
+
const numericOp = (fn) => (a, b) => fn(ensureNumber(a), ensureNumber(b));
|
|
76
127
|
export const defaultBinaryOperators = {
|
|
77
128
|
'!=': (a, b) => a !== b,
|
|
78
129
|
'==': (a, b) => a === b,
|
|
79
|
-
|
|
80
|
-
'
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
'+': (a, b) => {
|
|
86
|
-
// @ts-expect-error
|
|
87
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/restrict-plus-operands
|
|
88
|
-
return ensureNumber(a) + ensureNumber(b);
|
|
89
|
-
},
|
|
90
|
-
'-': (a, b) => {
|
|
91
|
-
// @ts-expect-error
|
|
92
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
93
|
-
return ensureNumber(a) - ensureNumber(b);
|
|
94
|
-
},
|
|
95
|
-
'/': (a, b) => {
|
|
96
|
-
// @ts-expect-error
|
|
97
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
98
|
-
return ensureNumber(a) / ensureNumber(b);
|
|
99
|
-
},
|
|
100
|
-
'mod': (a, b) => {
|
|
101
|
-
// @ts-expect-error
|
|
102
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
103
|
-
return ensureNumber(a) % ensureNumber(b);
|
|
104
|
-
},
|
|
105
|
-
'^': (a, b) => {
|
|
106
|
-
// @ts-expect-error
|
|
107
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
108
|
-
return ensureNumber(a) ** ensureNumber(b);
|
|
109
|
-
},
|
|
130
|
+
'*': numericOp((a, b) => a * b),
|
|
131
|
+
'+': numericOp((a, b) => a + b),
|
|
132
|
+
'-': numericOp((a, b) => a - b),
|
|
133
|
+
'/': numericOp((a, b) => a / b),
|
|
134
|
+
'mod': numericOp((a, b) => a % b),
|
|
135
|
+
'^': numericOp((a, b) => a ** b),
|
|
110
136
|
'&': (a, b) => castToString(a) + castToString(b),
|
|
111
137
|
'<': (a, b) => ensureRelationalComparable(a) < ensureRelationalComparable(b),
|
|
112
138
|
'<=': (a, b) => ensureRelationalComparable(a) <= ensureRelationalComparable(b),
|
|
113
139
|
'>': (a, b) => ensureRelationalComparable(a) > ensureRelationalComparable(b),
|
|
114
140
|
'>=': (a, b) => ensureRelationalComparable(a) >= ensureRelationalComparable(b),
|
|
115
|
-
//
|
|
141
|
+
// Check if key exists in container (Object/Array/Map)
|
|
116
142
|
'in': (a, b) => {
|
|
117
143
|
const bType = objToStringAlias.call(b);
|
|
118
144
|
switch (bType) {
|
|
@@ -125,7 +151,7 @@ export const defaultBinaryOperators = {
|
|
|
125
151
|
return a in b;
|
|
126
152
|
}
|
|
127
153
|
else {
|
|
128
|
-
throw new TypeError(`Wrong "in" operator usage - key value
|
|
154
|
+
throw new TypeError(`Wrong "in" operator usage - key value must be a safe integer`);
|
|
129
155
|
}
|
|
130
156
|
}
|
|
131
157
|
case '[object Map]':
|
|
@@ -136,391 +162,99 @@ export const defaultBinaryOperators = {
|
|
|
136
162
|
}
|
|
137
163
|
}
|
|
138
164
|
};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
'and':
|
|
144
|
-
'&&': logicalAndOperatorFn,
|
|
145
|
-
'or': logicalOrOperatorFn,
|
|
146
|
-
'||': logicalOrOperatorFn
|
|
147
|
-
};
|
|
148
|
-
const codePart = (codePart, ownerNode) => ({
|
|
149
|
-
code: codePart,
|
|
150
|
-
offsets: [{ len: codePart.length, location: ownerNode.location }]
|
|
151
|
-
});
|
|
152
|
-
const combineVisitResults = (parts) => {
|
|
153
|
-
return parts.reduce((res, it) => {
|
|
154
|
-
return {
|
|
155
|
-
code: res.code + it.code,
|
|
156
|
-
offsets: res.offsets.concat(it.offsets)
|
|
157
|
-
};
|
|
158
|
-
});
|
|
159
|
-
};
|
|
160
|
-
const visitors = {
|
|
161
|
-
Literal: node => {
|
|
162
|
-
const parts = [codePart(JSON.stringify(node.value), node)];
|
|
163
|
-
return parts;
|
|
164
|
-
},
|
|
165
|
-
Identifier: node => {
|
|
166
|
-
const parts = [
|
|
167
|
-
codePart(`get(scope,${JSON.stringify(node.name)})`, node)
|
|
168
|
-
];
|
|
169
|
-
return parts;
|
|
170
|
-
},
|
|
171
|
-
UnaryExpression: (node, visit) => {
|
|
172
|
-
const parts = [
|
|
173
|
-
codePart(`uop["${node.operator}"](`, node),
|
|
174
|
-
...visit(node.argument),
|
|
175
|
-
codePart(')', node)
|
|
176
|
-
];
|
|
177
|
-
return parts;
|
|
178
|
-
},
|
|
179
|
-
BinaryExpression: (node, visit) => {
|
|
180
|
-
const parts = [
|
|
181
|
-
codePart(`bop["${node.operator}"](`, node),
|
|
182
|
-
...visit(node.left),
|
|
183
|
-
codePart(',', node),
|
|
184
|
-
...visit(node.right),
|
|
185
|
-
codePart(')', node)
|
|
186
|
-
];
|
|
187
|
-
return parts;
|
|
188
|
-
},
|
|
189
|
-
LogicalExpression: (node, visit) => {
|
|
190
|
-
const parts = [
|
|
191
|
-
codePart(`lop["${node.operator}"](()=>(`, node),
|
|
192
|
-
...visit(node.left),
|
|
193
|
-
codePart('),()=>(', node),
|
|
194
|
-
...visit(node.right),
|
|
195
|
-
codePart('))', node)
|
|
196
|
-
];
|
|
197
|
-
return parts;
|
|
198
|
-
},
|
|
199
|
-
ConditionalExpression: (node, visit) => {
|
|
200
|
-
const parts = [
|
|
201
|
-
codePart('(bool(', node),
|
|
202
|
-
...visit(node.test),
|
|
203
|
-
codePart(')?', node),
|
|
204
|
-
...visit(node.consequent),
|
|
205
|
-
codePart(':', node),
|
|
206
|
-
...(node.alternate !== null
|
|
207
|
-
? visit(node.alternate)
|
|
208
|
-
: [codePart('undefined', node)]),
|
|
209
|
-
codePart(')', node)
|
|
210
|
-
];
|
|
211
|
-
return parts;
|
|
212
|
-
},
|
|
213
|
-
ObjectExpression: (node, visit) => {
|
|
214
|
-
const innerObj = node.properties
|
|
215
|
-
.map((p) => {
|
|
216
|
-
if (p.key.type === 'Identifier') {
|
|
217
|
-
return [codePart(p.key.name, p), visit(p.value)];
|
|
218
|
-
}
|
|
219
|
-
//
|
|
220
|
-
else if (p.key.type === 'Literal') {
|
|
221
|
-
// TODO look for ECMA spec
|
|
222
|
-
return [codePart(JSON.stringify(p.key.value), p), visit(p.value)];
|
|
223
|
-
}
|
|
224
|
-
//
|
|
225
|
-
else {
|
|
226
|
-
// TODO Restrict on parse step
|
|
227
|
-
// TODO Error with locations
|
|
228
|
-
throw new TypeError(`Incorrect object key type ${p.key.type}`);
|
|
229
|
-
}
|
|
230
|
-
})
|
|
231
|
-
.flatMap(([k, v]) => {
|
|
232
|
-
return [k, codePart(':', node), ...v, codePart(',', node)];
|
|
233
|
-
});
|
|
234
|
-
// remove last comma
|
|
235
|
-
if (innerObj.length > 1) {
|
|
236
|
-
innerObj.pop();
|
|
237
|
-
}
|
|
238
|
-
const parts = [
|
|
239
|
-
codePart('{', node),
|
|
240
|
-
...innerObj,
|
|
241
|
-
codePart('}', node)
|
|
242
|
-
];
|
|
243
|
-
return parts;
|
|
244
|
-
},
|
|
245
|
-
ArrayExpression: (node, visit) => {
|
|
246
|
-
const innerArrParts = node.elements.flatMap(el => {
|
|
247
|
-
return el === null
|
|
248
|
-
? [codePart(',', node)]
|
|
249
|
-
: [...visit(el), codePart(',', node)];
|
|
250
|
-
});
|
|
251
|
-
// remove last comma
|
|
252
|
-
if (innerArrParts.length > 1) {
|
|
253
|
-
innerArrParts.pop();
|
|
254
|
-
}
|
|
255
|
-
const parts = [
|
|
256
|
-
codePart('[', node),
|
|
257
|
-
...innerArrParts,
|
|
258
|
-
codePart(']', node)
|
|
259
|
-
];
|
|
260
|
-
return parts;
|
|
261
|
-
},
|
|
262
|
-
MemberExpression: (node, visit) => {
|
|
263
|
-
const { computed, object, property } = node;
|
|
264
|
-
// TODO Pass computed to prop?
|
|
265
|
-
const parts = [
|
|
266
|
-
codePart('prop(', node),
|
|
267
|
-
...visit(object),
|
|
268
|
-
codePart(',', node),
|
|
269
|
-
...(computed
|
|
270
|
-
? visit(property)
|
|
271
|
-
: [codePart(JSON.stringify(property.name), property)]),
|
|
272
|
-
codePart(')', node)
|
|
273
|
-
];
|
|
274
|
-
return parts;
|
|
275
|
-
},
|
|
276
|
-
CallExpression: (node, visit) => {
|
|
277
|
-
if (node.arguments.length > 0) {
|
|
278
|
-
const innerArgs = node.arguments.flatMap((arg, index) => [
|
|
279
|
-
...(arg.type === 'CurryPlaceholder'
|
|
280
|
-
? [codePart(`a${index}`, arg)]
|
|
281
|
-
: visit(arg)),
|
|
282
|
-
codePart(',', node)
|
|
283
|
-
]);
|
|
284
|
-
const curriedArgs = node.arguments.flatMap((arg, index) => arg.type === 'CurryPlaceholder' ? [`a${index}`] : []);
|
|
285
|
-
// remove last comma
|
|
286
|
-
innerArgs?.pop();
|
|
287
|
-
// call({{callee}},[{{arguments}}])
|
|
288
|
-
let parts = [
|
|
289
|
-
codePart('call(', node),
|
|
290
|
-
...visit(node.callee),
|
|
291
|
-
codePart(',[', node),
|
|
292
|
-
...innerArgs,
|
|
293
|
-
codePart('])', node)
|
|
294
|
-
];
|
|
295
|
-
if (curriedArgs.length > 0) {
|
|
296
|
-
parts = [
|
|
297
|
-
codePart(`(scope=>(${curriedArgs.join()})=>`, node),
|
|
298
|
-
...parts,
|
|
299
|
-
codePart(')(scope)', node)
|
|
300
|
-
];
|
|
301
|
-
}
|
|
302
|
-
return parts;
|
|
303
|
-
}
|
|
304
|
-
//
|
|
305
|
-
else {
|
|
306
|
-
const parts = [
|
|
307
|
-
codePart('call(', node),
|
|
308
|
-
...visit(node.callee),
|
|
309
|
-
codePart(',null)', node)
|
|
310
|
-
];
|
|
311
|
-
return parts;
|
|
312
|
-
}
|
|
313
|
-
},
|
|
314
|
-
NullishCoalescingExpression: (node, visit) => {
|
|
315
|
-
const parts = [
|
|
316
|
-
codePart('(', node),
|
|
317
|
-
...visit(node.left),
|
|
318
|
-
codePart('??', node),
|
|
319
|
-
...visit(node.right),
|
|
320
|
-
codePart(')', node)
|
|
321
|
-
];
|
|
322
|
-
return parts;
|
|
323
|
-
},
|
|
324
|
-
PipeSequence: (node, visit) => {
|
|
325
|
-
const headCode = visit(node.head);
|
|
326
|
-
const tailsCodeArrInner = node.tail.flatMap(t => {
|
|
327
|
-
const opt = t.operator === '|?';
|
|
328
|
-
const tailParts = [
|
|
329
|
-
codePart(`{opt:${opt},next:(scope=>topic=>{scope=[["%"],[topic],scope];return `, t.expression),
|
|
330
|
-
...visit(t.expression),
|
|
331
|
-
codePart(`})(scope)}`, t.expression),
|
|
332
|
-
codePart(`,`, t.expression)
|
|
333
|
-
];
|
|
334
|
-
return tailParts;
|
|
335
|
-
});
|
|
336
|
-
// remove last comma
|
|
337
|
-
tailsCodeArrInner.pop();
|
|
338
|
-
const parts = [
|
|
339
|
-
codePart('pipe(', node),
|
|
340
|
-
...headCode,
|
|
341
|
-
codePart(',[', node),
|
|
342
|
-
...tailsCodeArrInner,
|
|
343
|
-
codePart('])', node)
|
|
344
|
-
];
|
|
345
|
-
return parts;
|
|
346
|
-
},
|
|
347
|
-
TopicReference: node => {
|
|
348
|
-
const parts = [codePart(`get(scope,"${TOPIC_TOKEN}")`, node)];
|
|
349
|
-
return parts;
|
|
350
|
-
},
|
|
351
|
-
LambdaExpression: (node, visit) => {
|
|
352
|
-
// Lambda with parameters
|
|
353
|
-
if (node.params.length > 0) {
|
|
354
|
-
const paramsNames = node.params.map(p => p.name);
|
|
355
|
-
const fnParams = Array.from({ length: paramsNames.length }, (_, index) => `p${index}`);
|
|
356
|
-
const fnParamsList = fnParams.join();
|
|
357
|
-
const fnParamsNamesList = paramsNames.map(p => JSON.stringify(p)).join();
|
|
358
|
-
// TODO Is "...args" more performant?
|
|
359
|
-
// (params => function (p0, p1) {
|
|
360
|
-
// var scope = [params, [p0, p1], scope]
|
|
361
|
-
// return {{code}}
|
|
362
|
-
// })(["a", "b"])
|
|
363
|
-
const parts = [
|
|
364
|
-
codePart(`((scope,params)=>function(${fnParamsList}){scope=[params,[${fnParamsList}],scope];return `, node),
|
|
365
|
-
...visit(node.expression),
|
|
366
|
-
codePart(`})(scope,[${fnParamsNamesList}])`, node)
|
|
367
|
-
];
|
|
368
|
-
return parts;
|
|
369
|
-
}
|
|
370
|
-
// Lambda without parameters
|
|
371
|
-
else {
|
|
372
|
-
// (() => {{code}})
|
|
373
|
-
const parts = [
|
|
374
|
-
codePart(`(()=>`, node),
|
|
375
|
-
...visit(node.expression),
|
|
376
|
-
codePart(`)`, node)
|
|
377
|
-
];
|
|
378
|
-
return parts;
|
|
379
|
-
}
|
|
380
|
-
},
|
|
381
|
-
LetExpression: (node, visit) => {
|
|
382
|
-
const declarationsNamesSet = new Set();
|
|
383
|
-
for (const d of node.declarations) {
|
|
384
|
-
if (declarationsNamesSet.has(d.id.name)) {
|
|
385
|
-
throw new CompileError(`"${d.id.name}" name defined inside let expression was repeated`, '', d.id.location);
|
|
386
|
-
}
|
|
387
|
-
declarationsNamesSet.add(d.id.name);
|
|
388
|
-
}
|
|
389
|
-
// (scope=> {
|
|
390
|
-
// var _varNames = [];
|
|
391
|
-
// var _varValues = [];
|
|
392
|
-
// scope = [_varNames, _varValues, scope];
|
|
393
|
-
// // a = {{init}}
|
|
394
|
-
// _varNames.push("a");
|
|
395
|
-
// _varValues.push({{init}});
|
|
396
|
-
// // {{expression}}
|
|
397
|
-
// return {{expression}}
|
|
398
|
-
// })(scope)
|
|
399
|
-
const parts = [
|
|
400
|
-
codePart(`(scope=>{var _varNames=[];var _varValues=[];scope=[_varNames,_varValues,scope];`, node),
|
|
401
|
-
...node.declarations.flatMap(d => [
|
|
402
|
-
codePart(`_varValues.push(`, d),
|
|
403
|
-
...visit(d.init),
|
|
404
|
-
codePart(`);`, d),
|
|
405
|
-
codePart(`_varNames.push(`, d),
|
|
406
|
-
codePart(JSON.stringify(d.id.name), d.id),
|
|
407
|
-
codePart(`);`, d)
|
|
408
|
-
]),
|
|
409
|
-
codePart(`return `, node),
|
|
410
|
-
...visit(node.expression),
|
|
411
|
-
codePart(`})(scope)`, node)
|
|
412
|
-
];
|
|
413
|
-
return parts;
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
const visit = node => {
|
|
417
|
-
const nodeTypeVisitor = visitors[node.type];
|
|
418
|
-
if (nodeTypeVisitor === undefined) {
|
|
419
|
-
throw new Error(`No handler for node type - ${node.type}`);
|
|
420
|
-
}
|
|
421
|
-
const innerVisit = (childNode) => {
|
|
422
|
-
return visit(childNode, node);
|
|
423
|
-
};
|
|
424
|
-
// @ts-expect-error skip node is never
|
|
425
|
-
return nodeTypeVisitor(node, innerVisit);
|
|
426
|
-
};
|
|
427
|
-
export function traverse(tree) {
|
|
428
|
-
return combineVisitResults(visit(tree.expression, null));
|
|
165
|
+
/** Create the default logical operator map (and/&&, or/||). */
|
|
166
|
+
export function createDefaultLogicalOperators(bool) {
|
|
167
|
+
const and = (a, b) => bool(a()) && bool(b());
|
|
168
|
+
const or = (a, b) => bool(a()) || bool(b());
|
|
169
|
+
return { 'and': and, '&&': and, 'or': or, '||': or };
|
|
429
170
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// TODO Use class to access expression from visitors?
|
|
447
|
-
if (err instanceof CompileError) {
|
|
448
|
-
err.expression = expression;
|
|
449
|
-
}
|
|
450
|
-
throw err;
|
|
451
|
-
}
|
|
452
|
-
const { code: expressionCode, offsets } = traverseResult;
|
|
453
|
-
const bootstrapCodeHead = `
|
|
454
|
-
var bool=ctx.castToBoolean;
|
|
455
|
-
var bop=ctx.binaryOperators;
|
|
456
|
-
var lop=ctx.logicalOperators;
|
|
457
|
-
var uop=ctx.unaryOperators;
|
|
458
|
-
var call=ctx.callFunction;
|
|
459
|
-
var getIdentifierValue=ctx.getIdentifierValue;
|
|
460
|
-
var prop=ctx.getProperty;
|
|
461
|
-
var pipe=ctx.pipe;
|
|
462
|
-
var globals=ctx.globals??null;
|
|
171
|
+
export const defaultLogicalOperators = createDefaultLogicalOperators(castToBoolean);
|
|
172
|
+
// --- Bootstrap Code ---
|
|
173
|
+
const bootstrapCodeHead = `
|
|
174
|
+
var ${GEN.bool}=ctx.castToBoolean;
|
|
175
|
+
var ${GEN.str}=ctx.castToString;
|
|
176
|
+
var ${GEN.bop}=ctx.binaryOperators;
|
|
177
|
+
var ${GEN.lop}=ctx.logicalOperators;
|
|
178
|
+
var ${GEN.uop}=ctx.unaryOperators;
|
|
179
|
+
var ${GEN.call}=ctx.callFunction;
|
|
180
|
+
var ${GEN.ensObj}=ctx.ensureObject;
|
|
181
|
+
var ${GEN.ensArr}=ctx.ensureArray;
|
|
182
|
+
var ${GEN.getIdentifierValue}=ctx.getIdentifierValue;
|
|
183
|
+
var ${GEN.prop}=ctx.getProperty;
|
|
184
|
+
var ${GEN.pipe}=ctx.pipe;
|
|
185
|
+
var ${GEN.nna}=ctx.nonNullAssert;
|
|
186
|
+
var ${GEN.globals}=ctx.globals??null;
|
|
463
187
|
|
|
464
|
-
function _get(_scope,name){
|
|
465
|
-
if(_scope===null)return getIdentifierValue(name
|
|
466
|
-
var paramIndex
|
|
467
|
-
if(paramIndex===-1)return _get.call(this
|
|
468
|
-
return _scope[
|
|
188
|
+
function ${GEN._get}(${GEN._scope},name){
|
|
189
|
+
if(${GEN._scope}===null)return ${GEN.getIdentifierValue}(name,${GEN.globals},this);
|
|
190
|
+
var paramIndex=${GEN._scope}[${SCOPE_NAMES}].findIndex(it=>it===name);
|
|
191
|
+
if(paramIndex===-1)return ${GEN._get}.call(this,${GEN._scope}[${SCOPE_PARENT}],name);
|
|
192
|
+
return ${GEN._scope}[${SCOPE_VALUES}][paramIndex]
|
|
469
193
|
};
|
|
470
194
|
|
|
471
195
|
return data=>{
|
|
472
|
-
var scope=null;
|
|
473
|
-
var get
|
|
196
|
+
var ${GEN.scope}=null;
|
|
197
|
+
var ${GEN.get}=${GEN._get}.bind(data);
|
|
474
198
|
return
|
|
475
199
|
`
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
200
|
+
.split('\n')
|
|
201
|
+
.map(it => it.trim())
|
|
202
|
+
.filter(it => it !== '')
|
|
203
|
+
.join('') + ' ';
|
|
204
|
+
const bootstrapCodeHeadLen = bootstrapCodeHead.length;
|
|
205
|
+
/** Compile a SimplEx expression string into an executable function. */
|
|
206
|
+
export function compile(expression, options) {
|
|
207
|
+
const tree = parse(expression);
|
|
208
|
+
const traverseResult = traverse(tree, expression);
|
|
209
|
+
const { code: expressionCode, offsets } = traverseResult;
|
|
481
210
|
const functionCode = bootstrapCodeHead + expressionCode + '}';
|
|
482
|
-
|
|
211
|
+
const resolvedBool = options?.castToBoolean ?? castToBoolean;
|
|
483
212
|
const defaultOptions = {
|
|
484
213
|
...defaultContextHelpers,
|
|
214
|
+
// Recreate operators with custom castToBoolean so compile options
|
|
215
|
+
// are honored by logical (and/or) and unary (not) operators
|
|
485
216
|
...{
|
|
486
|
-
unaryOperators:
|
|
217
|
+
unaryOperators: options?.castToBoolean
|
|
218
|
+
? createDefaultUnaryOperators(resolvedBool)
|
|
219
|
+
: defaultUnaryOperators,
|
|
487
220
|
binaryOperators: defaultBinaryOperators,
|
|
488
|
-
logicalOperators:
|
|
221
|
+
logicalOperators: options?.castToBoolean
|
|
222
|
+
? createDefaultLogicalOperators(resolvedBool)
|
|
223
|
+
: defaultLogicalOperators
|
|
489
224
|
},
|
|
490
225
|
...options
|
|
491
226
|
};
|
|
227
|
+
if (options?.extensions && options.extensions.size > 0 && !options.getProperty) {
|
|
228
|
+
const extensionMap = options.extensions;
|
|
229
|
+
const classesKeys = [];
|
|
230
|
+
const classesValues = [];
|
|
231
|
+
for (const [key, methods] of extensionMap) {
|
|
232
|
+
if (typeof key !== 'string') {
|
|
233
|
+
classesKeys.push(key);
|
|
234
|
+
classesValues.push(methods);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
defaultOptions.getProperty = (obj, key, extension) => {
|
|
238
|
+
if (obj == null)
|
|
239
|
+
return undefined;
|
|
240
|
+
if (extension)
|
|
241
|
+
return getExtensionMethod(obj, key, extensionMap, classesKeys, classesValues);
|
|
242
|
+
return defaultGetProperty(obj, key, false);
|
|
243
|
+
};
|
|
244
|
+
}
|
|
492
245
|
const func = new Function('ctx', functionCode)(defaultOptions);
|
|
246
|
+
const errorMapper = options?.errorMapper !== undefined
|
|
247
|
+
? options.errorMapper
|
|
248
|
+
: getActiveErrorMapper();
|
|
249
|
+
if (errorMapper === null)
|
|
250
|
+
return func;
|
|
493
251
|
return function (data) {
|
|
494
252
|
try {
|
|
495
253
|
return func(data);
|
|
496
254
|
}
|
|
497
255
|
catch (err) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
const stackRows = err.stack?.split('\n').map(row => row.trim());
|
|
501
|
-
const evalRow = stackRows?.find(row => row.startsWith('at eval '));
|
|
502
|
-
if (evalRow === undefined) {
|
|
503
|
-
throw err;
|
|
504
|
-
}
|
|
505
|
-
ERROR_STACK_REGEX.lastIndex = 0;
|
|
506
|
-
const match = ERROR_STACK_REGEX.exec(evalRow);
|
|
507
|
-
if (match == null) {
|
|
508
|
-
throw err;
|
|
509
|
-
}
|
|
510
|
-
const rowOffsetStr = match.groups?.['row'];
|
|
511
|
-
const colOffsetStr = match.groups?.['col'];
|
|
512
|
-
if (rowOffsetStr === undefined || colOffsetStr === undefined) {
|
|
513
|
-
throw err;
|
|
514
|
-
}
|
|
515
|
-
const rowOffset = Number.parseInt(rowOffsetStr);
|
|
516
|
-
assert.equal(rowOffset, 3);
|
|
517
|
-
const colOffset = Number.parseInt(colOffsetStr);
|
|
518
|
-
const adjustedColOffset = colOffset - bootstrapCodeHeadLen;
|
|
519
|
-
assert.ok(adjustedColOffset >= 0);
|
|
520
|
-
const errorLocation = getExpressionErrorLocation(adjustedColOffset, offsets);
|
|
521
|
-
throw new ExpressionError(err.message, expression, errorLocation, {
|
|
522
|
-
cause: err
|
|
523
|
-
});
|
|
256
|
+
throw (errorMapper.mapError(err, expression, offsets, bootstrapCodeHeadLen) ??
|
|
257
|
+
err);
|
|
524
258
|
}
|
|
525
259
|
};
|
|
526
260
|
}
|