@atomic-ehr/fhirpath 0.0.2 → 0.0.3-canary.2be66fb.20250905161900

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +716 -238
  2. package/dist/index.d.ts +226 -120
  3. package/dist/index.js +11552 -5580
  4. package/dist/index.js.map +1 -1
  5. package/package.json +12 -5
  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 +939 -1204
  13. package/src/completion-provider.ts +209 -191
  14. package/src/complex-types/quantity-value.ts +410 -0
  15. package/src/complex-types/temporal.ts +1776 -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 +506 -468
  23. package/src/lexer.ts +192 -211
  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 +99 -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 +744 -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 +132 -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/extension-function.ts +84 -0
  64. package/src/operations/first-function.ts +1 -1
  65. package/src/operations/floor-function.ts +1 -1
  66. package/src/operations/greater-operator.ts +7 -9
  67. package/src/operations/greater-or-equal-operator.ts +7 -9
  68. package/src/operations/highBoundary-function.ts +120 -0
  69. package/src/operations/hourOf-function.ts +66 -0
  70. package/src/operations/iif-function.ts +193 -8
  71. package/src/operations/implies-operator.ts +2 -1
  72. package/src/operations/in-operator.ts +2 -1
  73. package/src/operations/index.ts +43 -0
  74. package/src/operations/indexOf-function.ts +1 -1
  75. package/src/operations/intersect-function.ts +1 -1
  76. package/src/operations/is-function.ts +70 -0
  77. package/src/operations/is-operator.ts +176 -13
  78. package/src/operations/isDistinct-function.ts +2 -1
  79. package/src/operations/join-function.ts +1 -1
  80. package/src/operations/last-function.ts +1 -1
  81. package/src/operations/lastIndexOf-function.ts +85 -0
  82. package/src/operations/length-function.ts +1 -1
  83. package/src/operations/less-operator.ts +8 -9
  84. package/src/operations/less-or-equal-operator.ts +7 -9
  85. package/src/operations/less-than.ts +8 -13
  86. package/src/operations/lowBoundary-function.ts +120 -0
  87. package/src/operations/lower-function.ts +1 -1
  88. package/src/operations/matches-function.ts +86 -0
  89. package/src/operations/matchesFull-function.ts +96 -0
  90. package/src/operations/millisecondOf-function.ts +66 -0
  91. package/src/operations/minus-operator.ts +76 -4
  92. package/src/operations/minuteOf-function.ts +66 -0
  93. package/src/operations/mod-operator.ts +8 -2
  94. package/src/operations/monthOf-function.ts +66 -0
  95. package/src/operations/multiply-operator.ts +27 -3
  96. package/src/operations/not-equal-operator.ts +24 -30
  97. package/src/operations/not-equivalent-operator.ts +13 -53
  98. package/src/operations/not-function.ts +10 -3
  99. package/src/operations/ofType-function.ts +43 -12
  100. package/src/operations/or-operator.ts +2 -1
  101. package/src/operations/plus-operator.ts +71 -7
  102. package/src/operations/power-function.ts +35 -10
  103. package/src/operations/precision-function.ts +146 -0
  104. package/src/operations/repeat-function.ts +169 -0
  105. package/src/operations/replace-function.ts +1 -1
  106. package/src/operations/replaceMatches-function.ts +125 -0
  107. package/src/operations/round-function.ts +1 -1
  108. package/src/operations/secondOf-function.ts +66 -0
  109. package/src/operations/select-function.ts +66 -5
  110. package/src/operations/single-function.ts +1 -1
  111. package/src/operations/skip-function.ts +1 -1
  112. package/src/operations/split-function.ts +1 -1
  113. package/src/operations/sqrt-function.ts +15 -8
  114. package/src/operations/startsWith-function.ts +1 -1
  115. package/src/operations/subsetOf-function.ts +6 -2
  116. package/src/operations/substring-function.ts +1 -1
  117. package/src/operations/supersetOf-function.ts +6 -2
  118. package/src/operations/tail-function.ts +1 -1
  119. package/src/operations/take-function.ts +1 -1
  120. package/src/operations/temporal-functions.ts +555 -0
  121. package/src/operations/timeOf-function.ts +67 -0
  122. package/src/operations/timezoneOffsetOf-function.ts +69 -0
  123. package/src/operations/toBoolean-function.ts +27 -8
  124. package/src/operations/toChars-function.ts +56 -0
  125. package/src/operations/toDecimal-function.ts +27 -8
  126. package/src/operations/toInteger-function.ts +15 -3
  127. package/src/operations/toLong-function.ts +98 -0
  128. package/src/operations/toQuantity-function.ts +181 -0
  129. package/src/operations/toString-function.ts +78 -15
  130. package/src/operations/trace-function.ts +1 -1
  131. package/src/operations/trim-function.ts +1 -1
  132. package/src/operations/truncate-function.ts +1 -1
  133. package/src/operations/unary-minus-operator.ts +2 -2
  134. package/src/operations/unary-plus-operator.ts +1 -1
  135. package/src/operations/union-function.ts +1 -1
  136. package/src/operations/union-operator.ts +16 -26
  137. package/src/operations/upper-function.ts +1 -1
  138. package/src/operations/where-function.ts +3 -3
  139. package/src/operations/xor-operator.ts +1 -1
  140. package/src/operations/yearOf-function.ts +66 -0
  141. package/src/{cursor-nodes.ts → parser/cursor-nodes.ts} +10 -7
  142. package/src/parser.ts +262 -503
  143. package/src/registry.ts +53 -42
  144. package/src/types.ts +129 -17
  145. package/src/utils/decimal.ts +76 -0
  146. package/src/utils/pprint.ts +151 -0
  147. package/src/quantity-value.ts +0 -198
