@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
package/src/operations/index.ts
CHANGED
|
@@ -34,6 +34,8 @@ export { containsOperator } from './contains-operator';
|
|
|
34
34
|
export { isOperator } from './is-operator';
|
|
35
35
|
export { asOperator } from './as-operator';
|
|
36
36
|
export { ofTypeFunction } from './ofType-function';
|
|
37
|
+
export { isFunction } from './is-function';
|
|
38
|
+
export { asFunction } from './as-function';
|
|
37
39
|
|
|
38
40
|
// Other operators
|
|
39
41
|
export { unionOperator } from './union-operator';
|
|
@@ -43,6 +45,7 @@ export { dotOperator } from './dot-operator';
|
|
|
43
45
|
// Functions
|
|
44
46
|
export { whereFunction } from './where-function';
|
|
45
47
|
export { selectFunction } from './select-function';
|
|
48
|
+
export { repeatFunction } from './repeat-function';
|
|
46
49
|
export { firstFunction } from './first-function';
|
|
47
50
|
export { lastFunction } from './last-function';
|
|
48
51
|
export { childrenFunction } from './children-function';
|
|
@@ -60,6 +63,19 @@ export { distinctFunction } from './distinct-function';
|
|
|
60
63
|
export { isDistinctFunction } from './isDistinct-function';
|
|
61
64
|
export { iifFunction } from './iif-function';
|
|
62
65
|
export { defineVariableFunction } from './defineVariable-function';
|
|
66
|
+
|
|
67
|
+
// Temporal functions
|
|
68
|
+
export {
|
|
69
|
+
nowFunction,
|
|
70
|
+
todayFunction,
|
|
71
|
+
timeOfDayFunction,
|
|
72
|
+
toDateFunction,
|
|
73
|
+
toDateTimeFunction,
|
|
74
|
+
toTimeFunction,
|
|
75
|
+
convertsToDateFunction,
|
|
76
|
+
convertsToDateTimeFunction,
|
|
77
|
+
convertsToTimeFunction
|
|
78
|
+
} from './temporal-functions';
|
|
63
79
|
export { joinFunction } from './join-function';
|
|
64
80
|
export { replaceFunction } from './replace-function';
|
|
65
81
|
export { unionFunction } from './union-function';
|
|
@@ -67,7 +83,12 @@ export { combineFunction } from './combine-function';
|
|
|
67
83
|
export { intersectFunction } from './intersect-function';
|
|
68
84
|
export { excludeFunction } from './exclude-function';
|
|
69
85
|
export { indexOfFunction } from './indexOf-function';
|
|
86
|
+
export { lastIndexOfFunction } from './lastIndexOf-function';
|
|
70
87
|
export { substringFunction } from './substring-function';
|
|
88
|
+
export { matchesFunction } from './matches-function';
|
|
89
|
+
export { matchesFullFunction } from './matchesFull-function';
|
|
90
|
+
export { replaceMatchesFunction } from './replaceMatches-function';
|
|
91
|
+
export { toCharsFunction } from './toChars-function';
|
|
71
92
|
export { containsFunction } from './contains-function';
|
|
72
93
|
export { startsWithFunction } from './startsWith-function';
|
|
73
94
|
export { endsWithFunction } from './endsWith-function';
|
|
@@ -88,9 +109,29 @@ export { toIntegerFunction } from './toInteger-function';
|
|
|
88
109
|
export { toDecimalFunction } from './toDecimal-function';
|
|
89
110
|
export { toStringFunction } from './toString-function';
|
|
90
111
|
export { toBooleanFunction } from './toBoolean-function';
|
|
112
|
+
export { toQuantityFunction } from './toQuantity-function';
|
|
113
|
+
export { toLongFunction } from './toLong-function';
|
|
114
|
+
export { convertsToBooleanFunction } from './convertsToBoolean-function';
|
|
115
|
+
export { convertsToIntegerFunction } from './convertsToInteger-function';
|
|
116
|
+
export { convertsToDecimalFunction } from './convertsToDecimal-function';
|
|
117
|
+
export { convertsToStringFunction } from './convertsToString-function';
|
|
118
|
+
export { convertsToQuantityFunction } from './convertsToQuantity-function';
|
|
119
|
+
export { convertsToLongFunction } from './convertsToLong-function';
|
|
91
120
|
|
|
92
121
|
// Utility functions
|
|
93
122
|
export { traceFunction } from './trace-function';
|
|
123
|
+
export { dateOfFunction } from './dateOf-function';
|
|
124
|
+
export { timeOfFunction } from './timeOf-function';
|
|
125
|
+
export { yearOfFunction } from './yearOf-function';
|
|
126
|
+
export { monthOfFunction } from './monthOf-function';
|
|
127
|
+
export { dayOfFunction } from './dayOf-function';
|
|
128
|
+
export { hourOfFunction } from './hourOf-function';
|
|
129
|
+
export { minuteOfFunction } from './minuteOf-function';
|
|
130
|
+
export { secondOfFunction } from './secondOf-function';
|
|
131
|
+
export { millisecondOfFunction } from './millisecondOf-function';
|
|
132
|
+
export { timezoneOffsetOfFunction } from './timezoneOffsetOf-function';
|
|
133
|
+
export { lowBoundaryFunction } from './lowBoundary-function';
|
|
134
|
+
export { highBoundaryFunction } from './highBoundary-function';
|
|
94
135
|
|
|
95
136
|
// Aggregate functions
|
|
96
137
|
export { aggregateFunction } from './aggregate-function';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator, LiteralNode } 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
|
// Check single item in input
|
|
@@ -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) {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { FunctionDefinition, FunctionEvaluator, ASTNode, RuntimeContext, NodeEvaluator } from '../types';
|
|
2
|
+
import type { FHIRPathValue } from '../interpreter/boxing';
|
|
3
|
+
import { NodeType, isIdentifierNode } from '../types';
|
|
4
|
+
import { box } from '../interpreter/boxing';
|
|
5
|
+
import { evaluate as isOperatorEvaluate } from './is-operator';
|
|
6
|
+
|
|
7
|
+
const isEvaluator: FunctionEvaluator = async (
|
|
8
|
+
input: FHIRPathValue[],
|
|
9
|
+
context: RuntimeContext,
|
|
10
|
+
args: ASTNode[],
|
|
11
|
+
evaluator: NodeEvaluator
|
|
12
|
+
) => {
|
|
13
|
+
// is() function takes one argument - the type name
|
|
14
|
+
if (args.length !== 1) {
|
|
15
|
+
return { value: [], context };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const typeArg = args[0];
|
|
19
|
+
if (!typeArg) {
|
|
20
|
+
return { value: [], context };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Extract type name from the argument AST node
|
|
24
|
+
let typeName: string;
|
|
25
|
+
|
|
26
|
+
if (isIdentifierNode(typeArg)) {
|
|
27
|
+
typeName = typeArg.name;
|
|
28
|
+
} else {
|
|
29
|
+
// For other node types, try to get the name
|
|
30
|
+
throw new Error(`is() requires a type name as argument, got ${typeArg.type}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Use the is operator implementation with the type name
|
|
34
|
+
return isOperatorEvaluate(input, context, input, [typeName]);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { isEvaluator };
|
|
38
|
+
|
|
39
|
+
export const isFunction: FunctionDefinition & { evaluate: typeof isEvaluator } = {
|
|
40
|
+
name: 'is',
|
|
41
|
+
category: ['type'],
|
|
42
|
+
description: 'Tests if the input is of the specified type',
|
|
43
|
+
examples: ['Patient.name.is(HumanName)', '"hello".is(String)', '5.is(Integer)'],
|
|
44
|
+
signatures: [
|
|
45
|
+
{
|
|
46
|
+
name: 'is-type-check',
|
|
47
|
+
parameters: [{
|
|
48
|
+
name: 'type',
|
|
49
|
+
type: { type: 'Any', singleton: true },
|
|
50
|
+
expression: true,
|
|
51
|
+
typeReference: true
|
|
52
|
+
}],
|
|
53
|
+
input: { type: 'Any', singleton: true },
|
|
54
|
+
result: { type: 'Boolean', singleton: true }
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
doesNotPropagateEmpty: false,
|
|
58
|
+
evaluate: isEvaluator
|
|
59
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { OperatorDefinition, TypeName } from '../types';
|
|
2
2
|
import { PRECEDENCE } from '../types';
|
|
3
3
|
import type { OperationEvaluator } from '../types';
|
|
4
|
-
import { box, unbox } from '../boxing';
|
|
4
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
5
|
+
import { isFHIRDate, isFHIRDateTime, isFHIRTime } from '../complex-types/temporal';
|
|
5
6
|
|
|
6
7
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
7
8
|
// Right operand should be a type identifier
|
|
@@ -14,7 +15,7 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
14
15
|
const item = unbox(boxedItem);
|
|
15
16
|
const typeName = right[0] as string; // Should be a type name like 'String', 'Integer', etc.
|
|
16
17
|
|
|
17
|
-
// If we have a ModelProvider
|
|
18
|
+
// If we have a ModelProvider and typeInfo, use it for accurate type checking (handles subtypes)
|
|
18
19
|
if (context.modelProvider && boxedItem?.typeInfo) {
|
|
19
20
|
const matchingType = context.modelProvider.ofType(boxedItem.typeInfo, typeName as TypeName);
|
|
20
21
|
return {
|
|
@@ -23,9 +24,8 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
// Check if the box has type information
|
|
27
|
+
// Check if the box has type information (without ModelProvider, just check exact match)
|
|
27
28
|
if (boxedItem?.typeInfo) {
|
|
28
|
-
// For now, just check exact type match (no subtype support without ModelProvider)
|
|
29
29
|
return {
|
|
30
30
|
value: [box(boxedItem.typeInfo.type === typeName, { type: 'Boolean', singleton: true })],
|
|
31
31
|
context
|
|
@@ -35,9 +35,7 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
35
35
|
// For FHIR resources without typeInfo, try to get it from modelProvider
|
|
36
36
|
if (context.modelProvider && item && typeof item === 'object' && 'resourceType' in item && typeof item.resourceType === 'string') {
|
|
37
37
|
// Use cached type if available
|
|
38
|
-
const typeInfo =
|
|
39
|
-
? (context.modelProvider as any).getTypeFromCache(item.resourceType)
|
|
40
|
-
: undefined;
|
|
38
|
+
const typeInfo = await context.modelProvider.getType(item.resourceType);
|
|
41
39
|
if (typeInfo) {
|
|
42
40
|
const matchingType = context.modelProvider.ofType(typeInfo, typeName as TypeName);
|
|
43
41
|
return {
|
|
@@ -63,10 +61,23 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
63
61
|
case 'Decimal':
|
|
64
62
|
return { value: [box(typeof item === 'number', { type: 'Boolean', singleton: true })], context };
|
|
65
63
|
case 'Date':
|
|
64
|
+
// Check if it's a FHIRDate instance or has Date type
|
|
65
|
+
if (item && typeof item === 'object') {
|
|
66
|
+
return { value: [box(isFHIRDate(item) || (item as any).kind === 'FHIRDate', { type: 'Boolean', singleton: true })], context };
|
|
67
|
+
}
|
|
68
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
66
69
|
case 'DateTime':
|
|
70
|
+
// Check if it's a FHIRDateTime instance or has DateTime type
|
|
71
|
+
if (item && typeof item === 'object') {
|
|
72
|
+
return { value: [box(isFHIRDateTime(item) || (item as any).kind === 'FHIRDateTime', { type: 'Boolean', singleton: true })], context };
|
|
73
|
+
}
|
|
74
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
67
75
|
case 'Time':
|
|
68
|
-
//
|
|
69
|
-
|
|
76
|
+
// Check if it's a FHIRTime instance or has Time type
|
|
77
|
+
if (item && typeof item === 'object') {
|
|
78
|
+
return { value: [box(isFHIRTime(item) || (item as any).kind === 'FHIRTime', { type: 'Boolean', singleton: true })], context };
|
|
79
|
+
}
|
|
80
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
70
81
|
default:
|
|
71
82
|
// For complex types, check resourceType
|
|
72
83
|
if (item && typeof item === 'object' && 'resourceType' in item) {
|
|
@@ -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
|
// isDistinct takes no arguments
|
|
@@ -35,6 +35,7 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
|
|
|
35
35
|
|
|
36
36
|
export const isDistinctFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
37
37
|
name: 'isDistinct',
|
|
38
|
+
doesNotPropagateEmpty: true, // Returns true for empty collections
|
|
38
39
|
category: ['existence'],
|
|
39
40
|
description: 'Returns true if all the items in the input collection are distinct. To determine whether two items are distinct, the equals (=) operator is used. If the input collection is empty, the result is true.',
|
|
40
41
|
examples: [
|
|
@@ -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 is empty, return empty
|
|
@@ -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) {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { FunctionDefinition, FunctionEvaluator, LiteralNode } 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('lastIndexOf', 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('lastIndexOf');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check arguments
|
|
26
|
+
if (args.length !== 1) {
|
|
27
|
+
throw Errors.wrongArgumentCount('lastIndexOf', 1, args.length);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Evaluate substring argument
|
|
31
|
+
const substringArg = args[0];
|
|
32
|
+
if (!substringArg) {
|
|
33
|
+
throw Errors.argumentRequired('lastIndexOf', 'substring argument');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const substringResult = await evaluator(substringArg, input, context);
|
|
37
|
+
|
|
38
|
+
if (substringResult.value.length === 0) {
|
|
39
|
+
return { value: [], context };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (substringResult.value.length > 1) {
|
|
43
|
+
throw Errors.singletonRequired('lastIndexOf substring', substringResult.value.length);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const boxedSubstring = substringResult.value[0];
|
|
47
|
+
if (!boxedSubstring) {
|
|
48
|
+
return { value: [], context };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const substring = unbox(boxedSubstring);
|
|
52
|
+
if (typeof substring !== 'string') {
|
|
53
|
+
throw Errors.invalidStringOperation('lastIndexOf', 'substring argument');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Handle empty substring - returns 0 per spec
|
|
57
|
+
if (substring === '') {
|
|
58
|
+
return { value: [box(0, { type: 'Integer', singleton: true })], context };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Find the last index
|
|
62
|
+
const index = inputValue.lastIndexOf(substring);
|
|
63
|
+
|
|
64
|
+
return { value: [box(index, { type: 'Integer', singleton: true })], context };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const lastIndexOfFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
68
|
+
name: 'lastIndexOf',
|
|
69
|
+
category: ['string'],
|
|
70
|
+
description: 'Returns the 0-based index of the last position substring is found in the input string, or -1 if it is not found',
|
|
71
|
+
examples: [
|
|
72
|
+
"'abcdefg'.lastIndexOf('bc')",
|
|
73
|
+
"'abcdefg'.lastIndexOf('x')",
|
|
74
|
+
"'abc abc'.lastIndexOf('a')"
|
|
75
|
+
],
|
|
76
|
+
signatures: [{
|
|
77
|
+
name: 'lastIndexOf',
|
|
78
|
+
input: { type: 'String', singleton: true },
|
|
79
|
+
parameters: [
|
|
80
|
+
{ name: 'substring', type: { type: 'String', singleton: true } }
|
|
81
|
+
],
|
|
82
|
+
result: { type: 'Integer', singleton: true }
|
|
83
|
+
}],
|
|
84
|
+
evaluate
|
|
85
|
+
};
|
|
@@ -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
|
// Check single item in input
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { OperatorDefinition } from '../types';
|
|
2
2
|
import { PRECEDENCE } from '../types';
|
|
3
3
|
import type { OperationEvaluator } from '../types';
|
|
4
|
-
import { compareQuantities } from '../quantity-value';
|
|
5
|
-
import type { QuantityValue } from '../quantity-value';
|
|
6
|
-
import { box, unbox } from '../boxing';
|
|
4
|
+
import { compareQuantities } from '../complex-types/quantity-value';
|
|
5
|
+
import type { QuantityValue } from '../complex-types/quantity-value';
|
|
6
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
7
7
|
|
|
8
8
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
9
9
|
if (left.length === 0 || right.length === 0) {
|
|
@@ -24,6 +24,23 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
24
24
|
return { value: result !== null ? [box(result < 0, { type: 'Boolean', singleton: true })] : [], context };
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Check if both are temporal values (Date, DateTime, Time)
|
|
28
|
+
if (l && typeof l === 'object' && 'kind' in l &&
|
|
29
|
+
r && typeof r === 'object' && 'kind' in r) {
|
|
30
|
+
const temporalL = l as any;
|
|
31
|
+
const temporalR = r as any;
|
|
32
|
+
const kinds = ['FHIRDate', 'FHIRDateTime', 'FHIRTime'];
|
|
33
|
+
if (kinds.includes(temporalL.kind) && kinds.includes(temporalR.kind)) {
|
|
34
|
+
const { compare } = await import('../complex-types/temporal');
|
|
35
|
+
const result = compare(temporalL, temporalR);
|
|
36
|
+
// null means incomparable (different precisions), returns empty
|
|
37
|
+
if (result === null) {
|
|
38
|
+
return { value: [], context };
|
|
39
|
+
}
|
|
40
|
+
return { value: [box(result < 0, { type: 'Boolean', singleton: true })], context };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
return { value: [box((l as any) < (r as any), { type: 'Boolean', singleton: true })], context };
|
|
28
45
|
};
|
|
29
46
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { OperatorDefinition } from '../types';
|
|
2
2
|
import { PRECEDENCE } from '../types';
|
|
3
3
|
import type { OperationEvaluator } from '../types';
|
|
4
|
-
import { compareQuantities } from '../quantity-value';
|
|
5
|
-
import type { QuantityValue } from '../quantity-value';
|
|
6
|
-
import { box, unbox } from '../boxing';
|
|
4
|
+
import { compareQuantities } from '../complex-types/quantity-value';
|
|
5
|
+
import type { QuantityValue } from '../complex-types/quantity-value';
|
|
6
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
7
7
|
|
|
8
8
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
9
9
|
if (left.length === 0 || right.length === 0) {
|
|
@@ -24,6 +24,23 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
24
24
|
return { value: result !== null ? [box(result <= 0, { type: 'Boolean', singleton: true })] : [], context };
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Check if both are temporal values (Date, DateTime, Time)
|
|
28
|
+
if (l && typeof l === 'object' && 'kind' in l &&
|
|
29
|
+
r && typeof r === 'object' && 'kind' in r) {
|
|
30
|
+
const temporalL = l as any;
|
|
31
|
+
const temporalR = r as any;
|
|
32
|
+
const kinds = ['FHIRDate', 'FHIRDateTime', 'FHIRTime'];
|
|
33
|
+
if (kinds.includes(temporalL.kind) && kinds.includes(temporalR.kind)) {
|
|
34
|
+
const { compare } = await import('../complex-types/temporal');
|
|
35
|
+
const result = compare(temporalL, temporalR);
|
|
36
|
+
// null means incomparable (different precisions), returns empty
|
|
37
|
+
if (result === null) {
|
|
38
|
+
return { value: [], context };
|
|
39
|
+
}
|
|
40
|
+
return { value: [box(result <= 0, { type: 'Boolean', singleton: true })], context };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
return { value: [box((l as any) <= (r as any), { type: 'Boolean', singleton: true })], context };
|
|
28
45
|
};
|
|
29
46
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { OperatorDefinition } from '../types';
|
|
2
2
|
import { PRECEDENCE } from '../types';
|
|
3
3
|
import type { OperationEvaluator } from '../types';
|
|
4
|
-
import { box, unbox } from '../boxing';
|
|
5
|
-
import { compareQuantities, type QuantityValue } from '../quantity-value';
|
|
4
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
5
|
+
import { compareQuantities, type QuantityValue } from '../complex-types/quantity-value';
|
|
6
6
|
|
|
7
7
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
8
8
|
if (left.length === 0 || right.length === 0) {
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// lowBoundary() function - Returns the least 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
|
+
getDateLowBoundary, getDateTimeLowBoundary, getTimeLowBoundary
|
|
7
|
+
} from '../complex-types/temporal';
|
|
8
|
+
import { getDecimalLowBoundary } from './decimal-boundaries';
|
|
9
|
+
import { Errors } from '../errors';
|
|
10
|
+
|
|
11
|
+
export const lowBoundaryEvaluator: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
12
|
+
// lowBoundary() takes optional precision parameter
|
|
13
|
+
if (args.length > 1) {
|
|
14
|
+
throw Errors.wrongArgumentCountRange('lowBoundary', 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('lowBoundary', 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('lowBoundary precision', precisionArg.length);
|
|
44
|
+
}
|
|
45
|
+
const precisionValue = unbox(precisionArg[0]!);
|
|
46
|
+
if (typeof precisionValue !== 'number' || !Number.isInteger(precisionValue)) {
|
|
47
|
+
throw Errors.invalidOperandType('lowBoundary precision', typeof precisionValue);
|
|
48
|
+
}
|
|
49
|
+
precision = precisionValue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Handle Date
|
|
53
|
+
if (isFHIRDate(value)) {
|
|
54
|
+
const result = getDateLowBoundary(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 = getDateTimeLowBoundary(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 = getTimeLowBoundary(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 = getDecimalLowBoundary(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 lowBoundaryFunction: FunctionDefinition & { evaluate: typeof lowBoundaryEvaluator } = {
|
|
98
|
+
name: 'lowBoundary',
|
|
99
|
+
category: ['utility'],
|
|
100
|
+
description: 'Returns the least possible value of the input to the specified precision',
|
|
101
|
+
examples: [
|
|
102
|
+
'@2014.lowBoundary(6)',
|
|
103
|
+
'@2014-01-01T08.lowBoundary(17)',
|
|
104
|
+
'@T10:30.lowBoundary(9)',
|
|
105
|
+
'1.587.lowBoundary()',
|
|
106
|
+
'1.587.lowBoundary(2)',
|
|
107
|
+
'1.lowBoundary(0)'
|
|
108
|
+
],
|
|
109
|
+
signatures: [
|
|
110
|
+
{
|
|
111
|
+
name: 'lowBoundary',
|
|
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: lowBoundaryEvaluator
|
|
120
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
// Handle empty input collection
|
|
@@ -0,0 +1,86 @@
|
|
|
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('matches', 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('matches');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check arguments
|
|
26
|
+
if (args.length !== 1) {
|
|
27
|
+
throw Errors.wrongArgumentCount('matches', 1, args.length);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Evaluate regex argument
|
|
31
|
+
const regexArg = args[0];
|
|
32
|
+
if (!regexArg) {
|
|
33
|
+
throw Errors.argumentRequired('matches', 'regex argument');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const regexResult = await evaluator(regexArg, input, context);
|
|
37
|
+
|
|
38
|
+
if (regexResult.value.length === 0) {
|
|
39
|
+
return { value: [], context };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (regexResult.value.length > 1) {
|
|
43
|
+
throw Errors.singletonRequired('matches regex', regexResult.value.length);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const boxedRegex = regexResult.value[0];
|
|
47
|
+
if (!boxedRegex) {
|
|
48
|
+
return { value: [], context };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const regexPattern = unbox(boxedRegex);
|
|
52
|
+
if (typeof regexPattern !== 'string') {
|
|
53
|
+
throw Errors.invalidStringOperation('matches', 'regex argument');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// Create regex with unicode support and single line mode (dotAll)
|
|
58
|
+
// Per spec: case-sensitive, single line mode, allow Unicode
|
|
59
|
+
const regex = new RegExp(regexPattern, 'us');
|
|
60
|
+
const result = regex.test(inputValue);
|
|
61
|
+
|
|
62
|
+
return { value: [box(result, { type: 'Boolean', singleton: true })], context };
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw new Error(`Invalid regular expression in matches(): ${(error as Error).message}`);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const matchesFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
69
|
+
name: 'matches',
|
|
70
|
+
category: ['string'],
|
|
71
|
+
description: 'Returns true when the input string matches the given regular expression',
|
|
72
|
+
examples: [
|
|
73
|
+
"'test string'.matches('t.+')",
|
|
74
|
+
"'test string'.matches('asd.+')",
|
|
75
|
+
"'first line\\nsecond line'.matches('line.second')"
|
|
76
|
+
],
|
|
77
|
+
signatures: [{
|
|
78
|
+
name: 'matches',
|
|
79
|
+
input: { type: 'String', singleton: true },
|
|
80
|
+
parameters: [
|
|
81
|
+
{ name: 'regex', type: { type: 'String', singleton: true } }
|
|
82
|
+
],
|
|
83
|
+
result: { type: 'Boolean', singleton: true }
|
|
84
|
+
}],
|
|
85
|
+
evaluate
|
|
86
|
+
};
|