@atomic-ehr/fhirpath 0.0.1 → 0.0.2-canary.261bb9b.20250811212042
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -56
- package/dist/index.d.ts +368 -99
- package/dist/index.js +3623 -1253
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/analyzer.ts +779 -120
- package/src/boxing.ts +124 -0
- package/src/completion-provider.ts +636 -0
- package/src/cursor-nodes.ts +111 -0
- package/src/errors.ts +247 -0
- package/src/index.ts +97 -14
- package/src/inspect.ts +309 -176
- package/src/interpreter.ts +380 -71
- package/src/lexer.ts +1 -0
- package/src/model-provider.ts +282 -39
- package/src/operations/abs-function.ts +40 -16
- package/src/operations/aggregate-function.ts +21 -10
- package/src/operations/all-function.ts +23 -11
- package/src/operations/allFalse-function.ts +14 -9
- package/src/operations/allTrue-function.ts +14 -9
- package/src/operations/and-operator.ts +21 -12
- package/src/operations/anyFalse-function.ts +11 -7
- package/src/operations/anyTrue-function.ts +11 -7
- package/src/operations/as-operator.ts +2 -1
- package/src/operations/ceiling-function.ts +14 -8
- package/src/operations/children-function.ts +98 -0
- package/src/operations/combine-function.ts +10 -6
- package/src/operations/combine-operator.ts +19 -5
- package/src/operations/contains-function.ts +26 -12
- package/src/operations/contains-operator.ts +15 -7
- package/src/operations/count-function.ts +7 -4
- package/src/operations/defineVariable-function.ts +11 -7
- package/src/operations/descendants-function.ts +64 -0
- package/src/operations/distinct-function.ts +21 -7
- package/src/operations/div-operator.ts +16 -3
- package/src/operations/divide-operator.ts +11 -6
- package/src/operations/dot-operator.ts +4 -2
- package/src/operations/empty-function.ts +7 -4
- package/src/operations/endsWith-function.ts +28 -14
- package/src/operations/equal-operator.ts +19 -7
- package/src/operations/equivalent-operator.ts +16 -11
- package/src/operations/exclude-function.ts +18 -14
- package/src/operations/exists-function.ts +19 -10
- package/src/operations/first-function.ts +12 -7
- package/src/operations/floor-function.ts +14 -8
- package/src/operations/greater-operator.ts +10 -5
- package/src/operations/greater-or-equal-operator.ts +10 -5
- package/src/operations/iif-function.ts +27 -23
- package/src/operations/implies-operator.ts +23 -8
- package/src/operations/in-operator.ts +18 -8
- package/src/operations/index.ts +3 -0
- package/src/operations/indexOf-function.ts +28 -14
- package/src/operations/intersect-function.ts +23 -24
- package/src/operations/is-operator.ts +67 -15
- package/src/operations/isDistinct-function.ts +17 -9
- package/src/operations/join-function.ts +22 -9
- package/src/operations/last-function.ts +12 -7
- package/src/operations/length-function.ts +17 -8
- package/src/operations/less-operator.ts +10 -5
- package/src/operations/less-or-equal-operator.ts +10 -5
- package/src/operations/less-than.ts +26 -2
- package/src/operations/lower-function.ts +17 -8
- package/src/operations/minus-operator.ts +10 -5
- package/src/operations/mod-operator.ts +20 -3
- package/src/operations/multiply-operator.ts +12 -7
- package/src/operations/not-equal-operator.ts +16 -7
- package/src/operations/not-equivalent-operator.ts +16 -11
- package/src/operations/not-function.ts +17 -7
- package/src/operations/ofType-function.ts +139 -0
- package/src/operations/or-operator.ts +21 -12
- package/src/operations/plus-operator.ts +19 -7
- package/src/operations/power-function.ts +27 -13
- package/src/operations/replace-function.ts +33 -14
- package/src/operations/round-function.ts +29 -12
- package/src/operations/select-function.ts +17 -9
- package/src/operations/single-function.ts +10 -6
- package/src/operations/skip-function.ts +18 -9
- package/src/operations/split-function.ts +30 -13
- package/src/operations/sqrt-function.ts +14 -8
- package/src/operations/startsWith-function.ts +26 -12
- package/src/operations/subsetOf-function.ts +20 -15
- package/src/operations/substring-function.ts +43 -24
- package/src/operations/supersetOf-function.ts +20 -15
- package/src/operations/tail-function.ts +8 -4
- package/src/operations/take-function.ts +18 -9
- package/src/operations/toBoolean-function.ts +23 -14
- package/src/operations/toDecimal-function.ts +18 -9
- package/src/operations/toInteger-function.ts +22 -12
- package/src/operations/toString-function.ts +26 -16
- package/src/operations/trace-function.ts +19 -10
- package/src/operations/trim-function.ts +16 -7
- package/src/operations/truncate-function.ts +14 -8
- package/src/operations/unary-minus-operator.ts +23 -13
- package/src/operations/unary-plus-operator.ts +2 -1
- package/src/operations/union-function.ts +25 -28
- package/src/operations/union-operator.ts +2 -1
- package/src/operations/upper-function.ts +17 -8
- package/src/operations/where-function.ts +20 -11
- package/src/operations/xor-operator.ts +9 -4
- package/src/parser.ts +527 -8
- package/src/quantity-value.ts +4 -8
- package/src/registry.ts +147 -5
- package/src/types.ts +42 -19
- package/src/parser-base.ts +0 -400
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export enum CursorContext {
|
|
2
|
+
Operator = 'operator',
|
|
3
|
+
Identifier = 'identifier',
|
|
4
|
+
Argument = 'argument',
|
|
5
|
+
Index = 'index',
|
|
6
|
+
Type = 'type',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CursorNode {
|
|
10
|
+
type: 'CursorNode';
|
|
11
|
+
context: CursorContext;
|
|
12
|
+
position: number;
|
|
13
|
+
range: {
|
|
14
|
+
start: { line: number; character: number; offset: number };
|
|
15
|
+
end: { line: number; character: number; offset: number };
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CursorOperatorNode extends CursorNode {
|
|
20
|
+
context: CursorContext.Operator;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CursorIdentifierNode extends CursorNode {
|
|
24
|
+
context: CursorContext.Identifier;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CursorArgumentNode extends CursorNode {
|
|
28
|
+
context: CursorContext.Argument;
|
|
29
|
+
functionName: string;
|
|
30
|
+
argumentIndex: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CursorIndexNode extends CursorNode {
|
|
34
|
+
context: CursorContext.Index;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CursorTypeNode extends CursorNode {
|
|
38
|
+
context: CursorContext.Type;
|
|
39
|
+
typeOperator: 'is' | 'as' | 'ofType';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type AnyCursorNode =
|
|
43
|
+
| CursorOperatorNode
|
|
44
|
+
| CursorIdentifierNode
|
|
45
|
+
| CursorArgumentNode
|
|
46
|
+
| CursorIndexNode
|
|
47
|
+
| CursorTypeNode;
|
|
48
|
+
|
|
49
|
+
export function isCursorNode(node: any): node is CursorNode {
|
|
50
|
+
return node && node.type === 'CursorNode';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createCursorOperatorNode(position: number): CursorOperatorNode {
|
|
54
|
+
const point = { line: 0, character: position, offset: position };
|
|
55
|
+
return {
|
|
56
|
+
type: 'CursorNode',
|
|
57
|
+
context: CursorContext.Operator,
|
|
58
|
+
position,
|
|
59
|
+
range: { start: point, end: point },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createCursorIdentifierNode(position: number): CursorIdentifierNode {
|
|
64
|
+
const point = { line: 0, character: position, offset: position };
|
|
65
|
+
return {
|
|
66
|
+
type: 'CursorNode',
|
|
67
|
+
context: CursorContext.Identifier,
|
|
68
|
+
position,
|
|
69
|
+
range: { start: point, end: point },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function createCursorArgumentNode(
|
|
74
|
+
position: number,
|
|
75
|
+
functionName: string,
|
|
76
|
+
argumentIndex: number
|
|
77
|
+
): CursorArgumentNode {
|
|
78
|
+
const point = { line: 0, character: position, offset: position };
|
|
79
|
+
return {
|
|
80
|
+
type: 'CursorNode',
|
|
81
|
+
context: CursorContext.Argument,
|
|
82
|
+
position,
|
|
83
|
+
functionName,
|
|
84
|
+
argumentIndex,
|
|
85
|
+
range: { start: point, end: point },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createCursorIndexNode(position: number): CursorIndexNode {
|
|
90
|
+
const point = { line: 0, character: position, offset: position };
|
|
91
|
+
return {
|
|
92
|
+
type: 'CursorNode',
|
|
93
|
+
context: CursorContext.Index,
|
|
94
|
+
position,
|
|
95
|
+
range: { start: point, end: point },
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function createCursorTypeNode(
|
|
100
|
+
position: number,
|
|
101
|
+
typeOperator: 'is' | 'as' | 'ofType'
|
|
102
|
+
): CursorTypeNode {
|
|
103
|
+
const point = { line: 0, character: position, offset: position };
|
|
104
|
+
return {
|
|
105
|
+
type: 'CursorNode',
|
|
106
|
+
context: CursorContext.Type,
|
|
107
|
+
position,
|
|
108
|
+
typeOperator,
|
|
109
|
+
range: { start: point, end: point },
|
|
110
|
+
};
|
|
111
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import type { Range, Diagnostic } from './types';
|
|
2
|
+
import { DiagnosticSeverity } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base error class for all FHIRPath errors
|
|
6
|
+
*/
|
|
7
|
+
export class FHIRPathError extends Error {
|
|
8
|
+
constructor(
|
|
9
|
+
public code: string,
|
|
10
|
+
message: string,
|
|
11
|
+
public location?: Range
|
|
12
|
+
) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'FHIRPathError';
|
|
15
|
+
|
|
16
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
17
|
+
if (Error.captureStackTrace) {
|
|
18
|
+
Error.captureStackTrace(this, this.constructor);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert FHIRPathError to Diagnostic for analyzer
|
|
25
|
+
*/
|
|
26
|
+
export function toDiagnostic(error: FHIRPathError, severity: DiagnosticSeverity = DiagnosticSeverity.Error): Diagnostic {
|
|
27
|
+
return {
|
|
28
|
+
code: error.code,
|
|
29
|
+
message: error.message,
|
|
30
|
+
severity,
|
|
31
|
+
range: error.location!,
|
|
32
|
+
source: 'fhirpath'
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Error factory with specialized constructors
|
|
38
|
+
*/
|
|
39
|
+
export const Errors = {
|
|
40
|
+
// Resolution errors (1000-1999)
|
|
41
|
+
unknownOperator(operator: string, location?: Range): FHIRPathError {
|
|
42
|
+
return new FHIRPathError(ErrorCodes.UNKNOWN_OPERATOR, `Unknown operator: ${operator}`, location);
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
unknownFunction(name: string, location?: Range): FHIRPathError {
|
|
46
|
+
return new FHIRPathError(ErrorCodes.UNKNOWN_FUNCTION, `Unknown function: ${name}`, location);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
unknownVariable(name: string, location?: Range): FHIRPathError {
|
|
50
|
+
return new FHIRPathError(ErrorCodes.UNKNOWN_VARIABLE, `Unknown variable: ${name}`, location);
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
unknownUserVariable(name: string, location?: Range): FHIRPathError {
|
|
54
|
+
return new FHIRPathError(ErrorCodes.UNKNOWN_USER_VARIABLE, `Unknown user variable: ${name}`, location);
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
unknownProperty(property: string, type: string, location?: Range): FHIRPathError {
|
|
58
|
+
return new FHIRPathError(ErrorCodes.UNKNOWN_PROPERTY, `Unknown property '${property}' on type ${type}`, location);
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
unknownNodeType(nodeType: string, location?: Range): FHIRPathError {
|
|
62
|
+
return new FHIRPathError(ErrorCodes.UNKNOWN_NODE_TYPE, `Unknown node type: ${nodeType}`, location);
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
noEvaluatorFound(evaluatorType: string, name: string, location?: Range): FHIRPathError {
|
|
66
|
+
return new FHIRPathError(ErrorCodes.NO_EVALUATOR_FOUND, `No evaluator found for ${evaluatorType}: ${name}`, location);
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
variableNotDefined(name: string, location?: Range): FHIRPathError {
|
|
70
|
+
return new FHIRPathError(ErrorCodes.VARIABLE_NOT_DEFINED, `Variable '${name}' is not defined in the current scope`, location);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Arity errors (2000-2999)
|
|
74
|
+
wrongArgumentCount(funcName: string, expected: number, actual: number, location?: Range): FHIRPathError {
|
|
75
|
+
return new FHIRPathError(ErrorCodes.WRONG_ARGUMENT_COUNT, `${funcName} expects ${expected} arguments, got ${actual}`, location);
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
wrongArgumentCountRange(funcName: string, min: number, max: number, actual: number, location?: Range): FHIRPathError {
|
|
79
|
+
return new FHIRPathError(ErrorCodes.WRONG_ARGUMENT_COUNT_RANGE, `${funcName} expects ${min} to ${max} arguments, got ${actual}`, location);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
singletonRequired(funcName: string, actualCount: number, location?: Range): FHIRPathError {
|
|
83
|
+
return new FHIRPathError(ErrorCodes.SINGLETON_REQUIRED, `${funcName} requires a single item, but collection has ${actualCount} items`, location);
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
stringSingletonRequired(funcName: string, actualCount: number, location?: Range): FHIRPathError {
|
|
87
|
+
return new FHIRPathError(ErrorCodes.SINGLETON_REQUIRED, `${funcName} can only be used on a single string, but collection has ${actualCount} items`, location);
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
emptyNotAllowed(funcName: string, location?: Range): FHIRPathError {
|
|
91
|
+
return new FHIRPathError(ErrorCodes.EMPTY_NOT_ALLOWED, `${funcName} cannot operate on empty collection`, location);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
argumentRequired(funcName: string, argumentName: string, location?: Range): FHIRPathError {
|
|
95
|
+
return new FHIRPathError(ErrorCodes.ARGUMENT_REQUIRED, `${funcName} requires ${argumentName}`, location);
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
singletonTypeRequired(funcName: string, actualType: string, location?: Range): FHIRPathError {
|
|
99
|
+
return new FHIRPathError(ErrorCodes.SINGLETON_REQUIRED, `${funcName} expects a singleton value, but received collection type ${actualType}`, location);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// Type errors (3000-3999)
|
|
103
|
+
|
|
104
|
+
operatorTypeMismatch(operator: string, leftType: string, rightType: string, location?: Range): FHIRPathError {
|
|
105
|
+
return new FHIRPathError(ErrorCodes.OPERATOR_TYPE_MISMATCH, `Operator '${operator}' cannot be applied to types ${leftType} and ${rightType}`, location);
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
argumentTypeMismatch(argIndex: number, funcName: string, expected: string, actual: string, location?: Range): FHIRPathError {
|
|
109
|
+
return new FHIRPathError(ErrorCodes.ARGUMENT_TYPE_MISMATCH, `Argument ${argIndex} of ${funcName}: expected ${expected}, got ${actual}`, location);
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
conversionFailed(value: string, targetType: string, location?: Range): FHIRPathError {
|
|
113
|
+
return new FHIRPathError(ErrorCodes.CONVERSION_FAILED, `Cannot convert '${value}' to ${targetType}`, location);
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
invalidValueType(expected: string, actual: string, location?: Range): FHIRPathError {
|
|
117
|
+
return new FHIRPathError(ErrorCodes.INVALID_VALUE_TYPE, `${expected} expected, got ${actual}`, location);
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
invalidOperandType(operation: string, type: string, location?: Range): FHIRPathError {
|
|
121
|
+
return new FHIRPathError(ErrorCodes.INVALID_OPERAND_TYPE, `Cannot apply ${operation} to ${type}`, location);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
stringOperationOnNonString(operation: string, location?: Range): FHIRPathError {
|
|
125
|
+
return new FHIRPathError(ErrorCodes.STRING_OPERATION_ON_NON_STRING, `${operation} can only be used on string values`, location);
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
numericOperationOnNonNumeric(operation: string, location?: Range): FHIRPathError {
|
|
129
|
+
return new FHIRPathError(ErrorCodes.NUMERIC_OPERATION_ON_NON_NUMERIC, `${operation} can only be applied to numeric values`, location);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
booleanOperationOnNonBoolean(operation: string, index: number, actualType: string, location?: Range): FHIRPathError {
|
|
133
|
+
return new FHIRPathError(ErrorCodes.BOOLEAN_OPERATION_ON_NON_BOOLEAN, `${operation} expects all items to be Boolean values, but item at index ${index} is ${actualType}`, location);
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
// Configuration errors (4000-4999)
|
|
137
|
+
modelProviderRequired(operation: string, location?: Range): FHIRPathError {
|
|
138
|
+
return new FHIRPathError(ErrorCodes.MODEL_PROVIDER_REQUIRED,
|
|
139
|
+
`ModelProvider required for '${operation}' operation. Even primitive types like Boolean can fail due to choice types (e.g., Patient.deceased is actually deceasedBoolean in FHIR data)`,
|
|
140
|
+
location);
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// Syntax errors (5000-5999)
|
|
144
|
+
unexpectedToken(token: string, location?: Range): FHIRPathError {
|
|
145
|
+
return new FHIRPathError(ErrorCodes.UNEXPECTED_TOKEN, `Unexpected token: ${token}`, location);
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
expectedToken(expected: string, actual: string, location?: Range): FHIRPathError {
|
|
149
|
+
return new FHIRPathError(ErrorCodes.EXPECTED_TOKEN, `Expected ${expected}, got ${actual}`, location);
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
invalidSyntax(details: string, location?: Range): FHIRPathError {
|
|
153
|
+
return new FHIRPathError(ErrorCodes.INVALID_SYNTAX, `Invalid syntax: ${details}`, location);
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
expectedIdentifier(after: string, actual: string, location?: Range): FHIRPathError {
|
|
157
|
+
return new FHIRPathError(ErrorCodes.EXPECTED_IDENTIFIER, `Expected identifier after '${after}', got: ${actual}`, location);
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
expectedTypeName(actual: string, location?: Range): FHIRPathError {
|
|
161
|
+
return new FHIRPathError(ErrorCodes.EXPECTED_TYPE_NAME, `Expected type name, got: ${actual}`, location);
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// Domain errors (6000-6999)
|
|
165
|
+
divisionByZero(location?: Range): FHIRPathError {
|
|
166
|
+
return new FHIRPathError(ErrorCodes.DIVISION_BY_ZERO, 'Division by zero', location);
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
invalidDateTimeFormat(format: string, location?: Range): FHIRPathError {
|
|
170
|
+
return new FHIRPathError(ErrorCodes.INVALID_DATE_TIME_FORMAT, `Invalid date/time format: '${format}'`, location);
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
incompatibleUnits(unit1: string, unit2: string, location?: Range): FHIRPathError {
|
|
174
|
+
return new FHIRPathError(ErrorCodes.INCOMPATIBLE_UNITS, `Cannot perform operation on incompatible units: ${unit1} and ${unit2}`, location);
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
indexOutOfBounds(index: number, size: number, location?: Range): FHIRPathError {
|
|
178
|
+
return new FHIRPathError(ErrorCodes.INDEX_OUT_OF_BOUNDS, `Index ${index} out of bounds for collection of size ${size}`, location);
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
invalidOperation(details: string, location?: Range): FHIRPathError {
|
|
182
|
+
return new FHIRPathError(ErrorCodes.INVALID_OPERATION, `Invalid operation: ${details}`, location);
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
invalidPrecision(operation: string, location?: Range): FHIRPathError {
|
|
186
|
+
return new FHIRPathError(ErrorCodes.INVALID_PRECISION, `${operation} precision must be a non-negative integer`, location);
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
invalidStringOperation(operation: string, paramName: string, location?: Range): FHIRPathError {
|
|
190
|
+
return new FHIRPathError(ErrorCodes.INVALID_STRING_OPERATION, `${operation} ${paramName} must be a string`, location);
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
invalidNumericOperation(operation: string, paramName: string, expectedType: string, location?: Range): FHIRPathError {
|
|
194
|
+
return new FHIRPathError(ErrorCodes.INVALID_NUMERIC_OPERATION, `${operation} ${paramName} must be ${expectedType}`, location);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Export error codes as enum for external use
|
|
199
|
+
export enum ErrorCodes {
|
|
200
|
+
// Resolution errors (1000-1999)
|
|
201
|
+
UNKNOWN_OPERATOR = 'FP1001',
|
|
202
|
+
UNKNOWN_FUNCTION = 'FP1002',
|
|
203
|
+
UNKNOWN_VARIABLE = 'FP1003',
|
|
204
|
+
UNKNOWN_USER_VARIABLE = 'FP1004',
|
|
205
|
+
UNKNOWN_PROPERTY = 'FP1005',
|
|
206
|
+
UNKNOWN_NODE_TYPE = 'FP1006',
|
|
207
|
+
NO_EVALUATOR_FOUND = 'FP1007',
|
|
208
|
+
VARIABLE_NOT_DEFINED = 'FP1008',
|
|
209
|
+
|
|
210
|
+
// Arity errors (2000-2999)
|
|
211
|
+
WRONG_ARGUMENT_COUNT = 'FP2001',
|
|
212
|
+
WRONG_ARGUMENT_COUNT_RANGE = 'FP2002',
|
|
213
|
+
SINGLETON_REQUIRED = 'FP2003',
|
|
214
|
+
EMPTY_NOT_ALLOWED = 'FP2004',
|
|
215
|
+
ARGUMENT_REQUIRED = 'FP2005',
|
|
216
|
+
|
|
217
|
+
// Type errors (3000-3999)
|
|
218
|
+
// FP3001 - removed (unified with FP3006)
|
|
219
|
+
OPERATOR_TYPE_MISMATCH = 'FP3002',
|
|
220
|
+
ARGUMENT_TYPE_MISMATCH = 'FP3003',
|
|
221
|
+
CONVERSION_FAILED = 'FP3004',
|
|
222
|
+
INVALID_VALUE_TYPE = 'FP3005',
|
|
223
|
+
INVALID_OPERAND_TYPE = 'FP3006',
|
|
224
|
+
STRING_OPERATION_ON_NON_STRING = 'FP3007',
|
|
225
|
+
NUMERIC_OPERATION_ON_NON_NUMERIC = 'FP3008',
|
|
226
|
+
BOOLEAN_OPERATION_ON_NON_BOOLEAN = 'FP3009',
|
|
227
|
+
|
|
228
|
+
// Configuration errors (4000-4999)
|
|
229
|
+
MODEL_PROVIDER_REQUIRED = 'FP4001',
|
|
230
|
+
|
|
231
|
+
// Syntax errors (5000-5999)
|
|
232
|
+
UNEXPECTED_TOKEN = 'FP5001',
|
|
233
|
+
EXPECTED_TOKEN = 'FP5002',
|
|
234
|
+
INVALID_SYNTAX = 'FP5003',
|
|
235
|
+
EXPECTED_IDENTIFIER = 'FP5004',
|
|
236
|
+
EXPECTED_TYPE_NAME = 'FP5005',
|
|
237
|
+
|
|
238
|
+
// Domain errors (6000-6999)
|
|
239
|
+
DIVISION_BY_ZERO = 'FP6001',
|
|
240
|
+
INVALID_DATE_TIME_FORMAT = 'FP6002',
|
|
241
|
+
INCOMPATIBLE_UNITS = 'FP6003',
|
|
242
|
+
INDEX_OUT_OF_BOUNDS = 'FP6004',
|
|
243
|
+
INVALID_OPERATION = 'FP6005',
|
|
244
|
+
INVALID_PRECISION = 'FP6006',
|
|
245
|
+
INVALID_STRING_OPERATION = 'FP6007',
|
|
246
|
+
INVALID_NUMERIC_OPERATION = 'FP6008'
|
|
247
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,16 +2,20 @@ import { Parser } from './parser';
|
|
|
2
2
|
import { Interpreter, RuntimeContextManager } from './interpreter';
|
|
3
3
|
import { Analyzer } from './analyzer';
|
|
4
4
|
import type { AnalysisResult } from './types';
|
|
5
|
+
import { box, unbox } from './boxing';
|
|
6
|
+
import { FHIRPathError, Errors } from './errors';
|
|
5
7
|
|
|
6
8
|
export interface EvaluateOptions {
|
|
7
9
|
input?: unknown;
|
|
8
10
|
variables?: Record<string, unknown>;
|
|
11
|
+
modelProvider?: import('./types').ModelProvider;
|
|
12
|
+
inputType?: import('./types').TypeInfo;
|
|
9
13
|
}
|
|
10
14
|
|
|
11
|
-
export function evaluate(
|
|
15
|
+
export async function evaluate(
|
|
12
16
|
expression: string,
|
|
13
17
|
options: EvaluateOptions = {}
|
|
14
|
-
): any[] {
|
|
18
|
+
): Promise<any[]> {
|
|
15
19
|
const parser = new Parser(expression);
|
|
16
20
|
const parseResult = parser.parse();
|
|
17
21
|
|
|
@@ -19,19 +23,60 @@ export function evaluate(
|
|
|
19
23
|
if (parseResult.errors.length > 0) {
|
|
20
24
|
// For backward compatibility, throw the first error
|
|
21
25
|
const firstError = parseResult.errors[0]!;
|
|
22
|
-
throw
|
|
26
|
+
throw Errors.invalidSyntax(firstError.message);
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
// ALWAYS analyze the AST
|
|
30
|
+
const analyzer = new Analyzer(options.modelProvider);
|
|
31
|
+
const analysisResult = await analyzer.analyze(
|
|
32
|
+
parseResult.ast,
|
|
33
|
+
options.variables,
|
|
34
|
+
options.inputType
|
|
35
|
+
);
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
// Check for analysis errors
|
|
38
|
+
const errors = analysisResult.diagnostics.filter(d => d.severity === 1); // DiagnosticSeverity.Error
|
|
39
|
+
if (errors.length > 0) {
|
|
40
|
+
// Throw the first error
|
|
41
|
+
const firstError = errors[0]!;
|
|
42
|
+
if (firstError.code) {
|
|
43
|
+
// Always throw as FHIRPathError if we have a code
|
|
44
|
+
throw new FHIRPathError(firstError.code, firstError.message, firstError.range);
|
|
45
|
+
} else {
|
|
46
|
+
// Otherwise throw a generic error
|
|
47
|
+
throw new Error(firstError.message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Use the analyzed AST with type information
|
|
52
|
+
const interpreter = new Interpreter(undefined, options.modelProvider);
|
|
28
53
|
const input = options.input === undefined ? [] : Array.isArray(options.input) ? options.input : [options.input];
|
|
29
54
|
|
|
55
|
+
// Box input with typeInfo if we have a modelProvider and the input is a FHIR resource
|
|
56
|
+
let boxedInput = input;
|
|
57
|
+
if (options.modelProvider) {
|
|
58
|
+
boxedInput = await Promise.all(input.map(async item => {
|
|
59
|
+
if (item && typeof item === 'object' && 'resourceType' in item && typeof item.resourceType === 'string') {
|
|
60
|
+
// Get type info asynchronously
|
|
61
|
+
const typeInfo = await options.modelProvider!.getType(item.resourceType);
|
|
62
|
+
if (typeInfo) {
|
|
63
|
+
return box(item, typeInfo);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return item;
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
|
|
30
70
|
// Create context with variables if provided
|
|
31
71
|
let context = RuntimeContextManager.create(input);
|
|
32
72
|
|
|
33
|
-
// Set $this to the input (required for expressions like $this.where(...))
|
|
34
|
-
context = RuntimeContextManager.setVariable(context, '$this',
|
|
73
|
+
// Set $this to the boxed input (required for expressions like $this.where(...))
|
|
74
|
+
context = RuntimeContextManager.setVariable(context, '$this', boxedInput);
|
|
75
|
+
|
|
76
|
+
// Add model provider to context if available
|
|
77
|
+
if (options.modelProvider) {
|
|
78
|
+
context.modelProvider = options.modelProvider;
|
|
79
|
+
}
|
|
35
80
|
|
|
36
81
|
if (options.variables) {
|
|
37
82
|
for (const [key, value] of Object.entries(options.variables)) {
|
|
@@ -40,12 +85,13 @@ export function evaluate(
|
|
|
40
85
|
}
|
|
41
86
|
}
|
|
42
87
|
|
|
43
|
-
const result = interpreter.evaluate(ast, input, context);
|
|
88
|
+
const result = await interpreter.evaluate(analysisResult.ast, input, context);
|
|
44
89
|
|
|
45
|
-
|
|
90
|
+
// Unbox the results before returning
|
|
91
|
+
return result.value.map(unbox);
|
|
46
92
|
}
|
|
47
93
|
|
|
48
|
-
export function analyze(
|
|
94
|
+
export async function analyze(
|
|
49
95
|
expression: string,
|
|
50
96
|
options: {
|
|
51
97
|
variables?: Record<string, unknown>;
|
|
@@ -53,7 +99,7 @@ export function analyze(
|
|
|
53
99
|
inputType?: import('./types').TypeInfo;
|
|
54
100
|
errorRecovery?: boolean;
|
|
55
101
|
} = {}
|
|
56
|
-
): AnalysisResult {
|
|
102
|
+
): Promise<AnalysisResult> {
|
|
57
103
|
// Use LSP mode with error recovery if requested
|
|
58
104
|
const parserOptions = options.errorRecovery
|
|
59
105
|
? { mode: 'lsp' as const, errorRecovery: true }
|
|
@@ -66,14 +112,14 @@ export function analyze(
|
|
|
66
112
|
if (!options.errorRecovery && parseResult.errors.length > 0) {
|
|
67
113
|
// For backward compatibility, throw the first error
|
|
68
114
|
const firstError = parseResult.errors[0]!;
|
|
69
|
-
throw
|
|
115
|
+
throw Errors.invalidSyntax(firstError.message);
|
|
70
116
|
}
|
|
71
117
|
|
|
72
118
|
const ast = parseResult.ast;
|
|
73
119
|
|
|
74
120
|
// Create analyzer with optional model provider
|
|
75
121
|
const analyzer = new Analyzer(options.modelProvider);
|
|
76
|
-
const analysisResult = analyzer.analyze(ast, options.variables, options.inputType);
|
|
122
|
+
const analysisResult = await analyzer.analyze(ast, options.variables, options.inputType);
|
|
77
123
|
|
|
78
124
|
// If error recovery is enabled, merge parse errors into diagnostics
|
|
79
125
|
if (options.errorRecovery && parseResult.errors.length > 0) {
|
|
@@ -109,4 +155,41 @@ export type { FHIRModelContext, FHIRModelProviderConfig } from './model-provider
|
|
|
109
155
|
|
|
110
156
|
// Export inspect API
|
|
111
157
|
export { inspect } from './inspect';
|
|
112
|
-
export type { InspectOptions, InspectResult,
|
|
158
|
+
export type { InspectOptions, InspectResult, ASTMetadata } from './inspect';
|
|
159
|
+
|
|
160
|
+
// Export error system
|
|
161
|
+
export { FHIRPathError, Errors, ErrorCodes } from './errors';
|
|
162
|
+
|
|
163
|
+
// Export LSP support - completion provider and cursor nodes
|
|
164
|
+
/**
|
|
165
|
+
* Provides context-aware code completions for FHIRPath expressions.
|
|
166
|
+
* @param expression - The FHIRPath expression being edited
|
|
167
|
+
* @param cursorPosition - The cursor position (0-based offset)
|
|
168
|
+
* @param options - Optional configuration including modelProvider and variables
|
|
169
|
+
* @returns Array of completion items with labels, kinds, and documentation
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* import { provideCompletions } from 'fhirpath';
|
|
174
|
+
*
|
|
175
|
+
* const completions = provideCompletions('Patient.', 8);
|
|
176
|
+
* // Returns completions for properties and functions after 'Patient.'
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export { provideCompletions, CompletionKind } from './completion-provider';
|
|
180
|
+
export type {
|
|
181
|
+
CompletionItem,
|
|
182
|
+
CompletionOptions
|
|
183
|
+
} from './completion-provider';
|
|
184
|
+
|
|
185
|
+
// Export cursor node types for LSP integration
|
|
186
|
+
export { CursorContext, isCursorNode } from './cursor-nodes';
|
|
187
|
+
export type {
|
|
188
|
+
CursorNode,
|
|
189
|
+
CursorOperatorNode,
|
|
190
|
+
CursorIdentifierNode,
|
|
191
|
+
CursorArgumentNode,
|
|
192
|
+
CursorIndexNode,
|
|
193
|
+
CursorTypeNode,
|
|
194
|
+
AnyCursorNode
|
|
195
|
+
} from './cursor-nodes';
|