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