@atomic-ehr/fhirpath 0.0.1-canary.69eb286.20250724163205
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.
- package/README.md +307 -0
- package/dist/index.d.ts +225 -0
- package/dist/index.js +8256 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
- package/src/analyzer/analyzer.ts +486 -0
- package/src/analyzer/model-provider.ts +244 -0
- package/src/analyzer/schemas/index.ts +2 -0
- package/src/analyzer/schemas/types.ts +40 -0
- package/src/analyzer/types.ts +142 -0
- package/src/api/builder.ts +148 -0
- package/src/api/errors.ts +134 -0
- package/src/api/expression.ts +149 -0
- package/src/api/index.ts +57 -0
- package/src/api/registry.ts +128 -0
- package/src/api/types.ts +154 -0
- package/src/compiler/compiler.ts +589 -0
- package/src/compiler/index.ts +2 -0
- package/src/compiler/types.ts +23 -0
- package/src/index.ts +52 -0
- package/src/interpreter/README.md +78 -0
- package/src/interpreter/context.ts +181 -0
- package/src/interpreter/interpreter.ts +484 -0
- package/src/interpreter/types.ts +132 -0
- package/src/lexer/char-tables.ts +37 -0
- package/src/lexer/errors.ts +31 -0
- package/src/lexer/index.ts +5 -0
- package/src/lexer/lexer.ts +745 -0
- package/src/lexer/token.ts +104 -0
- package/src/parser/ast.ts +123 -0
- package/src/parser/index.ts +3 -0
- package/src/parser/parser.ts +701 -0
- package/src/parser/pprint.ts +169 -0
- package/src/registry/default-analyzers.ts +257 -0
- package/src/registry/default-compilers.ts +31 -0
- package/src/registry/index.ts +93 -0
- package/src/registry/operations/arithmetic.ts +506 -0
- package/src/registry/operations/collection.ts +422 -0
- package/src/registry/operations/comparison.ts +432 -0
- package/src/registry/operations/existence.ts +719 -0
- package/src/registry/operations/filtering.ts +374 -0
- package/src/registry/operations/literals.ts +341 -0
- package/src/registry/operations/logical.ts +402 -0
- package/src/registry/operations/math.ts +128 -0
- package/src/registry/operations/membership.ts +132 -0
- package/src/registry/operations/string.ts +507 -0
- package/src/registry/operations/subsetting.ts +174 -0
- package/src/registry/operations/type-checking.ts +162 -0
- package/src/registry/operations/type-conversion.ts +404 -0
- package/src/registry/operations/type-operators.ts +307 -0
- package/src/registry/operations/utility.ts +553 -0
- package/src/registry/registry.ts +146 -0
- package/src/registry/types.ts +162 -0
- package/src/registry/utils/evaluation-helpers.ts +93 -0
- package/src/registry/utils/index.ts +3 -0
- package/src/registry/utils/type-system.ts +173 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import type { Function, Operator } from '../types';
|
|
2
|
+
import { defaultFunctionAnalyze, defaultOperatorAnalyze } from '../default-analyzers';
|
|
3
|
+
import { defaultFunctionCompile, defaultOperatorCompile } from '../default-compilers';
|
|
4
|
+
import { TokenType } from '../../lexer/token';
|
|
5
|
+
|
|
6
|
+
export const unionFunction: Function = {
|
|
7
|
+
name: 'union',
|
|
8
|
+
kind: 'function',
|
|
9
|
+
|
|
10
|
+
syntax: {
|
|
11
|
+
notation: 'union(other)'
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
signature: {
|
|
15
|
+
input: {
|
|
16
|
+
types: { kind: 'any' },
|
|
17
|
+
cardinality: 'any'
|
|
18
|
+
},
|
|
19
|
+
parameters: [
|
|
20
|
+
{
|
|
21
|
+
name: 'other',
|
|
22
|
+
kind: 'expression',
|
|
23
|
+
types: { kind: 'any' },
|
|
24
|
+
cardinality: 'any',
|
|
25
|
+
optional: false
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
output: {
|
|
29
|
+
type: 'preserve-input',
|
|
30
|
+
cardinality: 'collection'
|
|
31
|
+
},
|
|
32
|
+
propagatesEmpty: false,
|
|
33
|
+
deterministic: true
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
analyze: defaultFunctionAnalyze,
|
|
37
|
+
|
|
38
|
+
evaluate: (interpreter, context, input, otherExpr) => {
|
|
39
|
+
const otherResult = interpreter.evaluate(otherExpr, input, context);
|
|
40
|
+
const other = otherResult.value;
|
|
41
|
+
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
const result: any[] = [];
|
|
44
|
+
|
|
45
|
+
for (const item of input) {
|
|
46
|
+
const key = JSON.stringify(item);
|
|
47
|
+
if (!seen.has(key)) {
|
|
48
|
+
seen.add(key);
|
|
49
|
+
result.push(item);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const item of other) {
|
|
54
|
+
const key = JSON.stringify(item);
|
|
55
|
+
if (!seen.has(key)) {
|
|
56
|
+
seen.add(key);
|
|
57
|
+
result.push(item);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { value: result, context: otherResult.context };
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
compile: (compiler, input, args) => {
|
|
65
|
+
const otherExpr = args[0];
|
|
66
|
+
if (!otherExpr) {
|
|
67
|
+
throw new Error('union() requires an argument');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
fn: (ctx) => {
|
|
72
|
+
const inputVal = input.fn(ctx);
|
|
73
|
+
const otherVal = otherExpr.fn(ctx);
|
|
74
|
+
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
const result: any[] = [];
|
|
77
|
+
|
|
78
|
+
for (const item of inputVal) {
|
|
79
|
+
const key = JSON.stringify(item);
|
|
80
|
+
if (!seen.has(key)) {
|
|
81
|
+
seen.add(key);
|
|
82
|
+
result.push(item);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const item of otherVal) {
|
|
87
|
+
const key = JSON.stringify(item);
|
|
88
|
+
if (!seen.has(key)) {
|
|
89
|
+
seen.add(key);
|
|
90
|
+
result.push(item);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result;
|
|
95
|
+
},
|
|
96
|
+
type: input.type,
|
|
97
|
+
isSingleton: false,
|
|
98
|
+
source: `${input.source || ''}.union(${otherExpr.source || ''})`
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const combineFunction: Function = {
|
|
104
|
+
name: 'combine',
|
|
105
|
+
kind: 'function',
|
|
106
|
+
|
|
107
|
+
syntax: {
|
|
108
|
+
notation: 'combine(other)'
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
signature: {
|
|
112
|
+
input: {
|
|
113
|
+
types: { kind: 'any' },
|
|
114
|
+
cardinality: 'any'
|
|
115
|
+
},
|
|
116
|
+
parameters: [
|
|
117
|
+
{
|
|
118
|
+
name: 'other',
|
|
119
|
+
kind: 'expression',
|
|
120
|
+
types: { kind: 'any' },
|
|
121
|
+
cardinality: 'any',
|
|
122
|
+
optional: false
|
|
123
|
+
}
|
|
124
|
+
],
|
|
125
|
+
output: {
|
|
126
|
+
type: 'preserve-input',
|
|
127
|
+
cardinality: 'collection'
|
|
128
|
+
},
|
|
129
|
+
propagatesEmpty: false,
|
|
130
|
+
deterministic: true
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
analyze: defaultFunctionAnalyze,
|
|
134
|
+
|
|
135
|
+
evaluate: (interpreter, context, input, otherExpr) => {
|
|
136
|
+
const otherResult = interpreter.evaluate(otherExpr, input, context);
|
|
137
|
+
const other = otherResult.value;
|
|
138
|
+
|
|
139
|
+
return { value: [...input, ...other], context: otherResult.context };
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
compile: (compiler, input, args) => {
|
|
143
|
+
const otherExpr = args[0];
|
|
144
|
+
if (!otherExpr) {
|
|
145
|
+
throw new Error('combine() requires an argument');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
fn: (ctx) => {
|
|
150
|
+
const inputVal = input.fn(ctx);
|
|
151
|
+
const otherVal = otherExpr.fn(ctx);
|
|
152
|
+
return [...inputVal, ...otherVal];
|
|
153
|
+
},
|
|
154
|
+
type: input.type,
|
|
155
|
+
isSingleton: false,
|
|
156
|
+
source: `${input.source || ''}.combine(${otherExpr.source || ''})`
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const intersectFunction: Function = {
|
|
162
|
+
name: 'intersect',
|
|
163
|
+
kind: 'function',
|
|
164
|
+
|
|
165
|
+
syntax: {
|
|
166
|
+
notation: 'intersect(other)'
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
signature: {
|
|
170
|
+
input: {
|
|
171
|
+
types: { kind: 'any' },
|
|
172
|
+
cardinality: 'any'
|
|
173
|
+
},
|
|
174
|
+
parameters: [
|
|
175
|
+
{
|
|
176
|
+
name: 'other',
|
|
177
|
+
kind: 'expression',
|
|
178
|
+
types: { kind: 'any' },
|
|
179
|
+
cardinality: 'any',
|
|
180
|
+
optional: false
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
output: {
|
|
184
|
+
type: 'preserve-input',
|
|
185
|
+
cardinality: 'collection'
|
|
186
|
+
},
|
|
187
|
+
propagatesEmpty: true,
|
|
188
|
+
deterministic: true
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
analyze: defaultFunctionAnalyze,
|
|
192
|
+
|
|
193
|
+
evaluate: (interpreter, context, input, otherExpr) => {
|
|
194
|
+
const otherResult = interpreter.evaluate(otherExpr, input, context);
|
|
195
|
+
const other = otherResult.value;
|
|
196
|
+
|
|
197
|
+
const otherSet = new Set(other.map((item: any) => JSON.stringify(item)));
|
|
198
|
+
const result: any[] = [];
|
|
199
|
+
const seen = new Set();
|
|
200
|
+
|
|
201
|
+
for (const item of input) {
|
|
202
|
+
const key = JSON.stringify(item);
|
|
203
|
+
if (otherSet.has(key) && !seen.has(key)) {
|
|
204
|
+
seen.add(key);
|
|
205
|
+
result.push(item);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { value: result, context: otherResult.context };
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
compile: (compiler, input, args) => {
|
|
213
|
+
const otherExpr = args[0];
|
|
214
|
+
if (!otherExpr) {
|
|
215
|
+
throw new Error('intersect() requires an argument');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
fn: (ctx) => {
|
|
220
|
+
const inputVal = input.fn(ctx);
|
|
221
|
+
const otherVal = otherExpr.fn(ctx);
|
|
222
|
+
|
|
223
|
+
const otherSet = new Set(otherVal.map((item: any) => JSON.stringify(item)));
|
|
224
|
+
const result: any[] = [];
|
|
225
|
+
const seen = new Set();
|
|
226
|
+
|
|
227
|
+
for (const item of inputVal) {
|
|
228
|
+
const key = JSON.stringify(item);
|
|
229
|
+
if (otherSet.has(key) && !seen.has(key)) {
|
|
230
|
+
seen.add(key);
|
|
231
|
+
result.push(item);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return result;
|
|
236
|
+
},
|
|
237
|
+
type: input.type,
|
|
238
|
+
isSingleton: false,
|
|
239
|
+
source: `${input.source || ''}.intersect(${otherExpr.source || ''})`
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const excludeFunction: Function = {
|
|
245
|
+
name: 'exclude',
|
|
246
|
+
kind: 'function',
|
|
247
|
+
|
|
248
|
+
syntax: {
|
|
249
|
+
notation: 'exclude(other)'
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
signature: {
|
|
253
|
+
input: {
|
|
254
|
+
types: { kind: 'any' },
|
|
255
|
+
cardinality: 'any'
|
|
256
|
+
},
|
|
257
|
+
parameters: [
|
|
258
|
+
{
|
|
259
|
+
name: 'other',
|
|
260
|
+
kind: 'expression',
|
|
261
|
+
types: { kind: 'any' },
|
|
262
|
+
cardinality: 'any',
|
|
263
|
+
optional: false
|
|
264
|
+
}
|
|
265
|
+
],
|
|
266
|
+
output: {
|
|
267
|
+
type: 'preserve-input',
|
|
268
|
+
cardinality: 'collection'
|
|
269
|
+
},
|
|
270
|
+
propagatesEmpty: true,
|
|
271
|
+
deterministic: true
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
analyze: defaultFunctionAnalyze,
|
|
275
|
+
|
|
276
|
+
evaluate: (interpreter, context, input, otherExpr) => {
|
|
277
|
+
const otherResult = interpreter.evaluate(otherExpr, input, context);
|
|
278
|
+
const other = otherResult.value;
|
|
279
|
+
|
|
280
|
+
const excludeSet = new Set(other.map((item: any) => JSON.stringify(item)));
|
|
281
|
+
const result: any[] = [];
|
|
282
|
+
const seen = new Set();
|
|
283
|
+
|
|
284
|
+
for (const item of input) {
|
|
285
|
+
const key = JSON.stringify(item);
|
|
286
|
+
if (!excludeSet.has(key) && !seen.has(key)) {
|
|
287
|
+
seen.add(key);
|
|
288
|
+
result.push(item);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return { value: result, context: otherResult.context };
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
compile: (compiler, input, args) => {
|
|
296
|
+
const otherExpr = args[0];
|
|
297
|
+
if (!otherExpr) {
|
|
298
|
+
throw new Error('exclude() requires an argument');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
fn: (ctx) => {
|
|
303
|
+
const inputVal = input.fn(ctx);
|
|
304
|
+
const otherVal = otherExpr.fn(ctx);
|
|
305
|
+
|
|
306
|
+
const excludeSet = new Set(otherVal.map((item: any) => JSON.stringify(item)));
|
|
307
|
+
const result: any[] = [];
|
|
308
|
+
const seen = new Set();
|
|
309
|
+
|
|
310
|
+
for (const item of inputVal) {
|
|
311
|
+
const key = JSON.stringify(item);
|
|
312
|
+
if (!excludeSet.has(key) && !seen.has(key)) {
|
|
313
|
+
seen.add(key);
|
|
314
|
+
result.push(item);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return result;
|
|
319
|
+
},
|
|
320
|
+
type: input.type,
|
|
321
|
+
isSingleton: false,
|
|
322
|
+
source: `${input.source || ''}.exclude(${otherExpr.source || ''})`
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Union operator (|) - combines collections removing duplicates
|
|
328
|
+
export const unionOperator: Operator = {
|
|
329
|
+
name: '|',
|
|
330
|
+
kind: 'operator',
|
|
331
|
+
|
|
332
|
+
syntax: {
|
|
333
|
+
form: 'infix',
|
|
334
|
+
token: TokenType.PIPE,
|
|
335
|
+
precedence: 13, // Lower precedence than most operators
|
|
336
|
+
associativity: 'left',
|
|
337
|
+
notation: 'a | b'
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
signature: {
|
|
341
|
+
parameters: [
|
|
342
|
+
{ name: 'left' },
|
|
343
|
+
{ name: 'right' }
|
|
344
|
+
],
|
|
345
|
+
output: {
|
|
346
|
+
type: 'preserve-left',
|
|
347
|
+
cardinality: 'collection'
|
|
348
|
+
},
|
|
349
|
+
propagatesEmpty: false
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
analyze: defaultOperatorAnalyze,
|
|
353
|
+
|
|
354
|
+
evaluate: (interpreter, context, input, left, right) => {
|
|
355
|
+
// Union removes duplicates
|
|
356
|
+
const seen = new Set();
|
|
357
|
+
const result: any[] = [];
|
|
358
|
+
|
|
359
|
+
for (const item of left) {
|
|
360
|
+
const key = JSON.stringify(item);
|
|
361
|
+
if (!seen.has(key)) {
|
|
362
|
+
seen.add(key);
|
|
363
|
+
result.push(item);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (const item of right) {
|
|
368
|
+
const key = JSON.stringify(item);
|
|
369
|
+
if (!seen.has(key)) {
|
|
370
|
+
seen.add(key);
|
|
371
|
+
result.push(item);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return { value: result, context };
|
|
376
|
+
},
|
|
377
|
+
//TODO: Fix later
|
|
378
|
+
//@ts-expect-error it's okay currently
|
|
379
|
+
compile: (compiler, input, left, right) => {
|
|
380
|
+
return {
|
|
381
|
+
fn: (ctx) => {
|
|
382
|
+
// Evaluate both sides with SEPARATE context copies
|
|
383
|
+
// This prevents variable definitions from leaking between branches
|
|
384
|
+
const leftCtx = { ...ctx, env: { ...ctx.env } };
|
|
385
|
+
const rightCtx = { ...ctx, env: { ...ctx.env } };
|
|
386
|
+
|
|
387
|
+
const leftVal = left.fn(leftCtx);
|
|
388
|
+
const rightVal = right.fn(rightCtx);
|
|
389
|
+
|
|
390
|
+
// Union removes duplicates
|
|
391
|
+
const seen = new Set();
|
|
392
|
+
const result: any[] = [];
|
|
393
|
+
|
|
394
|
+
for (const item of leftVal) {
|
|
395
|
+
const key = JSON.stringify(item);
|
|
396
|
+
if (!seen.has(key)) {
|
|
397
|
+
seen.add(key);
|
|
398
|
+
result.push(item);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
for (const item of rightVal) {
|
|
403
|
+
const key = JSON.stringify(item);
|
|
404
|
+
if (!seen.has(key)) {
|
|
405
|
+
seen.add(key);
|
|
406
|
+
result.push(item);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return result;
|
|
411
|
+
},
|
|
412
|
+
type: left.type,
|
|
413
|
+
isSingleton: false,
|
|
414
|
+
source: `${left.source || ''} | ${right.source || ''}`
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// Export all collection operations
|
|
420
|
+
export const collectionOperators = [
|
|
421
|
+
unionOperator
|
|
422
|
+
];
|