@atomic-ehr/fhirpath 0.0.1-canary.1825db0.20250725140030
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 +400 -0
- package/dist/index.d.ts +398 -0
- package/dist/index.js +8372 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
- package/src/analyzer/analyzer.ts +486 -0
- package/src/analyzer/model-provider.ts +244 -0
- package/src/analyzer/schemas/index.ts +2 -0
- package/src/analyzer/schemas/types.ts +40 -0
- package/src/analyzer/types.ts +142 -0
- package/src/api/builder.ts +155 -0
- package/src/api/errors.ts +134 -0
- package/src/api/expression.ts +156 -0
- package/src/api/index.ts +70 -0
- package/src/api/inspect.ts +96 -0
- package/src/api/registry.ts +128 -0
- package/src/api/types.ts +210 -0
- package/src/compiler/compiler.ts +546 -0
- package/src/compiler/index.ts +2 -0
- package/src/compiler/prototype-context-adapter.ts +99 -0
- package/src/compiler/types.ts +24 -0
- package/src/index.ts +76 -0
- package/src/interpreter/README.md +78 -0
- package/src/interpreter/interpreter.ts +463 -0
- package/src/interpreter/types.ts +108 -0
- package/src/lexer/char-tables.ts +37 -0
- package/src/lexer/errors.ts +31 -0
- package/src/lexer/index.ts +5 -0
- package/src/lexer/lexer.ts +745 -0
- package/src/lexer/token.ts +104 -0
- package/src/parser/ast.ts +123 -0
- package/src/parser/index.ts +3 -0
- package/src/parser/parser.ts +701 -0
- package/src/parser/pprint.ts +169 -0
- package/src/registry/default-analyzers.ts +257 -0
- package/src/registry/default-compilers.ts +31 -0
- package/src/registry/index.ts +94 -0
- package/src/registry/operations/arithmetic.ts +506 -0
- package/src/registry/operations/collection.ts +425 -0
- package/src/registry/operations/comparison.ts +432 -0
- package/src/registry/operations/existence.ts +703 -0
- package/src/registry/operations/filtering.ts +358 -0
- package/src/registry/operations/literals.ts +341 -0
- package/src/registry/operations/logical.ts +439 -0
- package/src/registry/operations/math.ts +128 -0
- package/src/registry/operations/membership.ts +132 -0
- package/src/registry/operations/string.ts +507 -0
- package/src/registry/operations/subsetting.ts +174 -0
- package/src/registry/operations/type-checking.ts +162 -0
- package/src/registry/operations/type-conversion.ts +404 -0
- package/src/registry/operations/type-operators.ts +308 -0
- package/src/registry/operations/utility.ts +644 -0
- package/src/registry/registry.ts +146 -0
- package/src/registry/types.ts +161 -0
- package/src/registry/utils/evaluation-helpers.ts +93 -0
- package/src/registry/utils/index.ts +3 -0
- package/src/registry/utils/type-system.ts +173 -0
- package/src/runtime/context.ts +158 -0
- package/src/runtime/debug-context.ts +135 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
import type { Function } from '../types';
|
|
2
|
+
import { defaultFunctionAnalyze } from '../default-analyzers';
|
|
3
|
+
import { defaultFunctionCompile } from '../default-compilers';
|
|
4
|
+
import { RuntimeContextManager } from '../../runtime/context';
|
|
5
|
+
import { isTruthy } from '../utils';
|
|
6
|
+
import { isDebugContext, addTrace } from '../../runtime/debug-context';
|
|
7
|
+
|
|
8
|
+
export const aggregateFunction: Function = {
|
|
9
|
+
name: 'aggregate',
|
|
10
|
+
kind: 'function',
|
|
11
|
+
|
|
12
|
+
syntax: {
|
|
13
|
+
notation: 'aggregate(aggregator, init)'
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
signature: {
|
|
17
|
+
input: {
|
|
18
|
+
types: { kind: 'any' },
|
|
19
|
+
cardinality: 'collection'
|
|
20
|
+
},
|
|
21
|
+
parameters: [
|
|
22
|
+
{
|
|
23
|
+
name: 'aggregator',
|
|
24
|
+
kind: 'expression',
|
|
25
|
+
types: { kind: 'any' },
|
|
26
|
+
cardinality: 'collection',
|
|
27
|
+
optional: false
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'init',
|
|
31
|
+
kind: 'value',
|
|
32
|
+
types: { kind: 'any' },
|
|
33
|
+
cardinality: 'collection',
|
|
34
|
+
optional: true
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
output: {
|
|
38
|
+
type: 'any',
|
|
39
|
+
cardinality: 'collection'
|
|
40
|
+
},
|
|
41
|
+
propagatesEmpty: false,
|
|
42
|
+
deterministic: true
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
analyze: defaultFunctionAnalyze,
|
|
46
|
+
|
|
47
|
+
evaluate: (interpreter, context, input, aggregatorExpr, init) => {
|
|
48
|
+
if (!aggregatorExpr) {
|
|
49
|
+
throw new Error('aggregate() requires an aggregator expression');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let total = init !== undefined ? init : [];
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < input.length; i++) {
|
|
55
|
+
const item = input[i];
|
|
56
|
+
let iterContext = RuntimeContextManager.withIterator(context, item, i);
|
|
57
|
+
iterContext = RuntimeContextManager.setSpecialVariable(iterContext, 'total', total);
|
|
58
|
+
|
|
59
|
+
const result = interpreter.evaluate(aggregatorExpr, [item], iterContext);
|
|
60
|
+
total = result.value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { value: total, context };
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
compile: defaultFunctionCompile
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const childrenFunction: Function = {
|
|
70
|
+
name: 'children',
|
|
71
|
+
kind: 'function',
|
|
72
|
+
|
|
73
|
+
syntax: {
|
|
74
|
+
notation: 'children()'
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
signature: {
|
|
78
|
+
input: {
|
|
79
|
+
types: { kind: 'any' },
|
|
80
|
+
cardinality: 'collection'
|
|
81
|
+
},
|
|
82
|
+
parameters: [],
|
|
83
|
+
output: {
|
|
84
|
+
type: 'preserve-input',
|
|
85
|
+
cardinality: 'collection'
|
|
86
|
+
},
|
|
87
|
+
propagatesEmpty: true,
|
|
88
|
+
deterministic: true
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
analyze: defaultFunctionAnalyze,
|
|
92
|
+
|
|
93
|
+
evaluate: (interpreter, context, input) => {
|
|
94
|
+
const results: any[] = [];
|
|
95
|
+
|
|
96
|
+
for (const item of input) {
|
|
97
|
+
if (item && typeof item === 'object') {
|
|
98
|
+
for (const key of Object.keys(item)) {
|
|
99
|
+
if (!key.startsWith('_')) {
|
|
100
|
+
const value = item[key];
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
results.push(...value);
|
|
103
|
+
} else if (value !== null && value !== undefined) {
|
|
104
|
+
results.push(value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { value: results, context };
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
compile: defaultFunctionCompile
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const descendantsFunction: Function = {
|
|
118
|
+
name: 'descendants',
|
|
119
|
+
kind: 'function',
|
|
120
|
+
|
|
121
|
+
syntax: {
|
|
122
|
+
notation: 'descendants()'
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
signature: {
|
|
126
|
+
input: {
|
|
127
|
+
types: { kind: 'any' },
|
|
128
|
+
cardinality: 'collection'
|
|
129
|
+
},
|
|
130
|
+
parameters: [],
|
|
131
|
+
output: {
|
|
132
|
+
type: 'preserve-input',
|
|
133
|
+
cardinality: 'collection'
|
|
134
|
+
},
|
|
135
|
+
propagatesEmpty: true,
|
|
136
|
+
deterministic: true
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
analyze: defaultFunctionAnalyze,
|
|
140
|
+
|
|
141
|
+
evaluate: (interpreter, context, input) => {
|
|
142
|
+
const results: any[] = [];
|
|
143
|
+
const seen = new Set();
|
|
144
|
+
|
|
145
|
+
function collectDescendants(items: any[]) {
|
|
146
|
+
for (const item of items) {
|
|
147
|
+
if (item && typeof item === 'object') {
|
|
148
|
+
const key = JSON.stringify(item);
|
|
149
|
+
if (!seen.has(key)) {
|
|
150
|
+
seen.add(key);
|
|
151
|
+
results.push(item);
|
|
152
|
+
|
|
153
|
+
const children: any[] = [];
|
|
154
|
+
for (const prop of Object.keys(item)) {
|
|
155
|
+
if (!prop.startsWith('_')) {
|
|
156
|
+
const value = item[prop];
|
|
157
|
+
if (Array.isArray(value)) {
|
|
158
|
+
children.push(...value);
|
|
159
|
+
} else if (value !== null && value !== undefined && typeof value === 'object') {
|
|
160
|
+
children.push(value);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
collectDescendants(children);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const children: any[] = [];
|
|
171
|
+
for (const item of input) {
|
|
172
|
+
if (item && typeof item === 'object') {
|
|
173
|
+
for (const key of Object.keys(item)) {
|
|
174
|
+
if (!key.startsWith('_')) {
|
|
175
|
+
const value = item[key];
|
|
176
|
+
if (Array.isArray(value)) {
|
|
177
|
+
children.push(...value);
|
|
178
|
+
} else if (value !== null && value !== undefined) {
|
|
179
|
+
children.push(value);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
collectDescendants(children);
|
|
187
|
+
return { value: results, context };
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
compile: defaultFunctionCompile
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export const iifFunction: Function = {
|
|
194
|
+
name: 'iif',
|
|
195
|
+
kind: 'function',
|
|
196
|
+
|
|
197
|
+
syntax: {
|
|
198
|
+
notation: 'iif(condition, then [, else])'
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
signature: {
|
|
202
|
+
input: {
|
|
203
|
+
types: { kind: 'any' },
|
|
204
|
+
cardinality: 'collection'
|
|
205
|
+
},
|
|
206
|
+
parameters: [
|
|
207
|
+
{
|
|
208
|
+
name: 'condition',
|
|
209
|
+
kind: 'expression',
|
|
210
|
+
types: { kind: 'any' },
|
|
211
|
+
cardinality: 'collection',
|
|
212
|
+
optional: false
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'then',
|
|
216
|
+
kind: 'expression',
|
|
217
|
+
types: { kind: 'any' },
|
|
218
|
+
cardinality: 'collection',
|
|
219
|
+
optional: false
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: 'else',
|
|
223
|
+
kind: 'expression',
|
|
224
|
+
types: { kind: 'any' },
|
|
225
|
+
cardinality: 'collection',
|
|
226
|
+
optional: true
|
|
227
|
+
}
|
|
228
|
+
],
|
|
229
|
+
output: {
|
|
230
|
+
type: 'any',
|
|
231
|
+
cardinality: 'collection'
|
|
232
|
+
},
|
|
233
|
+
propagatesEmpty: false,
|
|
234
|
+
deterministic: true
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
analyze: defaultFunctionAnalyze,
|
|
238
|
+
|
|
239
|
+
evaluate: (interpreter, context, input, condition, thenBranch, elseBranch) => {
|
|
240
|
+
// Per spec: if input has multiple values, return empty
|
|
241
|
+
if (input.length > 1) {
|
|
242
|
+
return { value: [], context };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Set up context with $this = input
|
|
246
|
+
let condContext = RuntimeContextManager.copy(context);
|
|
247
|
+
if (input.length === 1) {
|
|
248
|
+
condContext = RuntimeContextManager.setSpecialVariable(condContext, 'this', input[0]);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const condResult = interpreter.evaluate(condition, input, condContext);
|
|
252
|
+
|
|
253
|
+
// Per spec: if condition is empty, treat as false
|
|
254
|
+
if (condResult.value.length === 0) {
|
|
255
|
+
if (elseBranch) {
|
|
256
|
+
return interpreter.evaluate(elseBranch, input, condResult.context);
|
|
257
|
+
} else {
|
|
258
|
+
// Two-parameter form: no else branch means return empty
|
|
259
|
+
return { value: [], context };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Per spec: if condition is not boolean singleton, return empty
|
|
264
|
+
if (condResult.value.length !== 1 || typeof condResult.value[0] !== 'boolean') {
|
|
265
|
+
return { value: [], context };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (condResult.value[0] === true) {
|
|
269
|
+
return interpreter.evaluate(thenBranch, input, condResult.context);
|
|
270
|
+
} else {
|
|
271
|
+
if (elseBranch) {
|
|
272
|
+
return interpreter.evaluate(elseBranch, input, condResult.context);
|
|
273
|
+
} else {
|
|
274
|
+
// Two-parameter form: no else branch means return empty
|
|
275
|
+
return { value: [], context };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
compile: (compiler, input, args) => {
|
|
281
|
+
const [condExpr, thenExpr, elseExpr] = args;
|
|
282
|
+
|
|
283
|
+
if (!condExpr || !thenExpr) {
|
|
284
|
+
throw new Error('iif() requires at least condition and then expressions');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
fn: (ctx) => {
|
|
289
|
+
const inputVal = input.fn(ctx);
|
|
290
|
+
|
|
291
|
+
// Per spec: if input has multiple values, return empty
|
|
292
|
+
if (inputVal.length > 1) {
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Set up context with $this = input
|
|
297
|
+
let condCtx = RuntimeContextManager.withInput(ctx, inputVal);
|
|
298
|
+
if (inputVal.length === 1) {
|
|
299
|
+
condCtx = RuntimeContextManager.setSpecialVariable(condCtx, 'this', inputVal[0]);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const condResult = condExpr.fn(condCtx);
|
|
303
|
+
|
|
304
|
+
// Per spec: if condition is empty, treat as false
|
|
305
|
+
if (condResult.length === 0) {
|
|
306
|
+
if (elseExpr) {
|
|
307
|
+
const elseCtx = RuntimeContextManager.withInput(ctx, inputVal);
|
|
308
|
+
return elseExpr.fn(elseCtx);
|
|
309
|
+
} else {
|
|
310
|
+
// Two-parameter form: no else branch means return empty
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Per spec: if condition is not boolean singleton, return empty
|
|
316
|
+
if (condResult.length !== 1 || typeof condResult[0] !== 'boolean') {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (condResult[0] === true) {
|
|
321
|
+
const thenCtx = RuntimeContextManager.withInput(ctx, inputVal);
|
|
322
|
+
return thenExpr.fn(thenCtx);
|
|
323
|
+
} else {
|
|
324
|
+
if (elseExpr) {
|
|
325
|
+
const elseCtx = RuntimeContextManager.withInput(ctx, inputVal);
|
|
326
|
+
return elseExpr.fn(elseCtx);
|
|
327
|
+
} else {
|
|
328
|
+
// Two-parameter form: no else branch means return empty
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
type: compiler.resolveType('Any'),
|
|
334
|
+
isSingleton: false,
|
|
335
|
+
source: elseExpr
|
|
336
|
+
? `${input.source || ''}.iif(${condExpr.source || ''}, ${thenExpr.source || ''}, ${elseExpr.source || ''})`
|
|
337
|
+
: `${input.source || ''}.iif(${condExpr.source || ''}, ${thenExpr.source || ''})`
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export const defineVariableFunction: Function = {
|
|
343
|
+
name: 'defineVariable',
|
|
344
|
+
kind: 'function',
|
|
345
|
+
|
|
346
|
+
syntax: {
|
|
347
|
+
notation: 'defineVariable(name [, value])'
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
signature: {
|
|
351
|
+
input: {
|
|
352
|
+
types: { kind: 'any' },
|
|
353
|
+
cardinality: 'collection'
|
|
354
|
+
},
|
|
355
|
+
parameters: [
|
|
356
|
+
{
|
|
357
|
+
name: 'name',
|
|
358
|
+
kind: 'value',
|
|
359
|
+
types: { kind: 'primitive', types: ['String'] },
|
|
360
|
+
cardinality: 'singleton',
|
|
361
|
+
optional: false
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: 'value',
|
|
365
|
+
kind: 'expression',
|
|
366
|
+
types: { kind: 'any' },
|
|
367
|
+
cardinality: 'collection',
|
|
368
|
+
optional: true
|
|
369
|
+
}
|
|
370
|
+
],
|
|
371
|
+
output: {
|
|
372
|
+
type: 'preserve-input',
|
|
373
|
+
cardinality: 'collection'
|
|
374
|
+
},
|
|
375
|
+
propagatesEmpty: false,
|
|
376
|
+
deterministic: true
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
analyze: defaultFunctionAnalyze,
|
|
380
|
+
|
|
381
|
+
evaluate: (interpreter, context, input, nameValue, valueExpr) => {
|
|
382
|
+
// Extract the string value from the name parameter
|
|
383
|
+
let varName: string;
|
|
384
|
+
|
|
385
|
+
// nameValue comes as evaluated value (array) when param.kind !== 'expression'
|
|
386
|
+
if (Array.isArray(nameValue) && nameValue.length === 1 && typeof nameValue[0] === 'string') {
|
|
387
|
+
varName = nameValue[0];
|
|
388
|
+
} else if (typeof nameValue === 'string') {
|
|
389
|
+
varName = nameValue;
|
|
390
|
+
} else {
|
|
391
|
+
throw new Error('defineVariable() requires a string literal as the first parameter');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
let value: any[];
|
|
395
|
+
|
|
396
|
+
if (valueExpr) {
|
|
397
|
+
// Create a new context where $this refers to the input
|
|
398
|
+
let valueContext = RuntimeContextManager.copy(context);
|
|
399
|
+
valueContext = RuntimeContextManager.setSpecialVariable(valueContext, 'this', input);
|
|
400
|
+
|
|
401
|
+
const result = interpreter.evaluate(valueExpr, input, valueContext);
|
|
402
|
+
value = result.value;
|
|
403
|
+
} else {
|
|
404
|
+
// If no value expression is provided, use the input collection
|
|
405
|
+
value = input;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Try to set the variable - setVariable will handle redefinition check
|
|
409
|
+
const newContext = RuntimeContextManager.setVariable(context, varName, value);
|
|
410
|
+
|
|
411
|
+
// If context didn't change, it means redefinition was attempted
|
|
412
|
+
if (newContext === context) {
|
|
413
|
+
return { value: [], context };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return { value: input, context: newContext };
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
compile: (compiler, input, args) => {
|
|
420
|
+
const [nameExpr, valueExpr] = args;
|
|
421
|
+
|
|
422
|
+
// For defineVariable, the name should be a literal string
|
|
423
|
+
// Try to extract it at compile time
|
|
424
|
+
let varName: string | undefined;
|
|
425
|
+
|
|
426
|
+
// The name parameter should evaluate to a constant string
|
|
427
|
+
try {
|
|
428
|
+
const nameResult = nameExpr?.fn(RuntimeContextManager.create([])) || [];
|
|
429
|
+
if (nameResult.length === 1 && typeof nameResult[0] === 'string') {
|
|
430
|
+
varName = nameResult[0];
|
|
431
|
+
}
|
|
432
|
+
} catch (e) {
|
|
433
|
+
// If we can't evaluate it at compile time, it might be invalid
|
|
434
|
+
throw new Error('defineVariable() requires a string literal as the first parameter');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (!varName) {
|
|
438
|
+
throw new Error('defineVariable() requires a string literal as the first parameter');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Return a compiled expression that modifies the context
|
|
442
|
+
return {
|
|
443
|
+
fn: (ctx) => {
|
|
444
|
+
// Check if variable is already defined before evaluating expressions
|
|
445
|
+
const existingValue = RuntimeContextManager.getVariable(ctx, varName);
|
|
446
|
+
if (existingValue !== undefined) {
|
|
447
|
+
// Variable already exists, return empty
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Evaluate the input expression
|
|
452
|
+
const inputVal = input.fn(ctx);
|
|
453
|
+
|
|
454
|
+
let value: any[];
|
|
455
|
+
|
|
456
|
+
if (valueExpr) {
|
|
457
|
+
// Create context for evaluating the value expression
|
|
458
|
+
// Set $this to the input value
|
|
459
|
+
let valueCtx = RuntimeContextManager.withInput(ctx, inputVal);
|
|
460
|
+
valueCtx = RuntimeContextManager.setSpecialVariable(valueCtx, 'this', inputVal);
|
|
461
|
+
|
|
462
|
+
// Evaluate the value expression
|
|
463
|
+
value = valueExpr.fn(valueCtx);
|
|
464
|
+
} else {
|
|
465
|
+
// If no value expression is provided, use the input collection
|
|
466
|
+
value = inputVal;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Set the variable - we already checked it doesn't exist
|
|
470
|
+
const newCtx = RuntimeContextManager.setVariable(ctx, varName, value, true); // allow setting since we checked
|
|
471
|
+
|
|
472
|
+
// IMPORTANT: For the compiler version, we need to propagate the variable
|
|
473
|
+
// to the parent context by copying the variables object
|
|
474
|
+
Object.assign(ctx.variables, newCtx.variables);
|
|
475
|
+
|
|
476
|
+
// Return the original input (not the value)
|
|
477
|
+
return inputVal;
|
|
478
|
+
},
|
|
479
|
+
type: input.type,
|
|
480
|
+
isSingleton: input.isSingleton,
|
|
481
|
+
source: valueExpr
|
|
482
|
+
? `${input.source || ''}.defineVariable('${varName}', ${valueExpr.source || ''})`
|
|
483
|
+
: `${input.source || ''}.defineVariable('${varName}')`
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
export const traceFunction: Function = {
|
|
489
|
+
name: 'trace',
|
|
490
|
+
kind: 'function',
|
|
491
|
+
|
|
492
|
+
syntax: {
|
|
493
|
+
notation: 'trace(name, selector)'
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
signature: {
|
|
497
|
+
input: {
|
|
498
|
+
types: { kind: 'any' },
|
|
499
|
+
cardinality: 'collection'
|
|
500
|
+
},
|
|
501
|
+
parameters: [
|
|
502
|
+
{
|
|
503
|
+
name: 'name',
|
|
504
|
+
kind: 'value',
|
|
505
|
+
types: { kind: 'primitive', types: ['String'] },
|
|
506
|
+
cardinality: 'singleton',
|
|
507
|
+
optional: true
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
name: 'selector',
|
|
511
|
+
kind: 'expression',
|
|
512
|
+
types: { kind: 'any' },
|
|
513
|
+
cardinality: 'collection',
|
|
514
|
+
optional: true
|
|
515
|
+
}
|
|
516
|
+
],
|
|
517
|
+
output: {
|
|
518
|
+
type: 'preserve-input',
|
|
519
|
+
cardinality: 'collection'
|
|
520
|
+
},
|
|
521
|
+
propagatesEmpty: false,
|
|
522
|
+
deterministic: false
|
|
523
|
+
},
|
|
524
|
+
|
|
525
|
+
analyze: defaultFunctionAnalyze,
|
|
526
|
+
|
|
527
|
+
evaluate: (interpreter, context, input, nameValue, selectorExpr) => {
|
|
528
|
+
// Extract the name from the parameter (it comes as an array when kind is 'value')
|
|
529
|
+
let traceName = 'trace';
|
|
530
|
+
if (nameValue && Array.isArray(nameValue) && nameValue.length > 0) {
|
|
531
|
+
traceName = String(nameValue[0]);
|
|
532
|
+
} else if (typeof nameValue === 'string') {
|
|
533
|
+
traceName = nameValue;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let values = input;
|
|
537
|
+
|
|
538
|
+
if (selectorExpr) {
|
|
539
|
+
const result = interpreter.evaluate(selectorExpr, input, context);
|
|
540
|
+
values = result.value;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Check if we're in debug mode
|
|
544
|
+
if (isDebugContext(context)) {
|
|
545
|
+
addTrace(context, traceName, values);
|
|
546
|
+
} else {
|
|
547
|
+
console.log(`[TRACE] ${traceName}:`, values);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return { value: input, context };
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
compile: (compiler, input, args) => {
|
|
554
|
+
const [nameExpr, selectorExpr] = args;
|
|
555
|
+
|
|
556
|
+
return {
|
|
557
|
+
fn: (ctx) => {
|
|
558
|
+
const inputVal = input.fn(ctx);
|
|
559
|
+
|
|
560
|
+
// Extract the name
|
|
561
|
+
let traceName = 'trace';
|
|
562
|
+
if (nameExpr) {
|
|
563
|
+
const nameResult = nameExpr.fn(ctx);
|
|
564
|
+
if (nameResult.length > 0) {
|
|
565
|
+
traceName = String(nameResult[0]);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
let values = inputVal;
|
|
570
|
+
|
|
571
|
+
if (selectorExpr) {
|
|
572
|
+
// Create context with input for selector evaluation
|
|
573
|
+
const selectorCtx = RuntimeContextManager.withInput(ctx, inputVal);
|
|
574
|
+
values = selectorExpr.fn(selectorCtx);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
console.log(`[TRACE] ${traceName}:`, values);
|
|
578
|
+
|
|
579
|
+
return inputVal;
|
|
580
|
+
},
|
|
581
|
+
type: input.type,
|
|
582
|
+
isSingleton: input.isSingleton,
|
|
583
|
+
source: selectorExpr
|
|
584
|
+
? `${input.source || ''}.trace('${nameExpr?.source || ''}', ${selectorExpr.source || ''})`
|
|
585
|
+
: nameExpr
|
|
586
|
+
? `${input.source || ''}.trace('${nameExpr.source || ''}')`
|
|
587
|
+
: `${input.source || ''}.trace()`
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
export const checkFunction: Function = {
|
|
593
|
+
name: 'check',
|
|
594
|
+
kind: 'function',
|
|
595
|
+
|
|
596
|
+
syntax: {
|
|
597
|
+
notation: 'check(error, condition)'
|
|
598
|
+
},
|
|
599
|
+
|
|
600
|
+
signature: {
|
|
601
|
+
input: {
|
|
602
|
+
types: { kind: 'any' },
|
|
603
|
+
cardinality: 'collection'
|
|
604
|
+
},
|
|
605
|
+
parameters: [
|
|
606
|
+
{
|
|
607
|
+
name: 'error',
|
|
608
|
+
kind: 'expression',
|
|
609
|
+
types: { kind: 'any' },
|
|
610
|
+
cardinality: 'collection',
|
|
611
|
+
optional: false
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
name: 'condition',
|
|
615
|
+
kind: 'expression',
|
|
616
|
+
types: { kind: 'any' },
|
|
617
|
+
cardinality: 'collection',
|
|
618
|
+
optional: false
|
|
619
|
+
}
|
|
620
|
+
],
|
|
621
|
+
output: {
|
|
622
|
+
type: 'preserve-input',
|
|
623
|
+
cardinality: 'collection'
|
|
624
|
+
},
|
|
625
|
+
propagatesEmpty: false,
|
|
626
|
+
deterministic: true
|
|
627
|
+
},
|
|
628
|
+
|
|
629
|
+
analyze: defaultFunctionAnalyze,
|
|
630
|
+
|
|
631
|
+
evaluate: (interpreter, context, input, errorExpr, conditionExpr) => {
|
|
632
|
+
const condResult = interpreter.evaluate(conditionExpr, input, context);
|
|
633
|
+
|
|
634
|
+
if (!isTruthy(condResult.value)) {
|
|
635
|
+
const errorResult = interpreter.evaluate(errorExpr, input, condResult.context);
|
|
636
|
+
const errorMessage = errorResult.value.join('');
|
|
637
|
+
throw new Error(`Check failed: ${errorMessage}`);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return { value: input, context: condResult.context };
|
|
641
|
+
},
|
|
642
|
+
|
|
643
|
+
compile: defaultFunctionCompile
|
|
644
|
+
};
|