@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
@@ -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
  // sqrt() takes no arguments
@@ -43,12 +43,19 @@ export const sqrtFunction: FunctionDefinition & { evaluate: FunctionEvaluator }
43
43
  '81.sqrt()',
44
44
  '(-1).sqrt()'
45
45
  ],
46
- signatures: [{
47
-
48
- name: 'sqrt',
49
- input: { type: 'Decimal', singleton: true },
50
- parameters: [],
51
- result: { type: 'Decimal', singleton: true }
52
- }],
46
+ signatures: [
47
+ {
48
+ name: 'sqrt-integer',
49
+ input: { type: 'Integer', singleton: true },
50
+ parameters: [],
51
+ result: { type: 'Decimal', singleton: true }
52
+ },
53
+ {
54
+ name: 'sqrt-decimal',
55
+ input: { type: 'Decimal', singleton: true },
56
+ parameters: [],
57
+ result: { type: 'Decimal', singleton: true }
58
+ }
59
+ ],
53
60
  evaluate
54
61
  };
@@ -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
  // startsWith() requires exactly 1 argument
@@ -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) {
@@ -19,7 +19,10 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
19
19
  }
20
20
  // Evaluate the argument with the root context ($this), not the current input
21
21
  // This allows expressions like Patient.name.given to work correctly
22
- const rootInput = context.variables['$this'] || context.input;
22
+ // $this contains the original input to the FHIRPath expression
23
+ // context.input contains the original input passed to evaluate()
24
+ // We should use $this if available, otherwise fall back to context.input
25
+ const rootInput = context.variables?.['$this'] || context.input || [];
23
26
  const otherResult = await evaluator(argNode, rootInput, context);
24
27
  const other = otherResult.value;
25
28
 
@@ -52,6 +55,7 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
52
55
 
53
56
  export const subsetOfFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
54
57
  name: 'subsetOf',
58
+ doesNotPropagateEmpty: true, // Returns true if input is empty
55
59
  category: ['existence'],
56
60
  description: 'Returns true if all items in the input collection are members of the collection passed as the other argument. Membership is determined using the equals (=) operation.',
57
61
  examples: [
@@ -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,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) {
@@ -14,7 +14,10 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
14
14
  }
15
15
  // Evaluate the argument with the root context ($this), not the current input
16
16
  // This allows expressions like Patient.name.given to work correctly
17
- const rootInput = context.variables['$this'] || context.input;
17
+ // $this contains the original input to the FHIRPath expression
18
+ // context.input contains the original input passed to evaluate()
19
+ // We should use $this if available, otherwise fall back to context.input
20
+ const rootInput = context.variables?.['$this'] || context.input || [];
18
21
  const otherResult = await evaluator(argNode, rootInput, context);
19
22
  const other = otherResult.value;
20
23
 
@@ -52,6 +55,7 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
52
55
 
53
56
  export const supersetOfFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
54
57
  name: 'supersetOf',
58
+ doesNotPropagateEmpty: true, // Returns false if input is empty (unless other is also empty)
55
59
  category: ['existence'],
56
60
  description: 'Returns true if all items in the collection passed as the other argument are members of the input collection. Membership is determined using the equals (=) operation.',
