@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.
Files changed (59) hide show
  1. package/README.md +400 -0
  2. package/dist/index.d.ts +398 -0
  3. package/dist/index.js +8372 -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 +155 -0
  12. package/src/api/errors.ts +134 -0
  13. package/src/api/expression.ts +156 -0
  14. package/src/api/index.ts +70 -0
  15. package/src/api/inspect.ts +96 -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 +76 -0
  23. package/src/interpreter/README.md +78 -0
  24. package/src/interpreter/interpreter.ts +463 -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/parser/ast.ts +123 -0
  32. package/src/parser/index.ts +3 -0
  33. package/src/parser/parser.ts +701 -0
  34. package/src/parser/pprint.ts +169 -0
  35. package/src/registry/default-analyzers.ts +257 -0
  36. package/src/registry/default-compilers.ts +31 -0
  37. package/src/registry/index.ts +94 -0
  38. package/src/registry/operations/arithmetic.ts +506 -0
  39. package/src/registry/operations/collection.ts +425 -0
  40. package/src/registry/operations/comparison.ts +432 -0
  41. package/src/registry/operations/existence.ts +703 -0
  42. package/src/registry/operations/filtering.ts +358 -0
  43. package/src/registry/operations/literals.ts +341 -0
  44. package/src/registry/operations/logical.ts +439 -0
  45. package/src/registry/operations/math.ts +128 -0
  46. package/src/registry/operations/membership.ts +132 -0
  47. package/src/registry/operations/string.ts +507 -0
  48. package/src/registry/operations/subsetting.ts +174 -0
  49. package/src/registry/operations/type-checking.ts +162 -0
  50. package/src/registry/operations/type-conversion.ts +404 -0
  51. package/src/registry/operations/type-operators.ts +308 -0
  52. package/src/registry/operations/utility.ts +644 -0
  53. package/src/registry/registry.ts +146 -0
  54. package/src/registry/types.ts +161 -0
  55. package/src/registry/utils/evaluation-helpers.ts +93 -0
  56. package/src/registry/utils/index.ts +3 -0
  57. package/src/registry/utils/type-system.ts +173 -0
  58. package/src/runtime/context.ts +158 -0
  59. 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
+ };