@@ -0,0 +1,169 @@
1
+ import type { FunctionDefinition, AnalysisContext, InternalAnalysisResult } from '../types';
2
+ import { Errors } from '../errors';
3
+ import { RuntimeContextManager } from '../interpreter/runtime-context';
4
+ import { type FunctionEvaluator } from '../types';
5
+ import { unbox, box } from '../interpreter/boxing';
6
+ import { collectionsEqual } from './comparison';
7
+
8
+ export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
9
+ // Repeat requires exactly one argument
10
+ if (args.length !== 1) {
11
+ throw Errors.wrongArgumentCount('repeat', 1, args.length);
12
+ }
13
+
14
+ const expression = args[0];
15
+ if (!expression) {
16
+ throw Errors.invalidOperation('repeat requires a projection expression');
17
+ }
18
+
19
+ // Result collection that will accumulate all unique items
20
+ const result: any[] = [];
21
+
22
+ // Track which items we've already seen to detect duplicates
23
+ const seen = new Set<any>();
24
+
25
+ // Helper to check if an item is already in the result
26
+ const isInResult = (item: any): boolean => {
27
+ for (const existing of result) {
28
+ // Use equals comparison to determine if items are the same
29
+ const equalResult = collectionsEqual([existing], [item]);
30
+ if (equalResult === true) {
31
+ return true;
32
+ }
33
+ }
34
+ return false;
35
+ };
36
+
37
+ // Initial evaluation on input collection
38
+ const initialResults: any[] = [];
39
+ for (let i = 0; i < input.length; i++) {
40
+ const boxedItem = input[i];
41
+ if (!boxedItem) continue;
42
+
43
+ const item = unbox(boxedItem);
44
+
45
+ // Create iterator context with $this and $index
46
+ let tempContext = RuntimeContextManager.withIterator(context, item, i);
47
+ tempContext = RuntimeContextManager.setVariable(tempContext, '$total', input.length);
48
+
49
+ // Evaluate expression with temporary context
50
+ const exprResult = await evaluator(expression, [boxedItem], tempContext);
51
+
52
+ // Add results from initial evaluation
53
+ for (const newItem of exprResult.value) {
54
+ if (newItem && !isInResult(newItem)) {
55
+ result.push(newItem);
56
+ initialResults.push(newItem);
57
+ }
58
+ }
59
+ }
60
+
61
+ // Now process the queue with items from the initial results
62
+ let queue = [...initialResults];
63
+
64
+ // Process items until queue is empty
65
+ while (queue.length > 0) {
66
+ const nextQueue: any[] = [];
67
+
68
+ for (let i = 0; i < queue.length; i++) {
69
+ const boxedItem = queue[i];
70
+ if (!boxedItem) continue;
71
+
72
+ const item = unbox(boxedItem);
73
+
74
+ // Create iterator context with $this and $index
75
+ let tempContext = RuntimeContextManager.withIterator(context, item, i);
76
+ tempContext = RuntimeContextManager.setVariable(tempContext, '$total', queue.length);
77
+
78
+ // Evaluate expression with temporary context
79
+ const exprResult = await evaluator(expression, [boxedItem], tempContext);
80
+
81
+ // Add new items to the result and next queue
82
+ for (const newItem of exprResult.value) {
83
+ if (newItem && !isInResult(newItem)) {
84
+ result.push(newItem);
85
+ nextQueue.push(newItem);
86
+ }
87
+ }
88
+ }
89
+
90
+ // Move to next iteration
91
+ queue = nextQueue;
92
+ }
93
+
94
+ return { value: result, context };
95
+ };
96
+
97
+ export const repeatFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
98
+ name: 'repeat',
99
+ category: ['collection'],
100
+ description: 'A version of select that will repeat the projection and add items to the output collection only if they are not already in the output collection as determined by the equals (=) operator. Can be used to traverse a tree by repeatedly selecting specific children.',
101
+ examples: [
102
+ 'ValueSet.expansion.repeat(contains)',
103
+ 'Questionnaire.repeat(item)',
104
+ 'Bundle.entry.repeat(resource.link)'
105
+ ],
106
+ signatures: [{
107
+ name: 'repeat',
108
+ input: { type: 'Any', singleton: false },
109
+ parameters: [
110
+ { name: 'projection', type: { type: 'Any', singleton: false }, expression: true },
111
+ ],
112
+ result: 'parameterType' as any,
113
+ }],
114
+ evaluate,
115
+
116
+ /**
117
+ * Analysis-time behavior for repeat.
118
+ * Similar to select, but with repeated application.
119
+ */
120
+ async analyze(context: AnalysisContext, args): Promise<InternalAnalysisResult> {
121
+ const diagnostics: any[] = [];
122
+
123
+ if (args.length !== 1) {
124
+ return {
125
+ type: { type: 'Any', singleton: false },
126
+ diagnostics: [{
127
+ message: 'repeat expects exactly 1 argument',
128
+ severity: 'error' as any,
129
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }
130
+ }]
131
+ };
132
+ }
133
+
134
+ // Get element type from input
135
+ const elementType = context.inputType.singleton
136
+ ? context.inputType
137
+ : { ...context.inputType, singleton: true };
138
+
139
+ // Create context for analyzing the projection
140
+ const projectionContext = context
141
+ .withInputType(elementType)
142
+ .withSystemVariable('$this', elementType)
143
+ .withSystemVariable('$index', { type: 'Integer', singleton: true })
144
+ .withSystemVariable('$total', { type: 'Integer', singleton: true });
145
+
146
+ // Analyze the projection expression
147
+ const projectionArg = args[0];
148
+ if (!projectionArg) {
149
+ return {
150
+ type: { type: 'Any', singleton: false },
151
+ diagnostics,
152
+ context
153
+ };
154
+ }
155
+ const projectionResult = await projectionContext.analyzeNode(projectionArg);
156
+ diagnostics.push(...projectionResult.diagnostics);
157
+
158
+ // Result type is the type returned by the projection (as a collection)
159
+ const resultType = projectionResult.type.singleton
160
+ ? { ...projectionResult.type, singleton: false }
161
+ : projectionResult.type;
162
+
163
+ return {
164
+ type: resultType,
165
+ diagnostics,
166
+ context
167
+ };
168
+ }
169
+ };
@@ -1,7 +1,7 @@
1
1
  import type { FunctionDefinition } from '../types';
