@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
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// timeOf() function - Extracts time component from DateTime
|
|
2
|
+
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
import { createTime, isFHIRDateTime } from '../complex-types/temporal';
|
|
5
|
+
import { Errors } from '../errors';
|
|
6
|
+
|
|
7
|
+
export const timeOfEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
8
|
+
// timeOf() takes no arguments
|
|
9
|
+
if (args.length !== 0) {
|
|
10
|
+
throw Errors.wrongArgumentCount('timeOf', 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('timeOf', 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 DateTime
|
|
31
|
+
if (isFHIRDateTime(value)) {
|
|
32
|
+
// Check if time component is present
|
|
33
|
+
if (value.hour === undefined) {
|
|
34
|
+
// No time component present
|
|
35
|
+
return { value: [], context };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Extract time component (preserve precision)
|
|
39
|
+
const time = createTime(value.hour, value.minute, value.second, value.millisecond);
|
|
40
|
+
return {
|
|
41
|
+
value: [box(time, { type: 'Time', singleton: true })],
|
|
42
|
+
context
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Not a DateTime, return empty
|
|
47
|
+
return { value: [], context };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const timeOfFunction: FunctionDefinition & { evaluate: typeof timeOfEvaluator } = {
|
|
51
|
+
name: 'timeOf',
|
|
52
|
+
category: ['temporal'],
|
|
53
|
+
description: 'Returns the time component of a DateTime value',
|
|
54
|
+
examples: [
|
|
55
|
+
'@2012-01-01T12:30:00.timeOf()',
|
|
56
|
+
'Observation.effectiveDateTime.timeOf()'
|
|
57
|
+
],
|
|
58
|
+
signatures: [
|
|
59
|
+
{
|
|
60
|
+
name: 'timeOf',
|
|
61
|
+
input: { type: 'Any', singleton: true },
|
|
62
|
+
parameters: [],
|
|
63
|
+
result: { type: 'Time', singleton: true }
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
evaluate: timeOfEvaluator
|
|
67
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// timezoneOffsetOf() function - Extracts timezone offset component from DateTime
|
|
2
|
+
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
import { isFHIRDateTime } from '../complex-types/temporal';
|
|
5
|
+
import { Errors } from '../errors';
|
|
6
|
+
|
|
7
|
+
export const timezoneOffsetOfEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
8
|
+
// timezoneOffsetOf() takes no arguments
|
|
9
|
+
if (args.length !== 0) {
|
|
10
|
+
throw Errors.wrongArgumentCount('timezoneOffsetOf', 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('timezoneOffsetOf', 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 DateTime with timezone
|
|
31
|
+
if (isFHIRDateTime(value)) {
|
|
32
|
+
// Check if timezone offset is present
|
|
33
|
+
if (value.timezoneOffset === undefined) {
|
|
34
|
+
return { value: [], context };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return the timezone offset as a Decimal (hours)
|
|
38
|
+
// The timezoneOffset is stored in minutes, convert to decimal hours
|
|
39
|
+
const offsetInHours = value.timezoneOffset / 60;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
value: [box(offsetInHours, { type: 'Decimal', singleton: true })],
|
|
43
|
+
context
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Not a DateTime, return empty
|
|
48
|
+
return { value: [], context };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const timezoneOffsetOfFunction: FunctionDefinition & { evaluate: typeof timezoneOffsetOfEvaluator } = {
|
|
52
|
+
name: 'timezoneOffsetOf',
|
|
53
|
+
category: ['temporal'],
|
|
54
|
+
description: 'Returns the timezone offset component of a DateTime value as decimal hours',
|
|
55
|
+
examples: [
|
|
56
|
+
'@2012-01-01T12:30:00.000-07:00.timezoneOffsetOf()',
|
|
57
|
+
'@2012-01-01T12:30:00.000+05:30.timezoneOffsetOf()',
|
|
58
|
+
'Patient.lastUpdated.timezoneOffsetOf()'
|
|
59
|
+
],
|
|
60
|
+
signatures: [
|
|
61
|
+
{
|
|
62
|
+
name: 'timezoneOffsetOf',
|
|
63
|
+
input: { type: 'Any', singleton: true },
|
|
64
|
+
parameters: [],
|
|
65
|
+
result: { type: 'Decimal', singleton: true }
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
evaluate: timezoneOffsetOfEvaluator
|
|
69
|
+
};
|
|
@@ -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
|
// toBoolean() takes no arguments
|
|
@@ -90,12 +90,31 @@ export const toBooleanFunction: FunctionDefinition & { evaluate: FunctionEvaluat
|
|
|
90
90
|
"1.0.toBoolean()",
|
|
91
91
|
"0.0.toBoolean()"
|
|
92
92
|
],
|
|
93
|
-
signatures: [
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
signatures: [
|
|
94
|
+
{
|
|
95
|
+
name: 'toBoolean-boolean',
|
|
96
|
+
input: { type: 'Boolean', singleton: true },
|
|
97
|
+
parameters: [],
|
|
98
|
+
result: { type: 'Boolean', singleton: true }
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'toBoolean-integer',
|
|
102
|
+
input: { type: 'Integer', singleton: true },
|
|
103
|
+
parameters: [],
|
|
104
|
+
result: { type: 'Boolean', singleton: true }
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'toBoolean-decimal',
|
|
108
|
+
input: { type: 'Decimal', singleton: true },
|
|
109
|
+
parameters: [],
|
|
110
|
+
result: { type: 'Boolean', singleton: true }
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'toBoolean-string',
|
|
114
|
+
input: { type: 'String', singleton: true },
|
|
115
|
+
parameters: [],
|
|
116
|
+
result: { type: 'Boolean', singleton: true }
|
|
117
|
+
}
|
|
118
|
+
],
|
|
100
119
|
evaluate
|
|
101
120
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
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('toChars', 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('toChars');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check arguments - toChars takes no arguments
|
|
26
|
+
if (args.length !== 0) {
|
|
27
|
+
throw Errors.wrongArgumentCount('toChars', 0, args.length);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Convert string to array of single-character strings
|
|
31
|
+
// Use Array.from to properly handle Unicode characters (including emoji)
|
|
32
|
+
const chars = Array.from(inputValue);
|
|
33
|
+
|
|
34
|
+
// Box each character as a String
|
|
35
|
+
const result = chars.map(char => box(char, { type: 'String', singleton: true }));
|
|
36
|
+
|
|
37
|
+
return { value: result, context };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const toCharsFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
41
|
+
name: 'toChars',
|
|
42
|
+
category: ['string'],
|
|
43
|
+
description: 'Returns the list of characters in the input string as a collection',
|
|
44
|
+
examples: [
|
|
45
|
+
"'abc'.toChars()",
|
|
46
|
+
"'Hello'.toChars()",
|
|
47
|
+
"''.toChars()"
|
|
48
|
+
],
|
|
49
|
+
signatures: [{
|
|
50
|
+
name: 'toChars',
|
|
51
|
+
input: { type: 'String', singleton: true },
|
|
52
|
+
parameters: [],
|
|
53
|
+
result: { type: 'String', singleton: false }
|
|
54
|
+
}],
|
|
55
|
+
evaluate
|
|
56
|
+
};
|
|
@@ -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
|
|
@@ -71,12 +71,31 @@ export const toDecimalFunction: FunctionDefinition & { evaluate: FunctionEvaluat
|
|
|
71
71
|
"true.toDecimal() // returns 1.0",
|
|
72
72
|
"false.toDecimal() // returns 0.0"
|
|
73
73
|
],
|
|
74
|
-
signatures: [
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
signatures: [
|
|
75
|
+
{
|
|
76
|
+
name: 'toDecimal-integer',
|
|
77
|
+
input: { type: 'Integer', singleton: true },
|
|
78
|
+
parameters: [],
|
|
79
|
+
result: { type: 'Decimal', singleton: true }
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'toDecimal-decimal',
|
|
83
|
+
input: { type: 'Decimal', singleton: true },
|
|
84
|
+
parameters: [],
|
|
85
|
+
result: { type: 'Decimal', singleton: true }
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'toDecimal-boolean',
|
|
89
|
+
input: { type: 'Boolean', singleton: true },
|
|
90
|
+
parameters: [],
|
|
91
|
+
result: { type: 'Decimal', singleton: true }
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'toDecimal-string',
|
|
95
|
+
input: { type: 'String', singleton: true },
|
|
96
|
+
parameters: [],
|
|
97
|
+
result: { type: 'Decimal', singleton: true }
|
|
98
|
+
}
|
|
99
|
+
],
|
|
81
100
|
evaluate
|
|
82
101
|
};
|
|
@@ -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
|
// toInteger() takes no arguments
|
|
@@ -67,8 +67,20 @@ export const toIntegerFunction: FunctionDefinition & { evaluate: FunctionEvaluat
|
|
|
67
67
|
],
|
|
68
68
|
signatures: [
|
|
69
69
|
{
|
|
70
|
-
name: 'toInteger',
|
|
71
|
-
input: { type: '
|
|
70
|
+
name: 'toInteger-integer',
|
|
71
|
+
input: { type: 'Integer', singleton: true },
|
|
72
|
+
parameters: [],
|
|
73
|
+
result: { type: 'Integer', singleton: true }
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'toInteger-string',
|
|
77
|
+
input: { type: 'String', singleton: true },
|
|
78
|
+
parameters: [],
|
|
79
|
+
result: { type: 'Integer', singleton: true }
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'toInteger-boolean',
|
|
83
|
+
input: { type: 'Boolean', singleton: true },
|
|
72
84
|
parameters: [],
|
|
73
85
|
result: { type: 'Integer', singleton: true }
|
|
74
86
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { FunctionDefinition } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
3
|
+
import { type FunctionEvaluator } from '../types';
|
|
4
|
+
import { unbox, box } from '../interpreter/boxing';
|
|
5
|
+
|
|
6
|
+
// 64-bit Long bounds
|
|
7
|
+
const MAX_LONG = BigInt('9223372036854775807');
|
|
8
|
+
const MIN_LONG = BigInt('-9223372036854775808');
|
|
9
|
+
|
|
10
|
+
// Regex for integer string format
|
|
11
|
+
const INTEGER_REGEX = /^[+-]?\d+$/;
|
|
12
|
+
|
|
13
|
+
export const evaluate: FunctionEvaluator = async (input, context, args) => {
|
|
14
|
+
// toLong takes no arguments
|
|
15
|
+
if (args.length !== 0) {
|
|
16
|
+
throw Errors.wrongArgumentCount('toLong', 0, args.length);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check singleton requirement
|
|
20
|
+
if (input.length > 1) {
|
|
21
|
+
throw Errors.singletonRequired('toLong', input.length);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (input.length === 0) {
|
|
25
|
+
return { value: [], context };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const item = input[0];
|
|
29
|
+
if (!item) {
|
|
30
|
+
return { value: [], context };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const unboxedItem = unbox(item);
|
|
34
|
+
const itemType = (item as any).typeInfo?.type || (item as any).type;
|
|
35
|
+
|
|
36
|
+
let resultValue: number | null = null;
|
|
37
|
+
|
|
38
|
+
// Handle different input types
|
|
39
|
+
if (itemType === 'Integer' || itemType === 'Long') {
|
|
40
|
+
// Integer/Long passthrough
|
|
41
|
+
resultValue = unboxedItem as number;
|
|
42
|
+
} else if (itemType === 'Boolean') {
|
|
43
|
+
// true → 1, false → 0
|
|
44
|
+
resultValue = unboxedItem ? 1 : 0;
|
|
45
|
+
} else if (itemType === 'String') {
|
|
46
|
+
// Try to parse the string as an integer
|
|
47
|
+
const str = unboxedItem as string;
|
|
48
|
+
|
|
49
|
+
// Check if string matches integer format
|
|
50
|
+
if (!INTEGER_REGEX.test(str)) {
|
|
51
|
+
return { value: [], context };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Use BigInt to parse and check bounds
|
|
56
|
+
const bigIntValue = BigInt(str);
|
|
57
|
+
|
|
58
|
+
// Check 64-bit bounds
|
|
59
|
+
if (bigIntValue < MIN_LONG || bigIntValue > MAX_LONG) {
|
|
60
|
+
return { value: [], context };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Convert back to number (safe within 64-bit bounds)
|
|
64
|
+
resultValue = Number(bigIntValue);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
// Parse failed
|
|
67
|
+
return { value: [], context };
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
// Other types return empty
|
|
71
|
+
return { value: [], context };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Box the result as Long type
|
|
75
|
+
return {
|
|
76
|
+
value: [box(resultValue, { type: 'Long', singleton: true })],
|
|
77
|
+
context
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const toLongFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
82
|
+
name: 'toLong',
|
|
83
|
+
category: ['conversion'],
|
|
84
|
+
description: 'Converts the input to a 64-bit Long integer. Integers are passed through, Strings are parsed if they match integer format and are within 64-bit bounds, Boolean true becomes 1 and false becomes 0. This is a Standard for Trial Use (STU) feature.',
|
|
85
|
+
examples: [
|
|
86
|
+
'1.toLong() // Returns 1',
|
|
87
|
+
'\'123\'.toLong() // Returns 123',
|
|
88
|
+
'true.toLong() // Returns 1',
|
|
89
|
+
'\'9223372036854775807\'.toLong() // Max 64-bit value'
|
|
90
|
+
],
|
|
91
|
+
signatures: [{
|
|
92
|
+
name: 'toLong',
|
|
93
|
+
input: { type: 'Any', singleton: true },
|
|
94
|
+
parameters: [],
|
|
95
|
+
result: { type: 'Long', singleton: true }
|
|
96
|
+
}],
|
|
97
|
+
evaluate
|
|
98
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { FunctionDefinition } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
3
|
+
import { type FunctionEvaluator } from '../types';
|
|
4
|
+
import { unbox, box } from '../interpreter/boxing';
|
|
5
|
+
import { createQuantity, CALENDAR_DURATION_UNITS } from '../complex-types/quantity-value';
|
|
6
|
+
import { ucum } from '@atomic-ehr/ucum';
|
|
7
|
+
|
|
8
|
+
// Regex for parsing quantity strings according to FHIRPath spec
|
|
9
|
+
// Matches: (?'value'(\+|-)?\d+(\.\d+)?)\s*('(?'unit'[^']+)'|(?'time'[a-zA-Z]+))?
|
|
10
|
+
const QUANTITY_REGEX = /^(?<value>[+-]?\d+(?:\.\d+)?)\s*(?:'(?<quotedUnit>[^']+)'|(?<unquotedUnit>[a-zA-Z]+))?$/;
|
|
11
|
+
|
|
12
|
+
// Calendar duration conversion factors (from spec)
|
|
13
|
+
const CALENDAR_CONVERSIONS: Record<string, Record<string, number>> = {
|
|
14
|
+
'year': { 'month': 12, 'months': 12, 'day': 365, 'days': 365, 'd': 365 },
|
|
15
|
+
'years': { 'month': 12, 'months': 12, 'day': 365, 'days': 365, 'd': 365 },
|
|
16
|
+
'month': { 'day': 30, 'days': 30, 'd': 30 },
|
|
17
|
+
'months': { 'day': 30, 'days': 30, 'd': 30 },
|
|
18
|
+
'week': { 'day': 7, 'days': 7, 'd': 7 },
|
|
19
|
+
'weeks': { 'day': 7, 'days': 7, 'd': 7 },
|
|
20
|
+
'wk': { 'day': 7, 'days': 7, 'd': 7 },
|
|
21
|
+
'day': { 'hour': 24, 'hours': 24, 'h': 24 },
|
|
22
|
+
'days': { 'hour': 24, 'hours': 24, 'h': 24 },
|
|
23
|
+
'd': { 'hour': 24, 'hours': 24, 'h': 24 },
|
|
24
|
+
'hour': { 'minute': 60, 'minutes': 60, 'min': 60 },
|
|
25
|
+
'hours': { 'minute': 60, 'minutes': 60, 'min': 60 },
|
|
26
|
+
'h': { 'minute': 60, 'minutes': 60, 'min': 60 },
|
|
27
|
+
'minute': { 'second': 60, 'seconds': 60, 's': 60 },
|
|
28
|
+
'minutes': { 'second': 60, 'seconds': 60, 's': 60 },
|
|
29
|
+
'min': { 'second': 60, 'seconds': 60, 's': 60 },
|
|
30
|
+
'second': { 's': 1 },
|
|
31
|
+
'seconds': { 's': 1 }
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Map common time units to their canonical forms
|
|
35
|
+
const UNIT_MAPPING: Record<string, string> = {
|
|
36
|
+
'year': 'year',
|
|
37
|
+
'years': 'year',
|
|
38
|
+
'month': 'month',
|
|
39
|
+
'months': 'month',
|
|
40
|
+
'week': 'week',
|
|
41
|
+
'weeks': 'week',
|
|
42
|
+
'day': 'd',
|
|
43
|
+
'days': 'd',
|
|
44
|
+
'hour': 'h',
|
|
45
|
+
'hours': 'h',
|
|
46
|
+
'minute': 'min',
|
|
47
|
+
'minutes': 'min',
|
|
48
|
+
'second': 's',
|
|
49
|
+
'seconds': 's',
|
|
50
|
+
'millisecond': 'ms',
|
|
51
|
+
'milliseconds': 'ms'
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
55
|
+
// toQuantity takes 0 or 1 argument (optional unit)
|
|
56
|
+
if (args.length > 1) {
|
|
57
|
+
throw Errors.wrongArgumentCountRange('toQuantity', 0, 1, args.length);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check singleton requirement
|
|
61
|
+
if (input.length > 1) {
|
|
62
|
+
throw Errors.singletonRequired('toQuantity', input.length);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (input.length === 0) {
|
|
66
|
+
return { value: [], context };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const item = input[0];
|
|
70
|
+
if (!item) {
|
|
71
|
+
return { value: [], context };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const unboxedItem = unbox(item);
|
|
75
|
+
const itemType = (item as any).typeInfo?.type || (item as any).type;
|
|
76
|
+
|
|
77
|
+
let resultQuantity: any = null;
|
|
78
|
+
|
|
79
|
+
// Handle different input types
|
|
80
|
+
if (itemType === 'Integer' || itemType === 'Decimal') {
|
|
81
|
+
// Numbers get default unit '1'
|
|
82
|
+
resultQuantity = createQuantity(unboxedItem as number, '1');
|
|
83
|
+
} else if (itemType === 'Quantity') {
|
|
84
|
+
// Already a quantity
|
|
85
|
+
resultQuantity = unboxedItem;
|
|
86
|
+
} else if (itemType === 'Boolean') {
|
|
87
|
+
// true → 1.0 '1', false → 0.0 '1'
|
|
88
|
+
const value = unboxedItem ? 1.0 : 0.0;
|
|
89
|
+
resultQuantity = createQuantity(value, '1');
|
|
90
|
+
} else if (itemType === 'String') {
|
|
91
|
+
// Try to parse the string as a quantity
|
|
92
|
+
const str = unboxedItem as string;
|
|
93
|
+
const match = QUANTITY_REGEX.exec(str);
|
|
94
|
+
|
|
95
|
+
if (match) {
|
|
96
|
+
const value = parseFloat(match.groups?.value || '0');
|
|
97
|
+
const unit = match.groups?.quotedUnit || match.groups?.unquotedUnit || '1';
|
|
98
|
+
|
|
99
|
+
// Map common time units to canonical forms
|
|
100
|
+
const mappedUnit = UNIT_MAPPING[unit] || unit;
|
|
101
|
+
resultQuantity = createQuantity(value, mappedUnit);
|
|
102
|
+
} else {
|
|
103
|
+
// String doesn't match quantity format
|
|
104
|
+
return { value: [], context };
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// Other types return empty
|
|
108
|
+
return { value: [], context };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// If unit argument is provided, attempt conversion
|
|
112
|
+
if (args.length === 1) {
|
|
113
|
+
const unitArg = args[0];
|
|
114
|
+
if (!unitArg) {
|
|
115
|
+
throw Errors.invalidOperation('toQuantity: unit parameter is required');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Evaluate the argument to get the unit string
|
|
119
|
+
const unitResult = await evaluator(unitArg, input, context);
|
|
120
|
+
if (unitResult.value.length !== 1) {
|
|
121
|
+
throw Errors.invalidOperation('toQuantity: unit parameter must be a single string value');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const unitValue = unitResult.value[0];
|
|
125
|
+
const unitValueType = (unitValue as any)?.typeInfo?.type || (unitValue as any)?.type;
|
|
126
|
+
if (!unitValue || unitValueType !== 'String') {
|
|
127
|
+
throw Errors.invalidOperation('toQuantity: unit parameter must be a string');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const targetUnit = unbox(unitValue) as string;
|
|
131
|
+
const mappedTargetUnit = UNIT_MAPPING[targetUnit] || targetUnit;
|
|
132
|
+
|
|
133
|
+
// Try calendar duration conversion first
|
|
134
|
+
const conversions = CALENDAR_CONVERSIONS[resultQuantity.unit];
|
|
135
|
+
if (conversions && conversions[mappedTargetUnit]) {
|
|
136
|
+
const factor = conversions[mappedTargetUnit];
|
|
137
|
+
resultQuantity = createQuantity(resultQuantity.value * factor, mappedTargetUnit);
|
|
138
|
+
} else if (resultQuantity.unit === mappedTargetUnit) {
|
|
139
|
+
// Same unit, no conversion needed
|
|
140
|
+
} else if (!CALENDAR_DURATION_UNITS.has(resultQuantity.unit) &&
|
|
141
|
+
!CALENDAR_DURATION_UNITS.has(mappedTargetUnit)) {
|
|
142
|
+
// Try UCUM conversion
|
|
143
|
+
try {
|
|
144
|
+
const convertedValue = ucum.convert(resultQuantity.value, resultQuantity.unit, mappedTargetUnit);
|
|
145
|
+
resultQuantity = createQuantity(convertedValue, mappedTargetUnit);
|
|
146
|
+
} catch (e) {
|
|
147
|
+
// Conversion failed, return empty
|
|
148
|
+
return { value: [], context };
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
// Can't convert between these units
|
|
152
|
+
return { value: [], context };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
value: [box(resultQuantity, { type: 'Quantity', singleton: true })],
|
|
158
|
+
context
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const toQuantityFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
163
|
+
name: 'toQuantity',
|
|
164
|
+
category: ['conversion'],
|
|
165
|
+
description: 'Converts the input to a Quantity value. Integers and Decimals are converted with default unit \'1\'. Strings are parsed for quantity format. Boolean true becomes 1.0 \'1\', false becomes 0.0 \'1\'. With optional unit parameter, attempts conversion to specified unit.',
|
|
166
|
+
examples: [
|
|
167
|
+
'1.toQuantity() // Returns 1 \'1\'',
|
|
168
|
+
'\'4 days\'.toQuantity() // Returns 4 \'d\'',
|
|
169
|
+
'\'1 \\\'wk\\\'\'.toQuantity(\'d\') // Returns 7 \'d\'',
|
|
170
|
+
'true.toQuantity() // Returns 1.0 \'1\''
|
|
171
|
+
],
|
|
172
|
+
signatures: [{
|
|
173
|
+
name: 'toQuantity',
|
|
174
|
+
input: { type: 'Any', singleton: true },
|
|
175
|
+
parameters: [
|
|
176
|
+
{ name: 'unit', type: { type: 'String', singleton: true }, optional: true }
|
|
177
|
+
],
|
|
178
|
+
result: { type: 'Quantity', singleton: true }
|
|
179
|
+
}],
|
|
180
|
+
evaluate
|
|
181
|
+
};
|
|
@@ -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
|
// toString takes no arguments
|
|
@@ -80,8 +80,50 @@ export const toStringFunction: FunctionDefinition & { evaluate: FunctionEvaluato
|
|
|
80
80
|
],
|
|
81
81
|
signatures: [
|
|
82
82
|
{
|
|
83
|
-
name: 'toString',
|
|
84
|
-
input: { type: '
|
|
83
|
+
name: 'toString-string',
|
|
84
|
+
input: { type: 'String', singleton: true },
|
|
85
|
+
parameters: [],
|
|
86
|
+
result: { type: 'String', singleton: true }
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'toString-integer',
|
|
90
|
+
input: { type: 'Integer', singleton: true },
|
|
91
|
+
parameters: [],
|
|
92
|
+
result: { type: 'String', singleton: true }
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'toString-decimal',
|
|
96
|
+
input: { type: 'Decimal', singleton: true },
|
|
97
|
+
parameters: [],
|
|
98
|
+
result: { type: 'String', singleton: true }
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'toString-boolean',
|
|
102
|
+
input: { type: 'Boolean', singleton: true },
|
|
103
|
+
parameters: [],
|
|
104
|
+
result: { type: 'String', singleton: true }
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'toString-date',
|
|
108
|
+
input: { type: 'Date', singleton: true },
|
|
109
|
+
parameters: [],
|
|
110
|
+
result: { type: 'String', singleton: true }
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'toString-datetime',
|
|
114
|
+
input: { type: 'DateTime', singleton: true },
|
|
115
|
+
parameters: [],
|
|
116
|
+
result: { type: 'String', singleton: true }
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'toString-time',
|
|
120
|
+
input: { type: 'Time', singleton: true },
|
|
121
|
+
parameters: [],
|
|
122
|
+
result: { type: 'String', singleton: true }
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'toString-quantity',
|
|
126
|
+
input: { type: 'Quantity', singleton: true },
|
|
85
127
|
parameters: [],
|
|
86
128
|
result: { type: 'String', singleton: true }
|
|
87
129
|
}
|
|
@@ -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
|
// trace() requires at least a name argument
|
|
@@ -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
|
// trim() takes no arguments
|
|
@@ -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
|
// truncate() takes no arguments
|