@atomic-ehr/fhirpath 0.0.1 → 0.0.2-canary.2fee5d9.20250808110507
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 +2 -0
- package/dist/index.d.ts +195 -77
- package/dist/index.js +2541 -981
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/analyzer.ts +536 -64
- package/src/boxing.ts +124 -0
- package/src/errors.ts +246 -0
- package/src/index.ts +57 -9
- package/src/inspect.ts +307 -174
- package/src/interpreter.ts +299 -48
- package/src/model-provider.ts +110 -8
- package/src/operations/abs-function.ts +19 -10
- package/src/operations/aggregate-function.ts +11 -3
- package/src/operations/all-function.ts +18 -8
- package/src/operations/allFalse-function.ts +9 -6
- package/src/operations/allTrue-function.ts +9 -6
- package/src/operations/and-operator.ts +20 -11
- package/src/operations/anyFalse-function.ts +6 -4
- package/src/operations/anyTrue-function.ts +6 -4
- package/src/operations/as-operator.ts +1 -0
- package/src/operations/ceiling-function.ts +9 -5
- package/src/operations/children-function.ts +94 -0
- package/src/operations/combine-function.ts +4 -2
- package/src/operations/combine-operator.ts +18 -4
- package/src/operations/contains-function.ts +20 -8
- package/src/operations/contains-operator.ts +14 -6
- package/src/operations/count-function.ts +2 -1
- package/src/operations/defineVariable-function.ts +5 -3
- package/src/operations/descendants-function.ts +62 -0
- package/src/operations/distinct-function.ts +16 -4
- package/src/operations/div-operator.ts +15 -2
- package/src/operations/divide-operator.ts +10 -5
- package/src/operations/dot-operator.ts +3 -1
- package/src/operations/empty-function.ts +2 -1
- package/src/operations/endsWith-function.ts +22 -10
- package/src/operations/equal-operator.ts +18 -6
- package/src/operations/equivalent-operator.ts +15 -10
- package/src/operations/exclude-function.ts +12 -10
- package/src/operations/exists-function.ts +14 -7
- package/src/operations/first-function.ts +7 -4
- package/src/operations/floor-function.ts +9 -5
- package/src/operations/greater-operator.ts +9 -4
- package/src/operations/greater-or-equal-operator.ts +9 -4
- package/src/operations/iif-function.ts +18 -16
- package/src/operations/implies-operator.ts +22 -7
- package/src/operations/in-operator.ts +17 -7
- package/src/operations/index.ts +3 -0
- package/src/operations/indexOf-function.ts +22 -10
- package/src/operations/intersect-function.ts +17 -20
- package/src/operations/is-operator.ts +63 -14
- package/src/operations/isDistinct-function.ts +12 -6
- package/src/operations/join-function.ts +15 -4
- package/src/operations/last-function.ts +7 -4
- package/src/operations/length-function.ts +12 -5
- package/src/operations/less-operator.ts +9 -4
- package/src/operations/less-or-equal-operator.ts +9 -4
- package/src/operations/less-than.ts +25 -1
- package/src/operations/lower-function.ts +12 -5
- package/src/operations/minus-operator.ts +9 -4
- package/src/operations/mod-operator.ts +19 -2
- package/src/operations/multiply-operator.ts +11 -6
- package/src/operations/not-equal-operator.ts +15 -6
- package/src/operations/not-equivalent-operator.ts +15 -10
- package/src/operations/not-function.ts +12 -4
- package/src/operations/ofType-function.ts +135 -0
- package/src/operations/or-operator.ts +20 -11
- package/src/operations/plus-operator.ts +18 -6
- package/src/operations/power-function.ts +21 -9
- package/src/operations/replace-function.ts +26 -9
- package/src/operations/round-function.ts +23 -8
- package/src/operations/select-function.ts +12 -6
- package/src/operations/single-function.ts +5 -3
- package/src/operations/skip-function.ts +12 -5
- package/src/operations/split-function.ts +24 -9
- package/src/operations/sqrt-function.ts +9 -5
- package/src/operations/startsWith-function.ts +20 -8
- package/src/operations/subsetOf-function.ts +14 -11
- package/src/operations/substring-function.ts +36 -19
- package/src/operations/supersetOf-function.ts +14 -11
- package/src/operations/tail-function.ts +3 -1
- package/src/operations/take-function.ts +12 -5
- package/src/operations/toBoolean-function.ts +18 -11
- package/src/operations/toDecimal-function.ts +13 -6
- package/src/operations/toInteger-function.ts +13 -6
- package/src/operations/toString-function.ts +17 -10
- package/src/operations/trace-function.ts +12 -5
- package/src/operations/trim-function.ts +11 -4
- package/src/operations/truncate-function.ts +9 -5
- package/src/operations/unary-minus-operator.ts +22 -12
- package/src/operations/unary-plus-operator.ts +1 -0
- package/src/operations/union-function.ts +19 -24
- package/src/operations/union-operator.ts +1 -0
- package/src/operations/upper-function.ts +12 -5
- package/src/operations/where-function.ts +15 -8
- package/src/operations/xor-operator.ts +8 -3
- package/src/parser.ts +391 -8
- package/src/quantity-value.ts +4 -8
- package/src/registry.ts +3 -3
- package/src/types.ts +10 -6
- package/src/parser-base.ts +0 -400
package/src/inspect.ts
CHANGED
|
@@ -1,204 +1,337 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { TypeInfo, ASTNode, Diagnostic } from './types';
|
|
2
|
+
import type { FHIRPathValue } from './boxing';
|
|
3
|
+
import { NodeType, DiagnosticSeverity } from './types';
|
|
4
|
+
import { parse } from './parser';
|
|
5
|
+
import { Analyzer } from './analyzer';
|
|
2
6
|
import { Interpreter, RuntimeContextManager } from './interpreter';
|
|
3
|
-
import type {
|
|
4
|
-
import { Registry } from './registry';
|
|
5
|
-
import * as operations from './operations';
|
|
7
|
+
import type { RuntimeContext } from './types';
|
|
6
8
|
|
|
7
|
-
export interface
|
|
8
|
-
|
|
9
|
-
values: any[];
|
|
9
|
+
export interface ASTMetadata {
|
|
10
|
+
complexity: number;
|
|
10
11
|
depth: number;
|
|
11
|
-
|
|
12
|
-
projection?: any[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface InspectOptions {
|
|
16
|
-
input?: unknown;
|
|
17
|
-
variables?: Record<string, unknown>;
|
|
18
|
-
maxTraces?: number;
|
|
12
|
+
operationCount: Map<string, number>;
|
|
19
13
|
}
|
|
20
14
|
|
|
21
15
|
export interface InspectResult {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
ast:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
result: FHIRPathValue[];
|
|
17
|
+
|
|
18
|
+
ast: {
|
|
19
|
+
node: ASTNode;
|
|
20
|
+
metadata: ASTMetadata;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
diagnostics: {
|
|
24
|
+
warnings: Diagnostic[];
|
|
25
|
+
hints: Array<{message: string; suggestion?: string}>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
performance: {
|
|
29
|
+
parseTime: number;
|
|
30
|
+
analyzeTime: number;
|
|
31
|
+
evalTime: number;
|
|
32
|
+
totalTime: number;
|
|
33
|
+
operationTimings: Map<string, number>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
traces?: Array<{
|
|
37
|
+
label: string;
|
|
38
|
+
value: FHIRPathValue[];
|
|
39
|
+
timestamp: number;
|
|
40
|
+
}>;
|
|
29
41
|
}
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
export interface InspectOptions {
|
|
44
|
+
input?: any;
|
|
45
|
+
variables?: Record<string, any>;
|
|
46
|
+
includeTraces?: boolean;
|
|
47
|
+
maxDepth?: number;
|
|
48
|
+
}
|
|
36
49
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
class PerformanceTracker {
|
|
51
|
+
private timings = new Map<string, number>();
|
|
52
|
+
private startTimes = new Map<string, number>();
|
|
53
|
+
|
|
54
|
+
start(name: string): void {
|
|
55
|
+
this.startTimes.set(name, performance.now());
|
|
40
56
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
|
|
58
|
+
end(name: string): void {
|
|
59
|
+
const startTime = this.startTimes.get(name);
|
|
60
|
+
if (startTime !== undefined) {
|
|
61
|
+
const duration = performance.now() - startTime;
|
|
62
|
+
this.timings.set(name, duration);
|
|
63
|
+
this.startTimes.delete(name);
|
|
45
64
|
}
|
|
46
|
-
|
|
47
|
-
const timestamp = Date.now() - this.startTime;
|
|
48
|
-
this.traces.push({
|
|
49
|
-
name,
|
|
50
|
-
values: [...values], // Clone to prevent mutation
|
|
51
|
-
depth: this.depth,
|
|
52
|
-
timestamp,
|
|
53
|
-
projection: projection ? [...projection] : undefined
|
|
54
|
-
});
|
|
55
65
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return
|
|
66
|
+
|
|
67
|
+
get(name: string): number {
|
|
68
|
+
return this.timings.get(name) ?? 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getAll(): Map<string, number> {
|
|
72
|
+
return new Map(this.timings);
|
|
59
73
|
}
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
function
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Evaluate the name argument
|
|
73
|
-
if (args.length === 0) {
|
|
74
|
-
collector.addTrace('(unnamed)', input);
|
|
75
|
-
return originalEvaluator(input, context, args, evaluator);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const nameResult = evaluator(args[0], input, context);
|
|
79
|
-
if (nameResult.value.length !== 1 || typeof nameResult.value[0] !== 'string') {
|
|
80
|
-
return originalEvaluator(input, context, args, evaluator);
|
|
81
|
-
}
|
|
76
|
+
function analyzeAST(node: ASTNode, maxDepth = 100): ASTMetadata {
|
|
77
|
+
const operationCount = new Map<string, number>();
|
|
78
|
+
let complexity = 0;
|
|
79
|
+
let maxDepthFound = 0;
|
|
80
|
+
|
|
81
|
+
function visit(n: ASTNode, depth: number): void {
|
|
82
|
+
if (depth > maxDepth) return;
|
|
83
|
+
maxDepthFound = Math.max(maxDepthFound, depth);
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
// Count operation types
|
|
86
|
+
const opKey = n.type;
|
|
87
|
+
operationCount.set(opKey, (operationCount.get(opKey) ?? 0) + 1);
|
|
84
88
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
// Calculate complexity based on node type
|
|
90
|
+
switch (n.type) {
|
|
91
|
+
case NodeType.Binary:
|
|
92
|
+
complexity += 2;
|
|
93
|
+
const binary = n as any;
|
|
94
|
+
visit(binary.left, depth + 1);
|
|
95
|
+
visit(binary.right, depth + 1);
|
|
96
|
+
break;
|
|
97
|
+
|
|
98
|
+
case NodeType.Function:
|
|
99
|
+
complexity += 3;
|
|
100
|
+
const func = n as any;
|
|
101
|
+
if (func.name) visit(func.name, depth + 1);
|
|
102
|
+
func.args?.forEach((arg: ASTNode) => visit(arg, depth + 1));
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
case NodeType.Unary:
|
|
106
|
+
complexity += 1;
|
|
107
|
+
visit((n as any).operand, depth + 1);
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
case NodeType.Index:
|
|
111
|
+
complexity += 2;
|
|
112
|
+
const index = n as any;
|
|
113
|
+
visit(index.expression, depth + 1);
|
|
114
|
+
visit(index.index, depth + 1);
|
|
115
|
+
break;
|
|
116
|
+
|
|
117
|
+
case NodeType.Collection:
|
|
118
|
+
complexity += 1;
|
|
119
|
+
(n as any).elements?.forEach((el: ASTNode) => visit(el, depth + 1));
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
case NodeType.MembershipTest:
|
|
123
|
+
complexity += 2;
|
|
124
|
+
const membership = n as any;
|
|
125
|
+
visit(membership.expression, depth + 1);
|
|
126
|
+
if (membership.typeExpression) visit(membership.typeExpression, depth + 1);
|
|
127
|
+
break;
|
|
128
|
+
|
|
129
|
+
case NodeType.TypeCast:
|
|
130
|
+
complexity += 2;
|
|
131
|
+
const typeCast = n as any;
|
|
132
|
+
visit(typeCast.expression, depth + 1);
|
|
133
|
+
if (typeCast.typeExpression) visit(typeCast.typeExpression, depth + 1);
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case NodeType.Literal:
|
|
137
|
+
case NodeType.Identifier:
|
|
138
|
+
case NodeType.Variable:
|
|
139
|
+
case NodeType.TypeReference:
|
|
140
|
+
case NodeType.TypeOrIdentifier:
|
|
141
|
+
complexity += 1;
|
|
142
|
+
break;
|
|
90
143
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
visit(node, 0);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
complexity,
|
|
150
|
+
depth: maxDepthFound,
|
|
151
|
+
operationCount
|
|
96
152
|
};
|
|
97
153
|
}
|
|
98
154
|
|
|
155
|
+
function generateHints(ast: ASTNode): Array<{message: string; suggestion?: string}> {
|
|
156
|
+
const hints: Array<{message: string; suggestion?: string}> = [];
|
|
157
|
+
|
|
158
|
+
function visit(node: ASTNode): void {
|
|
159
|
+
switch (node.type) {
|
|
160
|
+
case NodeType.Binary:
|
|
161
|
+
const binary = node as any;
|
|
162
|
+
|
|
163
|
+
// Hint for chain of where() calls
|
|
164
|
+
if (binary.operator === '.' &&
|
|
165
|
+
binary.right.type === NodeType.Function &&
|
|
166
|
+
(binary.right as any).name?.name === 'where') {
|
|
167
|
+
const leftFunc = binary.left.type === NodeType.Binary &&
|
|
168
|
+
(binary.left as any).right?.type === NodeType.Function &&
|
|
169
|
+
(binary.left as any).right?.name?.name === 'where';
|
|
170
|
+
if (leftFunc) {
|
|
171
|
+
hints.push({
|
|
172
|
+
message: 'Multiple where() calls detected',
|
|
173
|
+
suggestion: 'Consider combining conditions with "and" for better performance'
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Hint for unnecessary empty() check before first()
|
|
179
|
+
if (binary.operator === '.' &&
|
|
180
|
+
binary.right.type === NodeType.Function &&
|
|
181
|
+
(binary.right as any).name?.name === 'first' &&
|
|
182
|
+
binary.left.type === NodeType.Binary &&
|
|
183
|
+
(binary.left as any).right?.type === NodeType.Function &&
|
|
184
|
+
(binary.left as any).right?.name?.name === 'empty') {
|
|
185
|
+
hints.push({
|
|
186
|
+
message: 'Unnecessary empty() check before first()',
|
|
187
|
+
suggestion: 'first() returns empty collection if input is empty'
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
visit(binary.left);
|
|
192
|
+
visit(binary.right);
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case NodeType.Function:
|
|
196
|
+
const func = node as any;
|
|
197
|
+
|
|
198
|
+
// Hint for count() > 0 pattern
|
|
199
|
+
if (func.name?.name === 'count' && func.parent?.type === NodeType.Binary) {
|
|
200
|
+
const parent = func.parent as any;
|
|
201
|
+
if (parent.operator === '>' &&
|
|
202
|
+
parent.right?.type === NodeType.Literal &&
|
|
203
|
+
parent.right?.value === 0) {
|
|
204
|
+
hints.push({
|
|
205
|
+
message: 'count() > 0 pattern detected',
|
|
206
|
+
suggestion: 'Consider using exists() for better clarity'
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func.args?.forEach((arg: ASTNode) => visit(arg));
|
|
212
|
+
break;
|
|
213
|
+
|
|
214
|
+
case NodeType.Collection:
|
|
215
|
+
(node as any).elements?.forEach((el: ASTNode) => visit(el));
|
|
216
|
+
break;
|
|
217
|
+
|
|
218
|
+
case NodeType.Unary:
|
|
219
|
+
visit((node as any).operand);
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
case NodeType.Index:
|
|
223
|
+
const index = node as any;
|
|
224
|
+
visit(index.expression);
|
|
225
|
+
visit(index.index);
|
|
226
|
+
break;
|
|
227
|
+
|
|
228
|
+
case NodeType.MembershipTest:
|
|
229
|
+
const membership = node as any;
|
|
230
|
+
visit(membership.expression);
|
|
231
|
+
if (membership.typeExpression) visit(membership.typeExpression);
|
|
232
|
+
break;
|
|
233
|
+
|
|
234
|
+
case NodeType.TypeCast:
|
|
235
|
+
const typeCast = node as any;
|
|
236
|
+
visit(typeCast.expression);
|
|
237
|
+
if (typeCast.typeExpression) visit(typeCast.typeExpression);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
visit(ast);
|
|
243
|
+
return hints;
|
|
244
|
+
}
|
|
245
|
+
|
|
99
246
|
export function inspect(
|
|
100
|
-
expression: string,
|
|
247
|
+
expression: string,
|
|
101
248
|
options: InspectOptions = {}
|
|
102
249
|
): InspectResult {
|
|
103
|
-
const
|
|
250
|
+
const tracker = new PerformanceTracker();
|
|
251
|
+
const traces: InspectResult['traces'] = [];
|
|
104
252
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
253
|
+
tracker.start('total');
|
|
254
|
+
|
|
255
|
+
// Parse
|
|
256
|
+
tracker.start('parse');
|
|
257
|
+
const parseResult = parse(expression);
|
|
258
|
+
const ast = parseResult.ast;
|
|
259
|
+
tracker.end('parse');
|
|
260
|
+
|
|
261
|
+
// Analyze
|
|
262
|
+
tracker.start('analyze');
|
|
263
|
+
const analyzer = new Analyzer();
|
|
264
|
+
const analysisResult = analyzer.analyze(ast, options.variables);
|
|
265
|
+
const warnings = analysisResult.diagnostics.filter(d => d.severity === DiagnosticSeverity.Warning);
|
|
266
|
+
const hints = generateHints(ast);
|
|
267
|
+
tracker.end('analyze');
|
|
268
|
+
|
|
269
|
+
// Analyze AST metadata
|
|
270
|
+
const metadata = analyzeAST(ast, options.maxDepth);
|
|
271
|
+
|
|
272
|
+
// Setup context for evaluation
|
|
273
|
+
const input = options.input === undefined ? [] : Array.isArray(options.input) ? options.input : [options.input];
|
|
274
|
+
let context = RuntimeContextManager.create(input);
|
|
275
|
+
context = RuntimeContextManager.setVariable(context, '$this', input);
|
|
276
|
+
|
|
277
|
+
if (options.variables) {
|
|
278
|
+
for (const [key, value] of Object.entries(options.variables)) {
|
|
279
|
+
const varValue = Array.isArray(value) ? value : [value];
|
|
280
|
+
context = RuntimeContextManager.setVariable(context, key, varValue);
|
|
128
281
|
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Setup trace collection if requested
|
|
285
|
+
if (options.includeTraces) {
|
|
286
|
+
// We'll collect traces by intercepting the trace function during evaluation
|
|
287
|
+
// This will be implemented when trace() is called during evaluation
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Evaluate
|
|
291
|
+
tracker.start('eval');
|
|
292
|
+
const interpreter = new Interpreter();
|
|
293
|
+
|
|
294
|
+
// Track operation timings
|
|
295
|
+
const operationTimings = new Map<string, number>();
|
|
296
|
+
const originalEvaluate = interpreter.evaluate.bind(interpreter);
|
|
297
|
+
interpreter.evaluate = function(node: ASTNode, inputData: any[], ctx: RuntimeContext) {
|
|
298
|
+
const opStart = performance.now();
|
|
299
|
+
const result = originalEvaluate(node, inputData, ctx);
|
|
300
|
+
const opTime = performance.now() - opStart;
|
|
129
301
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// Create a custom registry with our trace interceptor
|
|
136
|
-
const registry = new Registry();
|
|
137
|
-
|
|
138
|
-
// Store original trace function
|
|
139
|
-
const originalTraceEvaluator = operations.traceFunction.evaluate;
|
|
140
|
-
|
|
141
|
-
// Replace with interceptor
|
|
142
|
-
(operations.traceFunction as any).evaluate = createTraceInterceptor(traceCollector);
|
|
302
|
+
const opKey = node.type === NodeType.Function ?
|
|
303
|
+
`Function:${(node as any).name?.name || 'unknown'}` :
|
|
304
|
+
node.type === NodeType.Binary ?
|
|
305
|
+
`Binary:${(node as any).operator}` :
|
|
306
|
+
node.type;
|
|
143
307
|
|
|
144
|
-
|
|
145
|
-
// Create interpreter with our registry
|
|
146
|
-
const interpreter = new Interpreter(registry);
|
|
147
|
-
|
|
148
|
-
const input = options.input === undefined ? [] : Array.isArray(options.input) ? options.input : [options.input];
|
|
149
|
-
|
|
150
|
-
// Create context with variables
|
|
151
|
-
let context = RuntimeContextManager.create(input);
|
|
152
|
-
context = RuntimeContextManager.setVariable(context, '$this', input);
|
|
153
|
-
|
|
154
|
-
if (options.variables) {
|
|
155
|
-
for (const [key, value] of Object.entries(options.variables)) {
|
|
156
|
-
const varValue = Array.isArray(value) ? value : [value];
|
|
157
|
-
context = RuntimeContextManager.setVariable(context, key, varValue);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
let result: any[] = [];
|
|
162
|
-
let evalErrors: Array<{ type: string; message: string }> = [];
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
const evalResult = interpreter.evaluate(ast, input, context);
|
|
166
|
-
result = evalResult.value;
|
|
167
|
-
} catch (error) {
|
|
168
|
-
evalErrors.push({
|
|
169
|
-
type: 'EvaluationError',
|
|
170
|
-
message: error instanceof Error ? error.message : String(error)
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const executionTime = Date.now() - startTime;
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
expression,
|
|
178
|
-
result,
|
|
179
|
-
ast,
|
|
180
|
-
traces: traceCollector.getTraces(),
|
|
181
|
-
executionTime,
|
|
182
|
-
errors: evalErrors.length > 0 ? evalErrors : undefined
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
} finally {
|
|
186
|
-
// Always restore original evaluator
|
|
187
|
-
(operations.traceFunction as any).evaluate = originalTraceEvaluator;
|
|
188
|
-
}
|
|
308
|
+
operationTimings.set(opKey, (operationTimings.get(opKey) ?? 0) + opTime);
|
|
189
309
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
310
|
+
return result;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const result = interpreter.evaluate(ast, input, context);
|
|
314
|
+
tracker.end('eval');
|
|
315
|
+
|
|
316
|
+
tracker.end('total');
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
result: result.value,
|
|
320
|
+
ast: {
|
|
321
|
+
node: ast,
|
|
322
|
+
metadata
|
|
323
|
+
},
|
|
324
|
+
diagnostics: {
|
|
325
|
+
warnings,
|
|
326
|
+
hints
|
|
327
|
+
},
|
|
328
|
+
performance: {
|
|
329
|
+
parseTime: tracker.get('parse'),
|
|
330
|
+
analyzeTime: tracker.get('analyze'),
|
|
331
|
+
evalTime: tracker.get('eval'),
|
|
332
|
+
totalTime: tracker.get('total'),
|
|
333
|
+
operationTimings
|
|
334
|
+
},
|
|
335
|
+
...(options.includeTraces && { traces })
|
|
336
|
+
};
|
|
204
337
|
}
|