2
2
  import { Errors } from '../errors';
3
3
  import type { FunctionEvaluator } from '../types';
4
- import { box, unbox } from '../boxing';
4
+ import { box, unbox } from '../interpreter/boxing';
5
5
 
6
6
  export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
7
7
  // Check if we have exactly 2 arguments
@@ -0,0 +1,125 @@
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('replaceMatches', 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('replaceMatches');
23
+ }
24
+
25
+ // Check arguments
26
+ if (args.length !== 2) {
27
+ throw Errors.wrongArgumentCount('replaceMatches', 2, args.length);
28
+ }
29
+
30
+ // Evaluate regex argument
31
+ const regexArg = args[0];
32
+ if (!regexArg) {
33
+ throw Errors.argumentRequired('replaceMatches', '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('replaceMatches 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('replaceMatches', 'regex argument');
54
+ }
55
+
56
+ // Evaluate substitution argument
57
+ const substitutionArg = args[1];
58
+ if (!substitutionArg) {
59
+ throw Errors.argumentRequired('replaceMatches', 'substitution argument');
60
+ }
61
+
62
+ const substitutionResult = await evaluator(substitutionArg, input, context);
63
+
64
+ if (substitutionResult.value.length === 0) {
65
+ return { value: [], context };
66
+ }
67
+
68
+ if (substitutionResult.value.length > 1) {
69
+ throw Errors.singletonRequired('replaceMatches substitution', substitutionResult.value.length);
70
+ }
71
+
72
+ const boxedSubstitution = substitutionResult.value[0];
73
+ if (!boxedSubstitution) {
74
+ return { value: [], context };
75
+ }
76
+
77
+ const substitution = unbox(boxedSubstitution);
78
+ if (typeof substitution !== 'string') {
79
+ throw Errors.invalidStringOperation('replaceMatches', 'substitution argument');
80
+ }
81
+
82
+ // FHIRPath spec: empty pattern should return the original string unchanged
83
+ if (regexPattern === '') {
84
+ return { value: [box(inputValue, { type: 'String', singleton: true })], context };
85
+ }
86
+
87
+ try {
88
+ // Create regex with unicode support, single line mode (dotAll), and global flag for all matches
89
+ // Per spec: case-sensitive, single line mode, allow Unicode
90
+ const regex = new RegExp(regexPattern, 'gus');
91
+
92
+ // JavaScript's replace supports $1, $2 etc for capture groups
93
+ // The spec also mentions named groups with ${name} syntax
94
+ // JavaScript natively supports both $1 and $<name> syntax
95
+ // We need to convert ${name} to $<name> for JavaScript compatibility
96
+ const jsSubstitution = substitution.replace(/\$\{([^}]+)\}/g, '$<$1>');
97
+
98
+ const result = inputValue.replace(regex, jsSubstitution);
99
+
100
+ return { value: [box(result, { type: 'String', singleton: true })], context };
101
+ } catch (error) {
102
+ throw new Error(`Invalid regular expression in replaceMatches(): ${(error as Error).message}`);
103
+ }
104
+ };
105
+
106
+ export const replaceMatchesFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
107
+ name: 'replaceMatches',
108
+ category: ['string'],
109
+ description: 'Replaces all matches of the regex pattern with the substitution string',
110
+ examples: [
111
+ "'test string'.replaceMatches('test', 'match')",
112
+ "'11/30/1972'.replaceMatches('\\\\b(\\\\d{1,2})/(\\\\d{1,2})/(\\\\d{2,4})\\\\b', '$2-$1-$3')",
113
+ "'test test'.replaceMatches('t', 'T')"
114
+ ],
115
+ signatures: [{
116
+ name: 'replaceMatches',
117
+ input: { type: 'String', singleton: true },
118
+ parameters: [
119
+ { name: 'regex', type: { type: 'String', singleton: true } },
120
+ { name: 'substitution', type: { type: 'String', singleton: true } }
121
+ ],
122
+ result: { type: 'String', singleton: true }
123
+ }],
124
+ evaluate
125
+ };
@@ -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
  // round() takes 0 or 1 argument (precision)
