@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
|
@@ -2,7 +2,7 @@ import type { OperatorDefinition } from '../types';
|
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
3
|
import { PRECEDENCE } from '../types';
|
|
4
4
|
import type { OperationEvaluator } from '../types';
|
|
5
|
-
import { box, unbox } from '../boxing';
|
|
5
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
6
6
|
|
|
7
7
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
8
8
|
// If right is empty, result is empty
|
|
@@ -40,6 +40,7 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
40
40
|
export const containsOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
41
41
|
symbol: 'contains',
|
|
42
42
|
name: 'contains',
|
|
43
|
+
doesNotPropagateEmpty: true, // Empty left operand returns false
|
|
43
44
|
category: ['membership'],
|
|
44
45
|
precedence: PRECEDENCE.IN_CONTAINS,
|
|
45
46
|
associativity: 'left',
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
// convertsToBoolean() takes no arguments
|
|
7
|
+
if (args.length !== 0) {
|
|
8
|
+
throw Errors.wrongArgumentCount('convertsToBoolean', 0, args.length);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// If input collection is empty, result is empty
|
|
12
|
+
if (input.length === 0) {
|
|
13
|
+
return { value: [], context };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// If input collection contains multiple items, signal an error
|
|
17
|
+
if (input.length > 1) {
|
|
18
|
+
throw Errors.singletonRequired('convertsToBoolean', input.length);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const boxedInputValue = input[0];
|
|
22
|
+
if (!boxedInputValue) {
|
|
23
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const inputValue = unbox(boxedInputValue);
|
|
27
|
+
|
|
28
|
+
// Check if the value can be converted to Boolean
|
|
29
|
+
|
|
30
|
+
// Boolean - always convertible
|
|
31
|
+
if (typeof inputValue === 'boolean') {
|
|
32
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Integer - 1 and 0 are convertible
|
|
36
|
+
if (typeof inputValue === 'number' && Number.isInteger(inputValue)) {
|
|
37
|
+
const canConvert = inputValue === 1 || inputValue === 0;
|
|
38
|
+
return { value: [box(canConvert, { type: 'Boolean', singleton: true })], context };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Decimal - 1.0 and 0.0 are convertible
|
|
42
|
+
if (typeof inputValue === 'number' && !Number.isInteger(inputValue)) {
|
|
43
|
+
const canConvert = inputValue === 1.0 || inputValue === 0.0;
|
|
44
|
+
return { value: [box(canConvert, { type: 'Boolean', singleton: true })], context };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// String - check if it's a valid boolean representation (case insensitive)
|
|
48
|
+
if (typeof inputValue === 'string') {
|
|
49
|
+
const lowerValue = inputValue.toLowerCase();
|
|
50
|
+
const validRepresentations = ['true', 't', 'yes', 'y', '1', '1.0', 'false', 'f', 'no', 'n', '0', '0.0'];
|
|
51
|
+
const canConvert = validRepresentations.includes(lowerValue);
|
|
52
|
+
return { value: [box(canConvert, { type: 'Boolean', singleton: true })], context };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// For all other types, return false
|
|
56
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const convertsToBooleanFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
60
|
+
name: 'convertsToBoolean',
|
|
61
|
+
category: ['type-conversion'],
|
|
62
|
+
description: 'Returns true if the input can be converted to a Boolean. Returns true for: Boolean (any), Integer (1 or 0), Decimal (1.0 or 0.0), String (\'true\'/\'t\'/\'yes\'/\'y\'/\'1\'/\'1.0\'/\'false\'/\'f\'/\'no\'/\'n\'/\'0\'/\'0.0\', case insensitive). Returns false for all other values.',
|
|
63
|
+
examples: [
|
|
64
|
+
"'true'.convertsToBoolean()",
|
|
65
|
+
"'invalid'.convertsToBoolean()",
|
|
66
|
+
"1.convertsToBoolean()",
|
|
67
|
+
"2.convertsToBoolean()"
|
|
68
|
+
],
|
|
69
|
+
signatures: [
|
|
70
|
+
{
|
|
71
|
+
name: 'convertsToBoolean',
|
|
72
|
+
input: { type: 'Any', singleton: true },
|
|
73
|
+
parameters: [],
|
|
74
|
+
result: { type: 'Boolean', singleton: true }
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
evaluate
|
|
78
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
// convertsToDecimal() takes no arguments
|
|
7
|
+
if (args.length !== 0) {
|
|
8
|
+
throw Errors.wrongArgumentCount('convertsToDecimal', 0, args.length);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// If input collection is empty, result is empty
|
|
12
|
+
if (input.length === 0) {
|
|
13
|
+
return { value: [], context };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// If input collection contains multiple items, signal an error
|
|
17
|
+
if (input.length > 1) {
|
|
18
|
+
throw Errors.singletonRequired('convertsToDecimal', input.length);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const boxedInputValue = input[0];
|
|
22
|
+
if (!boxedInputValue) {
|
|
23
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const inputValue = unbox(boxedInputValue);
|
|
27
|
+
|
|
28
|
+
// Check if the value can be converted to Decimal
|
|
29
|
+
|
|
30
|
+
// Integer or Decimal - always convertible
|
|
31
|
+
if (typeof inputValue === 'number') {
|
|
32
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Boolean - always convertible (true -> 1.0, false -> 0.0)
|
|
36
|
+
if (typeof inputValue === 'boolean') {
|
|
37
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// String - check if valid decimal format
|
|
41
|
+
if (typeof inputValue === 'string') {
|
|
42
|
+
// Use the regex from the spec: (\+|-)?\d+(\.\d+)?
|
|
43
|
+
const decimalRegex = /^(\+|-)?\d+(\.\d+)?$/;
|
|
44
|
+
|
|
45
|
+
if (!decimalRegex.test(inputValue)) {
|
|
46
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const parsedValue = parseFloat(inputValue);
|
|
50
|
+
|
|
51
|
+
// Check for valid number
|
|
52
|
+
if (isNaN(parsedValue)) {
|
|
53
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// For all other types, return false
|
|
60
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const convertsToDecimalFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
64
|
+
name: 'convertsToDecimal',
|
|
65
|
+
category: ['type-conversion'],
|
|
66
|
+
description: 'Returns true if the input can be converted to a Decimal. Returns true for: Integer (any), Decimal (any), Boolean (any), String matching regex (\\+|-)?\\d+(\\.\\d+)?. Returns false for all other types.',
|
|
67
|
+
examples: [
|
|
68
|
+
"'42'.convertsToDecimal()",
|
|
69
|
+
"'3.14'.convertsToDecimal()",
|
|
70
|
+
"true.convertsToDecimal()",
|
|
71
|
+
"'invalid'.convertsToDecimal()"
|
|
72
|
+
],
|
|
73
|
+
signatures: [
|
|
74
|
+
{
|
|
75
|
+
name: 'convertsToDecimal',
|
|
76
|
+
input: { type: 'Any', singleton: true },
|
|
77
|
+
parameters: [],
|
|
78
|
+
result: { type: 'Boolean', singleton: true }
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
evaluate
|
|
82
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
// convertsToInteger() takes no arguments
|
|
7
|
+
if (args.length !== 0) {
|
|
8
|
+
throw Errors.wrongArgumentCount('convertsToInteger', 0, args.length);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// If input collection is empty, result is empty
|
|
12
|
+
if (input.length === 0) {
|
|
13
|
+
return { value: [], context };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// If input collection contains multiple items, signal an error
|
|
17
|
+
if (input.length > 1) {
|
|
18
|
+
throw Errors.singletonRequired('convertsToInteger', input.length);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const boxedInputValue = input[0];
|
|
22
|
+
if (!boxedInputValue) {
|
|
23
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const inputValue = unbox(boxedInputValue);
|
|
27
|
+
|
|
28
|
+
// Check if the value can be converted to Integer
|
|
29
|
+
|
|
30
|
+
// Integer - always convertible
|
|
31
|
+
if (typeof inputValue === 'number' && Number.isInteger(inputValue)) {
|
|
32
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// String - check if valid integer format
|
|
36
|
+
if (typeof inputValue === 'string') {
|
|
37
|
+
// Regex from spec: (\+|-)?\d+
|
|
38
|
+
const integerRegex = /^(\+|-)?\d+$/;
|
|
39
|
+
const canConvert = integerRegex.test(inputValue);
|
|
40
|
+
return { value: [box(canConvert, { type: 'Boolean', singleton: true })], context };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Boolean - always convertible (true -> 1, false -> 0)
|
|
44
|
+
if (typeof inputValue === 'boolean') {
|
|
45
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// For all other types (including decimals), return false
|
|
49
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const convertsToIntegerFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
53
|
+
name: 'convertsToInteger',
|
|
54
|
+
category: ['type-conversion'],
|
|
55
|
+
description: 'Returns true if the input can be converted to an Integer. Returns true for: Integer (any), String matching regex (\\+|-)?\\d+, Boolean (any). Returns false for all other types including decimals.',
|
|
56
|
+
examples: [
|
|
57
|
+
"'42'.convertsToInteger()",
|
|
58
|
+
"'3.14'.convertsToInteger()",
|
|
59
|
+
"true.convertsToInteger()",
|
|
60
|
+
"3.14.convertsToInteger()"
|
|
61
|
+
],
|
|
62
|
+
signatures: [
|
|
63
|
+
{
|
|
64
|
+
name: 'convertsToInteger',
|
|
65
|
+
input: { type: 'Any', singleton: true },
|
|
66
|
+
parameters: [],
|
|
67
|
+
result: { type: 'Boolean', singleton: true }
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
evaluate
|
|
71
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
// convertsToLong() takes no arguments
|
|
7
|
+
if (args.length !== 0) {
|
|
8
|
+
throw Errors.wrongArgumentCount('convertsToLong', 0, args.length);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// If input collection is empty, result is empty
|
|
12
|
+
if (input.length === 0) {
|
|
13
|
+
return { value: [], context };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// If input collection contains multiple items, signal an error
|
|
17
|
+
if (input.length > 1) {
|
|
18
|
+
throw Errors.singletonRequired('convertsToLong', input.length);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const boxedInputValue = input[0];
|
|
22
|
+
if (!boxedInputValue) {
|
|
23
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const inputValue = unbox(boxedInputValue);
|
|
27
|
+
|
|
28
|
+
// Check if the value can be converted to Long
|
|
29
|
+
// Note: In JavaScript, we don't have a separate Long type, but we can check
|
|
30
|
+
// if the value would be a valid long (integer within safe bounds)
|
|
31
|
+
|
|
32
|
+
// Integer - always convertible to Long
|
|
33
|
+
if (typeof inputValue === 'number' && Number.isInteger(inputValue)) {
|
|
34
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// String - check if valid integer format (same as convertsToInteger)
|
|
38
|
+
if (typeof inputValue === 'string') {
|
|
39
|
+
// Regex from spec: (\+|-)?\d+
|
|
40
|
+
const integerRegex = /^(\+|-)?\d+$/;
|
|
41
|
+
if (!integerRegex.test(inputValue)) {
|
|
42
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// For Long, we should check if the value is within valid bounds
|
|
46
|
+
// In JavaScript, we can safely represent integers up to Number.MAX_SAFE_INTEGER
|
|
47
|
+
try {
|
|
48
|
+
const longValue = BigInt(inputValue);
|
|
49
|
+
// Check if it can be safely represented
|
|
50
|
+
// For FHIRPath, Long is typically 64-bit integer
|
|
51
|
+
const MAX_LONG = BigInt('9223372036854775807');
|
|
52
|
+
const MIN_LONG = BigInt('-9223372036854775808');
|
|
53
|
+
|
|
54
|
+
const canConvert = longValue >= MIN_LONG && longValue <= MAX_LONG;
|
|
55
|
+
return { value: [box(canConvert, { type: 'Boolean', singleton: true })], context };
|
|
56
|
+
} catch {
|
|
57
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Boolean - always convertible (true -> 1, false -> 0)
|
|
62
|
+
if (typeof inputValue === 'boolean') {
|
|
63
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// For all other types (including decimals), return false
|
|
67
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const convertsToLongFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
71
|
+
name: 'convertsToLong',
|
|
72
|
+
category: ['type-conversion'],
|
|
73
|
+
description: 'Returns true if the input can be converted to a Long (64-bit integer). Returns true for: Integer (any), String matching regex (\\+|-)?\\d+ within 64-bit bounds, Boolean (any). Returns false for all other types including decimals.',
|
|
74
|
+
examples: [
|
|
75
|
+
"'42'.convertsToLong()",
|
|
76
|
+
"'9223372036854775807'.convertsToLong()",
|
|
77
|
+
"'9223372036854775808'.convertsToLong()",
|
|
78
|
+
"true.convertsToLong()"
|
|
79
|
+
],
|
|
80
|
+
signatures: [
|
|
81
|
+
{
|
|
82
|
+
name: 'convertsToLong',
|
|
83
|
+
input: { type: 'Any', singleton: true },
|
|
84
|
+
parameters: [],
|
|
85
|
+
result: { type: 'Boolean', singleton: true }
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
evaluate
|
|
89
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
|
+
import { Errors } from '../errors';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
import { createQuantity, isValidQuantity } from '../complex-types/quantity-value';
|
|
5
|
+
|
|
6
|
+
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
7
|
+
// convertsToQuantity() takes no arguments
|
|
8
|
+
if (args.length !== 0) {
|
|
9
|
+
throw Errors.wrongArgumentCount('convertsToQuantity', 0, args.length);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// If input collection is empty, result is empty
|
|
13
|
+
if (input.length === 0) {
|
|
14
|
+
return { value: [], context };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// If input collection contains multiple items, signal an error
|
|
18
|
+
if (input.length > 1) {
|
|
19
|
+
throw Errors.singletonRequired('convertsToQuantity', input.length);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const boxedInputValue = input[0];
|
|
23
|
+
if (!boxedInputValue) {
|
|
24
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const inputValue = unbox(boxedInputValue);
|
|
28
|
+
const typeInfo = boxedInputValue.typeInfo;
|
|
29
|
+
|
|
30
|
+
// Check if the value can be converted to Quantity
|
|
31
|
+
|
|
32
|
+
// Already a Quantity
|
|
33
|
+
if (typeInfo?.type === 'Quantity') {
|
|
34
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check for Quantity object structure
|
|
38
|
+
if (inputValue && typeof inputValue === 'object') {
|
|
39
|
+
const obj = inputValue as any;
|
|
40
|
+
// Check if it looks like a Quantity (has value and unit properties)
|
|
41
|
+
if (typeof obj.value === 'number' && typeof obj.unit === 'string') {
|
|
42
|
+
// Validate that the unit is valid (either UCUM or calendar duration)
|
|
43
|
+
try {
|
|
44
|
+
const quantity = createQuantity(obj.value, obj.unit);
|
|
45
|
+
const isValid = isValidQuantity(quantity);
|
|
46
|
+
return { value: [box(isValid, { type: 'Boolean', singleton: true })], context };
|
|
47
|
+
} catch {
|
|
48
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// String - check if it can be parsed as a quantity (e.g., "10 mg", "5.5 km")
|
|
54
|
+
if (typeof inputValue === 'string') {
|
|
55
|
+
// Try to parse as quantity: number followed by space(s) and unit
|
|
56
|
+
// This matches the pattern: <number> <unit>
|
|
57
|
+
const quantityRegex = /^(\+|-)?\d+(\.\d+)?\s+.+$/;
|
|
58
|
+
|
|
59
|
+
if (!quantityRegex.test(inputValue.trim())) {
|
|
60
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Split into value and unit parts
|
|
64
|
+
const parts = inputValue.trim().split(/\s+/);
|
|
65
|
+
if (parts.length < 2) {
|
|
66
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const valueStr = parts[0];
|
|
70
|
+
const unit = parts.slice(1).join(' ');
|
|
71
|
+
|
|
72
|
+
const value = parseFloat(valueStr!);
|
|
73
|
+
if (isNaN(value)) {
|
|
74
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if the unit is valid
|
|
78
|
+
try {
|
|
79
|
+
const quantity = createQuantity(value, unit);
|
|
80
|
+
const isValid = isValidQuantity(quantity);
|
|
81
|
+
return { value: [box(isValid, { type: 'Boolean', singleton: true })], context };
|
|
82
|
+
} catch {
|
|
83
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Integer or Decimal with no unit - not a valid quantity
|
|
88
|
+
// (quantities must have units)
|
|
89
|
+
if (typeof inputValue === 'number') {
|
|
90
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// For all other types, return false
|
|
94
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const convertsToQuantityFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
98
|
+
name: 'convertsToQuantity',
|
|
99
|
+
category: ['type-conversion'],
|
|
100
|
+
description: 'Returns true if the input can be converted to a Quantity. Returns true for: Quantity (any), String in format "number unit" with valid UCUM or calendar duration unit. Returns false for numbers without units and all other types.',
|
|
101
|
+
examples: [
|
|
102
|
+
"'10 mg'.convertsToQuantity()",
|
|
103
|
+
"'5.5 km'.convertsToQuantity()",
|
|
104
|
+
"10.convertsToQuantity()",
|
|
105
|
+
"'invalid'.convertsToQuantity()"
|
|
106
|
+
],
|
|
107
|
+
signatures: [
|
|
108
|
+
{
|
|
109
|
+
name: 'convertsToQuantity',
|
|
110
|
+
input: { type: 'Any', singleton: true },
|
|
111
|
+
parameters: [],
|
|
112
|
+
result: { type: 'Boolean', singleton: true }
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
evaluate
|
|
116
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
// convertsToString() takes no arguments
|
|
7
|
+
if (args.length !== 0) {
|
|
8
|
+
throw Errors.wrongArgumentCount('convertsToString', 0, args.length);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// If input collection is empty, result is empty
|
|
12
|
+
if (input.length === 0) {
|
|
13
|
+
return { value: [], context };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// If input collection contains multiple items, signal an error
|
|
17
|
+
if (input.length > 1) {
|
|
18
|
+
throw Errors.singletonRequired('convertsToString', input.length);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const boxedInputValue = input[0];
|
|
22
|
+
if (!boxedInputValue) {
|
|
23
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const inputValue = unbox(boxedInputValue);
|
|
27
|
+
|
|
28
|
+
// Check if the value can be converted to String
|
|
29
|
+
|
|
30
|
+
// String - always convertible
|
|
31
|
+
if (typeof inputValue === 'string') {
|
|
32
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Integer or Decimal - always convertible
|
|
36
|
+
if (typeof inputValue === 'number') {
|
|
37
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Boolean - always convertible
|
|
41
|
+
if (typeof inputValue === 'boolean') {
|
|
42
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check for temporal types (Date, Time, DateTime) - these are convertible
|
|
46
|
+
if (inputValue && typeof inputValue === 'object') {
|
|
47
|
+
// Check if it has type property indicating temporal type
|
|
48
|
+
const objWithType = inputValue as any;
|
|
49
|
+
if (objWithType.type === 'Date' || objWithType.type === 'DateTime' || objWithType.type === 'Time') {
|
|
50
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check boxed type info
|
|
54
|
+
const typeInfo = boxedInputValue.typeInfo;
|
|
55
|
+
if (typeInfo?.type === 'Date' || typeInfo?.type === 'DateTime' || typeInfo?.type === 'Time') {
|
|
56
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check for Quantity type
|
|
60
|
+
if (objWithType.type === 'Quantity' || typeInfo?.type === 'Quantity') {
|
|
61
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// For complex objects and other types, return false
|
|
66
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const convertsToStringFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
70
|
+
name: 'convertsToString',
|
|
71
|
+
category: ['type-conversion'],
|
|
72
|
+
description: 'Returns true if the input can be converted to a String. Returns true for: String (any), Integer (any), Decimal (any), Boolean (any), Date, DateTime, Time, Quantity. Returns false for complex objects and resources.',
|
|
73
|
+
examples: [
|
|
74
|
+
"'test'.convertsToString()",
|
|
75
|
+
"42.convertsToString()",
|
|
76
|
+
"true.convertsToString()",
|
|
77
|
+
"Patient.convertsToString()"
|
|
78
|
+
],
|
|
79
|
+
signatures: [
|
|
80
|
+
{
|
|
81
|
+
name: 'convertsToString',
|
|
82
|
+
input: { type: 'Any', singleton: true },
|
|
83
|
+
parameters: [],
|
|
84
|
+
result: { type: 'Boolean', singleton: true }
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
evaluate
|
|
88
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition } from '../types';
|
|
2
2
|
import type { FunctionEvaluator } from '../types';
|
|
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
|
return {
|
|
@@ -11,6 +11,7 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
|
|
|
11
11
|
|
|
12
12
|
export const countFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
13
13
|
name: 'count',
|
|
14
|
+
doesNotPropagateEmpty: true,
|
|
14
15
|
category: ['collection'],
|
|
15
16
|
description: 'Returns the number of items in the collection',
|
|
16
17
|
examples: ['Patient.name.count()'],
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// dateOf() function - Extracts date component from Date or DateTime
|
|
2
|
+
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
import { createDate, isFHIRDate, isFHIRDateTime } from '../complex-types/temporal';
|
|
5
|
+
import { Errors } from '../errors';
|
|
6
|
+
|
|
7
|
+
export const dateOfEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
8
|
+
// dateOf() takes no arguments
|
|
9
|
+
if (args.length !== 0) {
|
|
10
|
+
throw Errors.wrongArgumentCount('dateOf', 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('dateOf', 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 Date or DateTime
|
|
31
|
+
if (isFHIRDate(value)) {
|
|
32
|
+
// Already a Date, return as-is
|
|
33
|
+
return {
|
|
34
|
+
value: [box(value, { type: 'Date', singleton: true })],
|
|
35
|
+
context
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isFHIRDateTime(value)) {
|
|
40
|
+
// Extract date component (preserve precision)
|
|
41
|
+
const date = createDate(value.year, value.month, value.day);
|
|
42
|
+
return {
|
|
43
|
+
value: [box(date, { type: 'Date', singleton: true })],
|
|
44
|
+
context
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Not a Date or DateTime, return empty
|
|
49
|
+
return { value: [], context };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const dateOfFunction: FunctionDefinition & { evaluate: typeof dateOfEvaluator } = {
|
|
53
|
+
name: 'dateOf',
|
|
54
|
+
category: ['temporal'],
|
|
55
|
+
description: 'Returns the date component of a Date or DateTime value',
|
|
56
|
+
examples: [
|
|
57
|
+
'@2012-01-01T12:30:00.dateOf()',
|
|
58
|
+
'Patient.birthDate.dateOf()'
|
|
59
|
+
],
|
|
60
|
+
signatures: [
|
|
61
|
+
{
|
|
62
|
+
name: 'dateOf',
|
|
63
|
+
input: { type: 'Any', singleton: true },
|
|
64
|
+
parameters: [],
|
|
65
|
+
result: { type: 'Date', singleton: true }
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
evaluate: dateOfEvaluator
|
|
69
|
+
};
|