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.
Files changed (51) hide show
  1. package/README.md +530 -13
  2. package/build/parser/index.js +1161 -479
  3. package/build/parser/index.js.map +1 -1
  4. package/build/src/compiler.d.ts +26 -18
  5. package/build/src/compiler.js +211 -457
  6. package/build/src/compiler.js.map +1 -1
  7. package/build/src/constants.d.ts +25 -0
  8. package/build/src/constants.js +29 -0
  9. package/build/src/constants.js.map +1 -0
  10. package/build/src/error-mapping.d.ts +31 -0
  11. package/build/src/error-mapping.js +72 -0
  12. package/build/src/error-mapping.js.map +1 -0
  13. package/build/src/errors.d.ts +8 -5
  14. package/build/src/errors.js +8 -10
  15. package/build/src/errors.js.map +1 -1
  16. package/build/src/index.d.ts +1 -0
  17. package/build/src/index.js +1 -0
  18. package/build/src/index.js.map +1 -1
  19. package/build/src/simplex-tree.d.ts +25 -3
  20. package/build/src/simplex.peggy +120 -3
  21. package/build/src/tools/index.d.ts +25 -6
  22. package/build/src/tools/index.js +91 -19
  23. package/build/src/tools/index.js.map +1 -1
  24. package/build/src/version.js +1 -1
  25. package/build/src/visitors.d.ts +15 -0
  26. package/build/src/visitors.js +330 -0
  27. package/build/src/visitors.js.map +1 -0
  28. package/package.json +10 -8
  29. package/parser/index.js +1161 -479
  30. package/parser/index.js.map +1 -1
  31. package/src/compiler.ts +332 -609
  32. package/src/constants.ts +30 -0
  33. package/src/error-mapping.ts +112 -0
  34. package/src/errors.ts +8 -12
  35. package/src/index.ts +1 -0
  36. package/src/simplex-tree.ts +30 -2
  37. package/src/simplex.peggy +120 -3
  38. package/src/tools/index.ts +117 -24
  39. package/src/visitors.ts +491 -0
  40. package/build/src/tools/cast.d.ts +0 -2
  41. package/build/src/tools/cast.js +0 -20
  42. package/build/src/tools/cast.js.map +0 -1
  43. package/build/src/tools/ensure.d.ts +0 -3
  44. package/build/src/tools/ensure.js +0 -30
  45. package/build/src/tools/ensure.js.map +0 -1
  46. package/build/src/tools/guards.d.ts +0 -2
  47. package/build/src/tools/guards.js +0 -20
  48. package/build/src/tools/guards.js.map +0 -1
  49. package/src/tools/cast.ts +0 -26
  50. package/src/tools/ensure.ts +0 -41
  51. package/src/tools/guards.ts +0 -29
@@ -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 { CompileError, ExpressionError, UnexpectedTypeError } from './errors.js';
5
- import assert from 'node:assert';
6
- import { castToBoolean } from './tools/cast.js';
7
- import { ensureFunction, ensureRelationalComparable, ensureNumber } from './tools/ensure.js';
8
- import { isSimpleValue } from './tools/guards.js';
9
- import { castToString, typeOf } from './tools/index.js';
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
- var ERROR_STACK_REGEX = /<anonymous>:(?<row>\d+):(?<col>\d+)/g;
12
- var TOPIC_TOKEN = '%';
13
- const defaultContextHelpers = {
14
- castToBoolean,
15
- ensureFunction,
16
- getIdentifierValue: (identifierName, globals, data) => {
17
- // TODO Should test on parse time?
18
- if (identifierName === TOPIC_TOKEN) {
19
- throw new Error(`Topic reference "${TOPIC_TOKEN}" is unbound; it must be inside a pipe body.`);
20
- }
21
- if (identifierName === 'undefined')
22
- return undefined;
23
- if (globals != null && Object.hasOwn(globals, identifierName)) {
24
- return globals[identifierName];
25
- }
26
- if (data != null && Object.hasOwn(data, identifierName)) {
27
- return data[identifierName];
28
- }
29
- throw new Error(`Unknown identifier - ${identifierName}`);
30
- },
31
- getProperty(obj, key) {
32
- if (obj == null)
33
- return obj;
34
- const typeofObj = typeof obj;
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
- callFunction(fn, args) {
51
- return (args === null
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
- pipe(head, tail) {
56
- var result = head;
57
- for (const it of tail) {
58
- if (it.opt && result == null)
59
- return result;
60
- result = it.next(result);
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
- return result;
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
- export const defaultUnaryOperators = {
66
- '+': val => ensureNumber(val),
67
- '-': val => -ensureNumber(val),
68
- 'not': val => !castToBoolean(val),
69
- 'typeof': val => typeof val
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
- // TIPS give the opportunity to get a base js error
75
- '*': (a, b) => {
76
- // @ts-expect-error
77
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
78
- return ensureNumber(a) * ensureNumber(b);
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
- if (isSimpleValue(a) && b != null && typeof b === 'object') {
112
- return Object.hasOwn(b, a);
113
- }
114
- else {
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
- return parts;
283
- }
284
- //
285
- else {
286
- const parts = [
287
- codePart('call(', node),
288
- ...visit(node.callee),
289
- codePart(',null)', node)
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
- declarationsNamesSet.add(d.id.name);
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
- export function traverse(tree) {
408
- 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 };
409
170
  }
410
- function getExpressionErrorLocation(colOffset, locations) {
411
- var curCol = 0;
412
- for (const loc of locations) {
413
- curCol += loc.len;
414
- if (curCol >= colOffset)
415
- return loc.location;
416
- }
417
- return null;
418
- }
419
- export function compile(expression, options) {
420
- const tree = parse(expression);
421
- let traverseResult;
422
- try {
423
- traverseResult = traverse(tree);
424
- }
425
- catch (err) {
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,globals,this);
446
- var paramIndex=_scope[0].findIndex(it=>it===name);
447
- if(paramIndex===-1)return _get.call(this,_scope[2],name);
448
- return _scope[1][paramIndex]
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=_get.bind(data);
196
+ var ${GEN.scope}=null;
197
+ var ${GEN.get}=${GEN._get}.bind(data);
454
198
  return
455
199
  `
456
- .split('\n')
457
- .map(it => it.trim())
458
- .filter(it => it !== '')
459
- .join('') + ' ';
460
- const bootstrapCodeHeadLen = bootstrapCodeHead.length;
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
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
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: defaultUnaryOperators,
217
+ unaryOperators: options?.castToBoolean
218
+ ? createDefaultUnaryOperators(resolvedBool)
219
+ : defaultUnaryOperators,
467
220
  binaryOperators: defaultBinaryOperators,
468
- logicalOperators: defaultLogicalOperators
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
- if (err instanceof Error === false)
479
- throw err;
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
  }