@atomic-ehr/fhirpath 0.0.2 → 0.0.3
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 +716 -238
- package/dist/index.d.ts +225 -119
- package/dist/index.js +10911 -5600
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
- package/src/analyzer/augmentor.ts +242 -0
- package/src/analyzer/cursor-services.ts +75 -0
- package/src/analyzer/scope-manager.ts +57 -0
- package/src/analyzer/trivia-indexer.ts +58 -0
- package/src/analyzer/type-compat.ts +157 -0
- package/src/analyzer/utils.ts +132 -0
- package/src/analyzer.ts +921 -1208
- package/src/completion-provider.ts +209 -191
- package/src/{quantity-value.ts → complex-types/quantity-value.ts} +112 -22
- package/src/complex-types/temporal.ts +1737 -0
- package/src/errors.ts +25 -3
- package/src/index.ts +17 -104
- package/src/inspect.ts +4 -4
- package/src/{boxing.ts → interpreter/boxing.ts} +1 -1
- package/src/interpreter/navigator.ts +94 -0
- package/src/interpreter/runtime-context.ts +273 -0
- package/src/interpreter.ts +435 -469
- package/src/lexer.ts +188 -210
- package/src/model-provider.ts +71 -43
- package/src/operations/abs-function.ts +1 -1
- package/src/operations/aggregate-function.ts +84 -5
- package/src/operations/all-function.ts +4 -3
- package/src/operations/allFalse-function.ts +2 -1
- package/src/operations/allTrue-function.ts +2 -1
- package/src/operations/and-operator.ts +2 -1
- package/src/operations/anyFalse-function.ts +2 -1
- package/src/operations/anyTrue-function.ts +2 -1
- package/src/operations/as-function.ts +58 -0
- package/src/operations/as-operator.ts +57 -19
- package/src/operations/ceiling-function.ts +1 -1
- package/src/operations/children-function.ts +14 -5
- package/src/operations/combine-function.ts +6 -3
- package/src/operations/combine-operator.ts +6 -7
- package/src/operations/comparison.ts +692 -0
- package/src/operations/contains-function.ts +1 -1
- package/src/operations/contains-operator.ts +2 -1
- package/src/operations/convertsToBoolean-function.ts +78 -0
- package/src/operations/convertsToDecimal-function.ts +82 -0
- package/src/operations/convertsToInteger-function.ts +71 -0
- package/src/operations/convertsToLong-function.ts +89 -0
- package/src/operations/convertsToQuantity-function.ts +116 -0
- package/src/operations/convertsToString-function.ts +88 -0
- package/src/operations/count-function.ts +2 -1
- package/src/operations/dateOf-function.ts +69 -0
- package/src/operations/dayOf-function.ts +66 -0
- package/src/operations/decimal-boundaries.ts +133 -0
- package/src/operations/defineVariable-function.ts +130 -17
- package/src/operations/distinct-function.ts +1 -1
- package/src/operations/div-operator.ts +1 -1
- package/src/operations/divide-operator.ts +12 -7
- package/src/operations/dot-operator.ts +1 -1
- package/src/operations/empty-function.ts +30 -21
- package/src/operations/endsWith-function.ts +6 -1
- package/src/operations/equal-operator.ts +23 -32
- package/src/operations/equivalent-operator.ts +13 -53
- package/src/operations/exclude-function.ts +2 -1
- package/src/operations/exists-function.ts +4 -3
- package/src/operations/first-function.ts +1 -1
- package/src/operations/floor-function.ts +1 -1
- package/src/operations/greater-operator.ts +20 -3
- package/src/operations/greater-or-equal-operator.ts +20 -3
- package/src/operations/highBoundary-function.ts +120 -0
- package/src/operations/hourOf-function.ts +66 -0
- package/src/operations/iif-function.ts +186 -7
- package/src/operations/implies-operator.ts +1 -1
- package/src/operations/in-operator.ts +2 -1
- package/src/operations/index.ts +41 -0
- package/src/operations/indexOf-function.ts +1 -1
- package/src/operations/intersect-function.ts +1 -1
- package/src/operations/is-function.ts +59 -0
- package/src/operations/is-operator.ts +20 -9
- package/src/operations/isDistinct-function.ts +2 -1
- package/src/operations/join-function.ts +1 -1
- package/src/operations/last-function.ts +1 -1
- package/src/operations/lastIndexOf-function.ts +85 -0
- package/src/operations/length-function.ts +1 -1
- package/src/operations/less-operator.ts +20 -3
- package/src/operations/less-or-equal-operator.ts +20 -3
- package/src/operations/less-than.ts +2 -2
- package/src/operations/lowBoundary-function.ts +120 -0
- package/src/operations/lower-function.ts +1 -1
- package/src/operations/matches-function.ts +86 -0
- package/src/operations/matchesFull-function.ts +96 -0
- package/src/operations/millisecondOf-function.ts +66 -0
- package/src/operations/minus-operator.ts +69 -4
- package/src/operations/minuteOf-function.ts +66 -0
- package/src/operations/mod-operator.ts +1 -1
- package/src/operations/monthOf-function.ts +66 -0
- package/src/operations/multiply-operator.ts +27 -3
- package/src/operations/not-equal-operator.ts +24 -30
- package/src/operations/not-equivalent-operator.ts +13 -53
- package/src/operations/not-function.ts +1 -1
- package/src/operations/ofType-function.ts +8 -12
- package/src/operations/or-operator.ts +2 -1
- package/src/operations/plus-operator.ts +71 -7
- package/src/operations/power-function.ts +35 -10
- package/src/operations/repeat-function.ts +169 -0
- package/src/operations/replace-function.ts +1 -1
- package/src/operations/replaceMatches-function.ts +120 -0
- package/src/operations/round-function.ts +1 -1
- package/src/operations/secondOf-function.ts +66 -0
- package/src/operations/select-function.ts +66 -5
- package/src/operations/single-function.ts +1 -1
- package/src/operations/skip-function.ts +1 -1
- package/src/operations/split-function.ts +1 -1
- package/src/operations/sqrt-function.ts +15 -8
- package/src/operations/startsWith-function.ts +1 -1
- package/src/operations/subsetOf-function.ts +6 -2
- package/src/operations/substring-function.ts +1 -1
- package/src/operations/supersetOf-function.ts +6 -2
- package/src/operations/tail-function.ts +1 -1
- package/src/operations/take-function.ts +1 -1
- package/src/operations/temporal-functions.ts +555 -0
- package/src/operations/timeOf-function.ts +67 -0
- package/src/operations/timezoneOffsetOf-function.ts +69 -0
- package/src/operations/toBoolean-function.ts +27 -8
- package/src/operations/toChars-function.ts +56 -0
- package/src/operations/toDecimal-function.ts +27 -8
- package/src/operations/toInteger-function.ts +15 -3
- package/src/operations/toLong-function.ts +98 -0
- package/src/operations/toQuantity-function.ts +181 -0
- package/src/operations/toString-function.ts +45 -3
- package/src/operations/trace-function.ts +1 -1
- package/src/operations/trim-function.ts +1 -1
- package/src/operations/truncate-function.ts +1 -1
- package/src/operations/unary-minus-operator.ts +2 -2
- package/src/operations/unary-plus-operator.ts +1 -1
- package/src/operations/union-function.ts +1 -1
- package/src/operations/union-operator.ts +16 -26
- package/src/operations/upper-function.ts +1 -1
- package/src/operations/where-function.ts +3 -3
- package/src/operations/xor-operator.ts +1 -1
- package/src/operations/yearOf-function.ts +66 -0
- package/src/{cursor-nodes.ts → parser/cursor-nodes.ts} +10 -7
- package/src/parser.ts +248 -501
- package/src/registry.ts +53 -42
- package/src/types.ts +128 -16
- package/src/utils/pprint.ts +151 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { OperatorDefinition } from '../types';
|
|
2
2
|
import { PRECEDENCE } from '../types';
|
|
3
3
|
import type { OperationEvaluator } from '../types';
|
|
4
|
-
import { box, unbox } from '../boxing';
|
|
4
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
5
5
|
|
|
6
6
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
7
7
|
// Three-valued logic implementation
|
|
@@ -30,6 +30,7 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
30
30
|
export const orOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
31
31
|
symbol: 'or',
|
|
32
32
|
name: 'or',
|
|
33
|
+
doesNotPropagateEmpty: true, // Three-valued logic: true or empty = true
|
|
33
34
|
category: ['logical'],
|
|
34
35
|
precedence: PRECEDENCE.OR,
|
|
35
36
|
associativity: 'left',
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { OperatorDefinition } from '../types';
|
|
2
2
|
import { PRECEDENCE } from '../types';
|
|
3
3
|
import type { OperationEvaluator } from '../types';
|
|
4
|
-
import { addQuantities } from '../quantity-value';
|
|
5
|
-
import type { QuantityValue } from '../quantity-value';
|
|
6
|
-
import { box, unbox } from '../boxing';
|
|
4
|
+
import { addQuantities } from '../complex-types/quantity-value';
|
|
5
|
+
import type { QuantityValue } from '../complex-types/quantity-value';
|
|
6
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
7
7
|
|
|
8
8
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
9
9
|
if (left.length === 0 || right.length === 0) {
|
|
@@ -20,6 +20,69 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
20
20
|
const l = unbox(boxedL);
|
|
21
21
|
const r = unbox(boxedR);
|
|
22
22
|
|
|
23
|
+
// Check for temporal arithmetic using boxed type information
|
|
24
|
+
const leftType = boxedL?.typeInfo?.type;
|
|
25
|
+
const rightType = boxedR?.typeInfo?.type;
|
|
26
|
+
|
|
27
|
+
if ((leftType === 'Date' || leftType === 'DateTime' || leftType === 'Time') && rightType === 'Quantity') {
|
|
28
|
+
// Left is temporal, right is quantity
|
|
29
|
+
const temporalType = leftType;
|
|
30
|
+
const temporal = l;
|
|
31
|
+
const quantity = r as QuantityValue;
|
|
32
|
+
|
|
33
|
+
// Import temporal utilities and create TimeQuantity
|
|
34
|
+
const { createTimeQuantity, add } = await import('../complex-types/temporal');
|
|
35
|
+
|
|
36
|
+
// Calendar duration units (allowed for temporal arithmetic)
|
|
37
|
+
const calendarUnits = ['year', 'years', 'month', 'months', 'week', 'weeks',
|
|
38
|
+
'day', 'days', 'hour', 'hours', 'minute', 'minutes',
|
|
39
|
+
'second', 'seconds', 'millisecond', 'milliseconds'];
|
|
40
|
+
|
|
41
|
+
// Variable duration UCUM units (not allowed for temporal arithmetic - they have calendar-dependent durations)
|
|
42
|
+
const variableDurationUnits = ['a', 'mo'];
|
|
43
|
+
|
|
44
|
+
// Fixed duration UCUM units (allowed - they map directly to calendar units)
|
|
45
|
+
const fixedDurationUnitMap: Record<string, string> = {
|
|
46
|
+
'd': 'day',
|
|
47
|
+
'wk': 'week',
|
|
48
|
+
'h': 'hour',
|
|
49
|
+
'min': 'minute',
|
|
50
|
+
's': 'second',
|
|
51
|
+
'ms': 'millisecond'
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Check if this is a variable duration unit (not allowed)
|
|
55
|
+
if (variableDurationUnits.includes(quantity.unit)) {
|
|
56
|
+
// Variable duration units like 'a' and 'mo' cannot be added to temporal values
|
|
57
|
+
// because they don't have fixed durations
|
|
58
|
+
const { Errors } = await import('../errors');
|
|
59
|
+
throw Errors.invalidTemporalUnit(temporalType, quantity.unit);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Map fixed duration UCUM units to calendar units
|
|
63
|
+
let mappedUnit = fixedDurationUnitMap[quantity.unit] || quantity.unit;
|
|
64
|
+
|
|
65
|
+
// Check if this is a valid calendar duration unit (after mapping)
|
|
66
|
+
if (!calendarUnits.includes(mappedUnit)) {
|
|
67
|
+
// Non-time units with temporal values return empty per FHIRPath spec
|
|
68
|
+
return { value: [], context };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const timeQuantity = createTimeQuantity(quantity.value, mappedUnit as any);
|
|
72
|
+
|
|
73
|
+
// Use the functional add operation
|
|
74
|
+
const result = add(temporal as any, timeQuantity);
|
|
75
|
+
|
|
76
|
+
if (temporalType === 'Date') {
|
|
77
|
+
return { value: [box(result, { type: 'Date', singleton: true })], context };
|
|
78
|
+
} else if (temporalType === 'DateTime') {
|
|
79
|
+
return { value: [box(result, { type: 'DateTime', singleton: true })], context };
|
|
80
|
+
} else if (temporalType === 'Time') {
|
|
81
|
+
// Let the error propagate - adding calendar units to Time should throw
|
|
82
|
+
return { value: [box(result, { type: 'Time', singleton: true })], context };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
23
86
|
// Check if both are quantities
|
|
24
87
|
if (l && typeof l === 'object' && 'unit' in l &&
|
|
25
88
|
r && typeof r === 'object' && 'unit' in r) {
|
|
@@ -27,8 +90,9 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
27
90
|
return { value: result ? [box(result, { type: 'Quantity', singleton: true })] : [], context };
|
|
28
91
|
}
|
|
29
92
|
|
|
30
|
-
|
|
31
|
-
|
|
93
|
+
// String concatenation only works for string + string
|
|
94
|
+
if (typeof l === 'string' && typeof r === 'string') {
|
|
95
|
+
return { value: [box(l + r, { type: 'String', singleton: true })], context };
|
|
32
96
|
}
|
|
33
97
|
|
|
34
98
|
if (typeof l === 'number' && typeof r === 'number') {
|
|
@@ -39,8 +103,8 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
39
103
|
return { value: [box(result, typeInfo)], context };
|
|
40
104
|
}
|
|
41
105
|
|
|
42
|
-
// For
|
|
43
|
-
return { value: [
|
|
106
|
+
// For incompatible types, return empty per FHIRPath spec
|
|
107
|
+
return { value: [], context };
|
|
44
108
|
};
|
|
45
109
|
|
|
46
110
|
export const plusOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
// power() takes exactly one argument (exponent)
|
|
@@ -75,14 +75,39 @@ export const powerFunction: FunctionDefinition & { evaluate: FunctionEvaluator }
|
|
|
75
75
|
'2.5.power(2)',
|
|
76
76
|
'(-1).power(0.5)'
|
|
77
77
|
],
|
|
78
|
-
signatures: [
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
signatures: [
|
|
79
|
+
{
|
|
80
|
+
name: 'power-integer',
|
|
81
|
+
input: { type: 'Integer', singleton: true },
|
|
82
|
+
parameters: [
|
|
83
|
+
{ name: 'exponent', type: { type: 'Integer', singleton: true }, optional: false }
|
|
84
|
+
],
|
|
85
|
+
result: { type: 'Integer', singleton: true }
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'power-decimal',
|
|
89
|
+
input: { type: 'Decimal', singleton: true },
|
|
90
|
+
parameters: [
|
|
91
|
+
{ name: 'exponent', type: { type: 'Decimal', singleton: true }, optional: false }
|
|
92
|
+
],
|
|
93
|
+
result: { type: 'Decimal', singleton: true }
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'power-integer-decimal',
|
|
97
|
+
input: { type: 'Integer', singleton: true },
|
|
98
|
+
parameters: [
|
|
99
|
+
{ name: 'exponent', type: { type: 'Decimal', singleton: true }, optional: false }
|
|
100
|
+
],
|
|
101
|
+
result: { type: 'Decimal', singleton: true }
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'power-decimal-integer',
|
|
105
|
+
input: { type: 'Decimal', singleton: true },
|
|
106
|
+
parameters: [
|
|
107
|
+
{ name: 'exponent', type: { type: 'Integer', singleton: true }, optional: false }
|
|
108
|
+
],
|
|
109
|
+
result: { type: 'Decimal', singleton: true }
|
|
110
|
+
}
|
|
111
|
+
],
|
|
87
112
|
evaluate
|
|
88
113
|
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { FunctionDefinition, AnalysisContext, InternalAnalysisResult } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
3
|
+
import { RuntimeContextManager } from '../interpreter/runtime-context';
|
|
4
|
+
import { type FunctionEvaluator } from '../types';
|
|
5
|
+
import { unbox, box } from '../interpreter/boxing';
|
|
6
|
+
import { collectionsEqual } from './comparison';
|
|
7
|
+
|
|
8
|
+
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
9
|
+
// Repeat requires exactly one argument
|
|
10
|
+
if (args.length !== 1) {
|
|
11
|
+
throw Errors.wrongArgumentCount('repeat', 1, args.length);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const expression = args[0];
|
|
15
|
+
if (!expression) {
|
|
16
|
+
throw Errors.invalidOperation('repeat requires a projection expression');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Result collection that will accumulate all unique items
|
|
20
|
+
const result: any[] = [];
|
|
21
|
+
|
|
22
|
+
// Track which items we've already seen to detect duplicates
|
|
23
|
+
const seen = new Set<any>();
|
|
24
|
+
|
|
25
|
+
// Helper to check if an item is already in the result
|
|
26
|
+
const isInResult = (item: any): boolean => {
|
|
27
|
+
for (const existing of result) {
|
|
28
|
+
// Use equals comparison to determine if items are the same
|
|
29
|
+
const equalResult = collectionsEqual([existing], [item]);
|
|
30
|
+
if (equalResult === true) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Initial evaluation on input collection
|
|
38
|
+
const initialResults: any[] = [];
|
|
39
|
+
for (let i = 0; i < input.length; i++) {
|
|
40
|
+
const boxedItem = input[i];
|
|
41
|
+
if (!boxedItem) continue;
|
|
42
|
+
|
|
43
|
+
const item = unbox(boxedItem);
|
|
44
|
+
|
|
45
|
+
// Create iterator context with $this and $index
|
|
46
|
+
let tempContext = RuntimeContextManager.withIterator(context, item, i);
|
|
47
|
+
tempContext = RuntimeContextManager.setVariable(tempContext, '$total', input.length);
|
|
48
|
+
|
|
49
|
+
// Evaluate expression with temporary context
|
|
50
|
+
const exprResult = await evaluator(expression, [boxedItem], tempContext);
|
|
51
|
+
|
|
52
|
+
// Add results from initial evaluation
|
|
53
|
+
for (const newItem of exprResult.value) {
|
|
54
|
+
if (newItem && !isInResult(newItem)) {
|
|
55
|
+
result.push(newItem);
|
|
56
|
+
initialResults.push(newItem);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Now process the queue with items from the initial results
|
|
62
|
+
let queue = [...initialResults];
|
|
63
|
+
|
|
64
|
+
// Process items until queue is empty
|
|
65
|
+
while (queue.length > 0) {
|
|
66
|
+
const nextQueue: any[] = [];
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < queue.length; i++) {
|
|
69
|
+
const boxedItem = queue[i];
|
|
70
|
+
if (!boxedItem) continue;
|
|
71
|
+
|
|
72
|
+
const item = unbox(boxedItem);
|
|
73
|
+
|
|
74
|
+
// Create iterator context with $this and $index
|
|
75
|
+
let tempContext = RuntimeContextManager.withIterator(context, item, i);
|
|
76
|
+
tempContext = RuntimeContextManager.setVariable(tempContext, '$total', queue.length);
|
|
77
|
+
|
|
78
|
+
// Evaluate expression with temporary context
|
|
79
|
+
const exprResult = await evaluator(expression, [boxedItem], tempContext);
|
|
80
|
+
|
|
81
|
+
// Add new items to the result and next queue
|
|
82
|
+
for (const newItem of exprResult.value) {
|
|
83
|
+
if (newItem && !isInResult(newItem)) {
|
|
84
|
+
result.push(newItem);
|
|
85
|
+
nextQueue.push(newItem);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Move to next iteration
|
|
91
|
+
queue = nextQueue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { value: result, context };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const repeatFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
98
|
+
name: 'repeat',
|
|
99
|
+
category: ['collection'],
|
|
100
|
+
description: 'A version of select that will repeat the projection and add items to the output collection only if they are not already in the output collection as determined by the equals (=) operator. Can be used to traverse a tree by repeatedly selecting specific children.',
|
|
101
|
+
examples: [
|
|
102
|
+
'ValueSet.expansion.repeat(contains)',
|
|
103
|
+
'Questionnaire.repeat(item)',
|
|
104
|
+
'Bundle.entry.repeat(resource.link)'
|
|
105
|
+
],
|
|
106
|
+
signatures: [{
|
|
107
|
+
name: 'repeat',
|
|
108
|
+
input: { type: 'Any', singleton: false },
|
|
109
|
+
parameters: [
|
|
110
|
+
{ name: 'projection', type: { type: 'Any', singleton: false }, expression: true },
|
|
111
|
+
],
|
|
112
|
+
result: 'parameterType' as any,
|
|
113
|
+
}],
|
|
114
|
+
evaluate,
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Analysis-time behavior for repeat.
|
|
118
|
+
* Similar to select, but with repeated application.
|
|
119
|
+
*/
|
|
120
|
+
async analyze(context: AnalysisContext, args): Promise<InternalAnalysisResult> {
|
|
121
|
+
const diagnostics: any[] = [];
|
|
122
|
+
|
|
123
|
+
if (args.length !== 1) {
|
|
124
|
+
return {
|
|
125
|
+
type: { type: 'Any', singleton: false },
|
|
126
|
+
diagnostics: [{
|
|
127
|
+
message: 'repeat expects exactly 1 argument',
|
|
128
|
+
severity: 'error' as any,
|
|
129
|
+
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }
|
|
130
|
+
}]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Get element type from input
|
|
135
|
+
const elementType = context.inputType.singleton
|
|
136
|
+
? context.inputType
|
|
137
|
+
: { ...context.inputType, singleton: true };
|
|
138
|
+
|
|
139
|
+
// Create context for analyzing the projection
|
|
140
|
+
const projectionContext = context
|
|
141
|
+
.withInputType(elementType)
|
|
142
|
+
.withSystemVariable('$this', elementType)
|
|
143
|
+
.withSystemVariable('$index', { type: 'Integer', singleton: true })
|
|
144
|
+
.withSystemVariable('$total', { type: 'Integer', singleton: true });
|
|
145
|
+
|
|
146
|
+
// Analyze the projection expression
|
|
147
|
+
const projectionArg = args[0];
|
|
148
|
+
if (!projectionArg) {
|
|
149
|
+
return {
|
|
150
|
+
type: { type: 'Any', singleton: false },
|
|
151
|
+
diagnostics,
|
|
152
|
+
context
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const projectionResult = await projectionContext.analyzeNode(projectionArg);
|
|
156
|
+
diagnostics.push(...projectionResult.diagnostics);
|
|
157
|
+
|
|
158
|
+
// Result type is the type returned by the projection (as a collection)
|
|
159
|
+
const resultType = projectionResult.type.singleton
|
|
160
|
+
? { ...projectionResult.type, singleton: false }
|
|
161
|
+
: projectionResult.type;
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
type: resultType,
|
|
165
|
+
diagnostics,
|
|
166
|
+
context
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FunctionDefinition } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
3
|
import type { FunctionEvaluator } from '../types';
|
|
4
|
-
import { box, unbox } from '../boxing';
|
|
4
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
5
5
|
|
|
6
6
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
7
7
|
// Check if we have exactly 2 arguments
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
|
|
5
|
+
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
|
+
// Check single item in input
|
|
7
|
+
if (input.length === 0) {
|
|
8
|
+
return { value: [], context };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (input.length > 1) {
|
|
12
|
+
throw Errors.stringSingletonRequired('replaceMatches', input.length);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const boxedInputValue = input[0];
|
|
16
|
+
if (!boxedInputValue) {
|
|
17
|
+
return { value: [], context };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const inputValue = unbox(boxedInputValue);
|
|
21
|
+
if (typeof inputValue !== 'string') {
|
|
22
|
+
throw Errors.stringOperationOnNonString('replaceMatches');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check arguments
|
|
26
|
+
if (args.length !== 2) {
|
|
27
|
+
throw Errors.wrongArgumentCount('replaceMatches', 2, args.length);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Evaluate regex argument
|
|
31
|
+
const regexArg = args[0];
|
|
32
|
+
if (!regexArg) {
|
|
33
|
+
throw Errors.argumentRequired('replaceMatches', 'regex argument');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const regexResult = await evaluator(regexArg, input, context);
|
|
37
|
+
|
|
38
|
+
if (regexResult.value.length === 0) {
|
|
39
|
+
return { value: [], context };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (regexResult.value.length > 1) {
|
|
43
|
+
throw Errors.singletonRequired('replaceMatches regex', regexResult.value.length);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const boxedRegex = regexResult.value[0];
|
|
47
|
+
if (!boxedRegex) {
|
|
48
|
+
return { value: [], context };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const regexPattern = unbox(boxedRegex);
|
|
52
|
+
if (typeof regexPattern !== 'string') {
|
|
53
|
+
throw Errors.invalidStringOperation('replaceMatches', 'regex argument');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Evaluate substitution argument
|
|
57
|
+
const substitutionArg = args[1];
|
|
58
|
+
if (!substitutionArg) {
|
|
59
|
+
throw Errors.argumentRequired('replaceMatches', 'substitution argument');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const substitutionResult = await evaluator(substitutionArg, input, context);
|
|
63
|
+
|
|
64
|
+
if (substitutionResult.value.length === 0) {
|
|
65
|
+
return { value: [], context };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (substitutionResult.value.length > 1) {
|
|
69
|
+
throw Errors.singletonRequired('replaceMatches substitution', substitutionResult.value.length);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const boxedSubstitution = substitutionResult.value[0];
|
|
73
|
+
if (!boxedSubstitution) {
|
|
74
|
+
return { value: [], context };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const substitution = unbox(boxedSubstitution);
|
|
78
|
+
if (typeof substitution !== 'string') {
|
|
79
|
+
throw Errors.invalidStringOperation('replaceMatches', 'substitution argument');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// Create regex with unicode support, single line mode (dotAll), and global flag for all matches
|
|
84
|
+
// Per spec: case-sensitive, single line mode, allow Unicode
|
|
85
|
+
const regex = new RegExp(regexPattern, 'gus');
|
|
86
|
+
|
|
87
|
+
// JavaScript's replace supports $1, $2 etc for capture groups
|
|
88
|
+
// The spec also mentions named groups with ${name} syntax
|
|
89
|
+
// JavaScript natively supports both $1 and $<name> syntax
|
|
90
|
+
// We need to convert ${name} to $<name> for JavaScript compatibility
|
|
91
|
+
const jsSubstitution = substitution.replace(/\$\{([^}]+)\}/g, '$<$1>');
|
|
92
|
+
|
|
93
|
+
const result = inputValue.replace(regex, jsSubstitution);
|
|
94
|
+
|
|
95
|
+
return { value: [box(result, { type: 'String', singleton: true })], context };
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw new Error(`Invalid regular expression in replaceMatches(): ${(error as Error).message}`);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const replaceMatchesFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
102
|
+
name: 'replaceMatches',
|
|
103
|
+
category: ['string'],
|
|
104
|
+
description: 'Replaces all matches of the regex pattern with the substitution string',
|
|
105
|
+
examples: [
|
|
106
|
+
"'test string'.replaceMatches('test', 'match')",
|
|
107
|
+
"'11/30/1972'.replaceMatches('\\\\b(\\\\d{1,2})/(\\\\d{1,2})/(\\\\d{2,4})\\\\b', '$2-$1-$3')",
|
|
108
|
+
"'test test'.replaceMatches('t', 'T')"
|
|
109
|
+
],
|
|
110
|
+
signatures: [{
|
|
111
|
+
name: 'replaceMatches',
|
|
112
|
+
input: { type: 'String', singleton: true },
|
|
113
|
+
parameters: [
|
|
114
|
+
{ name: 'regex', type: { type: 'String', singleton: true } },
|
|
115
|
+
{ name: 'substitution', type: { type: 'String', singleton: true } }
|
|
116
|
+
],
|
|
117
|
+
result: { type: 'String', singleton: true }
|
|
118
|
+
}],
|
|
119
|
+
evaluate
|
|
120
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
// round() takes 0 or 1 argument (precision)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// secondOf() function - Extracts second component from Time or DateTime
|
|
2
|
+
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
import { isFHIRTime, isFHIRDateTime } from '../complex-types/temporal';
|
|
5
|
+
import { Errors } from '../errors';
|
|
6
|
+
|
|
7
|
+
export const secondOfEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
8
|
+
// secondOf() takes no arguments
|
|
9
|
+
if (args.length !== 0) {
|
|
10
|
+
throw Errors.wrongArgumentCount('secondOf', 0, args.length);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Empty input returns empty
|
|
14
|
+
if (input.length === 0) {
|
|
15
|
+
return { value: [], context };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Multiple items throws error
|
|
19
|
+
if (input.length > 1) {
|
|
20
|
+
throw Errors.singletonRequired('secondOf', input.length);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const boxedValue = input[0];
|
|
24
|
+
if (!boxedValue) {
|
|
25
|
+
return { value: [], context };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const value = unbox(boxedValue);
|
|
29
|
+
|
|
30
|
+
// Check if it's a Time or DateTime
|
|
31
|
+
if (isFHIRTime(value) || isFHIRDateTime(value)) {
|
|
32
|
+
// Check if second component is present
|
|
33
|
+
if (value.second === undefined) {
|
|
34
|
+
return { value: [], context };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return the second as an Integer (0-59)
|
|
38
|
+
return {
|
|
39
|
+
value: [box(value.second, { type: 'Integer', singleton: true })],
|
|
40
|
+
context
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Not a Time or DateTime, return empty
|
|
45
|
+
return { value: [], context };
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const secondOfFunction: FunctionDefinition & { evaluate: typeof secondOfEvaluator } = {
|
|
49
|
+
name: 'secondOf',
|
|
50
|
+
category: ['temporal'],
|
|
51
|
+
description: 'Returns the second component of a Time or DateTime value (0-59)',
|
|
52
|
+
examples: [
|
|
53
|
+
'@T10:30:45.secondOf()',
|
|
54
|
+
'@2014-01-05T10:30:45.secondOf()',
|
|
55
|
+
'Observation.effectiveDateTime.secondOf()'
|
|
56
|
+
],
|
|
57
|
+
signatures: [
|
|
58
|
+
{
|
|
59
|
+
name: 'secondOf',
|
|
60
|
+
input: { type: 'Any', singleton: true },
|
|
61
|
+
parameters: [],
|
|
62
|
+
result: { type: 'Integer', singleton: true }
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
evaluate: secondOfEvaluator
|
|
66
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { FunctionDefinition } from '../types';
|
|
1
|
+
import type { FunctionDefinition, AnalysisContext, InternalAnalysisResult } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { RuntimeContextManager } from '../interpreter';
|
|
3
|
+
import { RuntimeContextManager } from '../interpreter/runtime-context';
|
|
4
4
|
import { type FunctionEvaluator } from '../types';
|
|
5
|
-
import { unbox } from '../boxing';
|
|
5
|
+
import { unbox } from '../interpreter/boxing';
|
|
6
6
|
|
|
7
7
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
8
8
|
// Select requires exactly one argument
|
|
@@ -55,5 +55,66 @@ export const selectFunction: FunctionDefinition & { evaluate: FunctionEvaluator
|
|
|
55
55
|
],
|
|
56
56
|
result: 'parameterType' as any,
|
|
57
57
|
}],
|
|
58
|
-
evaluate
|
|
59
|
-
|
|
58
|
+
evaluate,
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Analysis-time behavior for select.
|
|
62
|
+
* The projection expression needs to be analyzed with system variables in scope.
|
|
63
|
+
*/
|
|
64
|
+
async analyze(context: AnalysisContext, args): Promise<InternalAnalysisResult> {
|
|
65
|
+
const diagnostics: any[] = [];
|
|
66
|
+
|
|
67
|
+
if (args.length !== 1) {
|
|
68
|
+
return {
|
|
69
|
+
type: { type: 'Any', singleton: false },
|
|
70
|
+
diagnostics: [{
|
|
71
|
+
message: 'select expects exactly 1 argument',
|
|
72
|
+
severity: 'error' as any,
|
|
73
|
+
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }
|
|
74
|
+
}]
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// For select, we need to analyze the projection expression with:
|
|
79
|
+
// 1. $this set to each element type
|
|
80
|
+
// 2. $index available as Integer
|
|
81
|
+
// 3. User variables from the outer context preserved
|
|
82
|
+
|
|
83
|
+
// Get element type from input
|
|
84
|
+
const elementType = context.inputType.singleton
|
|
85
|
+
? context.inputType
|
|
86
|
+
: { ...context.inputType, singleton: true };
|
|
87
|
+
|
|
88
|
+
// Create context for analyzing the projection
|
|
89
|
+
// Add system variables but preserve user variables
|
|
90
|
+
// IMPORTANT: Also update input type to the element type for property navigation
|
|
91
|
+
const projectionContext = context
|
|
92
|
+
.withInputType(elementType)
|
|
93
|
+
.withSystemVariable('$this', elementType)
|
|
94
|
+
.withSystemVariable('$index', { type: 'Integer', singleton: true })
|
|
95
|
+
.withSystemVariable('$total', { type: 'Integer', singleton: true });
|
|
96
|
+
|
|
97
|
+
// Analyze the projection expression
|
|
98
|
+
const projectionArg = args[0];
|
|
99
|
+
if (!projectionArg) {
|
|
100
|
+
return {
|
|
101
|
+
type: { type: 'Any', singleton: false },
|
|
102
|
+
diagnostics,
|
|
103
|
+
context
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const projectionResult = await projectionContext.analyzeNode(projectionArg);
|
|
107
|
+
diagnostics.push(...projectionResult.diagnostics);
|
|
108
|
+
|
|
109
|
+
// Result type is the type returned by the projection (as a collection)
|
|
110
|
+
const resultType = projectionResult.type.singleton
|
|
111
|
+
? { ...projectionResult.type, singleton: false }
|
|
112
|
+
: projectionResult.type;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
type: resultType,
|
|
116
|
+
diagnostics,
|
|
117
|
+
context // Return original context - select doesn't modify outer scope
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
// single takes no arguments
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FunctionDefinition } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
3
|
import type { FunctionEvaluator } from '../types';
|
|
4
|
-
import { box, unbox } from '../boxing';
|
|
4
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
5
5
|
|
|
6
6
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
7
7
|
if (args.length !== 1) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
// Handle empty input collection
|