@atomic-ehr/fhirpath 0.0.1-canary.35b105d.20250724165800

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +307 -0
  2. package/dist/index.d.ts +225 -0
  3. package/dist/index.js +8185 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +51 -0
  6. package/src/analyzer/analyzer.ts +486 -0
  7. package/src/analyzer/model-provider.ts +244 -0
  8. package/src/analyzer/schemas/index.ts +2 -0
  9. package/src/analyzer/schemas/types.ts +40 -0
  10. package/src/analyzer/types.ts +142 -0
  11. package/src/api/builder.ts +148 -0
  12. package/src/api/errors.ts +134 -0
  13. package/src/api/expression.ts +152 -0
  14. package/src/api/index.ts +57 -0
  15. package/src/api/registry.ts +128 -0
  16. package/src/api/types.ts +154 -0
  17. package/src/compiler/compiler.ts +579 -0
  18. package/src/compiler/index.ts +2 -0
  19. package/src/compiler/prototype-context-adapter.ts +99 -0
  20. package/src/compiler/types.ts +23 -0
  21. package/src/index.ts +52 -0
  22. package/src/interpreter/README.md +78 -0
  23. package/src/interpreter/interpreter.ts +485 -0
  24. package/src/interpreter/types.ts +110 -0
  25. package/src/lexer/char-tables.ts +37 -0
  26. package/src/lexer/errors.ts +31 -0
  27. package/src/lexer/index.ts +5 -0
  28. package/src/lexer/lexer.ts +745 -0
  29. package/src/lexer/token.ts +104 -0
  30. package/src/parser/ast.ts +123 -0
  31. package/src/parser/index.ts +3 -0
  32. package/src/parser/parser.ts +701 -0
  33. package/src/parser/pprint.ts +169 -0
  34. package/src/registry/default-analyzers.ts +257 -0
  35. package/src/registry/default-compilers.ts +31 -0
  36. package/src/registry/index.ts +93 -0
  37. package/src/registry/operations/arithmetic.ts +506 -0
  38. package/src/registry/operations/collection.ts +425 -0
  39. package/src/registry/operations/comparison.ts +432 -0
  40. package/src/registry/operations/existence.ts +703 -0
  41. package/src/registry/operations/filtering.ts +358 -0
  42. package/src/registry/operations/literals.ts +341 -0
  43. package/src/registry/operations/logical.ts +402 -0
  44. package/src/registry/operations/math.ts +128 -0
  45. package/src/registry/operations/membership.ts +132 -0
  46. package/src/registry/operations/string.ts +507 -0
  47. package/src/registry/operations/subsetting.ts +174 -0
  48. package/src/registry/operations/type-checking.ts +162 -0
  49. package/src/registry/operations/type-conversion.ts +404 -0
  50. package/src/registry/operations/type-operators.ts +307 -0
  51. package/src/registry/operations/utility.ts +542 -0
  52. package/src/registry/registry.ts +146 -0
  53. package/src/registry/types.ts +161 -0
  54. package/src/registry/utils/evaluation-helpers.ts +93 -0
  55. package/src/registry/utils/index.ts +3 -0
  56. package/src/registry/utils/type-system.ts +173 -0
  57. package/src/runtime/context.ts +179 -0
