@atomic-ehr/fhirpath 0.0.1-canary.0c6931e.20250727185306

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