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