@@ -0,0 +1,66 @@
1
+ // secondOf() function - Extracts second 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 secondOfEvaluator: FunctionEvaluator = async (input, context, args) => {
8
+ // secondOf() takes no arguments
9
+ if (args.length !== 0) {
10
+ throw Errors.wrongArgumentCount('secondOf', 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('secondOf', 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 second component is present
33
+ if (value.second === undefined) {
34
+ return { value: [], context };
35
+ }
36
+
37
+ // Return the second as an Integer (0-59)
38
+ return {
39
+ value: [box(value.second, { 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 secondOfFunction: FunctionDefinition & { evaluate: typeof secondOfEvaluator } = {
49
+ name: 'secondOf',
50
+ category: ['temporal'],
51
+ description: 'Returns the second component of a Time or DateTime value (0-59)',
52
+ examples: [
53
+ '@T10:30:45.secondOf()',
54
+ '@2014-01-05T10:30:45.secondOf()',
55
+ 'Observation.effectiveDateTime.secondOf()'
56
+ ],
57
+ signatures: [
58
+ {
59
+ name: 'secondOf',
60
+ input: { type: 'Any', singleton: true },
61
+ parameters: [],
62
+ result: { type: 'Integer', singleton: true }
63
+ }
64
+ ],
65
+ evaluate: secondOfEvaluator
66
+ };
@@ -1,8 +1,8 @@
1
- import type { FunctionDefinition } from '../types';
1
+ import type { FunctionDefinition, AnalysisContext, InternalAnalysisResult } from '../types';
2
2
  import { Errors } from '../errors';
3
- import { RuntimeContextManager } from '../interpreter';
3
+ import { RuntimeContextManager } from '../interpreter/runtime-context';
4
4
  import { type FunctionEvaluator } from '../types';
5
- import { unbox } from '../boxing';
5
+ import { unbox } from '../interpreter/boxing';
6
6
 
7
7
  export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
8
8
  // Select requires exactly one argument
@@ -55,5 +55,66 @@ export const selectFunction: FunctionDefinition & { evaluate: FunctionEvaluator
55
55
  ],
56
56
  result: 'parameterType' as any,
57
57
  }],
58
- evaluate
59
- };
58
+ evaluate,
59
+
60
+ /**
61
+ * Analysis-time behavior for select.
62
+ * The projection expression needs to be analyzed with system variables in scope.
63
+ */
64
+ async analyze(context: AnalysisContext, args): Promise<InternalAnalysisResult> {
65
+ const diagnostics: any[] = [];
66
+
67
+ if (args.length !== 1) {
68
+ return {
69
+ type: { type: 'Any', singleton: false },
70
+ diagnostics: [{
71
+ message: 'select expects exactly 1 argument',
72
+ severity: 'error' as any,
73
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }
74
+ }]
75
+ };
76
+ }
77
+
78
+ // For select, we need to analyze the projection expression with:
79
+ // 1. $this set to each element type
80
+ // 2. $index available as Integer
81
+ // 3. User variables from the outer context preserved
82
+
83
+ // Get element type from input
84
+ const elementType = context.inputType.singleton
85
+ ? context.inputType
86
+ : { ...context.inputType, singleton: true };
87
+
88
+ // Create context for analyzing the projection
89
+ // Add system variables but preserve user variables
90
+ // IMPORTANT: Also update input type to the element type for property navigation
91
+ const projectionContext = context
92
+ .withInputType(elementType)
93
+ .withSystemVariable('$this', elementType)
94
+ .withSystemVariable('$index', { type: 'Integer', singleton: true })
95
+ .withSystemVariable('$total', { type: 'Integer', singleton: true });
96
+
97
+ // Analyze the projection expression
98
+ const projectionArg = args[0];
99
+ if (!projectionArg) {
100
+ return {
101
+ type: { type: 'Any', singleton: false },
102
+ diagnostics,
103
+ context
104
+ };
105
+ }
106
+ const projectionResult = await projectionContext.analyzeNode(projectionArg);
107
+ diagnostics.push(...projectionResult.diagnostics);
108
+
109
+ // Result type is the type returned by the projection (as a collection)
110
+ const resultType = projectionResult.type.singleton
111
+ ? { ...projectionResult.type, singleton: false }
112
+ : projectionResult.type;
113
+
114
+ return {
115
+ type: resultType,
116
+ diagnostics,
117
+ context // Return original context - select doesn't modify outer scope
118
+ };
119
+ }
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
  // single takes no arguments
@@ -1,7 +1,7 @@
1
1
  import type { FunctionDefinition } from '../types';
2
2
  import { Errors } from '../errors';
3
3
  import type { FunctionEvaluator } from '../types';
4
- import { box, unbox } from '../boxing';
4
+ import { box, unbox } from '../interpreter/boxing';
5
5
 
6
6
  export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
7
7
  if (args.length !== 1) {
@@ -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
@@ -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