57
61
  examples: [
@@ -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
  // tail() takes no arguments
@@ -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
  // Validate arguments
@@ -0,0 +1,555 @@
1
+ // Built-in temporal functions for FHIRPath
2
+ import type { FunctionDefinition } from '../types';
3
+ import type { FunctionEvaluator } from '../types';
4
+ import { box } from '../interpreter/boxing';
5
+ import { createDate, createDateTime, createTime, isFHIRDate, isFHIRDateTime, isFHIRTime } from '../complex-types/temporal';
6
+
7
+ // ============================================================================
8
+ // now() function - Returns current DateTime
9
+ // ============================================================================
10
+
11
+ export const nowEvaluator: FunctionEvaluator = async (input, context, args) => {
12
+ // Always use cached value if available (set at expression evaluation start)
13
+ const CACHE_KEY = '__fhirpath_now_cache__';
14
+
15
+ if (context.variables[CACHE_KEY]) {
16
+ return {
17
+ value: [context.variables[CACHE_KEY]],
18
+ context
19
+ };
20
+ }
21
+
22
+ // Fallback: create new value if not cached (shouldn't happen in normal evaluation)
23
+ const now = new Date();
24
+ const dateTime = createDateTime(
25
+ now.getFullYear(),
26
+ now.getMonth() + 1,
27
+ now.getDate(),
28
+ now.getHours(),
29
+ now.getMinutes(),
30
+ now.getSeconds(),
31
+ now.getMilliseconds(),
32
+ -now.getTimezoneOffset() // Convert to minutes from UTC (JS gives minutes to subtract from UTC)
33
+ );
34
+
35
+ return {
36
+ value: [box(dateTime, { type: 'DateTime', singleton: true })],
37
+ context
38
+ };
39
+ };
40
+
41
+ export const nowFunction: FunctionDefinition & { evaluate: typeof nowEvaluator } = {
42
+ name: 'now',
43
+ category: ['temporal'],
44
+ description: 'Returns the current date and time as a DateTime value',
45
+ examples: ['now()', 'Patient.birthDate < now()'],
46
+ signatures: [
47
+ {
48
+ name: 'now',
49
+ input: { type: 'Any', singleton: false },
50
+ parameters: [],
51
+ result: { type: 'DateTime', singleton: true }
52
+ }
53
+ ],
54
+ evaluate: nowEvaluator
55
+ };
56
+
57
+ // ============================================================================
58
+ // today() function - Returns current Date
59
+ // ============================================================================
60
+
61
+ export const todayEvaluator: FunctionEvaluator = async (input, context, args) => {
62
+ // Always use cached value if available (set at expression evaluation start)
63
+ const CACHE_KEY = '__fhirpath_today_cache__';
64
+
65
+ if (context.variables[CACHE_KEY]) {
66
+ return {
67
+ value: [context.variables[CACHE_KEY]],
68
+ context
69
+ };
70
+ }
71
+
72
+ // Fallback: create new value if not cached (shouldn't happen in normal evaluation)
73
+ const today = new Date();
74
+ const date = createDate(
75
+ today.getFullYear(),
76
+ today.getMonth() + 1,
77
+ today.getDate()
78
+ );
79
+
80
+ return {
81
+ value: [box(date, { type: 'Date', singleton: true })],
82
+ context
83
+ };
84
+ };
85
+
86
+ export const todayFunction: FunctionDefinition & { evaluate: typeof todayEvaluator } = {
87
+ name: 'today',
88
+ category: ['temporal'],
89
+ description: 'Returns the current date (no time component)',
90
+ examples: ['today()', 'Patient.birthDate < today()'],
91
+ signatures: [
92
+ {
93
+ name: 'today',
94
+ input: { type: 'Any', singleton: false },
95
+ parameters: [],
96
+ result: { type: 'Date', singleton: true }
97
+ }
98
+ ],
99
+ evaluate: todayEvaluator
100
+ };
101
+
102
+ // ============================================================================
103
+ // timeOfDay() function - Returns current Time
104
+ // ============================================================================
105
+
106
+ export const timeOfDayEvaluator: FunctionEvaluator = async (input, context, args) => {
107
+ // Always use cached value if available (set at expression evaluation start)
108
+ const CACHE_KEY = '__fhirpath_timeOfDay_cache__';
109
+
110
+ if (context.variables[CACHE_KEY]) {
111
+ return {
112
+ value: [context.variables[CACHE_KEY]],
113
+ context
114
+ };
115
+ }
116
+
117
+ // Fallback: create new value if not cached (shouldn't happen in normal evaluation)
118
+ const now = new Date();
119
+ const time = createTime(
120
+ now.getHours(),
121
+ now.getMinutes(),
122
+ now.getSeconds(),
123
+ now.getMilliseconds()
124
+ );
125
+
126
+ return {
127
+ value: [box(time, { type: 'Time', singleton: true })],
128
+ context
129
+ };
130
+ };
131
+
132
+ export const timeOfDayFunction: FunctionDefinition & { evaluate: typeof timeOfDayEvaluator } = {
133
+ name: 'timeOfDay',
134
+ category: ['temporal'],
135
+ description: 'Returns the current time of day',
136
+ examples: ['timeOfDay()', '@T09:00 < timeOfDay()'],
137
+ signatures: [
138
+ {
139
+ name: 'timeOfDay',
140
+ input: { type: 'Any', singleton: false },
141
+ parameters: [],
142
+ result: { type: 'Time', singleton: true }
143
+ }
144
+ ],
145
+ evaluate: timeOfDayEvaluator
146
+ };
147
+
148
+ // ============================================================================
149
+ // toDate() function - Convert to Date
150
+ // ============================================================================
151
+
152
+ export const toDateEvaluator: FunctionEvaluator = async (input, context, args) => {
153
+ if (input.length === 0) {
154
+ return { value: [], context };
155
+ }
156
+
157
+ const boxedItem = input[0];
158
+ if (!boxedItem) {
159
+ return { value: [], context };
160
+ }
161
+
162
+ const item = boxedItem.value;
163
+ const typeInfo = boxedItem.typeInfo;
164
+
165
+ // If it's already a Date, return as is
166
+ if (typeInfo?.type === 'Date') {
167
+ return { value: [boxedItem], context };
168
+ }
169
+
170
+ // If it's a DateTime, extract the date portion
171
+ if (typeInfo?.type === 'DateTime' && item && typeof item === 'object') {
172
+ const dateTime = item as any;
173
+ const date = createDate(dateTime.year, dateTime.month, dateTime.day);
174
+ return {
175
+ value: [box(date, { type: 'Date', singleton: true })],
176
+ context
177
+ };
178
+ }
179
+
180
+ // If it's a String, try to parse it
181
+ if (typeof item === 'string') {
182
+ try {
183
+ const { parseTemporalLiteral, isFHIRDate } = await import('../complex-types/temporal');
184
+ const temporal = parseTemporalLiteral('@' + item);
185
+ if (isFHIRDate(temporal)) {
186
+ return {
187
+ value: [box(temporal, { type: 'Date', singleton: true })],
188
+ context
189
+ };
190
+ }
191
+ } catch {
192
+ // Parsing failed, return empty
193
+ }
194
+ }
195
+
196
+ // For any other type (including Boolean), return empty
197
+ // This allows expressions like ({}).empty().toDate() to return []
198
+ return { value: [], context };
199
+ };
200
+
201
+ export const toDateFunction: FunctionDefinition & { evaluate: typeof toDateEvaluator } = {
202
+ name: 'toDate',
203
+ category: ['conversion', 'temporal'],
204
+ description: 'Converts the input to a Date value',
205
+ examples: ["'2020-01-15'.toDate()", '@2020-01-15T10:30:00.toDate()'],
206
+ signatures: [
207
+ {
208
+ name: 'any-toDate',
209
+ parameters: [],
210
+ input: { type: 'Any', singleton: true },
211
+ result: { type: 'Date', singleton: true }
212
+ }
213
+ ],
214
+ evaluate: toDateEvaluator
215
+ };
216
+
217
+ // ============================================================================
218
+ // toDateTime() function - Convert to DateTime
219
+ // ============================================================================
220
+
221
+ export const toDateTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
222
+ if (input.length === 0) {
223
+ return { value: [], context };
224
+ }
225
+
226
+ const boxedItem = input[0];
227
+ if (!boxedItem) {
228
+ return { value: [], context };
229
+ }
230
+
231
+ const item = boxedItem.value;
232
+ const typeInfo = boxedItem.typeInfo;
233
+
234
+ // If it's already a DateTime, return as is
235
+ if (typeInfo?.type === 'DateTime') {
236
+ return { value: [boxedItem], context };
237
+ }
238
+
239
+ // If it's a Date, convert to DateTime with time as 00:00:00
240
+ if (typeInfo?.type === 'Date' && item && typeof item === 'object') {
241
+ const date = item as any;
242
+ const dateTime = createDateTime(
243
+ date.year,
244
+ date.month,
245
+ date.day,
246
+ 0, 0, 0, 0
247
+ );
248
+ return {
249
+ value: [box(dateTime, { type: 'DateTime', singleton: true })],
250
+ context
251
+ };
252
+ }
253
+
254
+ // If it's a String, try to parse it
255
+ if (typeof item === 'string') {
256
+ try {
257
+ const { parseTemporalLiteral, isFHIRDate, isFHIRDateTime } = await import('../complex-types/temporal');
258
+ const temporal = parseTemporalLiteral('@' + item);
259
+ if (isFHIRDateTime(temporal)) {
260
+ return {
261
+ value: [box(temporal, { type: 'DateTime', singleton: true })],
262
+ context
263
+ };
264
+ } else if (isFHIRDate(temporal)) {
265
+ // Convert Date to DateTime (with time as 00:00:00)
266
+ const dateTime = createDateTime(
267
+ temporal.year,
268
+ temporal.month,
269
+ temporal.day,
270
+ 0, 0, 0, 0
271
+ );
272
+ return {
273
+ value: [box(dateTime, { type: 'DateTime', singleton: true })],
274
+ context
275
+ };
276
+ }
277
+ } catch {
278
+ // Parsing failed, return empty
279
+ }
280
+ }
281
+
282
+ // For any other type (including Boolean), return empty
283
+ // This allows expressions like ({}).empty().toDateTime() to return []
284
+ return { value: [], context };
285
+ };
286
+
287
+ export const toDateTimeFunction: FunctionDefinition & { evaluate: typeof toDateTimeEvaluator } = {
288
+ name: 'toDateTime',
289
+ category: ['conversion', 'temporal'],
290
+ description: 'Converts the input to a DateTime value',
291
+ examples: ["'2020-01-15T10:30:00Z'.toDateTime()", '@2020-01-15.toDateTime()'],
292
+ signatures: [
293
+ {
294
+ name: 'any-toDateTime',
295
+ parameters: [],
296
+ input: { type: 'Any', singleton: true },
297
+ result: { type: 'DateTime', singleton: true }
298
+ }
299
+ ],
300
+ evaluate: toDateTimeEvaluator
301
+ };
302
+
303
+ // ============================================================================
304
+ // toTime() function - Convert to Time
305
+ // ============================================================================
306
+
307
+ export const toTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
308
+ if (input.length === 0) {
309
+ return { value: [], context };
310
+ }
311
+
312
+ const boxedItem = input[0];
313
+ if (!boxedItem) {
314
+ return { value: [], context };
315
+ }
316
+
317
+ const item = boxedItem.value;
318
+ const typeInfo = boxedItem.typeInfo;
319
+
320
+ // If it's already a Time, return as is
321
+ if (typeInfo?.type === 'Time') {
322
+ return { value: [boxedItem], context };
323
+ }
324
+
325
+ // If it's a DateTime, extract the time portion
326
+ if (typeInfo?.type === 'DateTime' && item && typeof item === 'object') {
327
+ const dateTime = item as any;
328
+ if (dateTime.hour !== undefined) {
329
+ const time = createTime(dateTime.hour, dateTime.minute, dateTime.second, dateTime.millisecond);
330
+ return {
331
+ value: [box(time, { type: 'Time', singleton: true })],
332
+ context
333
+ };
334
+ }
335
+ }
336
+
337
+ // If it's a String, try to parse it
338
+ if (typeof item === 'string') {
339
+ try {
340
+ const { parseTemporalLiteral, isFHIRTime } = await import('../complex-types/temporal');
341
+ // For time strings, prepend T if not present
342
+ const timeString = item.startsWith('T') ? '@' + item : '@T' + item;
343
+ const temporal = parseTemporalLiteral(timeString);
344
+ if (isFHIRTime(temporal)) {
345
+ return {
346
+ value: [box(temporal, { type: 'Time', singleton: true })],
347
+ context
348
+ };
349
+ }
350
+ } catch {
351
+ // Parsing failed, return empty
352
+ }
353
+ }
354
+
355
+ // For any other type (including Boolean), return empty
356
+ // This allows expressions like ({}).empty().toTime() to return []
357
+ return { value: [], context };
358
+ };
359
+
360
+ export const toTimeFunction: FunctionDefinition & { evaluate: typeof toTimeEvaluator } = {
361
+ name: 'toTime',
362
+ category: ['conversion', 'temporal'],
363
+ description: 'Converts the input to a Time value',
364
+ examples: ["'14:30:00'.toTime()", '@2020-01-15T10:30:00.toTime()'],
365
+ signatures: [
366
+ {
367
+ name: 'any-toTime',
368
+ parameters: [],
369
+ input: { type: 'Any', singleton: true },
370
+ result: { type: 'Time', singleton: true }
371
+ }
372
+ ],
373
+ evaluate: toTimeEvaluator
374
+ };
375
+
376
+ // ============================================================================
377
+ // convertsToDate() function - Check if convertible to Date
378
+ // ============================================================================
379
+
380
+ export const convertsToDateEvaluator: FunctionEvaluator = async (input, context, args) => {
381
+ if (input.length === 0) {
382
+ return { value: [], context };
383
+ }
384
+
385
+ const boxedItem = input[0];
386
+ if (!boxedItem) {
387
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
388
+ }
389
+
390
+ const item = boxedItem.value;
391
+ const typeInfo = boxedItem.typeInfo;
392
+
393
+ // Already a Date
394
+ if (typeInfo?.type === 'Date') {
395
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
396
+ }
397
+
398
+ // DateTime can be converted
399
+ if (typeInfo?.type === 'DateTime') {
400
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
401
+ }
402
+
403
+ // Try to parse string
404
+ if (typeof item === 'string') {
405
+ try {
406
+ const { parseTemporalLiteral, isFHIRDate } = await import('../complex-types/temporal');
407
+ const temporal = parseTemporalLiteral('@' + item);
408
+ return {
409
+ value: [box(isFHIRDate(temporal), { type: 'Boolean', singleton: true })],
410
+ context
411
+ };
412
+ } catch {
413
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
414
+ }
415
+ }
416
+
417
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
418
+ };
419
+
420
+ export const convertsToDateFunction: FunctionDefinition & { evaluate: typeof convertsToDateEvaluator } = {
421
+ name: 'convertsToDate',
422
+ category: ['conversion', 'temporal'],
423
+ description: 'Returns true if the input can be converted to a Date',
424
+ examples: ["'2020-01-15'.convertsToDate()", "'invalid'.convertsToDate()"],
425
+ signatures: [
426
+ {
427
+ name: 'convertsToDate',
428
+ parameters: [],
429
+ input: { type: 'Any', singleton: true },
430
+ result: { type: 'Boolean', singleton: true }
431
+ }
432
+ ],
433
+ evaluate: convertsToDateEvaluator
434
+ };
435
+
436
+ // ============================================================================
437
+ // convertsToDateTime() function - Check if convertible to DateTime
438
+ // ============================================================================
439
+
440
+ export const convertsToDateTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
441
+ if (input.length === 0) {
442
+ return { value: [], context };
443
+ }
444
+
445
+ const boxedItem = input[0];
446
+ if (!boxedItem) {
447
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
448
+ }
449
+
450
+ const item = boxedItem.value;
451
+ const typeInfo = boxedItem.typeInfo;
452
+
453
+ // Already a DateTime
454
+ if (typeInfo?.type === 'DateTime') {
455
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
456
+ }
457
+
458
+ // Date can be converted
459
+ if (typeInfo?.type === 'Date') {
460
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
461
+ }
462
+
463
+ // Try to parse string
464
+ if (typeof item === 'string') {
465
+ try {
466
+ const { parseTemporalLiteral, isFHIRDate, isFHIRDateTime } = await import('../complex-types/temporal');
467
+ const temporal = parseTemporalLiteral('@' + item);
468
+ return {
469
+ value: [box(isFHIRDateTime(temporal) || isFHIRDate(temporal), { type: 'Boolean', singleton: true })],
470
+ context
471
+ };
472
+ } catch {
473
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
474
+ }
475
+ }
476
+
477
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
478
+ };
479
+
480
+ export const convertsToDateTimeFunction: FunctionDefinition & { evaluate: typeof convertsToDateTimeEvaluator } = {
481
+ name: 'convertsToDateTime',
482
+ category: ['conversion', 'temporal'],
483
+ description: 'Returns true if the input can be converted to a DateTime',
484
+ examples: ["'2020-01-15T10:30:00'.convertsToDateTime()", "'invalid'.convertsToDateTime()"],
485
+ signatures: [
486
+ {
487
+ name: 'convertsToDateTime',
488
+ parameters: [],
489
+ input: { type: 'Any', singleton: true },
490
+ result: { type: 'Boolean', singleton: true }
491
+ }
492
+ ],
493
+ evaluate: convertsToDateTimeEvaluator
494
+ };
495
+
496
+ // ============================================================================
497
+ // convertsToTime() function - Check if convertible to Time
498
+ // ============================================================================
499
+
500
+ export const convertsToTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
501
+ if (input.length === 0) {
502
+ return { value: [], context };
503
+ }
504
+
505
+ const boxedItem = input[0];
506
+ if (!boxedItem) {
507
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
508
+ }
509
+
510
+ const item = boxedItem.value;
511
+ const typeInfo = boxedItem.typeInfo;
512
+
513
+ // Already a Time
514
+ if (typeInfo?.type === 'Time') {
515
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
516
+ }
517
+
518
+ // DateTime can be converted if it has time components
519
+ if (typeInfo?.type === 'DateTime') {
520
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
521
+ }
522
+
523
+ // Try to parse string
524
+ if (typeof item === 'string') {
525
+ try {
526
+ const { parseTemporalLiteral, isFHIRTime } = await import('../complex-types/temporal');
527
+ const timeString = item.startsWith('T') ? '@' + item : '@T' + item;
528
+ const temporal = parseTemporalLiteral(timeString);
529
+ return {
530
+ value: [box(isFHIRTime(temporal), { type: 'Boolean', singleton: true })],
531
+ context
532
+ };
533
+ } catch {
534
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
535
+ }
536
+ }
537
+
538
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
539
+ };
540
+
541
+ export const convertsToTimeFunction: FunctionDefinition & { evaluate: typeof convertsToTimeEvaluator } = {
542
+ name: 'convertsToTime',
543
+ category: ['conversion', 'temporal'],
544
+ description: 'Returns true if the input can be converted to a Time',
545
+ examples: ["'14:30:00'.convertsToTime()", "'invalid'.convertsToTime()"],
546
+ signatures: [
547
+ {
548
+ name: 'convertsToTime',
549
+ parameters: [],
550
+ input: { type: 'Any', singleton: true },
551
+ result: { type: 'Boolean', singleton: true }
552
+ }
553
+ ],
554
+ evaluate: convertsToTimeEvaluator
555
+ };