@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.
Files changed (143) hide show
  1. package/README.md +716 -238
  2. package/dist/index.d.ts +225 -119
  3. package/dist/index.js +10911 -5600
  4. package/dist/index.js.map +1 -1
  5. package/package.json +9 -4
  6. package/src/analyzer/augmentor.ts +242 -0
  7. package/src/analyzer/cursor-services.ts +75 -0
  8. package/src/analyzer/scope-manager.ts +57 -0
  9. package/src/analyzer/trivia-indexer.ts +58 -0
  10. package/src/analyzer/type-compat.ts +157 -0
  11. package/src/analyzer/utils.ts +132 -0
  12. package/src/analyzer.ts +921 -1208
  13. package/src/completion-provider.ts +209 -191
  14. package/src/{quantity-value.ts → complex-types/quantity-value.ts} +112 -22
  15. package/src/complex-types/temporal.ts +1737 -0
  16. package/src/errors.ts +25 -3
  17. package/src/index.ts +17 -104
  18. package/src/inspect.ts +4 -4
  19. package/src/{boxing.ts → interpreter/boxing.ts} +1 -1
  20. package/src/interpreter/navigator.ts +94 -0
  21. package/src/interpreter/runtime-context.ts +273 -0
  22. package/src/interpreter.ts +435 -469
  23. package/src/lexer.ts +188 -210
  24. package/src/model-provider.ts +71 -43
  25. package/src/operations/abs-function.ts +1 -1
  26. package/src/operations/aggregate-function.ts +84 -5
  27. package/src/operations/all-function.ts +4 -3
  28. package/src/operations/allFalse-function.ts +2 -1
  29. package/src/operations/allTrue-function.ts +2 -1
  30. package/src/operations/and-operator.ts +2 -1
  31. package/src/operations/anyFalse-function.ts +2 -1
  32. package/src/operations/anyTrue-function.ts +2 -1
  33. package/src/operations/as-function.ts +58 -0
  34. package/src/operations/as-operator.ts +57 -19
  35. package/src/operations/ceiling-function.ts +1 -1
  36. package/src/operations/children-function.ts +14 -5
  37. package/src/operations/combine-function.ts +6 -3
  38. package/src/operations/combine-operator.ts +6 -7
  39. package/src/operations/comparison.ts +692 -0
  40. package/src/operations/contains-function.ts +1 -1
  41. package/src/operations/contains-operator.ts +2 -1
  42. package/src/operations/convertsToBoolean-function.ts +78 -0
  43. package/src/operations/convertsToDecimal-function.ts +82 -0
  44. package/src/operations/convertsToInteger-function.ts +71 -0
  45. package/src/operations/convertsToLong-function.ts +89 -0
  46. package/src/operations/convertsToQuantity-function.ts +116 -0
  47. package/src/operations/convertsToString-function.ts +88 -0
  48. package/src/operations/count-function.ts +2 -1
  49. package/src/operations/dateOf-function.ts +69 -0
  50. package/src/operations/dayOf-function.ts +66 -0
  51. package/src/operations/decimal-boundaries.ts +133 -0
  52. package/src/operations/defineVariable-function.ts +130 -17
  53. package/src/operations/distinct-function.ts +1 -1
  54. package/src/operations/div-operator.ts +1 -1
  55. package/src/operations/divide-operator.ts +12 -7
  56. package/src/operations/dot-operator.ts +1 -1
  57. package/src/operations/empty-function.ts +30 -21
  58. package/src/operations/endsWith-function.ts +6 -1
  59. package/src/operations/equal-operator.ts +23 -32
  60. package/src/operations/equivalent-operator.ts +13 -53
  61. package/src/operations/exclude-function.ts +2 -1
  62. package/src/operations/exists-function.ts +4 -3
  63. package/src/operations/first-function.ts +1 -1
  64. package/src/operations/floor-function.ts +1 -1
  65. package/src/operations/greater-operator.ts +20 -3
  66. package/src/operations/greater-or-equal-operator.ts +20 -3
  67. package/src/operations/highBoundary-function.ts +120 -0
  68. package/src/operations/hourOf-function.ts +66 -0
  69. package/src/operations/iif-function.ts +186 -7
  70. package/src/operations/implies-operator.ts +1 -1
  71. package/src/operations/in-operator.ts +2 -1
  72. package/src/operations/index.ts +41 -0
  73. package/src/operations/indexOf-function.ts +1 -1
  74. package/src/operations/intersect-function.ts +1 -1
  75. package/src/operations/is-function.ts +59 -0
  76. package/src/operations/is-operator.ts +20 -9
  77. package/src/operations/isDistinct-function.ts +2 -1
  78. package/src/operations/join-function.ts +1 -1
  79. package/src/operations/last-function.ts +1 -1
  80. package/src/operations/lastIndexOf-function.ts +85 -0
  81. package/src/operations/length-function.ts +1 -1
  82. package/src/operations/less-operator.ts +20 -3
  83. package/src/operations/less-or-equal-operator.ts +20 -3
  84. package/src/operations/less-than.ts +2 -2
  85. package/src/operations/lowBoundary-function.ts +120 -0
  86. package/src/operations/lower-function.ts +1 -1
  87. package/src/operations/matches-function.ts +86 -0
  88. package/src/operations/matchesFull-function.ts +96 -0
  89. package/src/operations/millisecondOf-function.ts +66 -0
  90. package/src/operations/minus-operator.ts +69 -4
  91. package/src/operations/minuteOf-function.ts +66 -0
  92. package/src/operations/mod-operator.ts +1 -1
  93. package/src/operations/monthOf-function.ts +66 -0
  94. package/src/operations/multiply-operator.ts +27 -3
  95. package/src/operations/not-equal-operator.ts +24 -30
  96. package/src/operations/not-equivalent-operator.ts +13 -53
  97. package/src/operations/not-function.ts +1 -1
  98. package/src/operations/ofType-function.ts +8 -12
  99. package/src/operations/or-operator.ts +2 -1
  100. package/src/operations/plus-operator.ts +71 -7
  101. package/src/operations/power-function.ts +35 -10
  102. package/src/operations/repeat-function.ts +169 -0
  103. package/src/operations/replace-function.ts +1 -1
  104. package/src/operations/replaceMatches-function.ts +120 -0
  105. package/src/operations/round-function.ts +1 -1
  106. package/src/operations/secondOf-function.ts +66 -0
  107. package/src/operations/select-function.ts +66 -5
  108. package/src/operations/single-function.ts +1 -1
  109. package/src/operations/skip-function.ts +1 -1
  110. package/src/operations/split-function.ts +1 -1
  111. package/src/operations/sqrt-function.ts +15 -8
  112. package/src/operations/startsWith-function.ts +1 -1
  113. package/src/operations/subsetOf-function.ts +6 -2
  114. package/src/operations/substring-function.ts +1 -1
  115. package/src/operations/supersetOf-function.ts +6 -2
  116. package/src/operations/tail-function.ts +1 -1
  117. package/src/operations/take-function.ts +1 -1
  118. package/src/operations/temporal-functions.ts +555 -0
  119. package/src/operations/timeOf-function.ts +67 -0
  120. package/src/operations/timezoneOffsetOf-function.ts +69 -0
  121. package/src/operations/toBoolean-function.ts +27 -8
  122. package/src/operations/toChars-function.ts +56 -0
  123. package/src/operations/toDecimal-function.ts +27 -8
  124. package/src/operations/toInteger-function.ts +15 -3
  125. package/src/operations/toLong-function.ts +98 -0
  126. package/src/operations/toQuantity-function.ts +181 -0
  127. package/src/operations/toString-function.ts +45 -3
  128. package/src/operations/trace-function.ts +1 -1
  129. package/src/operations/trim-function.ts +1 -1
  130. package/src/operations/truncate-function.ts +1 -1
  131. package/src/operations/unary-minus-operator.ts +2 -2
  132. package/src/operations/unary-plus-operator.ts +1 -1
  133. package/src/operations/union-function.ts +1 -1
  134. package/src/operations/union-operator.ts +16 -26
  135. package/src/operations/upper-function.ts +1 -1
  136. package/src/operations/where-function.ts +3 -3
  137. package/src/operations/xor-operator.ts +1 -1
  138. package/src/operations/yearOf-function.ts +66 -0
  139. package/src/{cursor-nodes.ts → parser/cursor-nodes.ts} +10 -7
  140. package/src/parser.ts +248 -501
  141. package/src/registry.ts +53 -42
  142. package/src/types.ts +128 -16
  143. package/src/utils/pprint.ts +151 -0
@@ -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 in context, use it for accurate type checking
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 = 'getTypeFromCache' in context.modelProvider
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
- // Simple check for date-like strings
69
- return { value: [box(typeof item === 'string' && !isNaN(Date.parse(item)), { type: 'Boolean', singleton: true })], context };
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
+ };