@atomic-ehr/fhirpath 0.0.1 → 0.0.2-canary.2fee5d9.20250808110507
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 +2 -0
- package/dist/index.d.ts +195 -77
- package/dist/index.js +2541 -981
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/analyzer.ts +536 -64
- package/src/boxing.ts +124 -0
- package/src/errors.ts +246 -0
- package/src/index.ts +57 -9
- package/src/inspect.ts +307 -174
- package/src/interpreter.ts +299 -48
- package/src/model-provider.ts +110 -8
- package/src/operations/abs-function.ts +19 -10
- package/src/operations/aggregate-function.ts +11 -3
- package/src/operations/all-function.ts +18 -8
- package/src/operations/allFalse-function.ts +9 -6
- package/src/operations/allTrue-function.ts +9 -6
- package/src/operations/and-operator.ts +20 -11
- package/src/operations/anyFalse-function.ts +6 -4
- package/src/operations/anyTrue-function.ts +6 -4
- package/src/operations/as-operator.ts +1 -0
- package/src/operations/ceiling-function.ts +9 -5
- package/src/operations/children-function.ts +94 -0
- package/src/operations/combine-function.ts +4 -2
- package/src/operations/combine-operator.ts +18 -4
- package/src/operations/contains-function.ts +20 -8
- package/src/operations/contains-operator.ts +14 -6
- package/src/operations/count-function.ts +2 -1
- package/src/operations/defineVariable-function.ts +5 -3
- package/src/operations/descendants-function.ts +62 -0
- package/src/operations/distinct-function.ts +16 -4
- package/src/operations/div-operator.ts +15 -2
- package/src/operations/divide-operator.ts +10 -5
- package/src/operations/dot-operator.ts +3 -1
- package/src/operations/empty-function.ts +2 -1
- package/src/operations/endsWith-function.ts +22 -10
- package/src/operations/equal-operator.ts +18 -6
- package/src/operations/equivalent-operator.ts +15 -10
- package/src/operations/exclude-function.ts +12 -10
- package/src/operations/exists-function.ts +14 -7
- package/src/operations/first-function.ts +7 -4
- package/src/operations/floor-function.ts +9 -5
- package/src/operations/greater-operator.ts +9 -4
- package/src/operations/greater-or-equal-operator.ts +9 -4
- package/src/operations/iif-function.ts +18 -16
- package/src/operations/implies-operator.ts +22 -7
- package/src/operations/in-operator.ts +17 -7
- package/src/operations/index.ts +3 -0
- package/src/operations/indexOf-function.ts +22 -10
- package/src/operations/intersect-function.ts +17 -20
- package/src/operations/is-operator.ts +63 -14
- package/src/operations/isDistinct-function.ts +12 -6
- package/src/operations/join-function.ts +15 -4
- package/src/operations/last-function.ts +7 -4
- package/src/operations/length-function.ts +12 -5
- package/src/operations/less-operator.ts +9 -4
- package/src/operations/less-or-equal-operator.ts +9 -4
- package/src/operations/less-than.ts +25 -1
- package/src/operations/lower-function.ts +12 -5
- package/src/operations/minus-operator.ts +9 -4
- package/src/operations/mod-operator.ts +19 -2
- package/src/operations/multiply-operator.ts +11 -6
- package/src/operations/not-equal-operator.ts +15 -6
- package/src/operations/not-equivalent-operator.ts +15 -10
- package/src/operations/not-function.ts +12 -4
- package/src/operations/ofType-function.ts +135 -0
- package/src/operations/or-operator.ts +20 -11
- package/src/operations/plus-operator.ts +18 -6
- package/src/operations/power-function.ts +21 -9
- package/src/operations/replace-function.ts +26 -9
- package/src/operations/round-function.ts +23 -8
- package/src/operations/select-function.ts +12 -6
- package/src/operations/single-function.ts +5 -3
- package/src/operations/skip-function.ts +12 -5
- package/src/operations/split-function.ts +24 -9
- package/src/operations/sqrt-function.ts +9 -5
- package/src/operations/startsWith-function.ts +20 -8
- package/src/operations/subsetOf-function.ts +14 -11
- package/src/operations/substring-function.ts +36 -19
- package/src/operations/supersetOf-function.ts +14 -11
- package/src/operations/tail-function.ts +3 -1
- package/src/operations/take-function.ts +12 -5
- package/src/operations/toBoolean-function.ts +18 -11
- package/src/operations/toDecimal-function.ts +13 -6
- package/src/operations/toInteger-function.ts +13 -6
- package/src/operations/toString-function.ts +17 -10
- package/src/operations/trace-function.ts +12 -5
- package/src/operations/trim-function.ts +11 -4
- package/src/operations/truncate-function.ts +9 -5
- package/src/operations/unary-minus-operator.ts +22 -12
- package/src/operations/unary-plus-operator.ts +1 -0
- package/src/operations/union-function.ts +19 -24
- package/src/operations/union-operator.ts +1 -0
- package/src/operations/upper-function.ts +12 -5
- package/src/operations/where-function.ts +15 -8
- package/src/operations/xor-operator.ts +8 -3
- package/src/parser.ts +391 -8
- package/src/quantity-value.ts +4 -8
- package/src/registry.ts +3 -3
- package/src/types.ts +10 -6
- package/src/parser-base.ts +0 -400
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
3
|
+
import { box, unbox } from '../boxing';
|
|
2
4
|
|
|
3
5
|
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
4
6
|
// abs() takes no arguments
|
|
5
7
|
if (args.length !== 0) {
|
|
6
|
-
throw
|
|
8
|
+
throw Errors.wrongArgumentCount('abs', 0, args.length);
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
// If input is empty, return empty
|
|
@@ -13,29 +15,36 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
|
|
|
13
15
|
|
|
14
16
|
// If input has multiple items, error
|
|
15
17
|
if (input.length > 1) {
|
|
16
|
-
throw
|
|
18
|
+
throw Errors.singletonRequired('abs', input.length);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const
|
|
21
|
+
const boxedValue = input[0];
|
|
22
|
+
if (!boxedValue) return { value: [], context };
|
|
23
|
+
const value = unbox(boxedValue);
|
|
20
24
|
|
|
21
25
|
// Handle different types
|
|
22
26
|
if (typeof value === 'number') {
|
|
23
|
-
|
|
27
|
+
const result = Math.abs(value);
|
|
28
|
+
const typeInfo = Number.isInteger(result) ?
|
|
29
|
+
{ type: 'Integer' as const, singleton: true } :
|
|
30
|
+
{ type: 'Decimal' as const, singleton: true };
|
|
31
|
+
return { value: [box(result, typeInfo)], context };
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
// Handle Quantity type
|
|
27
35
|
if (value && typeof value === 'object' && 'value' in value && 'unit' in value) {
|
|
36
|
+
const result = {
|
|
37
|
+
value: Math.abs(value.value),
|
|
38
|
+
unit: value.unit,
|
|
39
|
+
...(value._ucumQuantity && { _ucumQuantity: { ...value._ucumQuantity, value: Math.abs(value._ucumQuantity.value) } })
|
|
40
|
+
};
|
|
28
41
|
return {
|
|
29
|
-
value: [{
|
|
30
|
-
value: Math.abs(value.value),
|
|
31
|
-
unit: value.unit,
|
|
32
|
-
...(value._ucumQuantity && { _ucumQuantity: { ...value._ucumQuantity, value: Math.abs(value._ucumQuantity.value) } })
|
|
33
|
-
}],
|
|
42
|
+
value: [box(result, { type: 'Quantity', singleton: true })],
|
|
34
43
|
context
|
|
35
44
|
};
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
throw
|
|
47
|
+
throw Errors.invalidOperandType('abs', `${typeof value}`);
|
|
39
48
|
};
|
|
40
49
|
|
|
41
50
|
export const absFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
2
3
|
import { RuntimeContextManager } from '../interpreter';
|
|
4
|
+
import { box, unbox } from '../boxing';
|
|
3
5
|
|
|
4
6
|
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
5
7
|
// aggregator expression is required
|
|
6
8
|
if (args.length < 1) {
|
|
7
|
-
throw
|
|
9
|
+
throw Errors.invalidOperation('aggregate requires at least one argument (aggregator expression)');
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
const aggregatorExpr = args[0]!;
|
|
@@ -22,8 +24,14 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
|
|
|
22
24
|
// For each item in the input collection, evaluate the aggregator expression
|
|
23
25
|
input.forEach((item, index) => {
|
|
24
26
|
// Create a new context with $this, $index, and $total
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
// Note: $this needs unboxed value, but we pass boxed item to evaluator
|
|
28
|
+
const unboxedItem = unbox(item);
|
|
29
|
+
let aggregatorContext = RuntimeContextManager.withIterator(context, unboxedItem, index);
|
|
30
|
+
|
|
31
|
+
// For the first iteration without init, $total should be empty (not undefined)
|
|
32
|
+
// $total needs to be unboxed values for variable access
|
|
33
|
+
const unboxedTotal = total.map(v => unbox(v));
|
|
34
|
+
aggregatorContext = RuntimeContextManager.setVariable(aggregatorContext, '$total', unboxedTotal);
|
|
27
35
|
|
|
28
36
|
// Evaluate the aggregator expression
|
|
29
37
|
const result = evaluator(aggregatorExpr, [item], aggregatorContext);
|
|
@@ -1,42 +1,52 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
2
3
|
import { RuntimeContextManager } from '../interpreter';
|
|
4
|
+
import { box, unbox } from '../boxing';
|
|
3
5
|
|
|
4
6
|
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
5
7
|
// all() requires exactly one argument (the criteria expression)
|
|
6
8
|
if (!args || args.length !== 1) {
|
|
7
|
-
throw
|
|
9
|
+
throw Errors.invalidOperation('all requires exactly one argument');
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
// If the input collection is empty, the result is true per spec
|
|
11
13
|
if (input.length === 0) {
|
|
12
|
-
return { value: [true], context };
|
|
14
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
const criteriaExpression = args[0];
|
|
16
18
|
if (!criteriaExpression) {
|
|
17
|
-
throw
|
|
19
|
+
throw Errors.invalidOperation('all requires a criteria expression');
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
// Evaluate the criteria for each element in the input collection
|
|
21
23
|
for (let i = 0; i < input.length; i++) {
|
|
22
|
-
const
|
|
24
|
+
const boxedItem = input[i];
|
|
25
|
+
if (!boxedItem) continue;
|
|
26
|
+
|
|
27
|
+
const item = unbox(boxedItem);
|
|
23
28
|
|
|
24
29
|
// Create iterator context with $this and $index
|
|
25
30
|
let tempContext = RuntimeContextManager.withIterator(context, item, i);
|
|
26
31
|
tempContext = RuntimeContextManager.setVariable(tempContext, '$total', input.length);
|
|
27
32
|
|
|
28
33
|
// Evaluate the criteria expression with the current item as context
|
|
29
|
-
const result = evaluator(criteriaExpression, [
|
|
34
|
+
const result = evaluator(criteriaExpression, [boxedItem], tempContext);
|
|
30
35
|
|
|
31
36
|
// Check if the result is truthy
|
|
32
37
|
// If the result is empty or contains a falsy value, return false
|
|
33
|
-
if (result.value.length === 0
|
|
34
|
-
return { value: [false], context };
|
|
38
|
+
if (result.value.length === 0) {
|
|
39
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const resultValue = unbox(result.value[0]!);
|
|
43
|
+
if (resultValue !== true) {
|
|
44
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
35
45
|
}
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
// All criteria evaluations were true
|
|
39
|
-
return { value: [true], context };
|
|
49
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
40
50
|
};
|
|
41
51
|
|
|
42
52
|
export const allFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
3
|
+
import { box, unbox } from '../boxing';
|
|
2
4
|
|
|
3
5
|
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
4
6
|
// If the input is empty, the result is true
|
|
5
7
|
if (input.length === 0) {
|
|
6
|
-
return { value: [true], context };
|
|
8
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
// Verify all inputs are booleans
|
|
11
|
+
// Verify all inputs are booleans (unbox first)
|
|
10
12
|
for (let i = 0; i < input.length; i++) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
const unboxedValue = unbox(input[i]!);
|
|
14
|
+
if (typeof unboxedValue !== 'boolean') {
|
|
15
|
+
throw Errors.booleanOperationOnNonBoolean('allFalse', i, `${typeof unboxedValue}`);
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
// Return true if all items are false, false if any item is true
|
|
17
|
-
const result = input.every(item => item === false);
|
|
20
|
+
const result = input.every(item => unbox(item) === false);
|
|
18
21
|
|
|
19
|
-
return { value: [result], context };
|
|
22
|
+
return { value: [box(result, { type: 'Boolean', singleton: true })], context };
|
|
20
23
|
};
|
|
21
24
|
|
|
22
25
|
export const allFalseFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
3
|
+
import { box, unbox } from '../boxing';
|
|
2
4
|
|
|
3
5
|
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
4
6
|
// If the input is empty, the result is true
|
|
5
7
|
if (input.length === 0) {
|
|
6
|
-
return { value: [true], context };
|
|
8
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
// Verify all inputs are booleans
|
|
11
|
+
// Verify all inputs are booleans (unbox first)
|
|
10
12
|
for (let i = 0; i < input.length; i++) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
const unboxedValue = unbox(input[i]!);
|
|
14
|
+
if (typeof unboxedValue !== 'boolean') {
|
|
15
|
+
throw Errors.booleanOperationOnNonBoolean('allTrue', i, `${typeof unboxedValue}`);
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
// Return true if all items are true, false if any item is false
|
|
17
|
-
const result = input.every(item => item === true);
|
|
20
|
+
const result = input.every(item => unbox(item) === true);
|
|
18
21
|
|
|
19
|
-
return { value: [result], context };
|
|
22
|
+
return { value: [box(result, { type: 'Boolean', singleton: true })], context };
|
|
20
23
|
};
|
|
21
24
|
|
|
22
25
|
export const allTrueFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
@@ -1,21 +1,30 @@
|
|
|
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
5
|
|
|
5
6
|
export const evaluate: OperationEvaluator = (input, context, left, right) => {
|
|
6
7
|
// Three-valued logic implementation
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
|
|
9
|
+
// Get values safely
|
|
10
|
+
const leftBoxed = left.length > 0 ? left[0] : null;
|
|
11
|
+
const rightBoxed = right.length > 0 ? right[0] : null;
|
|
12
|
+
|
|
13
|
+
const leftValue = leftBoxed ? unbox(leftBoxed) : null;
|
|
14
|
+
const rightValue = rightBoxed ? unbox(rightBoxed) : null;
|
|
15
|
+
|
|
16
|
+
// If either operand is false, result is false
|
|
17
|
+
if (leftValue === false || rightValue === false) {
|
|
18
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// If both operands are true, result is true
|
|
22
|
+
if (leftValue === true && rightValue === true) {
|
|
23
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
17
24
|
}
|
|
18
|
-
|
|
25
|
+
|
|
26
|
+
// Otherwise (empty or non-boolean), result is empty
|
|
27
|
+
return { value: [], context };
|
|
19
28
|
};
|
|
20
29
|
|
|
21
30
|
export const andOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
|
+
import { box, unbox } from '../boxing';
|
|
2
3
|
|
|
3
4
|
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
4
5
|
// Empty input returns false per spec
|
|
5
6
|
if (input.length === 0) {
|
|
6
|
-
return { value: [false], context };
|
|
7
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
// Check if any item in the collection is false
|
|
10
|
-
for (const
|
|
11
|
+
for (const boxedItem of input) {
|
|
12
|
+
const item = unbox(boxedItem);
|
|
11
13
|
if (typeof item !== 'boolean') {
|
|
12
14
|
continue; // Skip non-boolean values
|
|
13
15
|
}
|
|
14
16
|
if (item === false) {
|
|
15
|
-
return { value: [true], context };
|
|
17
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
// All items are true or non-boolean
|
|
20
|
-
return { value: [false], context };
|
|
22
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export const anyFalseFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
|
+
import { box, unbox } from '../boxing';
|
|
2
3
|
|
|
3
4
|
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
4
5
|
// Empty input returns false per spec
|
|
5
6
|
if (input.length === 0) {
|
|
6
|
-
return { value: [false], context };
|
|
7
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
// Check if any item in the collection is true
|
|
10
|
-
for (const
|
|
11
|
+
for (const boxedItem of input) {
|
|
12
|
+
const item = unbox(boxedItem);
|
|
11
13
|
if (typeof item !== 'boolean') {
|
|
12
14
|
continue; // Skip non-boolean values
|
|
13
15
|
}
|
|
14
16
|
if (item === true) {
|
|
15
|
-
return { value: [true], context };
|
|
17
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
// All items are false or non-boolean
|
|
20
|
-
return { value: [false], context };
|
|
22
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export const anyTrueFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
@@ -1,6 +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
5
|
|
|
5
6
|
export const evaluate: OperationEvaluator = (input, context, left, right) => {
|
|
6
7
|
// 'as' operator performs type casting/filtering
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
3
|
+
import { box, unbox } from '../boxing';
|
|
2
4
|
|
|
3
5
|
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
4
6
|
// ceiling() takes no arguments
|
|
5
7
|
if (args.length !== 0) {
|
|
6
|
-
throw
|
|
8
|
+
throw Errors.wrongArgumentCount('ceiling', 0, args.length);
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
// If input is empty, return empty
|
|
@@ -13,19 +15,21 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
|
|
|
13
15
|
|
|
14
16
|
// If input has multiple items, error
|
|
15
17
|
if (input.length > 1) {
|
|
16
|
-
throw
|
|
18
|
+
throw Errors.singletonRequired('ceiling', input.length);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const
|
|
21
|
+
const boxedValue = input[0];
|
|
22
|
+
if (!boxedValue) return { value: [], context };
|
|
23
|
+
const value = unbox(boxedValue);
|
|
20
24
|
|
|
21
25
|
// Must be a number
|
|
22
26
|
if (typeof value !== 'number') {
|
|
23
|
-
throw
|
|
27
|
+
throw Errors.invalidOperandType('ceiling', `${typeof value}`);
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
// Math.ceil can return -0, normalize it to 0
|
|
27
31
|
const result = Math.ceil(value);
|
|
28
|
-
return { value: [Object.is(result, -0) ? 0 : result], context };
|
|
32
|
+
return { value: [box(Object.is(result, -0) ? 0 : result, { type: 'Integer', singleton: true })], context };
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
export const ceilingFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { FunctionDefinition, FunctionEvaluator, TypeInfo } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
3
|
+
import { box, unbox } from '../boxing';
|
|
4
|
+
|
|
5
|
+
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
6
|
+
if (args.length !== 0) {
|
|
7
|
+
throw Errors.wrongArgumentCount('children', 0, args.length);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const results: any[] = [];
|
|
11
|
+
const modelProvider = context.modelProvider;
|
|
12
|
+
|
|
13
|
+
// Process each item in the input collection
|
|
14
|
+
// input is already an array, not an object with value property
|
|
15
|
+
for (const boxedItem of input) {
|
|
16
|
+
const item = unbox(boxedItem);
|
|
17
|
+
|
|
18
|
+
if (item && typeof item === 'object') {
|
|
19
|
+
// Get the parent type info if available
|
|
20
|
+
let parentTypeInfo: TypeInfo | undefined;
|
|
21
|
+
if (modelProvider && boxedItem.typeInfo) {
|
|
22
|
+
parentTypeInfo = boxedItem.typeInfo;
|
|
23
|
+
} else if (modelProvider && 'resourceType' in item && typeof item.resourceType === 'string') {
|
|
24
|
+
// Try to get type info from resourceType
|
|
25
|
+
parentTypeInfo = modelProvider.getType(item.resourceType);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Collect all child properties
|
|
29
|
+
for (const propertyName in item) {
|
|
30
|
+
// Skip resourceType as it's not a child element in FHIRPath
|
|
31
|
+
if (propertyName === 'resourceType') {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Skip primitive element properties (those starting with _)
|
|
36
|
+
if (propertyName.startsWith('_')) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const value = item[propertyName];
|
|
41
|
+
|
|
42
|
+
// Skip null/undefined values
|
|
43
|
+
if (value === null || value === undefined) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Get type info for this element from model provider
|
|
48
|
+
let elementTypeInfo: TypeInfo | undefined;
|
|
49
|
+
if (modelProvider && parentTypeInfo) {
|
|
50
|
+
elementTypeInfo = modelProvider.getElementType(parentTypeInfo, propertyName);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Get primitive element if it exists
|
|
54
|
+
const primitiveElementName = `_${propertyName}`;
|
|
55
|
+
const primitiveElement = (primitiveElementName in item) ? item[primitiveElementName] : undefined;
|
|
56
|
+
|
|
57
|
+
if (Array.isArray(value)) {
|
|
58
|
+
// Add each array element
|
|
59
|
+
for (let i = 0; i < value.length; i++) {
|
|
60
|
+
const elementValue = value[i];
|
|
61
|
+
if (elementValue !== null && elementValue !== undefined) {
|
|
62
|
+
const elementPrimitive = primitiveElement?.[i];
|
|
63
|
+
// Make the type info singleton since it's a single element
|
|
64
|
+
const singletonTypeInfo = elementTypeInfo ? { ...elementTypeInfo, singleton: true } : undefined;
|
|
65
|
+
results.push(box(elementValue, singletonTypeInfo, elementPrimitive));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
// Add single value - ensure it's marked as singleton
|
|
70
|
+
const singletonTypeInfo = elementTypeInfo ? { ...elementTypeInfo, singleton: true } : undefined;
|
|
71
|
+
results.push(box(value, singletonTypeInfo, primitiveElement));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { value: results, context };
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const childrenFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
81
|
+
name: 'children',
|
|
82
|
+
category: ['navigation'],
|
|
83
|
+
description: 'Returns all immediate child nodes of all items in the input collection',
|
|
84
|
+
examples: [
|
|
85
|
+
'Patient.children()',
|
|
86
|
+
'Observation.children().ofType(CodeableConcept)'
|
|
87
|
+
],
|
|
88
|
+
signature: {
|
|
89
|
+
input: { type: 'Any', singleton: false },
|
|
90
|
+
parameters: [],
|
|
91
|
+
result: { type: 'Any', singleton: false }
|
|
92
|
+
},
|
|
93
|
+
evaluate
|
|
94
|
+
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
3
|
+
import { box, unbox } from '../boxing';
|
|
2
4
|
|
|
3
5
|
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
4
6
|
// combine() requires exactly one argument (the other collection)
|
|
5
7
|
if (!args || args.length !== 1) {
|
|
6
|
-
throw
|
|
8
|
+
throw Errors.invalidOperation('combine requires exactly one argument');
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
// Evaluate the argument to get the other collection
|
|
10
12
|
const otherArg = args[0];
|
|
11
13
|
if (!otherArg) {
|
|
12
|
-
throw
|
|
14
|
+
throw Errors.argumentRequired('combine', 'collection argument');
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
// Evaluate the argument expression
|
|
@@ -1,17 +1,18 @@
|
|
|
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
5
|
|
|
5
6
|
export const evaluate: OperationEvaluator = (input, context, left, right) => {
|
|
6
7
|
// Combine operator concatenates all values as strings
|
|
7
|
-
const leftStr = left.map(v => String(v)).join('');
|
|
8
|
-
const rightStr = right.map(v => String(v)).join('');
|
|
8
|
+
const leftStr = left.map(v => String(unbox(v))).join('');
|
|
9
|
+
const rightStr = right.map(v => String(unbox(v))).join('');
|
|
9
10
|
|
|
10
11
|
if (leftStr === '' && rightStr === '') {
|
|
11
12
|
return { value: [], context };
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
return { value: [leftStr + rightStr], context };
|
|
15
|
+
return { value: [box(leftStr + rightStr, { type: 'String', singleton: true })], context };
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
export const combineOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
@@ -22,6 +23,19 @@ export const combineOperator: OperatorDefinition & { evaluate: OperationEvaluato
|
|
|
22
23
|
associativity: 'left',
|
|
23
24
|
description: 'String concatenation operator',
|
|
24
25
|
examples: ['first & " " & last'],
|
|
25
|
-
signatures: [
|
|
26
|
+
signatures: [
|
|
27
|
+
{
|
|
28
|
+
name: 'string-combine',
|
|
29
|
+
left: { type: 'String', singleton: true },
|
|
30
|
+
right: { type: 'String', singleton: true },
|
|
31
|
+
result: { type: 'String', singleton: true },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'any-combine',
|
|
35
|
+
left: { type: 'Any' },
|
|
36
|
+
right: { type: 'Any' },
|
|
37
|
+
result: { type: 'String', singleton: true },
|
|
38
|
+
}
|
|
39
|
+
],
|
|
26
40
|
evaluate
|
|
27
41
|
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { FunctionDefinition } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
2
3
|
import type { FunctionEvaluator } from '../types';
|
|
4
|
+
import { box, unbox } from '../boxing';
|
|
3
5
|
|
|
4
6
|
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
5
7
|
// contains() requires exactly 1 argument
|
|
6
8
|
if (args.length !== 1) {
|
|
7
|
-
throw
|
|
9
|
+
throw Errors.wrongArgumentCount('contains', 1, args.length);
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
const substringExpr = args[0];
|
|
11
13
|
if (!substringExpr) {
|
|
12
|
-
throw
|
|
14
|
+
throw Errors.argumentRequired('contains', 'substring argument');
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
// If input collection is empty, result is empty
|
|
@@ -19,11 +21,16 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
|
|
|
19
21
|
|
|
20
22
|
// If input collection contains multiple items, signal an error
|
|
21
23
|
if (input.length > 1) {
|
|
22
|
-
throw
|
|
24
|
+
throw Errors.invalidOperation('contains can only be applied to a single string');
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
// Input must be a string
|
|
26
|
-
const
|
|
28
|
+
const boxedInputValue = input[0];
|
|
29
|
+
if (!boxedInputValue) {
|
|
30
|
+
return { value: [], context };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const inputValue = unbox(boxedInputValue);
|
|
27
34
|
if (typeof inputValue !== 'string') {
|
|
28
35
|
// Non-string input returns empty
|
|
29
36
|
return { value: [], context };
|
|
@@ -39,10 +46,15 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
|
|
|
39
46
|
|
|
40
47
|
// Substring must be a single string
|
|
41
48
|
if (substringResult.value.length > 1) {
|
|
42
|
-
throw
|
|
49
|
+
throw Errors.invalidOperation('contains substring argument must evaluate to a single value');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const boxedSubstring = substringResult.value[0];
|
|
53
|
+
if (!boxedSubstring) {
|
|
54
|
+
return { value: [], context };
|
|
43
55
|
}
|
|
44
56
|
|
|
45
|
-
const substring =
|
|
57
|
+
const substring = unbox(boxedSubstring);
|
|
46
58
|
if (typeof substring !== 'string') {
|
|
47
59
|
// Non-string substring returns empty
|
|
48
60
|
return { value: [], context };
|
|
@@ -50,12 +62,12 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
|
|
|
50
62
|
|
|
51
63
|
// If substring is empty string, result is true
|
|
52
64
|
if (substring === '') {
|
|
53
|
-
return { value: [true], context };
|
|
65
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
// Check if input string contains the substring
|
|
57
69
|
const result = inputValue.includes(substring);
|
|
58
|
-
return { value: [result], context };
|
|
70
|
+
return { value: [box(result, { type: 'Boolean', singleton: true })], context };
|
|
59
71
|
};
|
|
60
72
|
|
|
61
73
|
export const containsFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { OperatorDefinition } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
2
3
|
import { PRECEDENCE } from '../types';
|
|
3
4
|
import type { OperationEvaluator } from '../types';
|
|
5
|
+
import { box, unbox } from '../boxing';
|
|
4
6
|
|
|
5
7
|
export const evaluate: OperationEvaluator = (input, context, left, right) => {
|
|
6
8
|
// If right is empty, result is empty
|
|
@@ -10,23 +12,29 @@ export const evaluate: OperationEvaluator = (input, context, left, right) => {
|
|
|
10
12
|
|
|
11
13
|
// Right must have single item
|
|
12
14
|
if (right.length > 1) {
|
|
13
|
-
throw
|
|
15
|
+
throw Errors.invalidOperation('contains operator: right operand must be a single item');
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
// If left is empty, result is false
|
|
17
19
|
if (left.length === 0) {
|
|
18
|
-
return { value: [false], context };
|
|
20
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
// Check if the single right item is in left using equality
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
+
const boxedRightItem = right[0];
|
|
25
|
+
if (!boxedRightItem) {
|
|
26
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
27
|
+
}
|
|
28
|
+
const rightItem = unbox(boxedRightItem);
|
|
29
|
+
|
|
30
|
+
for (const boxedLeftItem of left) {
|
|
31
|
+
const leftItem = unbox(boxedLeftItem);
|
|
24
32
|
if (leftItem === rightItem) {
|
|
25
|
-
return { value: [true], context };
|
|
33
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
26
34
|
}
|
|
27
35
|
}
|
|
28
36
|
|
|
29
|
-
return { value: [false], context };
|
|
37
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
30
38
|
};
|
|
31
39
|
|
|
32
40
|
export const containsOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { FunctionDefinition } from '../types';
|
|
2
2
|
import type { FunctionEvaluator } from '../types';
|
|
3
|
+
import { box, unbox } from '../boxing';
|
|
3
4
|
|
|
4
5
|
export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
|
|
5
6
|
return {
|
|
6
|
-
value: [input.length],
|
|
7
|
+
value: [box(input.length, { type: 'Integer', singleton: true })],
|
|
7
8
|
context
|
|
8
9
|
};
|
|
9
10
|
};
|