@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,432 @@
1
+ import { TokenType } from '../../lexer/token';
2
+ import type { Operator } from '../types';
3
+ import { defaultOperatorAnalyze } from '../default-analyzers';
4
+ import { defaultOperatorCompile } from '../default-compilers';
5
+
6
+ // Helper function for equality comparison
7
+ function isEqual(left: any, right: any): boolean {
8
+ if (left === right) return true;
9
+ if (left == null || right == null) return false;
10
+
11
+ // Array comparison
12
+ if (Array.isArray(left) && Array.isArray(right)) {
13
+ if (left.length !== right.length) return false;
14
+ return left.every((v, i) => isEqual(v, right[i]));
15
+ }
16
+
17
+ // Object comparison (for complex types)
18
+ if (typeof left === 'object' && typeof right === 'object') {
19
+ const leftKeys = Object.keys(left);
20
+ const rightKeys = Object.keys(right);
21
+ if (leftKeys.length !== rightKeys.length) return false;
22
+ return leftKeys.every(key => isEqual(left[key], right[key]));
23
+ }
24
+
25
+ return false;
26
+ }
27
+
28
+ // Comparison operators
29
+ export const eqOperator: Operator = {
30
+ name: '=',
31
+ kind: 'operator',
32
+ syntax: {
33
+ form: 'infix',
34
+ token: TokenType.EQ,
35
+ precedence: 9,
36
+ associativity: 'left',
37
+ notation: 'a = b'
38
+ },
39
+ signature: {
40
+ parameters: [{ name: 'left' }, { name: 'right' }],
41
+ output: {
42
+ type: 'Boolean',
43
+ cardinality: 'singleton'
44
+ },
45
+ propagatesEmpty: false
46
+ },
47
+ analyze: defaultOperatorAnalyze,
48
+ evaluate: (interpreter, context, input, left, right) => {
49
+ if (left.length === 0 || right.length === 0) return { value: [], context };
50
+
51
+ // FHIRPath equality compares entire collections if both have multiple items
52
+ if (left.length > 1 || right.length > 1) {
53
+ // Collection equality
54
+ return { value: [isEqual(left, right)], context };
55
+ }
56
+
57
+ // Singleton comparison
58
+ return { value: [isEqual(left[0], right[0])], context };
59
+ },
60
+ compile: (compiler, input, args) => {
61
+ const [leftExpr, rightExpr] = args;
62
+ return {
63
+ fn: (ctx) => {
64
+ const left = leftExpr?.fn(ctx) || [];
65
+ const right = rightExpr?.fn(ctx) || [];
66
+ if (left.length === 0 || right.length === 0) return [];
67
+
68
+ // FHIRPath equality compares entire collections if both have multiple items
69
+ if (left.length > 1 || right.length > 1) {
70
+ return [isEqual(left, right)];
71
+ }
72
+
73
+ return [isEqual(left[0], right[0])];
74
+ },
75
+ type: compiler.resolveType('Boolean'),
76
+ isSingleton: true
77
+ };
78
+ }
79
+ };
80
+
81
+ export const neqOperator: Operator = {
82
+ name: '!=',
83
+ kind: 'operator',
84
+ syntax: {
85
+ form: 'infix',
86
+ token: TokenType.NEQ,
87
+ precedence: 9,
88
+ associativity: 'left',
89
+ notation: 'a != b'
90
+ },
91
+ signature: {
92
+ parameters: [{ name: 'left' }, { name: 'right' }],
93
+ output: {
94
+ type: 'Boolean',
95
+ cardinality: 'singleton'
96
+ },
97
+ propagatesEmpty: false
98
+ },
99
+ analyze: defaultOperatorAnalyze,
100
+ evaluate: (interpreter, context, input, left, right) => {
101
+ if (left.length === 0 || right.length === 0) return { value: [], context };
102
+
103
+ // FHIRPath inequality compares entire collections if both have multiple items
104
+ if (left.length > 1 || right.length > 1) {
105
+ return { value: [!isEqual(left, right)], context };
106
+ }
107
+
108
+ return { value: [!isEqual(left[0], right[0])], context };
109
+ },
110
+ compile: (compiler, input, args) => {
111
+ const [leftExpr, rightExpr] = args;
112
+ return {
113
+ fn: (ctx) => {
114
+ const left = leftExpr?.fn(ctx) || [];
115
+ const right = rightExpr?.fn(ctx) || [];
116
+ if (left.length === 0 || right.length === 0) return [];
117
+
118
+ // FHIRPath inequality compares entire collections if both have multiple items
119
+ if (left.length > 1 || right.length > 1) {
120
+ return [!isEqual(left, right)];
121
+ }
122
+
123
+ return [!isEqual(left[0], right[0])];
124
+ },
125
+ type: compiler.resolveType('Boolean'),
126
+ isSingleton: true
127
+ };
128
+ }
129
+ };
130
+
131
+ export const ltOperator: Operator = {
132
+ name: '<',
133
+ kind: 'operator',
134
+ syntax: {
135
+ form: 'infix',
136
+ token: TokenType.LT,
137
+ precedence: 8,
138
+ associativity: 'left',
139
+ notation: 'a < b'
140
+ },
141
+ signature: {
142
+ parameters: [{ name: 'left' }, { name: 'right' }],
143
+ output: {
144
+ type: 'Boolean',
145
+ cardinality: 'singleton'
146
+ },
147
+ propagatesEmpty: false
148
+ },
149
+ analyze: defaultOperatorAnalyze,
150
+ evaluate: (interpreter, context, input, left, right) => {
151
+ if (left.length === 0 || right.length === 0) return { value: [], context };
152
+ const l = left[0];
153
+ const r = right[0];
154
+ if (typeof l === 'number' && typeof r === 'number') return { value: [l < r], context };
155
+ if (typeof l === 'string' && typeof r === 'string') return { value: [l < r], context };
156
+ if (l instanceof Date && r instanceof Date) return { value: [l < r], context };
157
+ return { value: [], context };
158
+ },
159
+ compile: (compiler, input, args) => {
160
+ const [leftExpr, rightExpr] = args;
161
+ return {
162
+ fn: (ctx) => {
163
+ const left = leftExpr?.fn(ctx) || [];
164
+ const right = rightExpr?.fn(ctx) || [];
165
+ if (left.length === 0 || right.length === 0) return [];
166
+ const l = left[0];
167
+ const r = right[0];
168
+ if (typeof l === 'number' && typeof r === 'number') return [l < r];
169
+ if (typeof l === 'string' && typeof r === 'string') return [l < r];
170
+ if (l instanceof Date && r instanceof Date) return [l < r];
171
+ return [];
172
+ },
173
+ type: compiler.resolveType('Boolean'),
174
+ isSingleton: true
175
+ };
176
+ }
177
+ };
178
+
179
+ export const gtOperator: Operator = {
180
+ name: '>',
181
+ kind: 'operator',
182
+ syntax: {
183
+ form: 'infix',
184
+ token: TokenType.GT,
185
+ precedence: 8,
186
+ associativity: 'left',
187
+ notation: 'a > b'
188
+ },
189
+ signature: {
190
+ parameters: [{ name: 'left' }, { name: 'right' }],
191
+ output: {
192
+ type: 'Boolean',
193
+ cardinality: 'singleton'
194
+ },
195
+ propagatesEmpty: false
196
+ },
197
+ analyze: defaultOperatorAnalyze,
198
+ evaluate: (interpreter, context, input, left, right) => {
199
+ if (left.length === 0 || right.length === 0) return { value: [], context };
200
+ const l = left[0];
201
+ const r = right[0];
202
+ if (typeof l === 'number' && typeof r === 'number') return { value: [l > r], context };
203
+ if (typeof l === 'string' && typeof r === 'string') return { value: [l > r], context };
204
+ if (l instanceof Date && r instanceof Date) return { value: [l > r], context };
205
+ return { value: [], context };
206
+ },
207
+ compile: (compiler, input, args) => {
208
+ const [leftExpr, rightExpr] = args;
209
+ return {
210
+ fn: (ctx) => {
211
+ const left = leftExpr?.fn(ctx) || [];
212
+ const right = rightExpr?.fn(ctx) || [];
213
+ if (left.length === 0 || right.length === 0) return [];
214
+ const l = left[0];
215
+ const r = right[0];
216
+ if (typeof l === 'number' && typeof r === 'number') return [l > r];
217
+ if (typeof l === 'string' && typeof r === 'string') return [l > r];
218
+ if (l instanceof Date && r instanceof Date) return [l > r];
219
+ return [];
220
+ },
221
+ type: compiler.resolveType('Boolean'),
222
+ isSingleton: true
223
+ };
224
+ }
225
+ };
226
+
227
+ export const lteOperator: Operator = {
228
+ name: '<=',
229
+ kind: 'operator',
230
+ syntax: {
231
+ form: 'infix',
232
+ token: TokenType.LTE,
233
+ precedence: 8,
234
+ associativity: 'left',
235
+ notation: 'a <= b'
236
+ },
237
+ signature: {
238
+ parameters: [{ name: 'left' }, { name: 'right' }],
239
+ output: {
240
+ type: 'Boolean',
241
+ cardinality: 'singleton'
242
+ },
243
+ propagatesEmpty: false
244
+ },
245
+ analyze: defaultOperatorAnalyze,
246
+ evaluate: (interpreter, context, input, left, right) => {
247
+ if (left.length === 0 || right.length === 0) return { value: [], context };
248
+ const l = left[0];
249
+ const r = right[0];
250
+ if (typeof l === 'number' && typeof r === 'number') return { value: [l <= r], context };
251
+ if (typeof l === 'string' && typeof r === 'string') return { value: [l <= r], context };
252
+ if (l instanceof Date && r instanceof Date) return { value: [l <= r], context };
253
+ return { value: [], context };
254
+ },
255
+ compile: (compiler, input, args) => {
256
+ const [leftExpr, rightExpr] = args;
257
+ return {
258
+ fn: (ctx) => {
259
+ const left = leftExpr?.fn(ctx) || [];
260
+ const right = rightExpr?.fn(ctx) || [];
261
+ if (left.length === 0 || right.length === 0) return [];
262
+ const l = left[0];
263
+ const r = right[0];
264
+ if (typeof l === 'number' && typeof r === 'number') return [l <= r];
265
+ if (typeof l === 'string' && typeof r === 'string') return [l <= r];
266
+ if (l instanceof Date && r instanceof Date) return [l <= r];
267
+ return [];
268
+ },
269
+ type: compiler.resolveType('Boolean'),
270
+ isSingleton: true
271
+ };
272
+ }
273
+ };
274
+
275
+ export const gteOperator: Operator = {
276
+ name: '>=',
277
+ kind: 'operator',
278
+ syntax: {
279
+ form: 'infix',
280
+ token: TokenType.GTE,
281
+ precedence: 8,
282
+ associativity: 'left',
283
+ notation: 'a >= b'
284
+ },
285
+ signature: {
286
+ parameters: [{ name: 'left' }, { name: 'right' }],
287
+ output: {
288
+ type: 'Boolean',
289
+ cardinality: 'singleton'
290
+ },
291
+ propagatesEmpty: false
292
+ },
293
+ analyze: defaultOperatorAnalyze,
294
+ evaluate: (interpreter, context, input, left, right) => {
295
+ if (left.length === 0 || right.length === 0) return { value: [], context };
296
+ const l = left[0];
297
+ const r = right[0];
298
+ if (typeof l === 'number' && typeof r === 'number') return { value: [l >= r], context };
299
+ if (typeof l === 'string' && typeof r === 'string') return { value: [l >= r], context };
300
+ if (l instanceof Date && r instanceof Date) return { value: [l >= r], context };
301
+ return { value: [], context };
302
+ },
303
+ compile: (compiler, input, args) => {
304
+ const [leftExpr, rightExpr] = args;
305
+ return {
306
+ fn: (ctx) => {
307
+ const left = leftExpr?.fn(ctx) || [];
308
+ const right = rightExpr?.fn(ctx) || [];
309
+ if (left.length === 0 || right.length === 0) return [];
310
+ const l = left[0];
311
+ const r = right[0];
312
+ if (typeof l === 'number' && typeof r === 'number') return [l >= r];
313
+ if (typeof l === 'string' && typeof r === 'string') return [l >= r];
314
+ if (l instanceof Date && r instanceof Date) return [l >= r];
315
+ return [];
316
+ },
317
+ type: compiler.resolveType('Boolean'),
318
+ isSingleton: true
319
+ };
320
+ }
321
+ };
322
+
323
+ export const equivOperator: Operator = {
324
+ name: '~',
325
+ kind: 'operator',
326
+ syntax: {
327
+ form: 'infix',
328
+ token: TokenType.EQUIV,
329
+ precedence: 9,
330
+ associativity: 'left',
331
+ notation: 'a ~ b'
332
+ },
333
+ signature: {
334
+ parameters: [{ name: 'left' }, { name: 'right' }],
335
+ output: {
336
+ type: 'Boolean',
337
+ cardinality: 'singleton'
338
+ },
339
+ propagatesEmpty: false
340
+ },
341
+ analyze: defaultOperatorAnalyze,
342
+ evaluate: (interpreter, context, input, left, right) => {
343
+ if (left.length === 0 || right.length === 0) return { value: [], context };
344
+ // Equivalence is more lenient than equality
345
+ const l = left[0];
346
+ const r = right[0];
347
+ if (l == null && r == null) return { value: [true], context };
348
+ if (l == null || r == null) return { value: [false], context };
349
+ // For strings, case-insensitive comparison
350
+ if (typeof l === 'string' && typeof r === 'string') {
351
+ return { value: [l.toLowerCase() === r.toLowerCase()], context };
352
+ }
353
+ return { value: [isEqual(l, r)], context };
354
+ },
355
+ compile: (compiler, input, args) => {
356
+ const [leftExpr, rightExpr] = args;
357
+ return {
358
+ fn: (ctx) => {
359
+ const left = leftExpr?.fn(ctx) || [];
360
+ const right = rightExpr?.fn(ctx) || [];
361
+ if (left.length === 0 || right.length === 0) return [];
362
+ const l = left[0];
363
+ const r = right[0];
364
+ if (l == null && r == null) return [true];
365
+ if (l == null || r == null) return [false];
366
+ if (typeof l === 'string' && typeof r === 'string') {
367
+ return [l.toLowerCase() === r.toLowerCase()];
368
+ }
369
+ return [isEqual(l, r)];
370
+ },
371
+ type: compiler.resolveType('Boolean'),
372
+ isSingleton: true
373
+ };
374
+ }
375
+ };
376
+
377
+ export const nequivOperator: Operator = {
378
+ name: '!~',
379
+ kind: 'operator',
380
+ syntax: {
381
+ form: 'infix',
382
+ token: TokenType.NEQUIV,
383
+ precedence: 9,
384
+ associativity: 'left',
385
+ notation: 'a !~ b'
386
+ },
387
+ signature: {
388
+ parameters: [{ name: 'left' }, { name: 'right' }],
389
+ output: {
390
+ type: 'Boolean',
391
+ cardinality: 'singleton'
392
+ },
393
+ propagatesEmpty: false
394
+ },
395
+ analyze: defaultOperatorAnalyze,
396
+ evaluate: (interpreter, context, input, left, right) => {
397
+ const equivResult = equivOperator.evaluate(interpreter, context, input, left, right);
398
+ return equivResult.value.length > 0 ? { value: [!equivResult.value[0]], context } : { value: [], context };
399
+ },
400
+ compile: (compiler, input, args) => {
401
+ const [leftExpr, rightExpr] = args;
402
+ return {
403
+ fn: (ctx) => {
404
+ const left = leftExpr?.fn(ctx) || [];
405
+ const right = rightExpr?.fn(ctx) || [];
406
+ if (left.length === 0 || right.length === 0) return [];
407
+ const l = left[0];
408
+ const r = right[0];
409
+ if (l == null && r == null) return [false];
410
+ if (l == null || r == null) return [true];
411
+ if (typeof l === 'string' && typeof r === 'string') {
412
+ return [l.toLowerCase() !== r.toLowerCase()];
413
+ }
414
+ return [!isEqual(l, r)];
415
+ },
416
+ type: compiler.resolveType('Boolean'),
417
+ isSingleton: true
418
+ };
419
+ }
420
+ };
421
+
422
+ // Export all comparison operators
423
+ export const comparisonOperators = [
424
+ eqOperator,
425
+ neqOperator,
426
+ ltOperator,
427
+ gtOperator,
428
+ lteOperator,
429
+ gteOperator,
430
+ equivOperator,
431
+ nequivOperator
432
+ ];