@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,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
|
|
@@ -13,9 +14,132 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
13
14
|
const boxedItem = left[0];
|
|
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
|
-
// If we have a ModelProvider in context, use it for accurate type checking
|
|
17
|
+
// If we have a ModelProvider and typeInfo, use it for accurate type checking (handles subtypes)
|
|
18
18
|
if (context.modelProvider && boxedItem?.typeInfo) {
|
|
19
|
+
// Check for namespace-qualified types (e.g., System.Boolean, FHIR.boolean)
|
|
20
|
+
let checkNamespace: string | undefined;
|
|
21
|
+
let checkType: string;
|
|
22
|
+
|
|
23
|
+
if (typeName.includes('.')) {
|
|
24
|
+
const parts = typeName.split('.');
|
|
25
|
+
checkNamespace = parts[0];
|
|
26
|
+
checkType = parts.slice(1).join('.');
|
|
27
|
+
} else {
|
|
28
|
+
checkType = typeName;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If checking for System type, ensure it's NOT a FHIR type
|
|
32
|
+
if (checkNamespace === 'System') {
|
|
33
|
+
// System types should not match FHIR types
|
|
34
|
+
if (boxedItem.typeInfo.namespace === 'FHIR') {
|
|
35
|
+
return {
|
|
36
|
+
value: [box(false, { type: 'Boolean', singleton: true })],
|
|
37
|
+
context
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Check if the type matches (without namespace)
|
|
41
|
+
const normalizedType = checkType;
|
|
42
|
+
if (boxedItem.typeInfo.type === normalizedType) {
|
|
43
|
+
return {
|
|
44
|
+
value: [box(true, { type: 'Boolean', singleton: true })],
|
|
45
|
+
context
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If checking for FHIR type
|
|
51
|
+
if (checkNamespace === 'FHIR') {
|
|
52
|
+
// Must be a FHIR namespaced type
|
|
53
|
+
if (boxedItem.typeInfo.namespace === 'FHIR') {
|
|
54
|
+
// Check the name field for FHIR types (e.g., 'boolean', 'integer')
|
|
55
|
+
if (boxedItem.typeInfo.name === checkType) {
|
|
56
|
+
return {
|
|
57
|
+
value: [box(true, { type: 'Boolean', singleton: true })],
|
|
58
|
+
context
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Also check if asking for a resource type like FHIR.Patient
|
|
62
|
+
if (boxedItem.typeInfo.type === checkType || boxedItem.typeInfo.name === checkType) {
|
|
63
|
+
return {
|
|
64
|
+
value: [box(true, { type: 'Boolean', singleton: true })],
|
|
65
|
+
context
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
value: [box(false, { type: 'Boolean', singleton: true })],
|
|
71
|
+
context
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// When no namespace is specified (e.g., just "Boolean", "String", "string", "code")
|
|
76
|
+
if (!checkNamespace) {
|
|
77
|
+
// Check for System primitive types (capitalized)
|
|
78
|
+
const systemPrimitiveTypes = ['Boolean', 'String', 'Integer', 'Decimal', 'Date', 'DateTime', 'Time'];
|
|
79
|
+
if (systemPrimitiveTypes.includes(checkType)) {
|
|
80
|
+
// For System primitive types, only match if NOT a FHIR type
|
|
81
|
+
if (boxedItem.typeInfo.namespace === 'FHIR') {
|
|
82
|
+
return {
|
|
83
|
+
value: [box(false, { type: 'Boolean', singleton: true })],
|
|
84
|
+
context
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Check if the type matches
|
|
88
|
+
if (boxedItem.typeInfo.type === checkType) {
|
|
89
|
+
return {
|
|
90
|
+
value: [box(true, { type: 'Boolean', singleton: true })],
|
|
91
|
+
context
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check for FHIR primitive types (lowercase) like string, code, id, etc.
|
|
97
|
+
const fhirPrimitiveTypes = ['string', 'code', 'id', 'uri', 'url', 'canonical', 'uuid', 'oid',
|
|
98
|
+
'boolean', 'integer', 'decimal', 'base64Binary', 'instant',
|
|
99
|
+
'date', 'dateTime', 'time', 'unsignedInt', 'positiveInt', 'markdown'];
|
|
100
|
+
if (fhirPrimitiveTypes.includes(checkType)) {
|
|
101
|
+
// For FHIR primitive types, check both the name and underlying type
|
|
102
|
+
if (boxedItem.typeInfo.namespace === 'FHIR') {
|
|
103
|
+
// Check if the name matches
|
|
104
|
+
if (boxedItem.typeInfo.name === checkType) {
|
|
105
|
+
return {
|
|
106
|
+
value: [box(true, { type: 'Boolean', singleton: true })],
|
|
107
|
+
context
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// For 'string', also match code, id, etc. (they are all string-based)
|
|
111
|
+
if (checkType === 'string') {
|
|
112
|
+
const stringBasedTypes = ['code', 'id', 'uri', 'url', 'canonical', 'uuid', 'oid', 'markdown'];
|
|
113
|
+
if (boxedItem.typeInfo.name && stringBasedTypes.includes(boxedItem.typeInfo.name)) {
|
|
114
|
+
return {
|
|
115
|
+
value: [box(true, { type: 'Boolean', singleton: true })],
|
|
116
|
+
context
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Also check if underlying type is String
|
|
120
|
+
if (boxedItem.typeInfo.type === 'String') {
|
|
121
|
+
return {
|
|
122
|
+
value: [box(true, { type: 'Boolean', singleton: true })],
|
|
123
|
+
context
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
value: [box(false, { type: 'Boolean', singleton: true })],
|
|
130
|
+
context
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// For non-primitive types (like Patient, Observation), use model provider
|
|
135
|
+
const matchingType = context.modelProvider.ofType(boxedItem.typeInfo, typeName as TypeName);
|
|
136
|
+
return {
|
|
137
|
+
value: [box(matchingType !== undefined, { type: 'Boolean', singleton: true })],
|
|
138
|
+
context
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Default case - use model provider's ofType for other checks
|
|
19
143
|
const matchingType = context.modelProvider.ofType(boxedItem.typeInfo, typeName as TypeName);
|
|
20
144
|
return {
|
|
21
145
|
value: [box(matchingType !== undefined, { type: 'Boolean', singleton: true })],
|
|
@@ -23,11 +147,24 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
23
147
|
};
|
|
24
148
|
}
|
|
25
149
|
|
|
26
|
-
// Check if the box has type information
|
|
150
|
+
// Check if the box has type information (without ModelProvider, check with normalization)
|
|
27
151
|
if (boxedItem?.typeInfo) {
|
|
28
|
-
//
|
|
152
|
+
// Normalize both types for comparison
|
|
153
|
+
let boxedType = boxedItem.typeInfo.type;
|
|
154
|
+
let compareType = typeName;
|
|
155
|
+
|
|
156
|
+
// Normalize System.X to X for primitive types
|
|
157
|
+
if (compareType.startsWith('System.')) {
|
|
158
|
+
compareType = compareType.substring(7);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Also check if boxedType needs normalization (unlikely but consistent)
|
|
162
|
+
if (boxedType.startsWith('System.')) {
|
|
163
|
+
boxedType = boxedType.substring(7);
|
|
164
|
+
}
|
|
165
|
+
|
|
29
166
|
return {
|
|
30
|
-
value: [box(
|
|
167
|
+
value: [box(boxedType === compareType, { type: 'Boolean', singleton: true })],
|
|
31
168
|
context
|
|
32
169
|
};
|
|
33
170
|
}
|
|
@@ -35,9 +172,7 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
35
172
|
// For FHIR resources without typeInfo, try to get it from modelProvider
|
|
36
173
|
if (context.modelProvider && item && typeof item === 'object' && 'resourceType' in item && typeof item.resourceType === 'string') {
|
|
37
174
|
// Use cached type if available
|
|
38
|
-
const typeInfo =
|
|
39
|
-
? (context.modelProvider as any).getTypeFromCache(item.resourceType)
|
|
40
|
-
: undefined;
|
|
175
|
+
const typeInfo = await context.modelProvider.getType(item.resourceType);
|
|
41
176
|
if (typeInfo) {
|
|
42
177
|
const matchingType = context.modelProvider.ofType(typeInfo, typeName as TypeName);
|
|
43
178
|
return {
|
|
@@ -52,21 +187,49 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
52
187
|
};
|
|
53
188
|
}
|
|
54
189
|
|
|
55
|
-
//
|
|
56
|
-
|
|
190
|
+
// Normalize type names - strip namespace for System types
|
|
191
|
+
// System.Boolean -> Boolean, FHIR.boolean -> boolean, etc.
|
|
192
|
+
let normalizedTypeName = typeName;
|
|
193
|
+
if (typeName.startsWith('System.')) {
|
|
194
|
+
normalizedTypeName = typeName.substring(7); // Remove "System."
|
|
195
|
+
} else if (typeName.startsWith('FHIR.')) {
|
|
196
|
+
// For FHIR types, keep the namespace for model provider lookup
|
|
197
|
+
// but also check without namespace for primitive types
|
|
198
|
+
normalizedTypeName = typeName;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check primitive types (with normalized names)
|
|
202
|
+
switch (normalizedTypeName) {
|
|
57
203
|
case 'String':
|
|
204
|
+
case 'System.String':
|
|
58
205
|
return { value: [box(typeof item === 'string', { type: 'Boolean', singleton: true })], context };
|
|
59
206
|
case 'Boolean':
|
|
207
|
+
case 'System.Boolean':
|
|
60
208
|
return { value: [box(typeof item === 'boolean', { type: 'Boolean', singleton: true })], context };
|
|
61
209
|
case 'Integer':
|
|
210
|
+
case 'System.Integer':
|
|
62
211
|
return { value: [box(typeof item === 'number' && Number.isInteger(item), { type: 'Boolean', singleton: true })], context };
|
|
63
212
|
case 'Decimal':
|
|
213
|
+
case 'System.Decimal':
|
|
64
214
|
return { value: [box(typeof item === 'number', { type: 'Boolean', singleton: true })], context };
|
|
65
215
|
case 'Date':
|
|
216
|
+
// Check if it's a FHIRDate instance or has Date type
|
|
217
|
+
if (item && typeof item === 'object') {
|
|
218
|
+
return { value: [box(isFHIRDate(item) || (item as any).kind === 'FHIRDate', { type: 'Boolean', singleton: true })], context };
|
|
219
|
+
}
|
|
220
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
66
221
|
case 'DateTime':
|
|
222
|
+
// Check if it's a FHIRDateTime instance or has DateTime type
|
|
223
|
+
if (item && typeof item === 'object') {
|
|
224
|
+
return { value: [box(isFHIRDateTime(item) || (item as any).kind === 'FHIRDateTime', { type: 'Boolean', singleton: true })], context };
|
|
225
|
+
}
|
|
226
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
67
227
|
case 'Time':
|
|
68
|
-
//
|
|
69
|
-
|
|
228
|
+
// Check if it's a FHIRTime instance or has Time type
|
|
229
|
+
if (item && typeof item === 'object') {
|
|
230
|
+
return { value: [box(isFHIRTime(item) || (item as any).kind === 'FHIRTime', { type: 'Boolean', singleton: true })], context };
|
|
231
|
+
}
|
|
232
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
70
233
|
default:
|
|
71
234
|
// For complex types, check resourceType
|
|
72
235
|
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,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,14 @@ 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:
|
|
19
|
+
// Use the unified compare function which handles all types including FHIR Quantities
|
|
20
|
+
const comparisonResult = compare(l, r);
|
|
21
|
+
|
|
22
|
+
if (comparisonResult.kind === 'incomparable') {
|
|
23
|
+
return { value: [], context };
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
return { value: [box(
|
|
26
|
+
return { value: [box(comparisonResult.kind === 'less', { type: 'Boolean', singleton: true })], context };
|
|
28
27
|
};
|
|
29
28
|
|
|
30
29
|
export const lessOperator: 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 === 'less' || comparisonResult.kind === 'equal', { type: 'Boolean', singleton: true })], context };
|
|
28
26
|
};
|
|
29
27
|
|
|
30
28
|
export const lessOrEqualOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
@@ -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 {
|
|
4
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
5
|
+
import { compare } from './comparison';
|
|
6
6
|
|
|
7
7
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
8
8
|
if (left.length === 0 || right.length === 0) {
|
|
@@ -18,19 +18,14 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
18
18
|
const leftValue = unbox(boxedLeft);
|
|
19
19
|
const rightValue = unbox(boxedRight);
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Incompatible units - return empty
|
|
27
|
-
return { value: [], context };
|
|
28
|
-
}
|
|
29
|
-
return { value: [box(comparison < 0, { type: 'Boolean', singleton: true })], context };
|
|
21
|
+
// Use the unified compare function which handles all types including FHIR Quantities
|
|
22
|
+
const comparisonResult = compare(leftValue, rightValue);
|
|
23
|
+
|
|
24
|
+
if (comparisonResult.kind === 'incomparable') {
|
|
25
|
+
return { value: [], context };
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
return { value: [box((leftValue as any) < (rightValue as any), { type: 'Boolean', singleton: true })], context };
|
|
28
|
+
return { value: [box(comparisonResult.kind === 'less', { type: 'Boolean', singleton: true })], context };
|
|
34
29
|
};
|
|
35
30
|
|
|
36
31
|
export const lessThanOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
@@ -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
|