@atomic-ehr/fhirpath 0.0.1-canary.8687028.20250724113707

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 (54) hide show
  1. package/README.md +307 -0
  2. package/dist/index.js +7975 -0
  3. package/package.json +48 -0
  4. package/src/analyzer/analyzer.ts +486 -0
  5. package/src/analyzer/model-provider.ts +244 -0
  6. package/src/analyzer/schemas/index.ts +2 -0
  7. package/src/analyzer/schemas/types.ts +40 -0
  8. package/src/analyzer/types.ts +142 -0
  9. package/src/api/builder.ts +148 -0
  10. package/src/api/errors.ts +134 -0
  11. package/src/api/expression.ts +149 -0
  12. package/src/api/index.ts +57 -0
  13. package/src/api/registry.ts +128 -0
  14. package/src/api/types.ts +154 -0
  15. package/src/compiler/compiler.ts +550 -0
  16. package/src/compiler/index.ts +2 -0
  17. package/src/compiler/types.ts +23 -0
  18. package/src/index.ts +52 -0
  19. package/src/interpreter/README.md +78 -0
  20. package/src/interpreter/context.ts +181 -0
  21. package/src/interpreter/interpreter.ts +429 -0
  22. package/src/interpreter/types.ts +132 -0
  23. package/src/lexer/char-tables.ts +37 -0
  24. package/src/lexer/errors.ts +31 -0
  25. package/src/lexer/index.ts +5 -0
  26. package/src/lexer/lexer.ts +745 -0
  27. package/src/lexer/token.ts +104 -0
  28. package/src/parser/ast.ts +123 -0
  29. package/src/parser/index.ts +3 -0
  30. package/src/parser/parser.ts +701 -0
  31. package/src/parser/pprint.ts +169 -0
  32. package/src/registry/default-analyzers.ts +257 -0
  33. package/src/registry/default-compilers.ts +31 -0
  34. package/src/registry/index.ts +93 -0
  35. package/src/registry/operations/arithmetic.ts +506 -0
  36. package/src/registry/operations/collection.ts +384 -0
  37. package/src/registry/operations/comparison.ts +432 -0
  38. package/src/registry/operations/existence.ts +719 -0
  39. package/src/registry/operations/filtering.ts +374 -0
  40. package/src/registry/operations/literals.ts +341 -0
  41. package/src/registry/operations/logical.ts +402 -0
  42. package/src/registry/operations/math.ts +128 -0
  43. package/src/registry/operations/membership.ts +132 -0
  44. package/src/registry/operations/string.ts +507 -0
  45. package/src/registry/operations/subsetting.ts +174 -0
  46. package/src/registry/operations/type-checking.ts +162 -0
  47. package/src/registry/operations/type-conversion.ts +404 -0
  48. package/src/registry/operations/type-operators.ts +307 -0
  49. package/src/registry/operations/utility.ts +510 -0
  50. package/src/registry/registry.ts +146 -0
  51. package/src/registry/types.ts +162 -0
  52. package/src/registry/utils/evaluation-helpers.ts +93 -0
  53. package/src/registry/utils/index.ts +3 -0
  54. package/src/registry/utils/type-system.ts +173 -0
