@atomic-ehr/fhirpath 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +716 -238
- package/dist/index.d.ts +225 -119
- package/dist/index.js +10911 -5600
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
- package/src/analyzer/augmentor.ts +242 -0
- package/src/analyzer/cursor-services.ts +75 -0
- package/src/analyzer/scope-manager.ts +57 -0
- package/src/analyzer/trivia-indexer.ts +58 -0
- package/src/analyzer/type-compat.ts +157 -0
- package/src/analyzer/utils.ts +132 -0
- package/src/analyzer.ts +921 -1208
- package/src/completion-provider.ts +209 -191
- package/src/{quantity-value.ts → complex-types/quantity-value.ts} +112 -22
- package/src/complex-types/temporal.ts +1737 -0
- package/src/errors.ts +25 -3
- package/src/index.ts +17 -104
- package/src/inspect.ts +4 -4
- package/src/{boxing.ts → interpreter/boxing.ts} +1 -1
- package/src/interpreter/navigator.ts +94 -0
- package/src/interpreter/runtime-context.ts +273 -0
- package/src/interpreter.ts +435 -469
- package/src/lexer.ts +188 -210
- package/src/model-provider.ts +71 -43
- package/src/operations/abs-function.ts +1 -1
- package/src/operations/aggregate-function.ts +84 -5
- package/src/operations/all-function.ts +4 -3
- package/src/operations/allFalse-function.ts +2 -1
- package/src/operations/allTrue-function.ts +2 -1
- package/src/operations/and-operator.ts +2 -1
- package/src/operations/anyFalse-function.ts +2 -1
- package/src/operations/anyTrue-function.ts +2 -1
- package/src/operations/as-function.ts +58 -0
- package/src/operations/as-operator.ts +57 -19
- package/src/operations/ceiling-function.ts +1 -1
- package/src/operations/children-function.ts +14 -5
- package/src/operations/combine-function.ts +6 -3
- package/src/operations/combine-operator.ts +6 -7
- package/src/operations/comparison.ts +692 -0
- package/src/operations/contains-function.ts +1 -1
- package/src/operations/contains-operator.ts +2 -1
- package/src/operations/convertsToBoolean-function.ts +78 -0
- package/src/operations/convertsToDecimal-function.ts +82 -0
- package/src/operations/convertsToInteger-function.ts +71 -0
- package/src/operations/convertsToLong-function.ts +89 -0
- package/src/operations/convertsToQuantity-function.ts +116 -0
- package/src/operations/convertsToString-function.ts +88 -0
- package/src/operations/count-function.ts +2 -1
- package/src/operations/dateOf-function.ts +69 -0
- package/src/operations/dayOf-function.ts +66 -0
- package/src/operations/decimal-boundaries.ts +133 -0
- package/src/operations/defineVariable-function.ts +130 -17
- package/src/operations/distinct-function.ts +1 -1
- package/src/operations/div-operator.ts +1 -1
- package/src/operations/divide-operator.ts +12 -7
- package/src/operations/dot-operator.ts +1 -1
- package/src/operations/empty-function.ts +30 -21
- package/src/operations/endsWith-function.ts +6 -1
- package/src/operations/equal-operator.ts +23 -32
- package/src/operations/equivalent-operator.ts +13 -53
- package/src/operations/exclude-function.ts +2 -1
- package/src/operations/exists-function.ts +4 -3
- package/src/operations/first-function.ts +1 -1
- package/src/operations/floor-function.ts +1 -1
- package/src/operations/greater-operator.ts +20 -3
- package/src/operations/greater-or-equal-operator.ts +20 -3
- package/src/operations/highBoundary-function.ts +120 -0
- package/src/operations/hourOf-function.ts +66 -0
- package/src/operations/iif-function.ts +186 -7
- package/src/operations/implies-operator.ts +1 -1
- package/src/operations/in-operator.ts +2 -1
- package/src/operations/index.ts +41 -0
- package/src/operations/indexOf-function.ts +1 -1
- package/src/operations/intersect-function.ts +1 -1
- package/src/operations/is-function.ts +59 -0
- package/src/operations/is-operator.ts +20 -9
- package/src/operations/isDistinct-function.ts +2 -1
- package/src/operations/join-function.ts +1 -1
- package/src/operations/last-function.ts +1 -1
- package/src/operations/lastIndexOf-function.ts +85 -0
- package/src/operations/length-function.ts +1 -1
- package/src/operations/less-operator.ts +20 -3
- package/src/operations/less-or-equal-operator.ts +20 -3
- package/src/operations/less-than.ts +2 -2
- package/src/operations/lowBoundary-function.ts +120 -0
- package/src/operations/lower-function.ts +1 -1
- package/src/operations/matches-function.ts +86 -0
- package/src/operations/matchesFull-function.ts +96 -0
- package/src/operations/millisecondOf-function.ts +66 -0
- package/src/operations/minus-operator.ts +69 -4
- package/src/operations/minuteOf-function.ts +66 -0
- package/src/operations/mod-operator.ts +1 -1
- package/src/operations/monthOf-function.ts +66 -0
- package/src/operations/multiply-operator.ts +27 -3
- package/src/operations/not-equal-operator.ts +24 -30
- package/src/operations/not-equivalent-operator.ts +13 -53
- package/src/operations/not-function.ts +1 -1
- package/src/operations/ofType-function.ts +8 -12
- package/src/operations/or-operator.ts +2 -1
- package/src/operations/plus-operator.ts +71 -7
- package/src/operations/power-function.ts +35 -10
- package/src/operations/repeat-function.ts +169 -0
- package/src/operations/replace-function.ts +1 -1
- package/src/operations/replaceMatches-function.ts +120 -0
- package/src/operations/round-function.ts +1 -1
- package/src/operations/secondOf-function.ts +66 -0
- package/src/operations/select-function.ts +66 -5
- package/src/operations/single-function.ts +1 -1
- package/src/operations/skip-function.ts +1 -1
- package/src/operations/split-function.ts +1 -1
- package/src/operations/sqrt-function.ts +15 -8
- package/src/operations/startsWith-function.ts +1 -1
- package/src/operations/subsetOf-function.ts +6 -2
- package/src/operations/substring-function.ts +1 -1
- package/src/operations/supersetOf-function.ts +6 -2
- package/src/operations/tail-function.ts +1 -1
- package/src/operations/take-function.ts +1 -1
- package/src/operations/temporal-functions.ts +555 -0
- package/src/operations/timeOf-function.ts +67 -0
- package/src/operations/timezoneOffsetOf-function.ts +69 -0
- package/src/operations/toBoolean-function.ts +27 -8
- package/src/operations/toChars-function.ts +56 -0
- package/src/operations/toDecimal-function.ts +27 -8
- package/src/operations/toInteger-function.ts +15 -3
- package/src/operations/toLong-function.ts +98 -0
- package/src/operations/toQuantity-function.ts +181 -0
- package/src/operations/toString-function.ts +45 -3
- package/src/operations/trace-function.ts +1 -1
- package/src/operations/trim-function.ts +1 -1
- package/src/operations/truncate-function.ts +1 -1
- package/src/operations/unary-minus-operator.ts +2 -2
- package/src/operations/unary-plus-operator.ts +1 -1
- package/src/operations/union-function.ts +1 -1
- package/src/operations/union-operator.ts +16 -26
- package/src/operations/upper-function.ts +1 -1
- package/src/operations/where-function.ts +3 -3
- package/src/operations/xor-operator.ts +1 -1
- package/src/operations/yearOf-function.ts +66 -0
- package/src/{cursor-nodes.ts → parser/cursor-nodes.ts} +10 -7
- package/src/parser.ts +248 -501
- package/src/registry.ts +53 -42
- package/src/types.ts +128 -16
- package/src/utils/pprint.ts +151 -0
|
@@ -0,0 +1,96 @@
|
|
|
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('matchesFull', 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('matchesFull');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check arguments
|
|
26
|
+
if (args.length !== 1) {
|
|
27
|
+
throw Errors.wrongArgumentCount('matchesFull', 1, args.length);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Evaluate regex argument
|
|
31
|
+
const regexArg = args[0];
|
|
32
|
+
if (!regexArg) {
|
|
33
|
+
throw Errors.argumentRequired('matchesFull', '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('matchesFull 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('matchesFull', 'regex argument');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// matchesFull implicitly adds ^ and $ anchors to match the entire string
|
|
58
|
+
// Only add anchors if they're not already present
|
|
59
|
+
let fullPattern = regexPattern;
|
|
60
|
+
if (!regexPattern.startsWith('^')) {
|
|
61
|
+
fullPattern = '^' + fullPattern;
|
|
62
|
+
}
|
|
63
|
+
if (!regexPattern.endsWith('$')) {
|
|
64
|
+
fullPattern = fullPattern + '$';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Create regex with unicode support and single line mode (dotAll)
|
|
68
|
+
// Per spec: case-sensitive, single line mode, allow Unicode
|
|
69
|
+
const regex = new RegExp(fullPattern, 'us');
|
|
70
|
+
const result = regex.test(inputValue);
|
|
71
|
+
|
|
72
|
+
return { value: [box(result, { type: 'Boolean', singleton: true })], context };
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw new Error(`Invalid regular expression in matchesFull(): ${(error as Error).message}`);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const matchesFullFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
79
|
+
name: 'matchesFull',
|
|
80
|
+
category: ['string'],
|
|
81
|
+
description: 'Returns true when the input string completely matches the given regular expression',
|
|
82
|
+
examples: [
|
|
83
|
+
"'Library'.matchesFull('Library')",
|
|
84
|
+
"'http://example.org/Library'.matchesFull('Library')",
|
|
85
|
+
"'N8000123'.matchesFull('N[0-9]{7}')"
|
|
86
|
+
],
|
|
87
|
+
signatures: [{
|
|
88
|
+
name: 'matchesFull',
|
|
89
|
+
input: { type: 'String', singleton: true },
|
|
90
|
+
parameters: [
|
|
91
|
+
{ name: 'regex', type: { type: 'String', singleton: true } }
|
|
92
|
+
],
|
|
93
|
+
result: { type: 'Boolean', singleton: true }
|
|
94
|
+
}],
|
|
95
|
+
evaluate
|
|
96
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// millisecondOf() function - Extracts millisecond 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 millisecondOfEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
8
|
+
// millisecondOf() takes no arguments
|
|
9
|
+
if (args.length !== 0) {
|
|
10
|
+
throw Errors.wrongArgumentCount('millisecondOf', 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('millisecondOf', 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 millisecond component is present
|
|
33
|
+
if (value.millisecond === undefined) {
|
|
34
|
+
return { value: [], context };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return the millisecond as an Integer (0-999)
|
|
38
|
+
return {
|
|
39
|
+
value: [box(value.millisecond, { 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 millisecondOfFunction: FunctionDefinition & { evaluate: typeof millisecondOfEvaluator } = {
|
|
49
|
+
name: 'millisecondOf',
|
|
50
|
+
category: ['temporal'],
|
|
51
|
+
description: 'Returns the millisecond component of a Time or DateTime value (0-999)',
|
|
52
|
+
examples: [
|
|
53
|
+
'@T10:30:45.123.millisecondOf()',
|
|
54
|
+
'@2014-01-05T10:30:45.500.millisecondOf()',
|
|
55
|
+
'Observation.effectiveDateTime.millisecondOf()'
|
|
56
|
+
],
|
|
57
|
+
signatures: [
|
|
58
|
+
{
|
|
59
|
+
name: 'millisecondOf',
|
|
60
|
+
input: { type: 'Any', singleton: true },
|
|
61
|
+
parameters: [],
|
|
62
|
+
result: { type: 'Integer', singleton: true }
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
evaluate: millisecondOfEvaluator
|
|
66
|
+
};
|
|
@@ -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 { subtractQuantities } from '../quantity-value';
|
|
5
|
-
import type { QuantityValue } from '../quantity-value';
|
|
6
|
-
import { box, unbox } from '../boxing';
|
|
4
|
+
import { subtractQuantities } 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) {
|
|
@@ -17,6 +17,67 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
17
17
|
if (!boxedr) return { value: [], context };
|
|
18
18
|
const r = unbox(boxedr);
|
|
19
19
|
|
|
20
|
+
// Check for temporal arithmetic using boxed type information
|
|
21
|
+
const leftType = boxedl?.typeInfo?.type;
|
|
22
|
+
const rightType = boxedr?.typeInfo?.type;
|
|
23
|
+
|
|
24
|
+
if ((leftType === 'Date' || leftType === 'DateTime' || leftType === 'Time') && rightType === 'Quantity') {
|
|
25
|
+
// Left is temporal, right is quantity
|
|
26
|
+
const temporalType = leftType;
|
|
27
|
+
const quantity = r as QuantityValue;
|
|
28
|
+
|
|
29
|
+
// Import temporal utilities and create TimeQuantity
|
|
30
|
+
const { createTimeQuantity, subtract } = await import('../complex-types/temporal');
|
|
31
|
+
|
|
32
|
+
// Calendar duration units (allowed for temporal arithmetic)
|
|
33
|
+
const calendarUnits = ['year', 'years', 'month', 'months', 'week', 'weeks',
|
|
34
|
+
'day', 'days', 'hour', 'hours', 'minute', 'minutes',
|
|
35
|
+
'second', 'seconds', 'millisecond', 'milliseconds'];
|
|
36
|
+
|
|
37
|
+
// Variable duration UCUM units (not allowed for temporal arithmetic - they have calendar-dependent durations)
|
|
38
|
+
const variableDurationUnits = ['a', 'mo'];
|
|
39
|
+
|
|
40
|
+
// Fixed duration UCUM units (allowed - they map directly to calendar units)
|
|
41
|
+
const fixedDurationUnitMap: Record<string, string> = {
|
|
42
|
+
'd': 'day',
|
|
43
|
+
'wk': 'week',
|
|
44
|
+
'h': 'hour',
|
|
45
|
+
'min': 'minute',
|
|
46
|
+
's': 'second',
|
|
47
|
+
'ms': 'millisecond'
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Check if this is a variable duration unit (not allowed)
|
|
51
|
+
if (variableDurationUnits.includes(quantity.unit)) {
|
|
52
|
+
// Variable duration units like 'a' and 'mo' cannot be subtracted from temporal values
|
|
53
|
+
// because they don't have fixed durations
|
|
54
|
+
const { Errors } = await import('../errors');
|
|
55
|
+
throw Errors.invalidTemporalUnit(temporalType, quantity.unit);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Map fixed duration UCUM units to calendar units
|
|
59
|
+
let mappedUnit = fixedDurationUnitMap[quantity.unit] || quantity.unit;
|
|
60
|
+
|
|
61
|
+
// Check if this is a valid calendar duration unit (after mapping)
|
|
62
|
+
if (!calendarUnits.includes(mappedUnit)) {
|
|
63
|
+
// Non-time units with temporal values return empty per FHIRPath spec
|
|
64
|
+
return { value: [], context };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const timeQuantity = createTimeQuantity(quantity.value, mappedUnit as any);
|
|
68
|
+
|
|
69
|
+
// Use the functional subtract operation
|
|
70
|
+
const result = subtract(l as any, timeQuantity);
|
|
71
|
+
|
|
72
|
+
if (temporalType === 'Date') {
|
|
73
|
+
return { value: [box(result, { type: 'Date', singleton: true })], context };
|
|
74
|
+
} else if (temporalType === 'DateTime') {
|
|
75
|
+
return { value: [box(result, { type: 'DateTime', singleton: true })], context };
|
|
76
|
+
} else if (temporalType === 'Time') {
|
|
77
|
+
return { value: [box(result, { type: 'Time', singleton: true })], context };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
20
81
|
// Check if both are quantities
|
|
21
82
|
if (l && typeof l === 'object' && 'unit' in l &&
|
|
22
83
|
r && typeof r === 'object' && 'unit' in r) {
|
|
@@ -26,7 +87,11 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
|
|
|
26
87
|
|
|
27
88
|
// Handle numeric subtraction
|
|
28
89
|
if (typeof l === 'number' && typeof r === 'number') {
|
|
29
|
-
|
|
90
|
+
const result = l - r;
|
|
91
|
+
const typeInfo = Number.isInteger(result) ?
|
|
92
|
+
{ type: 'Integer' as const, singleton: true } :
|
|
93
|
+
{ type: 'Decimal' as const, singleton: true };
|
|
94
|
+
return { value: [box(result, typeInfo)], context };
|
|
30
95
|
}
|
|
31
96
|
|
|
32
97
|
// For other types, return empty
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// minuteOf() function - Extracts minute 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 minuteOfEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
8
|
+
// minuteOf() takes no arguments
|
|
9
|
+
if (args.length !== 0) {
|
|
10
|
+
throw Errors.wrongArgumentCount('minuteOf', 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('minuteOf', 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 minute component is present
|
|
33
|
+
if (value.minute === undefined) {
|
|
34
|
+
return { value: [], context };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return the minute as an Integer (0-59)
|
|
38
|
+
return {
|
|
39
|
+
value: [box(value.minute, { 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 minuteOfFunction: FunctionDefinition & { evaluate: typeof minuteOfEvaluator } = {
|
|
49
|
+
name: 'minuteOf',
|
|
50
|
+
category: ['temporal'],
|
|
51
|
+
description: 'Returns the minute component of a Time or DateTime value (0-59)',
|
|
52
|
+
examples: [
|
|
53
|
+
'@T10:30:00.minuteOf()',
|
|
54
|
+
'@2014-01-05T10:30:00.minuteOf()',
|
|
55
|
+
'Observation.effectiveDateTime.minuteOf()'
|
|
56
|
+
],
|
|
57
|
+
signatures: [
|
|
58
|
+
{
|
|
59
|
+
name: 'minuteOf',
|
|
60
|
+
input: { type: 'Any', singleton: true },
|
|
61
|
+
parameters: [],
|
|
62
|
+
result: { type: 'Integer', singleton: true }
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
evaluate: minuteOfEvaluator
|
|
66
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
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';
|
|
4
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
5
5
|
|
|
6
6
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
7
7
|
if (left.length === 0 || right.length === 0) {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// monthOf() function - Extracts month component from Date or DateTime
|
|
2
|
+
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
import { isFHIRDate, isFHIRDateTime } from '../complex-types/temporal';
|
|
5
|
+
import { Errors } from '../errors';
|
|
6
|
+
|
|
7
|
+
export const monthOfEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
8
|
+
// monthOf() takes no arguments
|
|
9
|
+
if (args.length !== 0) {
|
|
10
|
+
throw Errors.wrongArgumentCount('monthOf', 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('monthOf', input.length);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const boxedValue = input[0];
|
|
24
|
+
if (!boxedValue) {
|
|
25
|
+
return { value: [], context };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const value = unbox(boxedValue);
|
|
29
|
+
|
|
30
|
+
// Check if it's a Date or DateTime
|
|
31
|
+
if (isFHIRDate(value) || isFHIRDateTime(value)) {
|
|
32
|
+
// Check if month component is present
|
|
33
|
+
if (value.month === undefined) {
|
|
34
|
+
return { value: [], context };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return the month as an Integer (1-12)
|
|
38
|
+
return {
|
|
39
|
+
value: [box(value.month, { type: 'Integer', singleton: true })],
|
|
40
|
+
context
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Not a Date or DateTime, return empty
|
|
45
|
+
return { value: [], context };
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const monthOfFunction: FunctionDefinition & { evaluate: typeof monthOfEvaluator } = {
|
|
49
|
+
name: 'monthOf',
|
|
50
|
+
category: ['temporal'],
|
|
51
|
+
description: 'Returns the month component of a Date or DateTime value (1-12)',
|
|
52
|
+
examples: [
|
|
53
|
+
'@2014-01-05.monthOf()',
|
|
54
|
+
'@2014-01-05T10:30:00.monthOf()',
|
|
55
|
+
'Patient.birthDate.monthOf()'
|
|
56
|
+
],
|
|
57
|
+
signatures: [
|
|
58
|
+
{
|
|
59
|
+
name: 'monthOf',
|
|
60
|
+
input: { type: 'Any', singleton: true },
|
|
61
|
+
parameters: [],
|
|
62
|
+
result: { type: 'Integer', singleton: true }
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
evaluate: monthOfEvaluator
|
|
66
|
+
};
|
|
@@ -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 { multiplyQuantities } from '../quantity-value';
|
|
5
|
-
import type { QuantityValue } from '../quantity-value';
|
|
6
|
-
import { box, unbox } from '../boxing';
|
|
4
|
+
import { multiplyQuantities } 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) {
|
|
@@ -71,6 +71,30 @@ export const multiplyOperator: OperatorDefinition & { evaluate: OperationEvaluat
|
|
|
71
71
|
left: { type: 'Quantity', singleton: true },
|
|
72
72
|
right: { type: 'Quantity', singleton: true },
|
|
73
73
|
result: { type: 'Quantity', singleton: true },
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'quantity-integer-multiply',
|
|
77
|
+
left: { type: 'Quantity', singleton: true },
|
|
78
|
+
right: { type: 'Integer', singleton: true },
|
|
79
|
+
result: { type: 'Quantity', singleton: true },
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'integer-quantity-multiply',
|
|
83
|
+
left: { type: 'Integer', singleton: true },
|
|
84
|
+
right: { type: 'Quantity', singleton: true },
|
|
85
|
+
result: { type: 'Quantity', singleton: true },
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'quantity-decimal-multiply',
|
|
89
|
+
left: { type: 'Quantity', singleton: true },
|
|
90
|
+
right: { type: 'Decimal', singleton: true },
|
|
91
|
+
result: { type: 'Quantity', singleton: true },
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'decimal-quantity-multiply',
|
|
95
|
+
left: { type: 'Decimal', singleton: true },
|
|
96
|
+
right: { type: 'Quantity', singleton: true },
|
|
97
|
+
result: { type: 'Quantity', singleton: true },
|
|
74
98
|
}
|
|
75
99
|
],
|
|
76
100
|
evaluate
|
|
@@ -1,33 +1,19 @@
|
|
|
1
|
-
import type { OperatorDefinition } from '../types';
|
|
1
|
+
import type { OperatorDefinition, OperationEvaluator } from '../types';
|
|
2
2
|
import { PRECEDENCE } from '../types';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box } from '../interpreter/boxing';
|
|
4
|
+
import { collectionsNotEqual } from './comparison';
|
|
6
5
|
|
|
7
6
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const boxedl = left[0];
|
|
13
|
-
if (!boxedl) return { value: [], context };
|
|
14
|
-
const l = unbox(boxedl);
|
|
15
|
-
const boxedr = right[0];
|
|
16
|
-
if (!boxedr) return { value: [], context };
|
|
17
|
-
const r = unbox(boxedr);
|
|
7
|
+
// Use the unified comparison system
|
|
8
|
+
const result = collectionsNotEqual(left, right);
|
|
18
9
|
|
|
19
|
-
//
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
const comparison = compareQuantities(l as QuantityValue, r as QuantityValue);
|
|
23
|
-
// If quantities are incomparable (different dimensions), return empty
|
|
24
|
-
if (comparison === null) {
|
|
25
|
-
return { value: [], context };
|
|
26
|
-
}
|
|
27
|
-
return { value: [box(comparison !== 0, { type: 'Boolean', singleton: true })], context };
|
|
10
|
+
// null means incomparable (returns empty)
|
|
11
|
+
if (result === null) {
|
|
12
|
+
return { value: [], context };
|
|
28
13
|
}
|
|
29
14
|
|
|
30
|
-
|
|
15
|
+
// Return the boolean result
|
|
16
|
+
return { value: [box(result, { type: 'Boolean', singleton: true })], context };
|
|
31
17
|
};
|
|
32
18
|
|
|
33
19
|
export const notEqualOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
@@ -38,11 +24,19 @@ export const notEqualOperator: OperatorDefinition & { evaluate: OperationEvaluat
|
|
|
38
24
|
associativity: 'left',
|
|
39
25
|
description: 'The converse of the equals operator, returning true if equal returns false; false if equal returns true; and empty ({ }) if equal returns empty',
|
|
40
26
|
examples: ['name != "John"', 'Patient.gender != "male"', '5 != 3'],
|
|
41
|
-
signatures: [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
27
|
+
signatures: [
|
|
28
|
+
{
|
|
29
|
+
name: 'not-equal',
|
|
30
|
+
left: { type: 'Any', singleton: true },
|
|
31
|
+
right: { type: 'Any', singleton: true },
|
|
32
|
+
result: { type: 'Boolean', singleton: true },
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'not-equal',
|
|
36
|
+
left: { type: 'Any', singleton: false },
|
|
37
|
+
right: { type: 'Any', singleton: false },
|
|
38
|
+
result: { type: 'Boolean', singleton: true },
|
|
39
|
+
}
|
|
40
|
+
],
|
|
47
41
|
evaluate
|
|
48
42
|
};
|
|
@@ -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 { collectionsNotEquivalent } from './comparison';
|
|
5
6
|
|
|
6
7
|
export const evaluate: OperationEvaluator = async (input, context, left, right) => {
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Different sizes are not equivalent, so not-equivalent returns true
|
|
13
|
-
if (left.length !== right.length) {
|
|
14
|
-
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// For single items, check type-specific non-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 non-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 non-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 non-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 inequality for now
|
|
45
|
-
// TODO: Implement full non-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 collectionsNotEquivalent function from comparison.ts
|
|
9
|
+
const result = collectionsNotEquivalent(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(true, { 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 notEquivalentOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
|
|
@@ -67,6 +26,7 @@ export const notEquivalentOperator: OperatorDefinition & { evaluate: OperationEv
|
|
|
67
26
|
category: ['equality'],
|
|
68
27
|
precedence: PRECEDENCE.EQUALITY,
|
|
69
28
|
associativity: 'left',
|
|
29
|
+
doesNotPropagateEmpty: true, // Empty collections are valid operands for not-equivalence
|
|
70
30
|
description: 'The converse of the equivalent operator, returning true if equivalent returns false and false if equivalent returns true. In other words, A !~ B is short-hand for (A ~ B).not()',
|
|
71
31
|
examples: [
|
|
72
32
|
"'abc' !~ 'ABC'",
|
|
@@ -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
|
// Three-valued logic implementation
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { FunctionDefinition, RuntimeContext, ASTNode, TypeInfo, NodeEvaluator, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import type { FHIRPathValue } from '../boxing';
|
|
4
|
-
import { unbox } from '../boxing';
|
|
3
|
+
import type { FHIRPathValue } from '../interpreter/boxing';
|
|
4
|
+
import { unbox } from '../interpreter/boxing';
|
|
5
5
|
import { isIdentifierNode, isFunctionNode } from '../types';
|
|
6
|
-
import { NodeType } from '../types';
|
|
7
6
|
|
|
8
7
|
export const ofTypeFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
9
8
|
name: 'ofType',
|
|
@@ -21,7 +20,8 @@ export const ofTypeFunction: FunctionDefinition & { evaluate: FunctionEvaluator
|
|
|
21
20
|
{
|
|
22
21
|
name: 'type',
|
|
23
22
|
type: { type: 'Any', singleton: true },
|
|
24
|
-
expression: true
|
|
23
|
+
expression: true,
|
|
24
|
+
typeReference: true // This parameter expects a type name
|
|
25
25
|
}
|
|
26
26
|
],
|
|
27
27
|
result: 'inputType'
|
|
@@ -37,10 +37,6 @@ export const ofTypeFunction: FunctionDefinition & { evaluate: FunctionEvaluator
|
|
|
37
37
|
let targetTypeName: string;
|
|
38
38
|
if (isIdentifierNode(typeArg)) {
|
|
39
39
|
targetTypeName = typeArg.name;
|
|
40
|
-
} else if (typeArg.type === NodeType.TypeOrIdentifier) {
|
|
41
|
-
targetTypeName = (typeArg as any).name;
|
|
42
|
-
} else if (typeArg.type === NodeType.TypeReference) {
|
|
43
|
-
targetTypeName = (typeArg as any).name;
|
|
44
40
|
} else if (isFunctionNode(typeArg) && isIdentifierNode(typeArg.name)) {
|
|
45
41
|
// Handle cases like ofType(Patient())
|
|
46
42
|
targetTypeName = typeArg.name.name;
|
|
@@ -91,9 +87,9 @@ export const ofTypeFunction: FunctionDefinition & { evaluate: FunctionEvaluator
|
|
|
91
87
|
return matchingType !== undefined;
|
|
92
88
|
}
|
|
93
89
|
|
|
94
|
-
// Check if the box has type information
|
|
95
|
-
if (boxedItem.typeInfo) {
|
|
96
|
-
// If we have type info, use it for accurate filtering
|
|
90
|
+
// Check if the box has specific type information (not just "Any")
|
|
91
|
+
if (boxedItem.typeInfo && boxedItem.typeInfo.type !== 'Any') {
|
|
92
|
+
// If we have specific type info, use it for accurate filtering
|
|
97
93
|
return boxedItem.typeInfo.type === targetTypeName;
|
|
98
94
|
}
|
|
99
95
|
|
|
@@ -136,4 +132,4 @@ export const ofTypeFunction: FunctionDefinition & { evaluate: FunctionEvaluator
|
|
|
136
132
|
|
|
137
133
|
return { value: actualFiltered, context };
|
|
138
134
|
}
|
|
139
|
-
};
|
|
135
|
+
};
|