@atomic-ehr/fhirpath 0.0.2 → 0.0.3-canary.2be66fb.20250905161900
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 +226 -120
- package/dist/index.js +11552 -5580
- package/dist/index.js.map +1 -1
- package/package.json +12 -5
- 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 +939 -1204
- package/src/completion-provider.ts +209 -191
- package/src/complex-types/quantity-value.ts +410 -0
- package/src/complex-types/temporal.ts +1776 -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 +506 -468
- package/src/lexer.ts +192 -211
- 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 +99 -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 +744 -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 +132 -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/extension-function.ts +84 -0
- package/src/operations/first-function.ts +1 -1
- package/src/operations/floor-function.ts +1 -1
- package/src/operations/greater-operator.ts +7 -9
- package/src/operations/greater-or-equal-operator.ts +7 -9
- package/src/operations/highBoundary-function.ts +120 -0
- package/src/operations/hourOf-function.ts +66 -0
- package/src/operations/iif-function.ts +193 -8
- package/src/operations/implies-operator.ts +2 -1
- package/src/operations/in-operator.ts +2 -1
- package/src/operations/index.ts +43 -0
- package/src/operations/indexOf-function.ts +1 -1
- package/src/operations/intersect-function.ts +1 -1
- package/src/operations/is-function.ts +70 -0
- package/src/operations/is-operator.ts +176 -13
- 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 +8 -9
- package/src/operations/less-or-equal-operator.ts +7 -9
- package/src/operations/less-than.ts +8 -13
- 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 +76 -4
- package/src/operations/minuteOf-function.ts +66 -0
- package/src/operations/mod-operator.ts +8 -2
- 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 +10 -3
- package/src/operations/ofType-function.ts +43 -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/precision-function.ts +146 -0
- package/src/operations/repeat-function.ts +169 -0
- package/src/operations/replace-function.ts +1 -1
- package/src/operations/replaceMatches-function.ts +125 -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 +78 -15
- 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 +262 -503
- package/src/registry.ts +53 -42
- package/src/types.ts +129 -17
- package/src/utils/decimal.ts +76 -0
- package/src/utils/pprint.ts +151 -0
- package/src/quantity-value.ts +0 -198
|
@@ -1,64 +1,23 @@
|
|
|
1
1
|
import type { OperatorDefinition } from '../types';
|
|
2
2
|
import { PRECEDENCE } from '../types';
|
|
3
3
|
import type { OperationEvaluator } from '../types';
|
|
4
|
-
import { box
|
|
4
|
+
import { box } from '../interpreter/boxing';
|
|
5
|
+
import { collectionsEquivalent } from './comparison';
|
|
5
6
|
|
|
6
7
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Different sizes are not equivalent
|
|
13
|
-
if (left.length !== right.length) {
|
|
14
|
-
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// For single items, check type-specific equivalence
|
|
18
|
-
if (left.length === 1 && right.length === 1) {
|
|
19
|
-
const boxedl = left[0];
|
|
20
|
-
if (!boxedl) return { value: [], context };
|
|
21
|
-
const l = unbox(boxedl);
|
|
22
|
-
const boxedr = right[0];
|
|
23
|
-
if (!boxedr) return { value: [], context };
|
|
24
|
-
const r = unbox(boxedr);
|
|
25
|
-
|
|
26
|
-
// String equivalence: case-insensitive with normalized whitespace
|
|
27
|
-
if (typeof l === 'string' && typeof r === 'string') {
|
|
28
|
-
const normalizeString = (s: string) => s.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
29
|
-
return { value: [box(normalizeString(l) === normalizeString(r), { type: 'Boolean', singleton: true })], context };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Number equivalence (for Integer and Decimal)
|
|
33
|
-
if (typeof l === 'number' && typeof r === 'number') {
|
|
34
|
-
// For decimals with different precision, round to least precise
|
|
35
|
-
// This is a simplified implementation
|
|
36
|
-
return { value: [box(Math.abs(l - r) < Number.EPSILON, { type: 'Boolean', singleton: true })], context };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Boolean equivalence
|
|
40
|
-
if (typeof l === 'boolean' && typeof r === 'boolean') {
|
|
41
|
-
return { value: [box(l === r, { type: 'Boolean', singleton: true })], context };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// For complex types and other cases, use equality for now
|
|
45
|
-
// TODO: Implement full equivalence logic for Date/DateTime/Time and complex types
|
|
46
|
-
return { value: [box(l === r, { type: 'Boolean', singleton: true })], context };
|
|
47
|
-
}
|
|
8
|
+
// Use the new collectionsEquivalent function from comparison.ts
|
|
9
|
+
const result = collectionsEquivalent(left, right);
|
|
48
10
|
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const leftSorted = [...left].sort();
|
|
53
|
-
const rightSorted = [...right].sort();
|
|
54
|
-
|
|
55
|
-
for (let i = 0; i < leftSorted.length; i++) {
|
|
56
|
-
if (leftSorted[i] !== rightSorted[i]) {
|
|
57
|
-
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
58
|
-
}
|
|
11
|
+
// null result means incomparable - return empty collection
|
|
12
|
+
if (result === null) {
|
|
13
|
+
return { value: [], context };
|
|
59
14
|
}
|
|
60
15
|
|
|
61
|
-
|
|
16
|
+
// Return boolean result
|
|
17
|
+
return {
|
|
18
|
+
value: [box(result, { type: 'Boolean', singleton: true })],
|
|
19
|
+
context
|
|
20
|
+
};
|
|
62
21
|
};
|
|
63
22
|
|
|
64
23
|
export const equivalentOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
@@ -67,6 +26,7 @@ export const equivalentOperator: OperatorDefinition & { evaluate: OperationEvalu
|
|
|
67
26
|
category: ['equality'],
|
|
68
27
|
precedence: PRECEDENCE.EQUALITY,
|
|
69
28
|
associativity: 'left',
|
|
29
|
+
doesNotPropagateEmpty: true, // Empty collections are valid operands for equivalence
|
|
70
30
|
description: 'Returns true if the collections are the same. For single items: strings are compared case-insensitive with normalized whitespace, decimals are rounded to least precision, dates with different precision return false. For collections: order-independent comparison. Empty ~ empty returns true (unlike =)',
|
|
71
31
|
examples: [
|
|
72
32
|
"'abc' ~ 'ABC'",
|
|
@@ -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
|
if (args.length !== 1) {
|
|
@@ -44,6 +44,7 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
|
|
|
44
44
|
|
|
45
45
|
export const excludeFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
46
46
|
name: 'exclude',
|
|
47
|
+
doesNotPropagateEmpty: true, // exclude with empty argument returns the original collection
|
|
47
48
|
category: ['collection'],
|
|
48
49
|
description: 'Returns the set of elements that are not in the other collection. Duplicate items will not be eliminated by this function, and order will be preserved.',
|
|
49
50
|
examples: [
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { FunctionDefinition } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { RuntimeContextManager } from '../interpreter';
|
|
3
|
+
import { RuntimeContextManager } from '../interpreter/runtime-context';
|
|
4
4
|
import { type FunctionEvaluator } from '../types';
|
|
5
|
-
import { box, unbox } from '../boxing';
|
|
5
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
6
6
|
|
|
7
7
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
8
8
|
// No arguments - just check if input is not empty
|
|
@@ -42,6 +42,7 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
|
|
|
42
42
|
|
|
43
43
|
export const existsFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
44
44
|
name: 'exists',
|
|
45
|
+
doesNotPropagateEmpty: true,
|
|
45
46
|
category: ['collection', 'logical'],
|
|
46
47
|
description: 'Returns true if the collection has any items, or if any item satisfies the condition',
|
|
47
48
|
examples: ['Patient.name.exists()', 'Patient.name.exists(use = "official")'],
|
|
@@ -55,4 +56,4 @@ export const existsFunction: FunctionDefinition & { evaluate: FunctionEvaluator
|
|
|
55
56
|
result: { type: 'Boolean', singleton: true },
|
|
56
57
|
}],
|
|
57
58
|
evaluate
|
|
58
|
-
};
|
|
59
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { FunctionDefinition, FunctionEvaluator, ASTNode, RuntimeContext, NodeEvaluator } from '../types';
|
|
2
|
+
import type { FHIRPathValue } from '../interpreter/boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
import { Errors } from '../errors';
|
|
5
|
+
|
|
6
|
+
const extensionEvaluator: FunctionEvaluator = async (
|
|
7
|
+
input: FHIRPathValue[],
|
|
8
|
+
context: RuntimeContext,
|
|
9
|
+
args: ASTNode[],
|
|
10
|
+
evaluator: NodeEvaluator
|
|
11
|
+
) => {
|
|
12
|
+
// extension() function takes one argument - the URL to match
|
|
13
|
+
if (args.length !== 1) {
|
|
14
|
+
throw Errors.invalidOperation('extension() requires exactly one argument');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Evaluate the URL argument
|
|
18
|
+
const urlResult = await evaluator(args[0]!, input, context);
|
|
19
|
+
if (urlResult.value.length !== 1) {
|
|
20
|
+
throw Errors.invalidOperation('extension() URL argument must be a single value');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const urlToMatch = unbox(urlResult.value[0]);
|
|
24
|
+
if (typeof urlToMatch !== 'string') {
|
|
25
|
+
throw Errors.invalidOperation('extension() URL argument must be a string');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const results: FHIRPathValue[] = [];
|
|
29
|
+
|
|
30
|
+
// For each input item, look for extensions
|
|
31
|
+
for (const boxedItem of input) {
|
|
32
|
+
const item = unbox(boxedItem);
|
|
33
|
+
|
|
34
|
+
// First check if this is a primitive value with extensions in primitiveElement
|
|
35
|
+
if (boxedItem.primitiveElement?.extension) {
|
|
36
|
+
const extensions = boxedItem.primitiveElement.extension;
|
|
37
|
+
for (const ext of extensions) {
|
|
38
|
+
if (ext && typeof ext === 'object' && 'url' in ext && ext.url === urlToMatch) {
|
|
39
|
+
const boxedExt = box(ext, { type: 'Extension' as any, singleton: true });
|
|
40
|
+
results.push(boxedExt);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Then check for direct extension property on complex types
|
|
46
|
+
if (item && typeof item === 'object' && 'extension' in item) {
|
|
47
|
+
const extensions = (item as any).extension;
|
|
48
|
+
if (Array.isArray(extensions)) {
|
|
49
|
+
for (const ext of extensions) {
|
|
50
|
+
if (ext && typeof ext === 'object' && 'url' in ext && ext.url === urlToMatch) {
|
|
51
|
+
const boxedExt = box(ext, { type: 'Extension' as any, singleton: true });
|
|
52
|
+
results.push(boxedExt);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { value: results, context };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const extensionFunction: FunctionDefinition & { evaluate: typeof extensionEvaluator } = {
|
|
63
|
+
name: 'extension',
|
|
64
|
+
category: ['navigation'],
|
|
65
|
+
description: 'Returns extensions matching the specified URL',
|
|
66
|
+
examples: [
|
|
67
|
+
"Patient.birthDate.extension('http://hl7.org/fhir/StructureDefinition/patient-birthTime')",
|
|
68
|
+
"Observation.extension('http://example.com/fhir/StructureDefinition/patient-age')"
|
|
69
|
+
],
|
|
70
|
+
signatures: [
|
|
71
|
+
{
|
|
72
|
+
name: 'extension-by-url',
|
|
73
|
+
parameters: [{
|
|
74
|
+
name: 'url',
|
|
75
|
+
type: { type: 'String', singleton: true },
|
|
76
|
+
expression: true
|
|
77
|
+
}],
|
|
78
|
+
input: { type: 'Any', singleton: false },
|
|
79
|
+
result: { type: 'Extension' as any, singleton: false }
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
doesNotPropagateEmpty: false,
|
|
83
|
+
evaluate: extensionEvaluator
|
|
84
|
+
};
|
|
@@ -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
|
if (input.length > 0) {
|
|
@@ -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
|
// floor() takes no arguments
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { OperatorDefinition } from '../types';
|
|
2
2
|
import { PRECEDENCE } from '../types';
|
|
3
3
|
import type { OperationEvaluator } from '../types';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import { box, unbox } from '../boxing';
|
|
4
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
5
|
+
import { compare } from './comparison';
|
|
7
6
|
|
|
8
7
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
9
8
|
if (left.length === 0 || right.length === 0) {
|
|
@@ -17,14 +16,13 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
17
16
|
if (!boxedr) return { value: [], context };
|
|
18
17
|
const r = unbox(boxedr);
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return { value: result !== null ? [box(result > 0, { type: 'Boolean', singleton: true })] : [], context };
|
|
19
|
+
const comparisonResult = compare(l, r);
|
|
20
|
+
|
|
21
|
+
if (comparisonResult.kind === 'incomparable') {
|
|
22
|
+
return { value: [], context };
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
return { value: [box(
|
|
25
|
+
return { value: [box(comparisonResult.kind === 'greater', { type: 'Boolean', singleton: true })], context };
|
|
28
26
|
};
|
|
29
27
|
|
|
30
28
|
export const greaterOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { OperatorDefinition } from '../types';
|
|
2
2
|
import { PRECEDENCE } from '../types';
|
|
3
3
|
import type { OperationEvaluator } from '../types';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import { box, unbox } from '../boxing';
|
|
4
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
5
|
+
import { compare } from './comparison';
|
|
7
6
|
|
|
8
7
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
9
8
|
if (left.length === 0 || right.length === 0) {
|
|
@@ -17,14 +16,13 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
17
16
|
if (!boxedr) return { value: [], context };
|
|
18
17
|
const r = unbox(boxedr);
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return { value: result !== null ? [box(result >= 0, { type: 'Boolean', singleton: true })] : [], context };
|
|
19
|
+
const comparisonResult = compare(l, r);
|
|
20
|
+
|
|
21
|
+
if (comparisonResult.kind === 'incomparable') {
|
|
22
|
+
return { value: [], context };
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
return { value: [box(
|
|
25
|
+
return { value: [box(comparisonResult.kind === 'greater' || comparisonResult.kind === 'equal', { type: 'Boolean', singleton: true })], context };
|
|
28
26
|
};
|
|
29
27
|
|
|
30
28
|
export const greaterOrEqualOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// highBoundary() function - Returns the greatest possible value to the specified precision
|
|
2
|
+
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
import {
|
|
5
|
+
isFHIRDate, isFHIRDateTime, isFHIRTime,
|
|
6
|
+
getDateHighBoundary, getDateTimeHighBoundary, getTimeHighBoundary
|
|
7
|
+
} from '../complex-types/temporal';
|
|
8
|
+
import { getDecimalHighBoundary } from './decimal-boundaries';
|
|
9
|
+
import { Errors } from '../errors';
|
|
10
|
+
|
|
11
|
+
export const highBoundaryEvaluator: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
12
|
+
// highBoundary() takes optional precision parameter
|
|
13
|
+
if (args.length > 1) {
|
|
14
|
+
throw Errors.wrongArgumentCountRange('highBoundary', 0, 1, args.length);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Empty input returns empty
|
|
18
|
+
if (input.length === 0) {
|
|
19
|
+
return { value: [], context };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Multiple items throws error
|
|
23
|
+
if (input.length > 1) {
|
|
24
|
+
throw Errors.singletonRequired('highBoundary', input.length);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const boxedValue = input[0];
|
|
28
|
+
if (!boxedValue) {
|
|
29
|
+
return { value: [], context };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const value = unbox(boxedValue);
|
|
33
|
+
|
|
34
|
+
// Get precision if provided
|
|
35
|
+
let precision: number | undefined;
|
|
36
|
+
if (args.length === 1) {
|
|
37
|
+
const precisionResult = await evaluator(args[0]!, input, context);
|
|
38
|
+
const precisionArg = precisionResult.value;
|
|
39
|
+
if (precisionArg.length === 0) {
|
|
40
|
+
return { value: [], context };
|
|
41
|
+
}
|
|
42
|
+
if (precisionArg.length > 1) {
|
|
43
|
+
throw Errors.singletonRequired('highBoundary precision', precisionArg.length);
|
|
44
|
+
}
|
|
45
|
+
const precisionValue = unbox(precisionArg[0]!);
|
|
46
|
+
if (typeof precisionValue !== 'number' || !Number.isInteger(precisionValue)) {
|
|
47
|
+
throw Errors.invalidOperandType('highBoundary precision', typeof precisionValue);
|
|
48
|
+
}
|
|
49
|
+
precision = precisionValue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Handle Date
|
|
53
|
+
if (isFHIRDate(value)) {
|
|
54
|
+
const result = getDateHighBoundary(value, precision);
|
|
55
|
+
if (!result) {
|
|
56
|
+
return { value: [], context };
|
|
57
|
+
}
|
|
58
|
+
return { value: [box(result, { type: 'Date', singleton: true })], context };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Handle DateTime
|
|
62
|
+
if (isFHIRDateTime(value)) {
|
|
63
|
+
const result = getDateTimeHighBoundary(value, precision);
|
|
64
|
+
if (!result) {
|
|
65
|
+
return { value: [], context };
|
|
66
|
+
}
|
|
67
|
+
return { value: [box(result, { type: 'DateTime', singleton: true })], context };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle Time
|
|
71
|
+
if (isFHIRTime(value)) {
|
|
72
|
+
const result = getTimeHighBoundary(value, precision);
|
|
73
|
+
if (!result) {
|
|
74
|
+
return { value: [], context };
|
|
75
|
+
}
|
|
76
|
+
return { value: [box(result, { type: 'Time', singleton: true })], context };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// For Decimal/Integer types
|
|
80
|
+
if (typeof value === 'number') {
|
|
81
|
+
const result = getDecimalHighBoundary(value, precision);
|
|
82
|
+
if (result === null) {
|
|
83
|
+
return { value: [], context };
|
|
84
|
+
}
|
|
85
|
+
// Determine the result type based on whether it's an integer or decimal
|
|
86
|
+
const isInteger = Number.isInteger(result);
|
|
87
|
+
return {
|
|
88
|
+
value: [box(result, { type: isInteger ? 'Integer' : 'Decimal', singleton: true })],
|
|
89
|
+
context
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Invalid type returns empty
|
|
94
|
+
return { value: [], context };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const highBoundaryFunction: FunctionDefinition & { evaluate: typeof highBoundaryEvaluator } = {
|
|
98
|
+
name: 'highBoundary',
|
|
99
|
+
category: ['utility'],
|
|
100
|
+
description: 'Returns the greatest possible value of the input to the specified precision',
|
|
101
|
+
examples: [
|
|
102
|
+
'@2014.highBoundary(6)',
|
|
103
|
+
'@2014-01-01T08.highBoundary(17)',
|
|
104
|
+
'@T10:30.highBoundary(9)',
|
|
105
|
+
'1.587.highBoundary()',
|
|
106
|
+
'1.587.highBoundary(2)',
|
|
107
|
+
'1.highBoundary(0)'
|
|
108
|
+
],
|
|
109
|
+
signatures: [
|
|
110
|
+
{
|
|
111
|
+
name: 'highBoundary',
|
|
112
|
+
input: { type: 'Any' as const, singleton: true },
|
|
113
|
+
parameters: [
|
|
114
|
+
{ name: 'precision', type: { type: 'Integer' as const, singleton: true }, optional: true }
|
|
115
|
+
],
|
|
116
|
+
result: { type: 'Any' as const, singleton: true }
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
evaluate: highBoundaryEvaluator
|
|
120
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// hourOf() function - Extracts hour component from Time or DateTime
|
|
2
|
+
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
import { isFHIRTime, isFHIRDateTime } from '../complex-types/temporal';
|
|
5
|
+
import { Errors } from '../errors';
|
|
6
|
+
|
|
7
|
+
export const hourOfEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
8
|
+
// hourOf() takes no arguments
|
|
9
|
+
if (args.length !== 0) {
|
|
10
|
+
throw Errors.wrongArgumentCount('hourOf', 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('hourOf', input.length);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const boxedValue = input[0];
|
|
24
|
+
if (!boxedValue) {
|
|
25
|
+
return { value: [], context };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const value = unbox(boxedValue);
|
|
29
|
+
|
|
30
|
+
// Check if it's a Time or DateTime
|
|
31
|
+
if (isFHIRTime(value) || isFHIRDateTime(value)) {
|
|
32
|
+
// Check if hour component is present
|
|
33
|
+
if (value.hour === undefined) {
|
|
34
|
+
return { value: [], context };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return the hour as an Integer (0-23)
|
|
38
|
+
return {
|
|
39
|
+
value: [box(value.hour, { type: 'Integer', singleton: true })],
|
|
40
|
+
context
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Not a Time or DateTime, return empty
|
|
45
|
+
return { value: [], context };
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const hourOfFunction: FunctionDefinition & { evaluate: typeof hourOfEvaluator } = {
|
|
49
|
+
name: 'hourOf',
|
|
50
|
+
category: ['temporal'],
|
|
51
|
+
description: 'Returns the hour component of a Time or DateTime value (0-23)',
|
|
52
|
+
examples: [
|
|
53
|
+
'@T10:30:00.hourOf()',
|
|
54
|
+
'@2014-01-05T10:30:00.hourOf()',
|
|
55
|
+
'Patient.birthDate.hourOf()'
|
|
56
|
+
],
|
|
57
|
+
signatures: [
|
|
58
|
+
{
|
|
59
|
+
name: 'hourOf',
|
|
60
|
+
input: { type: 'Any', singleton: true },
|
|
61
|
+
parameters: [],
|
|
62
|
+
result: { type: 'Integer', singleton: true }
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
evaluate: hourOfEvaluator
|
|
66
|
+
};
|