@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,506 @@
1
+ import { TokenType } from '../../lexer/token';
2
+ import type { Operator, TypeRef } from '../types';
3
+ import { defaultOperatorAnalyze } from '../default-analyzers';
4
+ import { toSingleton } from '../utils';
5
+
6
+ // Helper to promote numeric types
7
+ function promoteNumericType(left: TypeRef, right: TypeRef): TypeRef {
8
+ const leftName = typeof left === 'string' ? left : (left as any).name;
9
+ const rightName = typeof right === 'string' ? right : (right as any).name;
10
+
11
+ if (leftName === 'Decimal' || rightName === 'Decimal') return 'Decimal';
12
+ if (leftName === 'Integer' && rightName === 'Integer') return 'Integer';
13
+ return 'Decimal'; // Default to Decimal for safety
14
+ }
15
+
16
+ export const plusOperator: Operator = {
17
+ name: '+',
18
+ kind: 'operator',
19
+
20
+ syntax: {
21
+ form: 'infix',
22
+ token: TokenType.PLUS,
23
+ precedence: 5,
24
+ associativity: 'left',
25
+ notation: 'a + b'
26
+ },
27
+
28
+ signature: {
29
+ parameters: [
30
+ { name: 'left', types: { kind: 'union', types: ['Integer', 'Decimal', 'String', 'Date', 'DateTime', 'Time'] }, cardinality: 'singleton' },
31
+ { name: 'right', types: { kind: 'union', types: ['Integer', 'Decimal', 'String', 'Quantity'] }, cardinality: 'singleton' }
32
+ ],
33
+ output: {
34
+ type: 'promote-numeric',
35
+ cardinality: 'singleton'
36
+ },
37
+ propagatesEmpty: true
38
+ },
39
+
40
+ analyze: function(analyzer, input, args) {
41
+ // First run default validation
42
+ const result = defaultOperatorAnalyze.call(this, analyzer, input, args);
43
+
44
+ // Additional validation: both operands should be same "category" (numeric vs string)
45
+ const leftType = args[0]?.type;
46
+ const rightType = args[1]?.type;
47
+
48
+ const leftTypeName = typeof leftType === 'string' ? leftType : (leftType as any).type || (leftType as any).name || 'unknown';
49
+ const rightTypeName = typeof rightType === 'string' ? rightType : (rightType as any).type || (rightType as any).name || 'unknown';
50
+
51
+ const isLeftString = leftTypeName === 'String' || leftTypeName === 'string';
52
+ const isRightString = rightTypeName === 'String' || rightTypeName === 'string';
53
+ const isLeftNumeric = ['Integer', 'Decimal', 'integer', 'decimal'].includes(leftTypeName);
54
+ const isRightNumeric = ['Integer', 'Decimal', 'integer', 'decimal'].includes(rightTypeName);
55
+
56
+ // If one is string and other is numeric, that's an error
57
+ if ((isLeftString && isRightNumeric) || (isLeftNumeric && isRightString)) {
58
+ analyzer.error(`Operator '+' cannot be applied to types ${leftTypeName} and ${rightTypeName}`);
59
+ }
60
+
61
+ return result;
62
+ },
63
+
64
+ evaluate: (interpreter, context, input, left, right) => {
65
+ if (left.length === 0 || right.length === 0) return { value: [], context };
66
+
67
+ const l = toSingleton(left);
68
+ const r = toSingleton(right);
69
+
70
+ // String concatenation
71
+ if (typeof l === 'string' || typeof r === 'string') {
72
+ return { value: [String(l) + String(r)], context };
73
+ }
74
+
75
+ // Numeric addition
76
+ return { value: [l + r], context };
77
+ },
78
+
79
+ compile: (compiler, input, args) => ({
80
+ fn: (ctx) => {
81
+ const left = args[0]?.fn(ctx) || [];
82
+ const right = args[1]?.fn(ctx) || [];
83
+ if (left.length === 0 || right.length === 0) return [];
84
+
85
+ const l = toSingleton(left);
86
+ const r = toSingleton(right);
87
+
88
+ if (typeof l === 'string' || typeof r === 'string') {
89
+ return [String(l) + String(r)];
90
+ }
91
+
92
+ return [l + r];
93
+ },
94
+ type: promoteNumericType(args[0]?.type, args[1]?.type),
95
+ isSingleton: true,
96
+ source: `${args[0]?.source || ''} + ${args[1]?.source || ''}`
97
+ })
98
+ };
99
+
100
+ export const minusOperator: Operator = {
101
+ name: '-',
102
+ kind: 'operator',
103
+
104
+ syntax: {
105
+ form: 'infix',
106
+ token: TokenType.MINUS,
107
+ precedence: 5,
108
+ associativity: 'left',
109
+ notation: 'a - b'
110
+ },
111
+
112
+ signature: {
113
+ parameters: [
114
+ { name: 'left', types: { kind: 'union', types: ['Integer', 'Decimal', 'Date', 'DateTime', 'Time'] }, cardinality: 'singleton' },
115
+ { name: 'right', types: { kind: 'union', types: ['Integer', 'Decimal', 'Quantity'] }, cardinality: 'singleton' }
116
+ ],
117
+ output: {
118
+ type: 'promote-numeric',
119
+ cardinality: 'singleton'
120
+ },
121
+ propagatesEmpty: true
122
+ },
123
+
124
+ analyze: defaultOperatorAnalyze,
125
+
126
+ evaluate: (interpreter, context, input, left, right) => {
127
+ if (left.length === 0 || right.length === 0) return { value: [], context };
128
+
129
+ const l = toSingleton(left);
130
+ const r = toSingleton(right);
131
+
132
+ return { value: [l - r], context };
133
+ },
134
+
135
+ compile: (compiler, input, args) => ({
136
+ fn: (ctx) => {
137
+ const left = args[0]?.fn(ctx) || [];
138
+ const right = args[1]?.fn(ctx) || [];
139
+ if (left.length === 0 || right.length === 0) return [];
140
+
141
+ return [toSingleton(left) - toSingleton(right)];
142
+ },
143
+ type: promoteNumericType(args[0]?.type, args[1]?.type),
144
+ isSingleton: true,
145
+ source: `${args[0]?.source || ''} - ${args[1]?.source || ''}`
146
+ })
147
+ };
148
+
149
+ export const multiplyOperator: Operator = {
150
+ name: '*',
151
+ kind: 'operator',
152
+
153
+ syntax: {
154
+ form: 'infix',
155
+ token: TokenType.STAR,
156
+ precedence: 6,
157
+ associativity: 'left',
158
+ notation: 'a * b'
159
+ },
160
+
161
+ signature: {
162
+ parameters: [
163
+ { name: 'left', types: { kind: 'union', types: ['Integer', 'Decimal'] }, cardinality: 'singleton' },
164
+ { name: 'right', types: { kind: 'union', types: ['Integer', 'Decimal'] }, cardinality: 'singleton' }
165
+ ],
166
+ output: {
167
+ type: 'promote-numeric',
168
+ cardinality: 'singleton'
169
+ },
170
+ propagatesEmpty: true
171
+ },
172
+
173
+ analyze: defaultOperatorAnalyze,
174
+
175
+ evaluate: (interpreter, context, input, left, right) => {
176
+ if (left.length === 0 || right.length === 0) return { value: [], context };
177
+
178
+ const l = toSingleton(left);
179
+ const r = toSingleton(right);
180
+
181
+ return { value: [l * r], context };
182
+ },
183
+
184
+ compile: (compiler, input, args) => ({
185
+ fn: (ctx) => {
186
+ const left = args[0]?.fn(ctx) || [];
187
+ const right = args[1]?.fn(ctx) || [];
188
+ if (left.length === 0 || right.length === 0) return [];
189
+
190
+ return [toSingleton(left) * toSingleton(right)];
191
+ },
192
+ type: promoteNumericType(args[0]?.type, args[1]?.type),
193
+ isSingleton: true,
194
+ source: `${args[0]?.source || ''} * ${args[1]?.source || ''}`
195
+ })
196
+ };
197
+
198
+ export const divideOperator: Operator = {
199
+ name: '/',
200
+ kind: 'operator',
201
+
202
+ syntax: {
203
+ form: 'infix',
204
+ token: TokenType.SLASH,
205
+ precedence: 6,
206
+ associativity: 'left',
207
+ notation: 'a / b'
208
+ },
209
+
210
+ signature: {
211
+ parameters: [
212
+ { name: 'left', types: { kind: 'union', types: ['Integer', 'Decimal'] }, cardinality: 'singleton' },
213
+ { name: 'right', types: { kind: 'union', types: ['Integer', 'Decimal'] }, cardinality: 'singleton' }
214
+ ],
215
+ output: {
216
+ type: 'Decimal', // Division always returns Decimal
217
+ cardinality: 'singleton'
218
+ },
219
+ propagatesEmpty: true
220
+ },
221
+
222
+ analyze: defaultOperatorAnalyze,
223
+
224
+ evaluate: (interpreter, context, input, left, right) => {
225
+ if (left.length === 0 || right.length === 0) return { value: [], context };
226
+
227
+ const l = toSingleton(left);
228
+ const r = toSingleton(right);
229
+
230
+ if (r === 0) return { value: [], context }; // Division by zero returns empty
231
+
232
+ return { value: [l / r], context };
233
+ },
234
+
235
+ compile: (compiler, input, args) => ({
236
+ fn: (ctx) => {
237
+ const left = args[0]?.fn(ctx) || [];
238
+ const right = args[1]?.fn(ctx) || [];
239
+ if (left.length === 0 || right.length === 0) return [];
240
+
241
+ const r = toSingleton(right);
242
+ if (r === 0) return [];
243
+
244
+ return [toSingleton(left) / r];
245
+ },
246
+ type: compiler.resolveType('Decimal'),
247
+ isSingleton: true,
248
+ source: `${args[0]?.source || ''} / ${args[1]?.source || ''}`
249
+ })
250
+ };
251
+
252
+ export const modOperator: Operator = {
253
+ name: 'mod',
254
+ kind: 'operator',
255
+
256
+ syntax: {
257
+ form: 'infix',
258
+ token: TokenType.MOD,
259
+ precedence: 6,
260
+ associativity: 'left',
261
+ notation: 'a mod b'
262
+ },
263
+
264
+ signature: {
265
+ parameters: [
266
+ { name: 'left', types: { kind: 'union', types: ['Integer', 'Decimal'] }, cardinality: 'singleton' },
267
+ { name: 'right', types: { kind: 'union', types: ['Integer', 'Decimal'] }, cardinality: 'singleton' }
268
+ ],
269
+ output: {
270
+ type: 'promote-numeric',
271
+ cardinality: 'singleton'
272
+ },
273
+ propagatesEmpty: true
274
+ },
275
+
276
+ analyze: defaultOperatorAnalyze,
277
+
278
+ evaluate: (interpreter, context, input, left, right) => {
279
+ if (left.length === 0 || right.length === 0) return { value: [], context };
280
+
281
+ const l = toSingleton(left);
282
+ const r = toSingleton(right);
283
+
284
+ if (r === 0) return { value: [], context }; // Modulo by zero returns empty
285
+
286
+ return { value: [l % r], context };
287
+ },
288
+
289
+ compile: (compiler, input, args) => ({
290
+ fn: (ctx) => {
291
+ const left = args[0]?.fn(ctx) || [];
292
+ const right = args[1]?.fn(ctx) || [];
293
+ if (left.length === 0 || right.length === 0) return [];
294
+
295
+ const r = toSingleton(right);
296
+ if (r === 0) return [];
297
+
298
+ return [toSingleton(left) % r];
299
+ },
300
+ type: promoteNumericType(args[0]?.type, args[1]?.type),
301
+ isSingleton: true,
302
+ source: `${args[0]?.source || ''} mod ${args[1]?.source || ''}`
303
+ })
304
+ };
305
+
306
+ export const divOperator: Operator = {
307
+ name: 'div',
308
+ kind: 'operator',
309
+
310
+ syntax: {
311
+ form: 'infix',
312
+ token: TokenType.DIV,
313
+ precedence: 6,
314
+ associativity: 'left',
315
+ notation: 'a div b'
316
+ },
317
+
318
+ signature: {
319
+ parameters: [
320
+ { name: 'left', types: { kind: 'union', types: ['Integer', 'Decimal'] }, cardinality: 'singleton' },
321
+ { name: 'right', types: { kind: 'union', types: ['Integer', 'Decimal'] }, cardinality: 'singleton' }
322
+ ],
323
+ output: {
324
+ type: 'Integer', // Integer division
325
+ cardinality: 'singleton'
326
+ },
327
+ propagatesEmpty: true
328
+ },
329
+
330
+ analyze: defaultOperatorAnalyze,
331
+
332
+ evaluate: (interpreter, context, input, left, right) => {
333
+ if (left.length === 0 || right.length === 0) return { value: [], context };
334
+
335
+ const l = toSingleton(left);
336
+ const r = toSingleton(right);
337
+
338
+ if (r === 0) return { value: [], context }; // Division by zero returns empty
339
+
340
+ return { value: [Math.floor(l / r)], context };
341
+ },
342
+
343
+ compile: (compiler, input, args) => ({
344
+ fn: (ctx) => {
345
+ const left = args[0]?.fn(ctx) || [];
346
+ const right = args[1]?.fn(ctx) || [];
347
+ if (left.length === 0 || right.length === 0) return [];
348
+
349
+ const r = toSingleton(right);
350
+ if (r === 0) return [];
351
+
352
+ return [Math.floor(toSingleton(left) / r)];
353
+ },
354
+ type: compiler.resolveType('Integer'),
355
+ isSingleton: true,
356
+ source: `${args[0]?.source || ''} div ${args[1]?.source || ''}`
357
+ })
358
+ };
359
+
360
+ export const unaryPlusOperator: Operator = {
361
+ name: 'unary+',
362
+ kind: 'operator',
363
+
364
+ syntax: {
365
+ form: 'prefix',
366
+ token: TokenType.PLUS,
367
+ precedence: 10, // High precedence for unary operators
368
+ notation: '+a'
369
+ },
370
+
371
+ signature: {
372
+ parameters: [
373
+ { name: 'operand', types: { kind: 'union', types: ['Integer', 'Decimal'] }, cardinality: 'singleton' }
374
+ ],
375
+ output: {
376
+ type: 'preserve-input',
377
+ cardinality: 'singleton'
378
+ },
379
+ propagatesEmpty: true
380
+ },
381
+
382
+ analyze: defaultOperatorAnalyze,
383
+
384
+ evaluate: (interpreter, context, input, operand) => {
385
+ if (operand.length === 0) return { value: [], context };
386
+ return { value: operand, context }; // Unary plus is identity
387
+ },
388
+
389
+ compile: (compiler, input, args) => ({
390
+ fn: (ctx) => args[0]?.fn(ctx) || [],
391
+ type: args[0]?.type,
392
+ isSingleton: true,
393
+ source: `+${args[0]?.source || ''}`
394
+ })
395
+ };
396
+
397
+ export const unaryMinusOperator: Operator = {
398
+ name: 'unary-',
399
+ kind: 'operator',
400
+
401
+ syntax: {
402
+ form: 'prefix',
403
+ token: TokenType.MINUS,
404
+ precedence: 10, // High precedence for unary operators
405
+ notation: '-a'
406
+ },
407
+
408
+ signature: {
409
+ parameters: [
410
+ { name: 'operand', types: { kind: 'union', types: ['Integer', 'Decimal'] }, cardinality: 'singleton' }
411
+ ],
412
+ output: {
413
+ type: 'preserve-input',
414
+ cardinality: 'singleton'
415
+ },
416
+ propagatesEmpty: true
417
+ },
418
+
419
+ analyze: defaultOperatorAnalyze,
420
+
421
+ evaluate: (interpreter, context, input, operand) => {
422
+ if (operand.length === 0) return { value: [], context };
423
+
424
+ const val = toSingleton(operand);
425
+ return { value: [-val], context };
426
+ },
427
+
428
+ compile: (compiler, input, args) => ({
429
+ fn: (ctx) => {
430
+ const operand = args[0]?.fn(ctx) || [];
431
+ if (operand.length === 0) return [];
432
+ return [-toSingleton(operand)];
433
+ },
434
+ type: args[0]?.type,
435
+ isSingleton: true,
436
+ source: `-${args[0]?.source || ''}`
437
+ })
438
+ };
439
+
440
+ export const concatOperator: Operator = {
441
+ name: '&',
442
+ kind: 'operator',
443
+
444
+ syntax: {
445
+ form: 'infix',
446
+ token: TokenType.CONCAT,
447
+ precedence: 5,
448
+ associativity: 'left',
449
+ notation: 'a & b'
450
+ },
451
+
452
+ signature: {
453
+ parameters: [
454
+ { name: 'left', types: { kind: 'primitive', types: ['String'] }, cardinality: 'singleton' },
455
+ { name: 'right', types: { kind: 'primitive', types: ['String'] }, cardinality: 'singleton' }
456
+ ],
457
+ output: {
458
+ type: 'String',
459
+ cardinality: 'singleton'
460
+ },
461
+ propagatesEmpty: true
462
+ },
463
+
464
+ analyze: defaultOperatorAnalyze,
465
+
466
+ evaluate: (interpreter, context, input, left, right) => {
467
+ if (left.length === 0 || right.length === 0) return { value: [], context };
468
+
469
+ // String concatenation requires both operands to be singletons
470
+ if (left.length > 1 || right.length > 1) return { value: [], context };
471
+
472
+ const l = left[0];
473
+ const r = right[0];
474
+
475
+ return { value: [String(l) + String(r)], context };
476
+ },
477
+
478
+ compile: (compiler, input, args) => ({
479
+ fn: (ctx) => {
480
+ const left = args[0]?.fn(ctx) || [];
481
+ const right = args[1]?.fn(ctx) || [];
482
+ if (left.length === 0 || right.length === 0) return [];
483
+
484
+ // String concatenation requires both operands to be singletons
485
+ if (left.length > 1 || right.length > 1) return [];
486
+
487
+ return [String(left[0]) + String(right[0])];
488
+ },
489
+ type: compiler.resolveType('String'),
490
+ isSingleton: true,
491
+ source: `${args[0]?.source || ''} & ${args[1]?.source || ''}`
492
+ })
493
+ };
494
+
495
+ // Export all arithmetic operators
496
+ export const arithmeticOperators = [
497
+ plusOperator,
498
+ minusOperator,
499
+ multiplyOperator,
500
+ divideOperator,
501
+ modOperator,
502
+ divOperator,
503
+ concatOperator,
504
+ unaryPlusOperator,
505
+ unaryMinusOperator
506
+ ];