@@ -0,0 +1,510 @@
1
+ import type { Function } from '../types';
2
+ import { defaultFunctionAnalyze } from '../default-analyzers';
3
+ import { defaultFunctionCompile } from '../default-compilers';
4
+ import { ContextManager } from '../../interpreter/context';
5
+ import { isTruthy } from '../utils';
6
+
7
+ export const aggregateFunction: Function = {
8
+ name: 'aggregate',
9
+ kind: 'function',
10
+
11
+ syntax: {
12
+ notation: 'aggregate(aggregator, init)'
13
+ },
14
+
15
+ signature: {
16
+ input: {
17
+ types: { kind: 'any' },
18
+ cardinality: 'collection'
19
+ },
20
+ parameters: [
21
+ {
22
+ name: 'aggregator',
23
+ kind: 'expression',
24
+ types: { kind: 'any' },
25
+ cardinality: 'collection',
26
+ optional: false
27
+ },
28
+ {
29
+ name: 'init',
30
+ kind: 'value',
31
+ types: { kind: 'any' },
32
+ cardinality: 'collection',
33
+ optional: true
34
+ }
35
+ ],
36
+ output: {
37
+ type: 'any',
38
+ cardinality: 'collection'
39
+ },
40
+ propagatesEmpty: false,
41
+ deterministic: true
42
+ },
43
+
44
+ analyze: defaultFunctionAnalyze,
45
+
46
+ evaluate: (interpreter, context, input, aggregatorExpr, init) => {
47
+ if (!aggregatorExpr) {
48
+ throw new Error('aggregate() requires an aggregator expression');
49
+ }
50
+
51
+ let total = init !== undefined ? init : [];
52
+
53
+ for (let i = 0; i < input.length; i++) {
54
+ const item = input[i];
55
+ const iterContext = {
56
+ ...ContextManager.setIteratorContext(context, item, i),
57
+ env: {
58
+ ...context.env,
59
+ $this: [item],
60
+ $index: i,
61
+ $total: total
62
+ }
63
+ };
64
+
65
+ const result = interpreter.evaluate(aggregatorExpr, [item], iterContext);
66
+ total = result.value;
67
+ }
68
+
69
+ return { value: total, context };
70
+ },
71
+
72
+ compile: defaultFunctionCompile
73
+ };
74
+
75
+ export const childrenFunction: Function = {
76
+ name: 'children',
77
+ kind: 'function',
78
+
79
+ syntax: {
80
+ notation: 'children()'
81
+ },
82
+
83
+ signature: {
84
+ input: {
85
+ types: { kind: 'any' },
86
+ cardinality: 'collection'
87
+ },
88
+ parameters: [],
89
+ output: {
90
+ type: 'preserve-input',
91
+ cardinality: 'collection'
92
+ },
93
+ propagatesEmpty: true,
94
+ deterministic: true
95
+ },
96
+
97
+ analyze: defaultFunctionAnalyze,
98
+
99
+ evaluate: (interpreter, context, input) => {
100
+ const results: any[] = [];
101
+
102
+ for (const item of input) {
103
+ if (item && typeof item === 'object') {
104
+ for (const key of Object.keys(item)) {
105
+ if (!key.startsWith('_')) {
106
+ const value = item[key];
107
+ if (Array.isArray(value)) {
108
+ results.push(...value);
109
+ } else if (value !== null && value !== undefined) {
110
+ results.push(value);
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ return { value: results, context };
118
+ },
119
+
120
+ compile: defaultFunctionCompile
121
+ };
122
+
123
+ export const descendantsFunction: Function = {
124
+ name: 'descendants',
125
+ kind: 'function',
126
+
127
+ syntax: {
128
+ notation: 'descendants()'
129
+ },
130
+
131
+ signature: {
132
+ input: {
133
+ types: { kind: 'any' },
134
+ cardinality: 'collection'
135
+ },
136
+ parameters: [],
137
+ output: {
138
+ type: 'preserve-input',
139
+ cardinality: 'collection'
140
+ },
141
+ propagatesEmpty: true,
142
+ deterministic: true
143
+ },
144
+
145
+ analyze: defaultFunctionAnalyze,
146
+
147
+ evaluate: (interpreter, context, input) => {
148
+ const results: any[] = [];
149
+ const seen = new Set();
150
+
151
+ function collectDescendants(items: any[]) {
152
+ for (const item of items) {
153
+ if (item && typeof item === 'object') {
154
+ const key = JSON.stringify(item);
155
+ if (!seen.has(key)) {
156
+ seen.add(key);
157
+ results.push(item);
158
+
159
+ const children: any[] = [];
160
+ for (const prop of Object.keys(item)) {
161
+ if (!prop.startsWith('_')) {
162
+ const value = item[prop];
163
+ if (Array.isArray(value)) {
164
+ children.push(...value);
165
+ } else if (value !== null && value !== undefined && typeof value === 'object') {
166
+ children.push(value);
167
+ }
168
+ }
169
+ }
170
+ collectDescendants(children);
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ const children: any[] = [];
177
+ for (const item of input) {
178
+ if (item && typeof item === 'object') {
179
+ for (const key of Object.keys(item)) {
180
+ if (!key.startsWith('_')) {
181
+ const value = item[key];
182
+ if (Array.isArray(value)) {
183
+ children.push(...value);
184
+ } else if (value !== null && value !== undefined) {
185
+ children.push(value);
186
+ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ collectDescendants(children);
193
+ return { value: results, context };
194
+ },
195
+
196
+ compile: defaultFunctionCompile
197
+ };
198
+
199
+ export const iifFunction: Function = {
200
+ name: 'iif',
201
+ kind: 'function',
202
+
203
+ syntax: {
204
+ notation: 'iif(condition, then, else)'
205
+ },
206
+
207
+ signature: {
208
+ input: {
209
+ types: { kind: 'any' },
210
+ cardinality: 'collection'
211
+ },
212
+ parameters: [
213
+ {
214
+ name: 'condition',
215
+ kind: 'expression',
216
+ types: { kind: 'any' },
217
+ cardinality: 'collection',
218
+ optional: false
219
+ },
220
+ {
221
+ name: 'then',
222
+ kind: 'expression',
223
+ types: { kind: 'any' },
224
+ cardinality: 'collection',
225
+ optional: false
226
+ },
227
+ {
228
+ name: 'else',
229
+ kind: 'expression',
230
+ types: { kind: 'any' },
231
+ cardinality: 'collection',
232
+ optional: false
233
+ }
234
+ ],
235
+ output: {
236
+ type: 'any',
237
+ cardinality: 'collection'
238
+ },
239
+ propagatesEmpty: false,
240
+ deterministic: true
241
+ },
242
+
243
+ analyze: defaultFunctionAnalyze,
244
+
245
+ evaluate: (interpreter, context, input, condition, thenBranch, elseBranch) => {
246
+ const condResult = interpreter.evaluate(condition, input, context);
247
+
248
+ if (isTruthy(condResult.value)) {
249
+ return interpreter.evaluate(thenBranch, input, condResult.context);
250
+ } else {
251
+ return interpreter.evaluate(elseBranch, input, condResult.context);
252
+ }
253
+ },
254
+
255
+ compile: (compiler, input, args) => {
256
+ const [condExpr, thenExpr, elseExpr] = args;
257
+
258
+ if (!condExpr || !thenExpr || !elseExpr) {
259
+ throw new Error('iif() requires condition, then, and else expressions');
260
+ }
261
+
262
+ return {
263
+ fn: (ctx) => {
264
+ const inputVal = input.fn(ctx);
265
+ const condCtx = { ...ctx, input: inputVal, focus: inputVal };
266
+ const condResult = condExpr.fn(condCtx);
267
+
268
+ if (isTruthy(condResult)) {
269
+ const thenCtx = { ...ctx, input: inputVal, focus: inputVal };
270
+ return thenExpr.fn(thenCtx);
271
+ } else {
272
+ const elseCtx = { ...ctx, input: inputVal, focus: inputVal };
273
+ return elseExpr.fn(elseCtx);
274
+ }
275
+ },
276
+ type: compiler.resolveType('Any'),
277
+ isSingleton: false,
278
+ source: `${input.source || ''}.iif(${condExpr.source || ''}, ${thenExpr.source || ''}, ${elseExpr.source || ''})`
279
+ };
280
+ }
281
+ };
282
+
283
+ export const defineVariableFunction: Function = {
284
+ name: 'defineVariable',
285
+ kind: 'function',
286
+
287
+ syntax: {
288
+ notation: 'defineVariable(name, value)'
289
+ },
290
+
291
+ signature: {
292
+ input: {
293
+ types: { kind: 'any' },
294
+ cardinality: 'collection'
295
+ },
296
+ parameters: [
297
+ {
298
+ name: 'name',
299
+ kind: 'value',
300
+ types: { kind: 'primitive', types: ['String'] },
301
+ cardinality: 'singleton',
302
+ optional: false
303
+ },
304
+ {
305
+ name: 'value',
306
+ kind: 'expression',
307
+ types: { kind: 'any' },
308
+ cardinality: 'collection',
309
+ optional: false
310
+ }
311
+ ],
312
+ output: {
313
+ type: 'preserve-input',
314
+ cardinality: 'collection'
315
+ },
316
+ propagatesEmpty: false,
317
+ deterministic: true
318
+ },
319
+
320
+ analyze: defaultFunctionAnalyze,
321
+
322
+ evaluate: (interpreter, context, input, nameValue, valueExpr) => {
323
+ // Extract the string value from the name parameter
324
+ let varName: string;
325
+
326
+ // nameValue comes as evaluated value (array) when param.kind !== 'expression'
327
+ if (Array.isArray(nameValue) && nameValue.length === 1 && typeof nameValue[0] === 'string') {
328
+ varName = nameValue[0];
329
+ } else if (typeof nameValue === 'string') {
330
+ varName = nameValue;
331
+ } else {
332
+ throw new Error('defineVariable() requires a string literal as the first parameter');
333
+ }
334
+
335
+ // Create a new context where $this refers to the input
336
+ const valueContext = ContextManager.copy(context);
337
+ valueContext.env = { ...valueContext.env, $this: input };
338
+
339
+ const result = interpreter.evaluate(valueExpr, input, valueContext);
340
+ const newContext = ContextManager.setVariable(result.context, varName, result.value);
341
+ return { value: input, context: newContext };
342
+ },
343
+
344
+ compile: (compiler, input, args) => {
345
+ const [nameExpr, valueExpr] = args;
346
+
347
+ if (!valueExpr) {
348
+ throw new Error('defineVariable() requires name and value parameters');
349
+ }
350
+
351
+ // For defineVariable, the name should be a literal string
352
+ // Try to extract it at compile time
353
+ let varName: string | undefined;
354
+
355
+ // The name parameter should evaluate to a constant string
356
+ try {
357
+ const nameResult = nameExpr?.fn({ input: [], focus: [], env: {} }) || [];
358
+ if (nameResult.length === 1 && typeof nameResult[0] === 'string') {
359
+ varName = nameResult[0];
360
+ }
361
+ } catch (e) {
362
+ // If we can't evaluate it at compile time, it might be invalid
363
+ throw new Error('defineVariable() requires a string literal as the first parameter');
364
+ }
365
+
366
+ if (!varName) {
367
+ throw new Error('defineVariable() requires a string literal as the first parameter');
368
+ }
369
+
370
+ // Return a compiled expression that modifies the context
371
+ return {
372
+ fn: (ctx) => {
373
+ // Evaluate the input expression
374
+ const inputVal = input.fn(ctx);
375
+
376
+ // Create context for evaluating the value expression
377
+ // Set $this to the input value
378
+ const valueCtx = {
379
+ ...ctx,
380
+ input: inputVal,
381
+ focus: inputVal,
382
+ env: { ...ctx.env, $this: inputVal }
383
+ };
384
+
385
+ // Evaluate the value expression
386
+ const value = valueExpr.fn(valueCtx);
387
+
388
+ // IMPORTANT: We need to modify the context object that was passed in
389
+ // so that subsequent operations can see the variable
390
+ // This is done by modifying the env object in place
391
+ ctx.env[varName] = value;
392
+
393
+ // Return the original input (not the value)
394
+ return inputVal;
395
+ },
396
+ type: input.type,
397
+ isSingleton: input.isSingleton,
398
+ source: `${input.source || ''}.defineVariable('${varName}', ${valueExpr.source || ''})`
399
+ };
400
+ }
401
+ };
402
+
403
+ export const traceFunction: Function = {
404
+ name: 'trace',
405
+ kind: 'function',
406
+
407
+ syntax: {
408
+ notation: 'trace(name, selector)'
409
+ },
410
+
411
+ signature: {
412
+ input: {
413
+ types: { kind: 'any' },
414
+ cardinality: 'collection'
415
+ },
416
+ parameters: [
417
+ {
418
+ name: 'name',
419
+ kind: 'value',
420
+ types: { kind: 'primitive', types: ['String'] },
421
+ cardinality: 'singleton',
422
+ optional: true
423
+ },
424
+ {
425
+ name: 'selector',
426
+ kind: 'expression',
427
+ types: { kind: 'any' },
428
+ cardinality: 'collection',
429
+ optional: true
430
+ }
431
+ ],
432
+ output: {
433
+ type: 'preserve-input',
434
+ cardinality: 'collection'
435
+ },
436
+ propagatesEmpty: false,
437
+ deterministic: false
438
+ },
439
+
440
+ analyze: defaultFunctionAnalyze,
441
+
442
+ evaluate: (interpreter, context, input, nameExpr, selectorExpr) => {
443
+ let values = input;
444
+
445
+ if (selectorExpr) {
446
+ const result = interpreter.evaluate(selectorExpr, input, context);
447
+ values = result.value;
448
+ }
449
+
450
+ console.log(`[TRACE] ${nameExpr || 'trace'}:`, values);
451
+
452
+ return { value: input, context };
453
+ },
454
+
455
+ compile: defaultFunctionCompile
456
+ };
457
+
458
+ export const checkFunction: Function = {
459
+ name: 'check',
460
+ kind: 'function',
461
+
462
+ syntax: {
463
+ notation: 'check(error, condition)'
464
+ },
465
+
466
+ signature: {
467
+ input: {
468
+ types: { kind: 'any' },
469
+ cardinality: 'collection'
470
+ },
471
+ parameters: [
472
+ {
473
+ name: 'error',
474
+ kind: 'expression',
475
+ types: { kind: 'any' },
476
+ cardinality: 'collection',
477
+ optional: false
478
+ },
479
+ {
480
+ name: 'condition',
481
+ kind: 'expression',
482
+ types: { kind: 'any' },
483
+ cardinality: 'collection',
484
+ optional: false
485
+ }
486
+ ],
487
+ output: {
488
+ type: 'preserve-input',
489
+ cardinality: 'collection'
490
+ },
491
+ propagatesEmpty: false,
492
+ deterministic: true
493
+ },
494
+
495
+ analyze: defaultFunctionAnalyze,
496
+
497
+ evaluate: (interpreter, context, input, errorExpr, conditionExpr) => {
498
+ const condResult = interpreter.evaluate(conditionExpr, input, context);
499
+
500
+ if (!isTruthy(condResult.value)) {
501
+ const errorResult = interpreter.evaluate(errorExpr, input, condResult.context);
502
+ const errorMessage = errorResult.value.join('');
503
+ throw new Error(`Check failed: ${errorMessage}`);
504
+ }
505
+
506
+ return { value: input, context: condResult.context };
507
+ },
508
+
509
+ compile: defaultFunctionCompile
510
+ };
@@ -0,0 +1,146 @@
1
+ import { TokenType } from '../lexer/token';
2
+ import type { Operation, Operator, Function, Literal } from './types';
3
+
4
+ export class Registry {
5
+ private static operations = new Map<string, Operation>();
6
+ private static tokenToOperation = new Map<TokenType, Operation>();
7
+ private static prefixOperators = new Map<TokenType, Operator>();
8
+ private static infixOperators = new Map<TokenType, Operator>();
9
+ private static postfixOperators = new Map<TokenType, Operator>();
10
+ private static precedenceTable = new Map<TokenType, number>();
11
+ private static literals: Literal[] = [];
12
+ private static keywords = new Set<string>();
13
+
14
+ static register(op: Operation) {
15
+ this.operations.set(op.name, op);
16
+
17
+ // Type-based registration
18
+ switch (op.kind) {
19
+ case 'operator':
20
+ // Register by form to handle operators with same token but different forms
21
+ if (op.syntax.form === 'prefix') {
22
+ this.prefixOperators.set(op.syntax.token, op);
23
+ } else if (op.syntax.form === 'infix') {
24
+ this.infixOperators.set(op.syntax.token, op);
25
+ } else if (op.syntax.form === 'postfix') {
26
+ this.postfixOperators.set(op.syntax.token, op);
27
+ }
28
+
29
+ // For backward compatibility, store in tokenToOperation (prioritize infix)
30
+ if (op.syntax.form === 'infix' || !this.tokenToOperation.has(op.syntax.token)) {
31
+ this.tokenToOperation.set(op.syntax.token, op);
32
+ }
33
+
34
+ // Only set precedence for infix operators in the precedence table
35
+ // Prefix and postfix operators have their own precedence but don't affect the infix precedence table
36
+ if (op.syntax.form === 'infix') {
37
+ this.precedenceTable.set(op.syntax.token, op.syntax.precedence);
38
+ }
39
+ // Register keyword operators (and, or, not, etc.)
40
+ if (/^[a-z]+$/.test(op.name)) {
41
+ this.keywords.add(op.name);
42
+ }
43
+ break;
44
+
45
+ case 'literal':
46
+ this.literals.push(op);
47
+ // Register keyword literals
48
+ if (op.syntax.keywords) {
49
+ op.syntax.keywords.forEach(kw => this.keywords.add(kw));
50
+ }
51
+ break;
52
+
53
+ case 'function':
54
+ // Functions don't need special registration
55
+ break;
56
+ }
57
+ }
58
+
59
+ static get(name: string): Operation | undefined {
60
+ return this.operations.get(name);
61
+ }
62
+
63
+ static getByToken(token: TokenType, form?: 'prefix' | 'infix' | 'postfix'): Operation | undefined {
64
+ if (form === 'prefix') {
65
+ return this.prefixOperators.get(token);
66
+ } else if (form === 'infix') {
67
+ return this.infixOperators.get(token);
68
+ } else if (form === 'postfix') {
69
+ return this.postfixOperators.get(token);
70
+ }
71
+ // Default fallback
72
+ return this.tokenToOperation.get(token);
73
+ }
74
+
75
+ static getPrecedence(token: TokenType): number {
76
+ return this.precedenceTable.get(token) ?? 0;
77
+ }
78
+
79
+ static isKeyword(word: string): boolean {
80
+ return this.keywords.has(word);
81
+ }
82
+
83
+ static getLiterals(): Literal[] {
84
+ return this.literals;
85
+ }
86
+
87
+ static matchLiteral(text: string): { operation: Literal; value: any } | null {
88
+ for (const literal of this.literals) {
89
+ if (literal.syntax.pattern && literal.syntax.pattern.test(text)) {
90
+ return {
91
+ operation: literal,
92
+ value: literal.parse(text)
93
+ };
94
+ }
95
+ if (literal.syntax.keywords && literal.syntax.keywords.includes(text)) {
96
+ return {
97
+ operation: literal,
98
+ value: literal.parse(text)
99
+ };
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+
105
+ // For special forms
106
+ static getSpecialForms(): Operator[] {
107
+ return Array.from(this.operations.values())
108
+ .filter((op): op is Operator => op.kind === 'operator' && op.syntax.special === true);
109
+ }
110
+
111
+ // Check if token starts a composite operator
112
+ static isCompositeOperatorStart(token: TokenType): boolean {
113
+ // Used by lexer to know when to look ahead
114
+ return [TokenType.LT, TokenType.GT, TokenType.NEQ, TokenType.NEQUIV]
115
+ .includes(token);
116
+ }
117
+
118
+ // Clear registry (useful for testing)
119
+ static clear() {
120
+ this.operations.clear();
121
+ this.tokenToOperation.clear();
122
+ this.prefixOperators.clear();
123
+ this.infixOperators.clear();
124
+ this.postfixOperators.clear();
125
+ this.precedenceTable.clear();
126
+ this.literals = [];
127
+ this.keywords.clear();
128
+ }
129
+
130
+ // Get all registered operations
131
+ static getAllOperations(): Operation[] {
132
+ return Array.from(this.operations.values());
133
+ }
134
+
135
+ // Get operators by form
136
+ static getOperatorsByForm(form: 'prefix' | 'infix' | 'postfix'): Operator[] {
137
+ return Array.from(this.operations.values())
138
+ .filter((op): op is Operator => op.kind === 'operator' && op.syntax.form === form);
139
+ }
140
+
141
+ // Get all functions
142
+ static getAllFunctions(): Function[] {
143
+ return Array.from(this.operations.values())
144
+ .filter((op): op is Function => op.kind === 'function');
145
+ }
146
+ }