@@ -0,0 +1,579 @@
1
+ import type {
2
+ ASTNode,
3
+ LiteralNode,
4
+ IdentifierNode,
5
+ VariableNode,
6
+ BinaryNode,
7
+ UnaryNode,
8
+ FunctionNode,
9
+ CollectionNode,
10
+ IndexNode,
11
+ UnionNode,
12
+ MembershipTestNode,
13
+ TypeCastNode,
14
+ TypeReferenceNode,
15
+ TypeOrIdentifierNode
16
+ } from '../parser/ast';
17
+ import { NodeType } from '../parser/ast';
18
+ import { TokenType } from '../lexer/token';
19
+ import type { CompiledNode } from './types';
20
+ import type { EvaluationResult } from '../interpreter/types';
21
+ import { EvaluationError, CollectionUtils } from '../interpreter/types';
22
+ import { RuntimeContextManager } from '../runtime/context';
23
+ import { isTruthy, toSingleton } from '../registry/utils';
24
+ import type { Compiler as ICompiler, CompiledExpression, TypeRef } from '../registry/types';
25
+ import type { RuntimeContext } from '../runtime/context';
26
+ // Import the global registry to ensure all operations are registered
27
+ import '../registry';
28
+ import { Registry } from '../registry';
29
+
30
+ /**
31
+ * FHIRPath to JavaScript Closure Compiler
32
+ *
33
+ * Transforms FHIRPath AST nodes into JavaScript functions that implement
34
+ * the same stream-processing semantics as the interpreter.
35
+ */
36
+ export class Compiler implements ICompiler {
37
+ /**
38
+ * Main entry point - compiles an AST into an executable function
39
+ */
40
+ compile(node: ASTNode, input?: CompiledExpression): CompiledExpression {
41
+ const compiled = this.compileNode(node);
42
+
43
+ // Wrap the compiled function to ensure $this is set
44
+ return {
45
+ ...compiled,
46
+ fn: (ctx: RuntimeContext) => {
47
+ // Ensure $this is set if not already present
48
+ if (!ctx.env?.$this) {
49
+ ctx = {
50
+ ...ctx,
51
+ env: {
52
+ ...ctx.env,
53
+ $this: ctx.input
54
+ }
55
+ };
56
+ }
57
+ return compiled.fn(ctx);
58
+ }
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Resolve a type name to a TypeRef
64
+ */
65
+ resolveType(typeName: string): TypeRef {
66
+ // For now, return a simple type reference
67
+ // In the future, this should use a model provider
68
+ return { type: typeName } as TypeRef;
69
+ }
70
+
71
+ /**
72
+ * Dispatches to specific compilation methods based on node type
73
+ */
74
+ private compileNode(node: ASTNode): CompiledExpression {
75
+ switch (node.type) {
76
+ case NodeType.Literal:
77
+ return this.compileLiteral(node as LiteralNode);
78
+ case NodeType.Identifier:
79
+ return this.compileIdentifier(node as IdentifierNode);
80
+ case NodeType.TypeOrIdentifier:
81
+ return this.compileTypeOrIdentifier(node as TypeOrIdentifierNode);
82
+ case NodeType.Variable:
83
+ return this.compileVariable(node as VariableNode);
84
+ case NodeType.Binary:
85
+ return this.compileBinary(node as BinaryNode);
86
+ case NodeType.Unary:
87
+ return this.compileUnary(node as UnaryNode);
88
+ case NodeType.Function:
89
+ return this.compileFunction(node as FunctionNode);
90
+ case NodeType.Collection:
91
+ return this.compileCollection(node as CollectionNode);
92
+ case NodeType.Index:
93
+ return this.compileIndex(node as IndexNode);
94
+ case NodeType.Union:
95
+ return this.compileUnion(node as UnionNode);
96
+ case NodeType.MembershipTest:
97
+ return this.compileMembershipTest(node as MembershipTestNode);
98
+ case NodeType.TypeCast:
99
+ return this.compileTypeCast(node as TypeCastNode);
100
+ case NodeType.TypeReference:
101
+ return this.compileTypeReference(node as TypeReferenceNode);
102
+ default:
103
+ throw new EvaluationError(
104
+ `Unknown node type: ${(node as any).type}`,
105
+ node.position
106
+ );
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Compiles a literal node - returns a constant value
112
+ */
113
+ private compileLiteral(node: LiteralNode): CompiledExpression {
114
+ const value = node.value;
115
+
116
+ // Check if literal is an operation reference
117
+ if (typeof value === 'string') {
118
+ const operation = Registry.get(value);
119
+ if (operation && operation.kind === 'literal') {
120
+ return operation.compile(this, { fn: () => [], type: this.resolveType('Any'), isSingleton: false }, []);
121
+ }
122
+ }
123
+
124
+ // Return a compiled expression for the literal value
125
+ return {
126
+ fn: (ctx: RuntimeContext) => value === null ? [] : [value],
127
+ type: this.resolveType(this.getLiteralType(value)),
128
+ isSingleton: true,
129
+ source: JSON.stringify(value)
130
+ };
131
+ }
132
+
133
+ private getLiteralType(value: any): string {
134
+ if (value === null || value === undefined) return 'Any';
135
+ if (typeof value === 'boolean') return 'Boolean';
136
+ if (typeof value === 'string') return 'String';
137
+ if (typeof value === 'number') {
138
+ return Number.isInteger(value) ? 'Integer' : 'Decimal';
139
+ }
140
+ return 'Any';
141
+ }
142
+
143
+ /**
144
+ * Compiles an identifier node - performs property navigation
145
+ */
146
+ private compileIdentifier(node: IdentifierNode): CompiledExpression {
147
+ const name = node.name;
148
+
149
+ return {
150
+ fn: (ctx: RuntimeContext) => {
151
+ const input = ctx.focus || ctx.input || [];
152
+ const results: any[] = [];
153
+
154
+ for (const item of input) {
155
+ if (item == null || typeof item !== 'object') {
156
+ continue;
157
+ }
158
+
159
+ const value = item[name];
160
+ if (value !== undefined) {
161
+ if (Array.isArray(value)) {
162
+ results.push(...value);
163
+ } else {
164
+ results.push(value);
165
+ }
166
+ }
167
+ }
168
+
169
+ return results;
170
+ },
171
+ type: this.resolveType('Any'), // Would need type inference in a real implementation
172
+ isSingleton: false,
173
+ source: name
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Compiles a TypeOrIdentifier node - handles both type filtering and property navigation
179
+ */
180
+ private compileTypeOrIdentifier(node: TypeOrIdentifierNode): CompiledExpression {
181
+ const name = node.name;
182
+
183
+ return {
184
+ fn: (ctx: RuntimeContext) => {
185
+ const input = ctx.focus || ctx.input || [];
186
+
187
+ // First, check if this is a type filter (e.g., Patient in Patient.name)
188
+ // Check if any input items have this as their resourceType
189
+ const hasMatchingResourceType = input.some((item: any) =>
190
+ item && typeof item === 'object' && item.resourceType === name
191
+ );
192
+
193
+ if (hasMatchingResourceType) {
194
+ // This is a type filter - return only items matching this resourceType
195
+ return input.filter((item: any) =>
196
+ item && typeof item === 'object' && item.resourceType === name
197
+ );
198
+ }
199
+
200
+ // Not a type filter, treat as property navigation
201
+ const results: any[] = [];
202
+
203
+ for (const item of input) {
204
+ if (item == null || typeof item !== 'object') {
205
+ continue;
206
+ }
207
+
208
+ const value = item[name];
209
+ if (value !== undefined) {
210
+ if (Array.isArray(value)) {
211
+ results.push(...value);
212
+ } else {
213
+ results.push(value);
214
+ }
215
+ }
216
+ }
217
+
218
+ return results;
219
+ },
220
+ type: this.resolveType('Any'),
221
+ isSingleton: false,
222
+ source: name
223
+ };
224
+ }
225
+
226
+ /**
227
+ * Compiles a variable node - looks up value from context
228
+ */
229
+ private compileVariable(node: VariableNode): CompiledExpression {
230
+ const name = node.name;
231
+
232
+ return {
233
+ fn: (ctx: RuntimeContext) => {
234
+ if (name.startsWith('$')) {
235
+ // Special environment variables
236
+ switch (name) {
237
+ case '$this':
238
+ return ctx.env.$this || [];
239
+ case '$index':
240
+ return ctx.env.$index !== undefined ? [ctx.env.$index] : [];
241
+ case '$total':
242
+ return ctx.env.$total !== undefined ? [ctx.env.$total] : [];
243
+ default:
244
+ throw new EvaluationError(`Unknown special variable: ${name}`, node.position);
245
+ }
246
+ } else {
247
+ // User-defined variables (remove % prefix if present)
248
+ const varName = name.startsWith('%') ? name.substring(1) : name;
249
+
250
+ // Special root variables
251
+ switch (varName) {
252
+ case 'context':
253
+ return ctx.env.$context || ctx.input || [];
254
+ case 'resource':
255
+ return ctx.env.$resource || ctx.input || [];
256
+ case 'rootResource':
257
+ return ctx.env.$rootResource || ctx.input || [];
258
+ default:
259
+ const value = ctx.env[varName];
260
+ if (value === undefined) {
261
+ return [];
262
+ }
263
+ // Wrap non-array values in an array to create a singleton collection
264
+ return Array.isArray(value) ? value : [value];
265
+ }
266
+ }
267
+ },
268
+ type: this.resolveType('Any'),
269
+ isSingleton: false,
270
+ source: name
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Compiles a binary operator node
276
+ */
277
+ private compileBinary(node: BinaryNode): CompiledExpression {
278
+ const operator = node.operator;
279
+
280
+ // Handle case where parser incorrectly creates BinaryNode for unary minus
281
+ if (!node.left && !node.right && (node as any).operand) {
282
+ // This is actually a unary operation
283
+ return this.compileUnary(node as any);
284
+ }
285
+
286
+ // Special handling for dot operator - it's a pipeline
287
+ if (operator === TokenType.DOT) {
288
+ const left = this.compileNode(node.left);
289
+ const right = this.compileNode(node.right);
290
+
291
+ return {
292
+ fn: (ctx: RuntimeContext) => {
293
+ // Execute left side with the original context
294
+ const leftResult = left.fn(ctx);
295
+
296
+ // Execute right side with left's result as input
297
+ // Use withInput to maintain prototype chain
298
+ const rightCtx = RuntimeContextManager.withInput(ctx, leftResult);
299
+ return right.fn(rightCtx);
300
+ },
301
+ type: right.type,
302
+ isSingleton: right.isSingleton,
303
+ source: `${left.source || ''}.${right.source || ''}`
304
+ };
305
+ }
306
+
307
+ // Get operation from registry
308
+ const operation = node.operation || Registry.getByToken(operator, 'infix');
309
+ if (!operation || operation.kind !== 'operator') {
310
+ throw new EvaluationError(`Unknown operator: ${operator}`, node.position);
311
+ }
312
+
313
+ // Compile operands
314
+ const left = this.compileNode(node.left);
315
+ const right = this.compileNode(node.right);
316
+
317
+ // Use operation's compile method
318
+ // For operators, pass both operands in args array
319
+ return operation.compile(this, left, [left, right]);
320
+ }
321
+
322
+ /**
323
+ * Compiles a unary operator node
324
+ */
325
+ private compileUnary(node: UnaryNode): CompiledExpression {
326
+ const operator = node.operator;
327
+
328
+ // Get operation from registry
329
+ // Don't use node.operation as parser might have assigned wrong operation
330
+ const operation = Registry.getByToken(operator, 'prefix');
331
+ if (!operation || operation.kind !== 'operator') {
332
+ throw new EvaluationError(`Unknown unary operator: ${operator}`, node.position);
333
+ }
334
+
335
+ // Compile operand
336
+ const operand = this.compileNode(node.operand);
337
+
338
+ // Use operation's compile method
339
+ // For unary operators, pass operand in args array
340
+ return operation.compile(this, operand, [operand]);
341
+ }
342
+
343
+ /**
344
+ * Compiles a function call node
345
+ */
346
+ private compileFunction(node: FunctionNode): CompiledExpression {
347
+ // For now, handle only identifier function names
348
+ if (node.name.type !== NodeType.Identifier) {
349
+ throw new EvaluationError('Dynamic function names not yet supported', node.position);
350
+ }
351
+
352
+ const functionName = (node.name as IdentifierNode).name;
353
+
354
+ // Check if function is registered
355
+ const operation = Registry.get(functionName);
356
+ if (!operation || operation.kind !== 'function') {
357
+ throw new EvaluationError(`Unknown function: ${functionName}`, node.position);
358
+ }
359
+
360
+ // Compile arguments
361
+ const compiledArgs = node.arguments.map(arg => this.compileNode(arg));
362
+
363
+ // Use operation's compile method
364
+ // For functions, the input is passed as the first compiled expression
365
+ const inputExpr: CompiledExpression = {
366
+ fn: (ctx) => ctx.focus || ctx.input || [],
367
+ type: this.resolveType('Any'),
368
+ isSingleton: false
369
+ };
370
+
371
+ return operation.compile(this, inputExpr, compiledArgs);
372
+ }
373
+
374
+ /**
375
+ * Compiles a collection node
376
+ */
377
+ private compileCollection(node: CollectionNode): CompiledExpression {
378
+ const compiledElements = node.elements.map(elem => this.compileNode(elem));
379
+
380
+ return {
381
+ fn: (ctx: RuntimeContext) => {
382
+ const results: any[] = [];
383
+
384
+ for (const element of compiledElements) {
385
+ const elementResult = element.fn(ctx);
386
+ results.push(...elementResult);
387
+ }
388
+
389
+ return results;
390
+ },
391
+ type: this.resolveType('Any'),
392
+ isSingleton: false,
393
+ source: `{${compiledElements.map(e => e.source || '').join(', ')}}`
394
+ };
395
+ }
396
+
397
+ /**
398
+ * Compiles an index node
399
+ */
400
+ private compileIndex(node: IndexNode): CompiledExpression {
401
+ const expression = this.compileNode(node.expression);
402
+ const index = this.compileNode(node.index);
403
+
404
+ return {
405
+ fn: (ctx: RuntimeContext) => {
406
+ const exprResult = expression.fn(ctx);
407
+ // Evaluate index in the original context
408
+ const indexResult = index.fn(ctx);
409
+
410
+ if (indexResult.length === 0) {
411
+ return [];
412
+ }
413
+
414
+ const idx = toSingleton(indexResult);
415
+ if (typeof idx !== 'number' || !Number.isInteger(idx)) {
416
+ throw new EvaluationError('Index must be an integer', node.position);
417
+ }
418
+
419
+ if (idx < 0 || idx >= exprResult.length) {
420
+ return [];
421
+ }
422
+
423
+ return [exprResult[idx]];
424
+ },
425
+ type: expression.type,
426
+ isSingleton: true,
427
+ source: `${expression.source || ''}[${index.source || ''}]`
428
+ };
429
+ }
430
+
431
+ /**
432
+ * Compiles a union node
433
+ */
434
+ private compileUnion(node: UnionNode): CompiledExpression {
435
+ const compiledOperands = node.operands.map(op => this.compileNode(op));
436
+
437
+ return {
438
+ fn: (ctx: RuntimeContext) => {
439
+ const results: any[] = [];
440
+ const seen = new Set();
441
+
442
+ for (const operand of compiledOperands) {
443
+ // Create a fresh context copy for each operand
444
+ // This prevents variable definitions from leaking between branches
445
+ const operandCtx = { ...ctx, env: { ...ctx.env } };
446
+ const operandResult = operand.fn(operandCtx);
447
+
448
+ // Remove duplicates
449
+ for (const item of operandResult) {
450
+ const key = JSON.stringify(item);
451
+ if (!seen.has(key)) {
452
+ seen.add(key);
453
+ results.push(item);
454
+ }
455
+ }
456
+ }
457
+
458
+ return results;
459
+ },
460
+ type: this.resolveType('Any'),
461
+ isSingleton: false,
462
+ source: compiledOperands.map(o => o.source || '').join(' | ')
463
+ };
464
+ }
465
+
466
+ /**
467
+ * Compiles membership test (is operator)
468
+ */
469
+ private compileMembershipTest(node: MembershipTestNode): CompiledExpression {
470
+ // Get the 'is' operator from registry
471
+ const operation = Registry.getByToken(TokenType.IS, 'infix');
472
+ if (!operation || operation.kind !== 'operator') {
473
+ throw new EvaluationError('is operator not found in registry', node.position);
474
+ }
475
+
476
+ const expression = this.compileNode(node.expression);
477
+ const typeExpr: CompiledExpression = {
478
+ fn: () => [node.targetType],
479
+ type: this.resolveType('String'),
480
+ isSingleton: true,
481
+ source: node.targetType
482
+ };
483
+
484
+ try {
485
+ return operation.compile(this, expression, [expression, typeExpr]);
486
+ } catch (error: any) {
487
+ // If the error doesn't have position, add it from the node
488
+ if (error instanceof EvaluationError && !error.position) {
489
+ error.position = node.position;
490
+ }
491
+ throw error;
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Compiles type cast (as operator)
497
+ */
498
+ private compileTypeCast(node: TypeCastNode): CompiledExpression {
499
+ // Get the 'as' operator from registry
500
+ const operation = Registry.getByToken(TokenType.AS, 'infix');
501
+ if (!operation || operation.kind !== 'operator') {
502
+ throw new EvaluationError('as operator not found in registry', node.position);
503
+ }
504
+
505
+ const expression = this.compileNode(node.expression);
506
+ const typeExpr: CompiledExpression = {
507
+ fn: () => [node.targetType],
508
+ type: this.resolveType('String'),
509
+ isSingleton: true,
510
+ source: node.targetType
511
+ };
512
+
513
+ try {
514
+ return operation.compile(this, expression, [typeExpr]);
515
+ } catch (error: any) {
516
+ // If the error doesn't have position, add it from the node
517
+ if (error instanceof EvaluationError && !error.position) {
518
+ error.position = node.position;
519
+ }
520
+ throw error;
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Compiles type reference - should not be evaluated directly
526
+ */
527
+ private compileTypeReference(node: TypeReferenceNode): CompiledExpression {
528
+ // Type references are used in ofType() and similar functions
529
+ // They should compile to return the type name as a string
530
+ const typeName = node.typeName;
531
+ return {
532
+ fn: (ctx: RuntimeContext) => [typeName],
533
+ type: this.resolveType('String'),
534
+ isSingleton: true,
535
+ source: typeName
536
+ };
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Helper function to compile a FHIRPath expression
542
+ */
543
+ export function compile(expression: string | ASTNode): CompiledExpression {
544
+ // Parse if string
545
+ const ast = typeof expression === 'string'
546
+ ? require('../parser').parse(expression)
547
+ : expression;
548
+
549
+ // Create compiler and compile
550
+ const compiler = new Compiler();
551
+ return compiler.compile(ast);
552
+ }
553
+
554
+ /**
555
+ * Helper function to compile and evaluate a FHIRPath expression
556
+ */
557
+ export function evaluateCompiled(
558
+ expression: string | ASTNode,
559
+ input: any,
560
+ context?: RuntimeContext
561
+ ): any[] {
562
+ // Compile the expression
563
+ const compiled = compile(expression);
564
+
565
+ // Convert input to collection
566
+ const inputCollection = CollectionUtils.toCollection(input);
567
+
568
+ // Create runtime context
569
+ const runtimeContext: RuntimeContext = context || {
570
+ input: inputCollection,
571
+ focus: inputCollection,
572
+ env: {
573
+ $this: inputCollection
574
+ }
575
+ };
576
+
577
+ // Execute the compiled function
578
+ return compiled.fn(runtimeContext);
579
+ }
@@ -0,0 +1,2 @@
1
+ export { Compiler, compile, evaluateCompiled } from './compiler';
2
+ export type { CompiledNode, NodeCompiler, CompilationContext } from './types';
@@ -0,0 +1,99 @@
1
+ import { RuntimeContextManager } from '../runtime/context';
2
+ import type { RuntimeContext } from '../runtime/context';
3
+
4
+ /**
5
+ * Adapter functions to help transition compiler operations to prototype-based context.
6
+ * These can be used to gradually migrate operations.
7
+ */
8
+
9
+ /**
10
+ * Create an iterator context using prototype inheritance
11
+ * instead of spread operator copying.
12
+ */
13
+ export function createIteratorContext(
14
+ ctx: RuntimeContext,
15
+ item: any,
16
+ index: number
17
+ ): RuntimeContext {
18
+ return RuntimeContextManager.withIterator(ctx, item, index);
19
+ }
20
+
21
+ /**
22
+ * Create a context with new input/focus using prototype inheritance
23
+ */
24
+ export function createInputContext(
25
+ ctx: RuntimeContext,
26
+ input: any[],
27
+ focus?: any[]
28
+ ): RuntimeContext {
29
+ return RuntimeContextManager.withInput(ctx, input, focus);
30
+ }
31
+
32
+ /**
33
+ * Set a variable in context using prototype inheritance
34
+ */
35
+ export function setContextVariable(
36
+ ctx: RuntimeContext,
37
+ name: string,
38
+ value: any[]
39
+ ): RuntimeContext {
40
+ return RuntimeContextManager.setVariable(ctx, name, value);
41
+ }
42
+
43
+ /**
44
+ * Example of how to update the where operation to use prototype-based context
45
+ */
46
+ export function whereWithPrototypeContext(
47
+ input: { fn: (ctx: RuntimeContext) => any[] },
48
+ criteria: { fn: (ctx: RuntimeContext) => any[] }
49
+ ) {
50
+ return {
51
+ fn: (ctx: RuntimeContext) => {
52
+ const inputValue = input.fn(ctx);
53
+ const results: any[] = [];
54
+
55
+ for (let i = 0; i < inputValue.length; i++) {
56
+ const item = inputValue[i];
57
+ // Use prototype-based context instead of spread operator
58
+ const iterCtx = createIteratorContext(ctx, item, i);
59
+ const predicateResult = criteria.fn(iterCtx);
60
+
61
+ if (isTruthy(predicateResult)) {
62
+ results.push(item);
63
+ }
64
+ }
65
+
66
+ return results;
67
+ }
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Example of how to update the select operation to use prototype-based context
73
+ */
74
+ export function selectWithPrototypeContext(
75
+ input: { fn: (ctx: RuntimeContext) => any[] },
76
+ expression: { fn: (ctx: RuntimeContext) => any[] }
77
+ ) {
78
+ return {
79
+ fn: (ctx: RuntimeContext) => {
80
+ const inputValue = input.fn(ctx);
81
+ const results: any[] = [];
82
+
83
+ for (let i = 0; i < inputValue.length; i++) {
84
+ const item = inputValue[i];
85
+ // Use prototype-based context instead of spread operator
86
+ const iterCtx = createIteratorContext(ctx, item, i);
87
+ const exprResult = expression.fn(iterCtx);
88
+ results.push(...exprResult);
89
+ }
90
+
91
+ return results;
92
+ }
93
+ };
94
+ }
95
+
96
+ // Helper function (should be imported from utils)
97
+ function isTruthy(value: any[]): boolean {
98
+ return value.length > 0 && value[0] === true;
99
+ }
@@ -0,0 +1,23 @@
1
+ import type { Context, EvaluationResult } from '../interpreter/types';
2
+ import type { ASTNode } from '../parser/ast';
3
+
4
+ /**
5
+ * A compiled FHIRPath node - a JavaScript function that implements
6
+ * the stream-processing model: (input, context) → (output, new context)
7
+ */
8
+ export type CompiledNode = (input: any[], context: Context) => EvaluationResult;
9
+
10
+ /**
11
+ * A function that compiles a specific AST node type into a JavaScript closure
12
+ */
13
+ export type NodeCompiler<T extends ASTNode = ASTNode> = (node: T) => CompiledNode;
14
+
15
+ /**
16
+ * Compilation context for tracking state during compilation
17
+ */
18
+ export interface CompilationContext {
19
+ // Future: optimization flags, source map info, etc.
20
+ }
21
+
22
+ // Re-export types from registry to avoid conflicts
23
+ export type { CompiledExpression, RuntimeContext } from '../registry/types';