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/src/compiler.ts
CHANGED
|
@@ -2,183 +2,272 @@
|
|
|
2
2
|
|
|
3
3
|
// eslint-disable-next-line n/no-missing-import
|
|
4
4
|
import { parse } from '../parser/index.js'
|
|
5
|
-
import {
|
|
5
|
+
import { ExpressionError, UnexpectedTypeError } from './errors.js'
|
|
6
|
+
import {
|
|
7
|
+
getActiveErrorMapper,
|
|
8
|
+
getExpressionErrorLocation
|
|
9
|
+
} from './error-mapping.js'
|
|
10
|
+
import type { ErrorMapper } from './error-mapping.js'
|
|
6
11
|
import {
|
|
7
12
|
BinaryExpression,
|
|
8
|
-
Expression,
|
|
9
|
-
ExpressionByType,
|
|
10
13
|
ExpressionStatement,
|
|
11
|
-
Location,
|
|
12
14
|
LogicalExpression,
|
|
13
15
|
UnaryExpression
|
|
14
16
|
} from './simplex-tree.js'
|
|
15
|
-
import assert from 'node:assert'
|
|
16
|
-
import { castToBoolean } from './tools/cast.js'
|
|
17
17
|
import {
|
|
18
|
+
castToBoolean,
|
|
19
|
+
castToString,
|
|
18
20
|
ensureFunction,
|
|
21
|
+
ensureNumber,
|
|
22
|
+
ensureArray,
|
|
23
|
+
ensureObject,
|
|
19
24
|
ensureRelationalComparable,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
isSimpleValue,
|
|
26
|
+
objToStringAlias,
|
|
27
|
+
typeOf
|
|
28
|
+
} from './tools/index.js'
|
|
29
|
+
import { traverse } from './visitors.js'
|
|
30
|
+
import type { SourceLocation, VisitResult } from './visitors.js'
|
|
31
|
+
import { GEN, SCOPE_NAMES, SCOPE_VALUES, SCOPE_PARENT } from './constants.js'
|
|
32
|
+
|
|
33
|
+
export type { SourceLocation, VisitResult, ErrorMapper }
|
|
34
|
+
export { traverse, getExpressionErrorLocation }
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
// --- Context Helpers ---
|
|
37
|
+
|
|
38
|
+
export interface ContextHelpers<Data, Globals> {
|
|
26
39
|
castToBoolean(this: void, val: unknown): boolean
|
|
40
|
+
castToString(this: void, val: unknown): string
|
|
27
41
|
ensureFunction(this: void, val: unknown): Function
|
|
42
|
+
ensureObject(this: void, val: unknown): object
|
|
43
|
+
ensureArray(this: void, val: unknown): unknown[]
|
|
44
|
+
nonNullAssert(this: void, val: unknown): unknown
|
|
28
45
|
getIdentifierValue(
|
|
29
46
|
this: void,
|
|
30
47
|
identifierName: string,
|
|
31
48
|
globals: Globals,
|
|
32
49
|
data: Data
|
|
33
50
|
): unknown
|
|
34
|
-
getProperty(
|
|
51
|
+
getProperty(
|
|
52
|
+
this: void,
|
|
53
|
+
obj: unknown,
|
|
54
|
+
key: unknown,
|
|
55
|
+
extension: boolean
|
|
56
|
+
): unknown
|
|
35
57
|
callFunction(this: void, fn: unknown, args: unknown[] | null): unknown
|
|
36
58
|
pipe(
|
|
37
59
|
this: void,
|
|
38
60
|
head: unknown,
|
|
39
|
-
tail: { opt: boolean; next: (topic: unknown) => unknown }[]
|
|
61
|
+
tail: { opt: boolean; fwd: boolean; next: (topic: unknown) => unknown }[]
|
|
40
62
|
): unknown
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
var hasOwn = Object.hasOwn
|
|
44
|
-
var ERROR_STACK_REGEX = /<anonymous>:(?<row>\d+):(?<col>\d+)/g
|
|
45
|
-
var TOPIC_TOKEN = '%'
|
|
46
66
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
/** Look up an identifier in globals first, then data; throw on miss. */
|
|
68
|
+
function defaultGetIdentifierValue(
|
|
69
|
+
identifierName: string,
|
|
70
|
+
globals: Record<string, unknown>,
|
|
71
|
+
data: Record<string, unknown>
|
|
72
|
+
): unknown {
|
|
73
|
+
if (identifierName === 'undefined') return undefined
|
|
52
74
|
|
|
53
|
-
|
|
75
|
+
if (globals != null && Object.hasOwn(globals, identifierName)) {
|
|
76
|
+
return globals[identifierName]
|
|
77
|
+
}
|
|
54
78
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
throw new Error(
|
|
59
|
-
`Topic reference "${TOPIC_TOKEN}" is unbound; it must be inside a pipe body.`
|
|
60
|
-
)
|
|
61
|
-
}
|
|
79
|
+
if (data != null && Object.hasOwn(data, identifierName)) {
|
|
80
|
+
return data[identifierName]
|
|
81
|
+
}
|
|
62
82
|
|
|
63
|
-
|
|
83
|
+
throw new Error(`Unknown identifier - ${identifierName}`)
|
|
84
|
+
}
|
|
64
85
|
|
|
65
|
-
|
|
66
|
-
|
|
86
|
+
/** Look up an extension method for the given object type and bind obj as first argument. */
|
|
87
|
+
function getExtensionMethod(
|
|
88
|
+
obj: unknown,
|
|
89
|
+
key: unknown,
|
|
90
|
+
extensionMap: Map<string | object | Function, Record<string, Function>>,
|
|
91
|
+
classesKeys: (object | Function)[],
|
|
92
|
+
classesValues: Record<string, Function>[]
|
|
93
|
+
): Function {
|
|
94
|
+
var typeofObj = typeof obj
|
|
95
|
+
var methods: Record<string, Function> | undefined
|
|
96
|
+
|
|
97
|
+
if (typeofObj === 'object') {
|
|
98
|
+
for (var i = 0; i < classesKeys.length; i++) {
|
|
99
|
+
// @ts-expect-error supports objects with Symbol.hasInstance
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
101
|
+
if (obj instanceof classesKeys[i]!) {
|
|
102
|
+
methods = classesValues[i]
|
|
103
|
+
break
|
|
104
|
+
}
|
|
67
105
|
}
|
|
106
|
+
} else {
|
|
107
|
+
methods = extensionMap.get(typeofObj)
|
|
108
|
+
}
|
|
68
109
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
110
|
+
if (methods === undefined) {
|
|
111
|
+
throw new TypeError(
|
|
112
|
+
`No extension methods defined for type "${typeofObj}"`
|
|
113
|
+
)
|
|
114
|
+
}
|
|
72
115
|
|
|
73
|
-
|
|
74
|
-
|
|
116
|
+
var method = methods[key as string]
|
|
117
|
+
if (method === undefined) {
|
|
118
|
+
throw new TypeError(
|
|
119
|
+
`Extension method "${String(key)}" is not defined for type "${typeofObj}"`
|
|
120
|
+
)
|
|
121
|
+
}
|
|
75
122
|
|
|
76
|
-
|
|
77
|
-
|
|
123
|
+
return method.bind(null, obj) as Function
|
|
124
|
+
}
|
|
78
125
|
|
|
79
|
-
|
|
126
|
+
/** Resolve property access on an object, Map, or string (null-safe). */
|
|
127
|
+
function defaultGetProperty(
|
|
128
|
+
obj: unknown,
|
|
129
|
+
key: unknown,
|
|
130
|
+
extension: boolean
|
|
131
|
+
): unknown {
|
|
132
|
+
if (obj == null) return undefined
|
|
133
|
+
|
|
134
|
+
if (extension) {
|
|
135
|
+
throw new ExpressionError(
|
|
136
|
+
'Extension member expression (::) is reserved and not implemented',
|
|
137
|
+
'',
|
|
138
|
+
null
|
|
139
|
+
)
|
|
140
|
+
}
|
|
80
141
|
|
|
81
|
-
|
|
82
|
-
return (obj as string)[key]
|
|
83
|
-
}
|
|
142
|
+
var typeofObj = typeof obj
|
|
84
143
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
144
|
+
if (typeofObj === 'string' && typeof key === 'number') {
|
|
145
|
+
return (obj as string)[key]
|
|
146
|
+
}
|
|
88
147
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
148
|
+
if (typeofObj !== 'object') {
|
|
149
|
+
throw new UnexpectedTypeError(['object'], obj)
|
|
150
|
+
}
|
|
92
151
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
152
|
+
if (isSimpleValue(key) === false) {
|
|
153
|
+
throw new UnexpectedTypeError(['simple type object key'], key)
|
|
154
|
+
}
|
|
97
155
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
156
|
+
if (hasOwn(obj, key as any)) {
|
|
157
|
+
// @ts-expect-error Type cannot be used as an index type
|
|
158
|
+
return obj[key] as unknown
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (obj instanceof Map) {
|
|
162
|
+
return obj.get(key) as unknown
|
|
163
|
+
}
|
|
101
164
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
165
|
+
return undefined
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Call a function value; null/undefined silently returns undefined. */
|
|
169
|
+
function defaultCallFunction(fn: unknown, args: unknown[] | null): unknown {
|
|
170
|
+
return fn == null
|
|
171
|
+
? undefined
|
|
172
|
+
: ((args === null
|
|
173
|
+
? ensureFunction(fn)()
|
|
174
|
+
: ensureFunction(fn).apply(null, args)) as unknown)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Assert that a value is not null or undefined; throw on null/undefined. */
|
|
178
|
+
function defaultNonNullAssert(val: unknown): unknown {
|
|
179
|
+
if (val == null) {
|
|
180
|
+
throw new ExpressionError(
|
|
181
|
+
'Non-null assertion failed: value is ' +
|
|
182
|
+
(val === null ? 'null' : 'undefined'),
|
|
183
|
+
'',
|
|
184
|
+
null
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
return val
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Execute a pipe sequence, threading each result through the next step. */
|
|
191
|
+
function defaultPipe(
|
|
192
|
+
head: unknown,
|
|
193
|
+
tail: { opt: boolean; fwd: boolean; next: (topic: unknown) => unknown }[]
|
|
194
|
+
): unknown {
|
|
195
|
+
var result = head
|
|
196
|
+
for (const it of tail) {
|
|
197
|
+
if (it.fwd) {
|
|
198
|
+
throw new ExpressionError(
|
|
199
|
+
'Pipe forward operator (|>) is reserved and not implemented',
|
|
200
|
+
'',
|
|
201
|
+
null
|
|
202
|
+
)
|
|
118
203
|
}
|
|
119
|
-
return result
|
|
204
|
+
if (it.opt && result == null) return result
|
|
205
|
+
result = it.next(result)
|
|
120
206
|
}
|
|
207
|
+
return result
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const defaultContextHelpers: ContextHelpers<
|
|
211
|
+
Record<string, unknown>,
|
|
212
|
+
Record<string, unknown>
|
|
213
|
+
> = {
|
|
214
|
+
castToBoolean,
|
|
215
|
+
castToString,
|
|
216
|
+
ensureFunction,
|
|
217
|
+
ensureObject,
|
|
218
|
+
ensureArray,
|
|
219
|
+
nonNullAssert: defaultNonNullAssert,
|
|
220
|
+
getIdentifierValue: defaultGetIdentifierValue,
|
|
221
|
+
getProperty: defaultGetProperty,
|
|
222
|
+
callFunction: defaultCallFunction,
|
|
223
|
+
pipe: defaultPipe
|
|
121
224
|
}
|
|
122
225
|
|
|
123
|
-
|
|
226
|
+
// --- Operators ---
|
|
227
|
+
|
|
228
|
+
export type ExpressionUnaryOperators = Record<
|
|
124
229
|
UnaryExpression['operator'],
|
|
125
230
|
(val: unknown) => unknown
|
|
126
231
|
>
|
|
127
232
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
233
|
+
/** Create the default unary operator map (+, -, not, typeof). */
|
|
234
|
+
export function createDefaultUnaryOperators(
|
|
235
|
+
bool: (val: unknown) => boolean
|
|
236
|
+
): ExpressionUnaryOperators {
|
|
237
|
+
return {
|
|
238
|
+
'+': val => ensureNumber(val),
|
|
239
|
+
'-': val => -ensureNumber(val),
|
|
240
|
+
'not': val => !bool(val),
|
|
241
|
+
'typeof': val => typeof val
|
|
242
|
+
}
|
|
133
243
|
}
|
|
134
244
|
|
|
135
|
-
|
|
245
|
+
export const defaultUnaryOperators: ExpressionUnaryOperators =
|
|
246
|
+
createDefaultUnaryOperators(castToBoolean)
|
|
247
|
+
|
|
248
|
+
export type ExpressionBinaryOperators = Record<
|
|
136
249
|
BinaryExpression['operator'],
|
|
137
250
|
(left: unknown, right: unknown) => unknown
|
|
138
251
|
>
|
|
139
252
|
|
|
253
|
+
const numericOp =
|
|
254
|
+
(
|
|
255
|
+
fn: (a: number, b: number) => number
|
|
256
|
+
): ((a: unknown, b: unknown) => unknown) =>
|
|
257
|
+
(a, b) =>
|
|
258
|
+
fn(ensureNumber(a) as number, ensureNumber(b) as number)
|
|
259
|
+
|
|
140
260
|
export const defaultBinaryOperators: ExpressionBinaryOperators = {
|
|
141
261
|
'!=': (a, b) => a !== b,
|
|
142
262
|
|
|
143
263
|
'==': (a, b) => a === b,
|
|
144
264
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
'
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
'+': (a, b) => {
|
|
154
|
-
// @ts-expect-error
|
|
155
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/restrict-plus-operands
|
|
156
|
-
return ensureNumber(a) + ensureNumber(b)
|
|
157
|
-
},
|
|
158
|
-
|
|
159
|
-
'-': (a, b) => {
|
|
160
|
-
// @ts-expect-error
|
|
161
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
162
|
-
return ensureNumber(a) - ensureNumber(b)
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
'/': (a, b) => {
|
|
166
|
-
// @ts-expect-error
|
|
167
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
168
|
-
return ensureNumber(a) / ensureNumber(b)
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
'mod': (a, b) => {
|
|
172
|
-
// @ts-expect-error
|
|
173
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
174
|
-
return ensureNumber(a) % ensureNumber(b)
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
'^': (a, b) => {
|
|
178
|
-
// @ts-expect-error
|
|
179
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
180
|
-
return ensureNumber(a) ** ensureNumber(b)
|
|
181
|
-
},
|
|
265
|
+
'*': numericOp((a, b) => a * b),
|
|
266
|
+
'+': numericOp((a, b) => a + b),
|
|
267
|
+
'-': numericOp((a, b) => a - b),
|
|
268
|
+
'/': numericOp((a, b) => a / b),
|
|
269
|
+
'mod': numericOp((a, b) => a % b),
|
|
270
|
+
'^': numericOp((a, b) => a ** b),
|
|
182
271
|
|
|
183
272
|
'&': (a, b) => castToString(a) + castToString(b),
|
|
184
273
|
|
|
@@ -192,7 +281,7 @@ export const defaultBinaryOperators: ExpressionBinaryOperators = {
|
|
|
192
281
|
'>=': (a, b) =>
|
|
193
282
|
ensureRelationalComparable(a) >= ensureRelationalComparable(b),
|
|
194
283
|
|
|
195
|
-
//
|
|
284
|
+
// Check if key exists in container (Object/Array/Map)
|
|
196
285
|
'in': (a, b) => {
|
|
197
286
|
const bType = objToStringAlias.call(b)
|
|
198
287
|
|
|
@@ -207,7 +296,7 @@ export const defaultBinaryOperators: ExpressionBinaryOperators = {
|
|
|
207
296
|
return a in b
|
|
208
297
|
} else {
|
|
209
298
|
throw new TypeError(
|
|
210
|
-
`Wrong "in" operator usage - key value
|
|
299
|
+
`Wrong "in" operator usage - key value must be a safe integer`
|
|
211
300
|
)
|
|
212
301
|
}
|
|
213
302
|
}
|
|
@@ -224,31 +313,29 @@ export const defaultBinaryOperators: ExpressionBinaryOperators = {
|
|
|
224
313
|
}
|
|
225
314
|
}
|
|
226
315
|
|
|
227
|
-
type LogicalOperatorFunction = (
|
|
316
|
+
export type LogicalOperatorFunction = (
|
|
228
317
|
left: () => unknown,
|
|
229
318
|
right: () => unknown
|
|
230
319
|
) => unknown
|
|
231
320
|
|
|
232
|
-
type ExpressionLogicalOperators = Record<
|
|
321
|
+
export type ExpressionLogicalOperators = Record<
|
|
233
322
|
LogicalExpression['operator'],
|
|
234
323
|
LogicalOperatorFunction
|
|
235
324
|
>
|
|
236
325
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// TODO Use castToBoolean from compile options?
|
|
245
|
-
'and': logicalAndOperatorFn,
|
|
246
|
-
'&&': logicalAndOperatorFn,
|
|
247
|
-
'or': logicalOrOperatorFn,
|
|
248
|
-
'||': logicalOrOperatorFn
|
|
326
|
+
/** Create the default logical operator map (and/&&, or/||). */
|
|
327
|
+
export function createDefaultLogicalOperators(
|
|
328
|
+
bool: (val: unknown) => boolean
|
|
329
|
+
): ExpressionLogicalOperators {
|
|
330
|
+
const and: LogicalOperatorFunction = (a, b) => bool(a()) && bool(b())
|
|
331
|
+
const or: LogicalOperatorFunction = (a, b) => bool(a()) || bool(b())
|
|
332
|
+
return { 'and': and, '&&': and, 'or': or, '||': or }
|
|
249
333
|
}
|
|
250
334
|
|
|
251
|
-
|
|
335
|
+
export const defaultLogicalOperators: ExpressionLogicalOperators =
|
|
336
|
+
createDefaultLogicalOperators(castToBoolean)
|
|
337
|
+
|
|
338
|
+
export interface ExpressionOperators {
|
|
252
339
|
unaryOperators: Record<UnaryExpression['operator'], (val: unknown) => unknown>
|
|
253
340
|
binaryOperators: Record<
|
|
254
341
|
BinaryExpression['operator'],
|
|
@@ -260,408 +347,55 @@ interface ExpressionOperators {
|
|
|
260
347
|
>
|
|
261
348
|
}
|
|
262
349
|
|
|
263
|
-
|
|
264
|
-
len: number
|
|
265
|
-
location: Location
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export interface VisitResult {
|
|
269
|
-
code: string
|
|
270
|
-
offsets: SourceLocation[]
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
type Visit = (node: Expression) => VisitResult[]
|
|
274
|
-
|
|
275
|
-
const codePart = (
|
|
276
|
-
codePart: string,
|
|
277
|
-
ownerNode: { location: Location }
|
|
278
|
-
): VisitResult => ({
|
|
279
|
-
code: codePart,
|
|
280
|
-
offsets: [{ len: codePart.length, location: ownerNode.location }]
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
const combineVisitResults = (parts: VisitResult[]) => {
|
|
284
|
-
return parts.reduce((res, it) => {
|
|
285
|
-
return {
|
|
286
|
-
code: res.code + it.code,
|
|
287
|
-
offsets: res.offsets.concat(it.offsets)
|
|
288
|
-
} as VisitResult
|
|
289
|
-
})
|
|
290
|
-
}
|
|
350
|
+
// --- Bootstrap Code ---
|
|
291
351
|
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
codePart(`uop["${node.operator}"](`, node),
|
|
315
|
-
...visit(node.argument),
|
|
316
|
-
codePart(')', node)
|
|
317
|
-
]
|
|
318
|
-
|
|
319
|
-
return parts
|
|
320
|
-
},
|
|
321
|
-
|
|
322
|
-
BinaryExpression: (node, visit) => {
|
|
323
|
-
const parts: VisitResult[] = [
|
|
324
|
-
codePart(`bop["${node.operator}"](`, node),
|
|
325
|
-
...visit(node.left),
|
|
326
|
-
codePart(',', node),
|
|
327
|
-
...visit(node.right),
|
|
328
|
-
codePart(')', node)
|
|
329
|
-
]
|
|
330
|
-
|
|
331
|
-
return parts
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
LogicalExpression: (node, visit) => {
|
|
335
|
-
const parts: VisitResult[] = [
|
|
336
|
-
codePart(`lop["${node.operator}"](()=>(`, node),
|
|
337
|
-
...visit(node.left),
|
|
338
|
-
codePart('),()=>(', node),
|
|
339
|
-
...visit(node.right),
|
|
340
|
-
codePart('))', node)
|
|
341
|
-
]
|
|
342
|
-
|
|
343
|
-
return parts
|
|
344
|
-
},
|
|
345
|
-
|
|
346
|
-
ConditionalExpression: (node, visit) => {
|
|
347
|
-
const parts: VisitResult[] = [
|
|
348
|
-
codePart('(bool(', node),
|
|
349
|
-
...visit(node.test),
|
|
350
|
-
codePart(')?', node),
|
|
351
|
-
...visit(node.consequent),
|
|
352
|
-
codePart(':', node),
|
|
353
|
-
...(node.alternate !== null
|
|
354
|
-
? visit(node.alternate)
|
|
355
|
-
: [codePart('undefined', node)]),
|
|
356
|
-
codePart(')', node)
|
|
357
|
-
]
|
|
358
|
-
|
|
359
|
-
return parts
|
|
360
|
-
},
|
|
361
|
-
|
|
362
|
-
ObjectExpression: (node, visit) => {
|
|
363
|
-
const innerObj = node.properties
|
|
364
|
-
.map((p): [VisitResult, VisitResult[]] => {
|
|
365
|
-
if (p.key.type === 'Identifier') {
|
|
366
|
-
return [codePart(p.key.name, p), visit(p.value)]
|
|
367
|
-
}
|
|
368
|
-
//
|
|
369
|
-
else if (p.key.type === 'Literal') {
|
|
370
|
-
// TODO look for ECMA spec
|
|
371
|
-
return [codePart(JSON.stringify(p.key.value), p), visit(p.value)]
|
|
372
|
-
}
|
|
373
|
-
//
|
|
374
|
-
else {
|
|
375
|
-
// TODO Restrict on parse step
|
|
376
|
-
// TODO Error with locations
|
|
377
|
-
throw new TypeError(`Incorrect object key type ${p.key.type}`)
|
|
378
|
-
}
|
|
379
|
-
})
|
|
380
|
-
.flatMap(([k, v]) => {
|
|
381
|
-
return [k, codePart(':', node), ...v, codePart(',', node)]
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
// remove last comma
|
|
385
|
-
if (innerObj.length > 1) {
|
|
386
|
-
innerObj.pop()
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const parts: VisitResult[] = [
|
|
390
|
-
codePart('{', node),
|
|
391
|
-
...innerObj,
|
|
392
|
-
codePart('}', node)
|
|
393
|
-
]
|
|
394
|
-
|
|
395
|
-
return parts
|
|
396
|
-
},
|
|
397
|
-
|
|
398
|
-
ArrayExpression: (node, visit) => {
|
|
399
|
-
const innerArrParts = node.elements.flatMap(el => {
|
|
400
|
-
return el === null
|
|
401
|
-
? [codePart(',', node)]
|
|
402
|
-
: [...visit(el), codePart(',', node)]
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
// remove last comma
|
|
406
|
-
if (innerArrParts.length > 1) {
|
|
407
|
-
innerArrParts.pop()
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const parts: VisitResult[] = [
|
|
411
|
-
codePart('[', node),
|
|
412
|
-
...innerArrParts,
|
|
413
|
-
codePart(']', node)
|
|
414
|
-
]
|
|
415
|
-
|
|
416
|
-
return parts
|
|
417
|
-
},
|
|
418
|
-
|
|
419
|
-
MemberExpression: (node, visit) => {
|
|
420
|
-
const { computed, object, property } = node
|
|
421
|
-
|
|
422
|
-
// TODO Pass computed to prop?
|
|
423
|
-
|
|
424
|
-
const parts: VisitResult[] = [
|
|
425
|
-
codePart('prop(', node),
|
|
426
|
-
...visit(object),
|
|
427
|
-
codePart(',', node),
|
|
428
|
-
...(computed
|
|
429
|
-
? visit(property)
|
|
430
|
-
: [codePart(JSON.stringify(property.name), property)]),
|
|
431
|
-
codePart(')', node)
|
|
432
|
-
]
|
|
433
|
-
|
|
434
|
-
return parts
|
|
435
|
-
},
|
|
436
|
-
|
|
437
|
-
CallExpression: (node, visit) => {
|
|
438
|
-
if (node.arguments.length > 0) {
|
|
439
|
-
const innerArgs = node.arguments.flatMap((arg, index) => [
|
|
440
|
-
...(arg.type === 'CurryPlaceholder'
|
|
441
|
-
? [codePart(`a${index}`, arg)]
|
|
442
|
-
: visit(arg)),
|
|
443
|
-
codePart(',', node)
|
|
444
|
-
])
|
|
445
|
-
|
|
446
|
-
const curriedArgs = node.arguments.flatMap((arg, index) =>
|
|
447
|
-
arg.type === 'CurryPlaceholder' ? [`a${index}`] : []
|
|
448
|
-
)
|
|
449
|
-
|
|
450
|
-
// remove last comma
|
|
451
|
-
innerArgs?.pop()
|
|
452
|
-
|
|
453
|
-
// call({{callee}},[{{arguments}}])
|
|
454
|
-
let parts: VisitResult[] = [
|
|
455
|
-
codePart('call(', node),
|
|
456
|
-
...visit(node.callee),
|
|
457
|
-
codePart(',[', node),
|
|
458
|
-
...innerArgs,
|
|
459
|
-
codePart('])', node)
|
|
460
|
-
]
|
|
461
|
-
|
|
462
|
-
if (curriedArgs.length > 0) {
|
|
463
|
-
parts = [
|
|
464
|
-
codePart(`(scope=>(${curriedArgs.join()})=>`, node),
|
|
465
|
-
...parts,
|
|
466
|
-
codePart(')(scope)', node)
|
|
467
|
-
]
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
return parts
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
//
|
|
474
|
-
else {
|
|
475
|
-
const parts: VisitResult[] = [
|
|
476
|
-
codePart('call(', node),
|
|
477
|
-
...visit(node.callee),
|
|
478
|
-
codePart(',null)', node)
|
|
479
|
-
]
|
|
480
|
-
|
|
481
|
-
return parts
|
|
482
|
-
}
|
|
483
|
-
},
|
|
484
|
-
|
|
485
|
-
NullishCoalescingExpression: (node, visit) => {
|
|
486
|
-
const parts: VisitResult[] = [
|
|
487
|
-
codePart('(', node),
|
|
488
|
-
...visit(node.left),
|
|
489
|
-
codePart('??', node),
|
|
490
|
-
...visit(node.right),
|
|
491
|
-
codePart(')', node)
|
|
492
|
-
]
|
|
493
|
-
|
|
494
|
-
return parts
|
|
495
|
-
},
|
|
496
|
-
|
|
497
|
-
PipeSequence: (node, visit) => {
|
|
498
|
-
const headCode = visit(node.head)
|
|
499
|
-
|
|
500
|
-
const tailsCodeArrInner = node.tail.flatMap(t => {
|
|
501
|
-
const opt = t.operator === '|?'
|
|
502
|
-
|
|
503
|
-
const tailParts: VisitResult[] = [
|
|
504
|
-
codePart(
|
|
505
|
-
`{opt:${opt},next:(scope=>topic=>{scope=[["%"],[topic],scope];return `,
|
|
506
|
-
t.expression
|
|
507
|
-
),
|
|
508
|
-
...visit(t.expression),
|
|
509
|
-
codePart(`})(scope)}`, t.expression),
|
|
510
|
-
codePart(`,`, t.expression)
|
|
511
|
-
]
|
|
512
|
-
|
|
513
|
-
return tailParts
|
|
514
|
-
})
|
|
515
|
-
|
|
516
|
-
// remove last comma
|
|
517
|
-
tailsCodeArrInner.pop()
|
|
518
|
-
|
|
519
|
-
const parts: VisitResult[] = [
|
|
520
|
-
codePart('pipe(', node),
|
|
521
|
-
...headCode,
|
|
522
|
-
codePart(',[', node),
|
|
523
|
-
...tailsCodeArrInner,
|
|
524
|
-
codePart('])', node)
|
|
525
|
-
]
|
|
526
|
-
|
|
527
|
-
return parts
|
|
528
|
-
},
|
|
529
|
-
|
|
530
|
-
TopicReference: node => {
|
|
531
|
-
const parts: VisitResult[] = [codePart(`get(scope,"${TOPIC_TOKEN}")`, node)]
|
|
532
|
-
return parts
|
|
533
|
-
},
|
|
534
|
-
|
|
535
|
-
LambdaExpression: (node, visit) => {
|
|
536
|
-
// Lambda with parameters
|
|
537
|
-
if (node.params.length > 0) {
|
|
538
|
-
const paramsNames = node.params.map(p => p.name)
|
|
539
|
-
|
|
540
|
-
const fnParams = Array.from(
|
|
541
|
-
{ length: paramsNames.length },
|
|
542
|
-
(_, index) => `p${index}`
|
|
543
|
-
)
|
|
544
|
-
|
|
545
|
-
const fnParamsList = fnParams.join()
|
|
546
|
-
const fnParamsNamesList = paramsNames.map(p => JSON.stringify(p)).join()
|
|
547
|
-
|
|
548
|
-
// TODO Is "...args" more performant?
|
|
549
|
-
// (params => function (p0, p1) {
|
|
550
|
-
// var scope = [params, [p0, p1], scope]
|
|
551
|
-
// return {{code}}
|
|
552
|
-
// })(["a", "b"])
|
|
553
|
-
const parts: VisitResult[] = [
|
|
554
|
-
codePart(
|
|
555
|
-
`((scope,params)=>function(${fnParamsList}){scope=[params,[${fnParamsList}],scope];return `,
|
|
556
|
-
node
|
|
557
|
-
),
|
|
558
|
-
...visit(node.expression),
|
|
559
|
-
codePart(`})(scope,[${fnParamsNamesList}])`, node)
|
|
560
|
-
]
|
|
561
|
-
|
|
562
|
-
return parts
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// Lambda without parameters
|
|
566
|
-
else {
|
|
567
|
-
// (() => {{code}})
|
|
568
|
-
const parts: VisitResult[] = [
|
|
569
|
-
codePart(`(()=>`, node),
|
|
570
|
-
...visit(node.expression),
|
|
571
|
-
codePart(`)`, node)
|
|
572
|
-
]
|
|
573
|
-
|
|
574
|
-
return parts
|
|
575
|
-
}
|
|
576
|
-
},
|
|
577
|
-
|
|
578
|
-
LetExpression: (node, visit) => {
|
|
579
|
-
const declarationsNamesSet = new Set()
|
|
580
|
-
|
|
581
|
-
for (const d of node.declarations) {
|
|
582
|
-
if (declarationsNamesSet.has(d.id.name)) {
|
|
583
|
-
throw new CompileError(
|
|
584
|
-
`"${d.id.name}" name defined inside let expression was repeated`,
|
|
585
|
-
'',
|
|
586
|
-
d.id.location
|
|
587
|
-
)
|
|
588
|
-
}
|
|
589
|
-
declarationsNamesSet.add(d.id.name)
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// (scope=> {
|
|
593
|
-
// var _varNames = [];
|
|
594
|
-
// var _varValues = [];
|
|
595
|
-
// scope = [_varNames, _varValues, scope];
|
|
596
|
-
|
|
597
|
-
// // a = {{init}}
|
|
598
|
-
// _varNames.push("a");
|
|
599
|
-
// _varValues.push({{init}});
|
|
600
|
-
|
|
601
|
-
// // {{expression}}
|
|
602
|
-
// return {{expression}}
|
|
603
|
-
// })(scope)
|
|
604
|
-
|
|
605
|
-
const parts: VisitResult[] = [
|
|
606
|
-
codePart(
|
|
607
|
-
`(scope=>{var _varNames=[];var _varValues=[];scope=[_varNames,_varValues,scope];`,
|
|
608
|
-
node
|
|
609
|
-
),
|
|
610
|
-
...node.declarations.flatMap(d => [
|
|
611
|
-
codePart(`_varValues.push(`, d),
|
|
612
|
-
...visit(d.init),
|
|
613
|
-
codePart(`);`, d),
|
|
614
|
-
codePart(`_varNames.push(`, d),
|
|
615
|
-
codePart(JSON.stringify(d.id.name), d.id),
|
|
616
|
-
codePart(`);`, d)
|
|
617
|
-
]),
|
|
618
|
-
codePart(`return `, node),
|
|
619
|
-
...visit(node.expression),
|
|
620
|
-
codePart(`})(scope)`, node)
|
|
621
|
-
]
|
|
622
|
-
|
|
623
|
-
return parts
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
const visit: (
|
|
628
|
-
node: Expression,
|
|
629
|
-
parentNode: Expression | null
|
|
630
|
-
) => VisitResult[] = node => {
|
|
631
|
-
const nodeTypeVisitor = visitors[node.type]
|
|
632
|
-
|
|
633
|
-
if (nodeTypeVisitor === undefined) {
|
|
634
|
-
throw new Error(`No handler for node type - ${node.type}`)
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const innerVisit: Visit = (childNode: Expression) => {
|
|
638
|
-
return visit(childNode, node)
|
|
639
|
-
}
|
|
352
|
+
const bootstrapCodeHead =
|
|
353
|
+
`
|
|
354
|
+
var ${GEN.bool}=ctx.castToBoolean;
|
|
355
|
+
var ${GEN.str}=ctx.castToString;
|
|
356
|
+
var ${GEN.bop}=ctx.binaryOperators;
|
|
357
|
+
var ${GEN.lop}=ctx.logicalOperators;
|
|
358
|
+
var ${GEN.uop}=ctx.unaryOperators;
|
|
359
|
+
var ${GEN.call}=ctx.callFunction;
|
|
360
|
+
var ${GEN.ensObj}=ctx.ensureObject;
|
|
361
|
+
var ${GEN.ensArr}=ctx.ensureArray;
|
|
362
|
+
var ${GEN.getIdentifierValue}=ctx.getIdentifierValue;
|
|
363
|
+
var ${GEN.prop}=ctx.getProperty;
|
|
364
|
+
var ${GEN.pipe}=ctx.pipe;
|
|
365
|
+
var ${GEN.nna}=ctx.nonNullAssert;
|
|
366
|
+
var ${GEN.globals}=ctx.globals??null;
|
|
367
|
+
|
|
368
|
+
function ${GEN._get}(${GEN._scope},name){
|
|
369
|
+
if(${GEN._scope}===null)return ${GEN.getIdentifierValue}(name,${GEN.globals},this);
|
|
370
|
+
var paramIndex=${GEN._scope}[${SCOPE_NAMES}].findIndex(it=>it===name);
|
|
371
|
+
if(paramIndex===-1)return ${GEN._get}.call(this,${GEN._scope}[${SCOPE_PARENT}],name);
|
|
372
|
+
return ${GEN._scope}[${SCOPE_VALUES}][paramIndex]
|
|
373
|
+
};
|
|
640
374
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
}
|
|
375
|
+
return data=>{
|
|
376
|
+
var ${GEN.scope}=null;
|
|
377
|
+
var ${GEN.get}=${GEN._get}.bind(data);
|
|
378
|
+
return
|
|
379
|
+
`
|
|
380
|
+
.split('\n')
|
|
381
|
+
.map(it => it.trim())
|
|
382
|
+
.filter(it => it !== '')
|
|
383
|
+
.join('') + ' '
|
|
644
384
|
|
|
645
|
-
|
|
646
|
-
return combineVisitResults(visit(tree.expression, null))
|
|
647
|
-
}
|
|
385
|
+
const bootstrapCodeHeadLen = bootstrapCodeHead.length
|
|
648
386
|
|
|
649
|
-
|
|
650
|
-
colOffset: number,
|
|
651
|
-
locations: SourceLocation[]
|
|
652
|
-
): Location | null {
|
|
653
|
-
var curCol = 0
|
|
654
|
-
for (const loc of locations) {
|
|
655
|
-
curCol += loc.len
|
|
656
|
-
if (curCol >= colOffset) return loc.location
|
|
657
|
-
}
|
|
658
|
-
return null
|
|
659
|
-
}
|
|
387
|
+
// --- Compile ---
|
|
660
388
|
|
|
661
389
|
export type CompileOptions<Data, Globals> = Partial<
|
|
662
|
-
ContextHelpers<Data, Globals> &
|
|
390
|
+
ContextHelpers<Data, Globals> &
|
|
391
|
+
ExpressionOperators & {
|
|
392
|
+
globals: Globals
|
|
393
|
+
extensions: Map<string | object | Function, Record<string, Function>>
|
|
394
|
+
errorMapper: ErrorMapper | null
|
|
395
|
+
}
|
|
663
396
|
>
|
|
664
397
|
|
|
398
|
+
/** Compile a SimplEx expression string into an executable function. */
|
|
665
399
|
export function compile<
|
|
666
400
|
Data = Record<string, unknown>,
|
|
667
401
|
Globals = Record<string, unknown>
|
|
@@ -670,111 +404,75 @@ export function compile<
|
|
|
670
404
|
options?: CompileOptions<Data, Globals>
|
|
671
405
|
): (data?: Data) => unknown {
|
|
672
406
|
const tree = parse(expression) as ExpressionStatement
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
try {
|
|
676
|
-
traverseResult = traverse(tree)
|
|
677
|
-
} catch (err) {
|
|
678
|
-
// TODO Use class to access expression from visitors?
|
|
679
|
-
if (err instanceof CompileError) {
|
|
680
|
-
err.expression = expression
|
|
681
|
-
}
|
|
682
|
-
throw err
|
|
683
|
-
}
|
|
407
|
+
const traverseResult = traverse(tree, expression)
|
|
684
408
|
|
|
685
409
|
const { code: expressionCode, offsets } = traverseResult
|
|
686
410
|
|
|
687
|
-
const bootstrapCodeHead =
|
|
688
|
-
`
|
|
689
|
-
var bool=ctx.castToBoolean;
|
|
690
|
-
var bop=ctx.binaryOperators;
|
|
691
|
-
var lop=ctx.logicalOperators;
|
|
692
|
-
var uop=ctx.unaryOperators;
|
|
693
|
-
var call=ctx.callFunction;
|
|
694
|
-
var getIdentifierValue=ctx.getIdentifierValue;
|
|
695
|
-
var prop=ctx.getProperty;
|
|
696
|
-
var pipe=ctx.pipe;
|
|
697
|
-
var globals=ctx.globals??null;
|
|
698
|
-
|
|
699
|
-
function _get(_scope,name){
|
|
700
|
-
if(_scope===null)return getIdentifierValue(name,globals,this);
|
|
701
|
-
var paramIndex=_scope[0].findIndex(it=>it===name);
|
|
702
|
-
if(paramIndex===-1)return _get.call(this,_scope[2],name);
|
|
703
|
-
return _scope[1][paramIndex]
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
return data=>{
|
|
707
|
-
var scope=null;
|
|
708
|
-
var get=_get.bind(data);
|
|
709
|
-
return
|
|
710
|
-
`
|
|
711
|
-
.split('\n')
|
|
712
|
-
.map(it => it.trim())
|
|
713
|
-
.filter(it => it !== '')
|
|
714
|
-
.join('') + ' '
|
|
715
|
-
|
|
716
|
-
const bootstrapCodeHeadLen = bootstrapCodeHead.length
|
|
717
|
-
|
|
718
411
|
const functionCode = bootstrapCodeHead + expressionCode + '}'
|
|
719
412
|
|
|
720
|
-
|
|
413
|
+
const resolvedBool = options?.castToBoolean ?? castToBoolean
|
|
414
|
+
|
|
721
415
|
const defaultOptions: CompileOptions<Data, Globals> = {
|
|
722
416
|
...defaultContextHelpers,
|
|
417
|
+
// Recreate operators with custom castToBoolean so compile options
|
|
418
|
+
// are honored by logical (and/or) and unary (not) operators
|
|
723
419
|
...{
|
|
724
|
-
unaryOperators:
|
|
420
|
+
unaryOperators: options?.castToBoolean
|
|
421
|
+
? createDefaultUnaryOperators(resolvedBool)
|
|
422
|
+
: defaultUnaryOperators,
|
|
725
423
|
binaryOperators: defaultBinaryOperators,
|
|
726
|
-
logicalOperators:
|
|
424
|
+
logicalOperators: options?.castToBoolean
|
|
425
|
+
? createDefaultLogicalOperators(resolvedBool)
|
|
426
|
+
: defaultLogicalOperators
|
|
727
427
|
},
|
|
728
428
|
...(options as any)
|
|
729
429
|
}
|
|
730
430
|
|
|
431
|
+
if (options?.extensions && options.extensions.size > 0 && !options.getProperty) {
|
|
432
|
+
const extensionMap = options.extensions
|
|
433
|
+
const classesKeys: (object | Function)[] = []
|
|
434
|
+
const classesValues: Record<string, Function>[] = []
|
|
435
|
+
|
|
436
|
+
for (const [key, methods] of extensionMap) {
|
|
437
|
+
if (typeof key !== 'string') {
|
|
438
|
+
classesKeys.push(key)
|
|
439
|
+
classesValues.push(methods)
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
defaultOptions.getProperty = (obj, key, extension) => {
|
|
444
|
+
if (obj == null) return undefined
|
|
445
|
+
if (extension)
|
|
446
|
+
return getExtensionMethod(
|
|
447
|
+
obj,
|
|
448
|
+
key,
|
|
449
|
+
extensionMap,
|
|
450
|
+
classesKeys,
|
|
451
|
+
classesValues
|
|
452
|
+
)
|
|
453
|
+
return defaultGetProperty(obj, key, false)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
731
457
|
const func = new Function('ctx', functionCode)(defaultOptions) as (
|
|
732
458
|
data?: Data
|
|
733
459
|
) => unknown
|
|
734
460
|
|
|
461
|
+
const errorMapper =
|
|
462
|
+
options?.errorMapper !== undefined
|
|
463
|
+
? options.errorMapper
|
|
464
|
+
: getActiveErrorMapper()
|
|
465
|
+
|
|
466
|
+
if (errorMapper === null) return func
|
|
467
|
+
|
|
735
468
|
return function (data?: Data) {
|
|
736
469
|
try {
|
|
737
470
|
return func(data)
|
|
738
471
|
} catch (err) {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
const evalRow = stackRows?.find(row => row.startsWith('at eval '))
|
|
744
|
-
|
|
745
|
-
if (evalRow === undefined) {
|
|
746
|
-
throw err
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
ERROR_STACK_REGEX.lastIndex = 0
|
|
750
|
-
const match = ERROR_STACK_REGEX.exec(evalRow)
|
|
751
|
-
|
|
752
|
-
if (match == null) {
|
|
753
|
-
throw err
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
const rowOffsetStr = match.groups?.['row']
|
|
757
|
-
const colOffsetStr = match.groups?.['col']
|
|
758
|
-
|
|
759
|
-
if (rowOffsetStr === undefined || colOffsetStr === undefined) {
|
|
760
|
-
throw err
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
const rowOffset = Number.parseInt(rowOffsetStr)
|
|
764
|
-
assert.equal(rowOffset, 3)
|
|
765
|
-
|
|
766
|
-
const colOffset = Number.parseInt(colOffsetStr)
|
|
767
|
-
const adjustedColOffset = colOffset - bootstrapCodeHeadLen
|
|
768
|
-
assert.ok(adjustedColOffset >= 0)
|
|
769
|
-
|
|
770
|
-
const errorLocation = getExpressionErrorLocation(
|
|
771
|
-
adjustedColOffset,
|
|
772
|
-
offsets
|
|
472
|
+
throw (
|
|
473
|
+
errorMapper.mapError(err, expression, offsets, bootstrapCodeHeadLen) ??
|
|
474
|
+
err
|
|
773
475
|
)
|
|
774
|
-
|
|
775
|
-
throw new ExpressionError(err.message, expression, errorLocation, {
|
|
776
|
-
cause: err
|
|
777
|
-
})
|
|
778
476
|
}
|
|
779
477
|
}
|
|
780
478
|
}
|