@atomic-ehr/fhirpath 0.0.1-canary.35b105d.20250724165800

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 (57) hide show
  1. package/README.md +307 -0
  2. package/dist/index.d.ts +225 -0
  3. package/dist/index.js +8185 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +51 -0
  6. package/src/analyzer/analyzer.ts +486 -0
  7. package/src/analyzer/model-provider.ts +244 -0
  8. package/src/analyzer/schemas/index.ts +2 -0
  9. package/src/analyzer/schemas/types.ts +40 -0
  10. package/src/analyzer/types.ts +142 -0
  11. package/src/api/builder.ts +148 -0
  12. package/src/api/errors.ts +134 -0
  13. package/src/api/expression.ts +152 -0
  14. package/src/api/index.ts +57 -0
  15. package/src/api/registry.ts +128 -0
  16. package/src/api/types.ts +154 -0
  17. package/src/compiler/compiler.ts +579 -0
  18. package/src/compiler/index.ts +2 -0
  19. package/src/compiler/prototype-context-adapter.ts +99 -0
  20. package/src/compiler/types.ts +23 -0
  21. package/src/index.ts +52 -0
  22. package/src/interpreter/README.md +78 -0
  23. package/src/interpreter/interpreter.ts +485 -0
  24. package/src/interpreter/types.ts +110 -0
  25. package/src/lexer/char-tables.ts +37 -0
  26. package/src/lexer/errors.ts +31 -0
  27. package/src/lexer/index.ts +5 -0
  28. package/src/lexer/lexer.ts +745 -0
  29. package/src/lexer/token.ts +104 -0
  30. package/src/parser/ast.ts +123 -0
  31. package/src/parser/index.ts +3 -0
  32. package/src/parser/parser.ts +701 -0
  33. package/src/parser/pprint.ts +169 -0
  34. package/src/registry/default-analyzers.ts +257 -0
  35. package/src/registry/default-compilers.ts +31 -0
  36. package/src/registry/index.ts +93 -0
  37. package/src/registry/operations/arithmetic.ts +506 -0
  38. package/src/registry/operations/collection.ts +425 -0
  39. package/src/registry/operations/comparison.ts +432 -0
  40. package/src/registry/operations/existence.ts +703 -0
  41. package/src/registry/operations/filtering.ts +358 -0
  42. package/src/registry/operations/literals.ts +341 -0
  43. package/src/registry/operations/logical.ts +402 -0
  44. package/src/registry/operations/math.ts +128 -0
  45. package/src/registry/operations/membership.ts +132 -0
  46. package/src/registry/operations/string.ts +507 -0
  47. package/src/registry/operations/subsetting.ts +174 -0
  48. package/src/registry/operations/type-checking.ts +162 -0
  49. package/src/registry/operations/type-conversion.ts +404 -0
  50. package/src/registry/operations/type-operators.ts +307 -0
  51. package/src/registry/operations/utility.ts +542 -0
  52. package/src/registry/registry.ts +146 -0
  53. package/src/registry/types.ts +161 -0
  54. package/src/registry/utils/evaluation-helpers.ts +93 -0
  55. package/src/registry/utils/index.ts +3 -0
  56. package/src/registry/utils/type-system.ts +173 -0
  57. package/src/runtime/context.ts +179 -0
@@ -0,0 +1,244 @@
1
+ import type { ModelProvider as IModelProvider, PropertyInfo, TypeRef } from './types';
2
+ import type { FHIRSchema, SchemaRegistry, SchemaTypeRef } from './schemas/types';
3
+
4
+ /**
5
+ * Model provider implementation using FHIR schemas
6
+ * Resolves types and properties through a schema registry
7
+ */
8
+ export class ModelProvider implements IModelProvider {
9
+ private registry: SchemaRegistry;
10
+ private schemaListCache = new Map<string, FHIRSchema[]>();
11
+
12
+ constructor(registry: SchemaRegistry) {
13
+ this.registry = registry;
14
+ }
15
+
16
+ resolveType(typeName: string): TypeRef | undefined {
17
+ // Normalize type names - handle both "String" and "string" formats
18
+ const normalizedName = this.normalizeTypeName(typeName);
19
+
20
+ const schemas = this.getSchemaList(normalizedName);
21
+ if (!schemas.length) return undefined;
22
+
23
+ // Return opaque TypeRef (which is actually SchemaTypeRef)
24
+ const schemaTypeRef: SchemaTypeRef = {
25
+ schemas,
26
+ type: this.isPrimitiveType(normalizedName) ? normalizedName : 'complex-type'
27
+ // array is not set here - it's property-specific
28
+ };
29
+ return schemaTypeRef as unknown as TypeRef;
30
+ }
31
+
32
+ getPropertyType(type: TypeRef, propertyName: string): PropertyInfo | undefined {
33
+ const schemaRef = type as unknown as SchemaTypeRef;
34
+
35
+ // Search through type.schemas for the property
36
+ for (const schema of schemaRef.schemas) {
37
+ const element = schema.elements?.[propertyName];
38
+ if (element) {
39
+ // Handle union types
40
+ if (element.union === true && element.types) {
41
+ // For now, return Any type for unions
42
+ // In future, this could return a proper union type
43
+ return {
44
+ type: this.resolveType('Any')!,
45
+ isSingleton: !element.array
46
+ };
47
+ }
48
+
49
+ // Build TypeRef for the property's type
50
+ let propSchemas: FHIRSchema[];
51
+
52
+ if (element.elements) {
53
+ // Inline type with nested elements
54
+ const inlineSchema: FHIRSchema = {
55
+ name: `${this.getSchemaName(schema)}.${propertyName}`,
56
+ base: element.type || 'BackboneElement',
57
+ elements: element.elements
58
+ };
59
+
60
+ // Get base type schemas if any
61
+ const baseSchemas = element.type ? this.getSchemaList(element.type) : [];
62
+ propSchemas = [inlineSchema, ...baseSchemas];
63
+ } else if (element.type) {
64
+ // Simple type reference
65
+ propSchemas = this.getSchemaList(element.type);
66
+ } else {
67
+ // No type specified - shouldn't happen in valid schemas
68
+ continue;
69
+ }
70
+
71
+ const propTypeRef: SchemaTypeRef = {
72
+ schemas: propSchemas,
73
+ type: element.type && this.isPrimitiveType(element.type) ? element.type : 'complex-type',
74
+ array: element.array
75
+ };
76
+
77
+ return {
78
+ type: propTypeRef as unknown as TypeRef,
79
+ isSingleton: !element.array
80
+ };
81
+ }
82
+ }
83
+
84
+ return undefined;
85
+ }
86
+
87
+ isAssignable(from: TypeRef, to: TypeRef): boolean {
88
+ const fromRef = from as unknown as SchemaTypeRef;
89
+ const toRef = to as unknown as SchemaTypeRef;
90
+
91
+ // Same type is always assignable
92
+ if (fromRef.type === toRef.type && fromRef.type !== 'complex-type') {
93
+ return true;
94
+ }
95
+
96
+ // Any is assignable to/from everything
97
+ if (fromRef.type === 'Any' || toRef.type === 'Any') {
98
+ return true;
99
+ }
100
+
101
+ // For complex types, check if 'from' is a subtype of 'to'
102
+ if (fromRef.type === 'complex-type' && toRef.type === 'complex-type') {
103
+ const fromSchema = fromRef.schemas?.[0];
104
+ const toSchema = toRef.schemas?.[0];
105
+
106
+ if (!fromSchema || !toSchema) return false;
107
+
108
+ const fromName = this.getSchemaName(fromSchema);
109
+ const toName = this.getSchemaName(toSchema);
110
+
111
+ // Check if 'to' appears in 'from's schema list (inheritance chain)
112
+ return fromRef.schemas.some(schema => this.getSchemaName(schema) === toName);
113
+ }
114
+
115
+ // Handle primitive type inheritance (e.g., code extends string)
116
+ if (this.isPrimitiveType(fromRef.type) && this.isPrimitiveType(toRef.type)) {
117
+ const fromSchemas = this.getSchemaList(fromRef.type);
118
+ return fromSchemas.some(schema => schema.name === toRef.type);
119
+ }
120
+
121
+ return false;
122
+ }
123
+
124
+ getTypeName(type: TypeRef): string {
125
+ // Handle undefined type
126
+ if (!type || typeof type !== 'object') {
127
+ return 'Unknown';
128
+ }
129
+
130
+ // Cast to our internal SchemaTypeRef structure
131
+ const typeRef = type as unknown as SchemaTypeRef;
132
+
133
+ if (typeRef.type !== 'complex-type') {
134
+ // Capitalize primitive type names for display
135
+ return typeRef.type.charAt(0).toUpperCase() + typeRef.type.slice(1);
136
+ }
137
+
138
+ // For complex types, use the name of the first schema
139
+ const schema = typeRef.schemas?.[0];
140
+ if (!schema) return 'Unknown';
141
+ return this.getSchemaName(schema);
142
+ }
143
+
144
+ isCollectionType(type: TypeRef): boolean {
145
+ // The collection nature is carried in the TypeRef itself
146
+ const typeRef = type as unknown as SchemaTypeRef;
147
+ return typeRef.array || false;
148
+ }
149
+
150
+ getCommonType(types: TypeRef[]): TypeRef | undefined {
151
+ if (types.length === 0) {
152
+ return undefined;
153
+ }
154
+
155
+ if (types.length === 1) {
156
+ return types[0];
157
+ }
158
+
159
+ // For simplicity, if all types are the same, return that type
160
+ const typeNames = types.map(t => this.getTypeName(t));
161
+ if (typeNames.every(name => name === typeNames[0])) {
162
+ return types[0];
163
+ }
164
+
165
+ // Otherwise return Any
166
+ return this.resolveType('Any');
167
+ }
168
+
169
+ private getSchemaList(typeName: string): FHIRSchema[] {
170
+ // Check cache first
171
+ if (this.schemaListCache.has(typeName)) {
172
+ return this.schemaListCache.get(typeName)!;
173
+ }
174
+
175
+ // Build schema list by following base chain
176
+ const schemaList: FHIRSchema[] = [];
177
+ let currentType = typeName;
178
+
179
+ while (currentType) {
180
+ const schema = this.registry.resolve(currentType);
181
+ if (!schema) break;
182
+
183
+ // Add name to schema if not present (for primitives)
184
+ if (!schema.name) {
185
+ schema.name = currentType;
186
+ }
187
+
188
+ schemaList.push(schema);
189
+ currentType = schema.base || '';
190
+ }
191
+
192
+ // Cache the result
193
+ this.schemaListCache.set(typeName, schemaList);
194
+ return schemaList;
195
+ }
196
+
197
+ private getSchemaName(schema: FHIRSchema): string {
198
+ // Schema should have a name, either explicit or set during resolution
199
+ return schema.name || 'Unknown';
200
+ }
201
+
202
+ private isPrimitiveType(typeName: string): boolean {
203
+ // List of known primitive types
204
+ const primitives = [
205
+ 'string', 'integer', 'decimal', 'boolean',
206
+ 'date', 'dateTime', 'time', 'instant',
207
+ 'code', 'id', 'uri', 'url', 'canonical',
208
+ 'base64Binary', 'markdown', 'unsignedInt', 'positiveInt',
209
+ 'Any'
210
+ ];
211
+
212
+ return primitives.includes(typeName);
213
+ }
214
+
215
+ private normalizeTypeName(typeName: string): string {
216
+ // Handle different naming conventions
217
+ // "String" -> "string", "Integer" -> "integer", etc.
218
+
219
+ // First check if it's a primitive type with capital letter
220
+ const lowerName = typeName.toLowerCase();
221
+ if (this.isPrimitiveType(lowerName)) {
222
+ return lowerName;
223
+ }
224
+
225
+ // Handle System namespace
226
+ if (typeName.startsWith('System.')) {
227
+ const baseName = typeName.substring(7);
228
+ const lowerBase = baseName.toLowerCase();
229
+ if (this.isPrimitiveType(lowerBase)) {
230
+ return lowerBase;
231
+ }
232
+ // System.Quantity -> Quantity
233
+ return baseName;
234
+ }
235
+
236
+ // Handle FHIR namespace
237
+ if (typeName.startsWith('FHIR.')) {
238
+ return typeName.substring(5);
239
+ }
240
+
241
+ // Default - return as is
242
+ return typeName;
243
+ }
244
+ }
@@ -0,0 +1,2 @@
1
+ // Export schema types and registry components
2
+ export * from './types';
@@ -0,0 +1,40 @@
1
+ // FHIR Schema type definitions
2
+ // Note: Actual schema definitions are not included in the analyzer
3
+ // In production, schemas should be loaded from the fhirschema library
4
+ // Test schemas are located in test/test-schemas.ts
5
+
6
+ export interface FHIRSchemaElement {
7
+ type?: string; // Simple type reference
8
+ array?: boolean; // Is this a collection?
9
+ union?: boolean | string; // Union type indicator or discriminator
10
+ types?: string[]; // Union type options
11
+ elements?: Record<string, FHIRSchemaElement>; // Nested elements
12
+ valueset?: string; // Terminology binding
13
+ }
14
+
15
+ export interface FHIRSchema {
16
+ name?: string; // Schema name (for primitives and inline types)
17
+ base?: string; // Parent type
18
+ primitive?: boolean; // Is this a primitive type?
19
+ elements?: Record<string, FHIRSchemaElement>;
20
+ }
21
+
22
+ export interface SchemaRegistry {
23
+ resolve(typeName: string): FHIRSchema | undefined;
24
+ }
25
+
26
+ // Static implementation for testing
27
+ export class StaticSchemaRegistry implements SchemaRegistry {
28
+ constructor(private schemas: Record<string, FHIRSchema>) {}
29
+
30
+ resolve(typeName: string): FHIRSchema | undefined {
31
+ return this.schemas[typeName];
32
+ }
33
+ }
34
+
35
+ // Schema-based type reference used internally by ModelProvider
36
+ export interface SchemaTypeRef {
37
+ schemas: FHIRSchema[]; // List of schemas in inheritance order
38
+ type: string; // 'complex-type' for FHIR types, or primitive name
39
+ array?: boolean; // Optional, indicates if this is an array type
40
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Type system interfaces for FHIRPath type analysis
3
+ *
4
+ * Uses opaque type references to allow flexible implementation
5
+ */
6
+
7
+ // Opaque type reference - implementation details hidden from analyzer
8
+ export type TypeRef = unknown;
9
+
10
+ /**
11
+ * Information about a property on a type
12
+ */
13
+ export interface PropertyInfo {
14
+ type: TypeRef;
15
+ isSingleton: boolean;
16
+ }
17
+
18
+ /**
19
+ * Model provider interface for type resolution and navigation
20
+ */
21
+ export interface ModelProvider {
22
+ /**
23
+ * Resolve a type name to an opaque type reference
24
+ */
25
+ resolveType(typeName: string): TypeRef | undefined;
26
+
27
+ /**
28
+ * Get property type from an opaque type reference
29
+ */
30
+ getPropertyType(type: TypeRef, propertyName: string): PropertyInfo | undefined;
31
+
32
+ /**
33
+ * Check if one type is assignable to another
34
+ */
35
+ isAssignable(from: TypeRef, to: TypeRef): boolean;
36
+
37
+ /**
38
+ * Get a human-readable name for a type (for error messages)
39
+ */
40
+ getTypeName(type: TypeRef): string;
41
+
42
+ /**
43
+ * Check if a type is a collection type
44
+ */
45
+ isCollectionType?(type: TypeRef): boolean;
46
+
47
+ /**
48
+ * Get common base type for union types
49
+ */
50
+ getCommonType?(types: TypeRef[]): TypeRef | undefined;
51
+ }
52
+
53
+ /**
54
+ * Parameter type information for functions
55
+ */
56
+ export interface ParameterTypeInfo {
57
+ type?: TypeRef | 'expression' | 'any';
58
+ requiresSingleton?: boolean;
59
+ optional?: boolean;
60
+ }
61
+
62
+ /**
63
+ * Function type signature
64
+ */
65
+ export interface FunctionTypeSignature {
66
+ // Input requirements
67
+ requiresSingleton: boolean;
68
+ propagateEmptyInput?: boolean;
69
+ requiresInputType?: string; // Required input type (e.g., 'string' for string functions)
70
+
71
+ // Parameter type information
72
+ parameters?: ParameterTypeInfo[];
73
+
74
+ // Return type calculation
75
+ returnType: (
76
+ inputType: TypeRef | undefined,
77
+ paramTypes: (TypeRef | undefined)[],
78
+ provider: ModelProvider
79
+ ) => TypeRef | undefined;
80
+
81
+ // Return cardinality calculation
82
+ returnsSingleton: (
83
+ inputIsSingleton: boolean,
84
+ paramAreSingleton: boolean[]
85
+ ) => boolean;
86
+ }
87
+
88
+ /**
89
+ * Operator type signature
90
+ */
91
+ export interface OperatorTypeSignature {
92
+ requiresLeftSingleton: boolean;
93
+ requiresRightSingleton: boolean;
94
+
95
+ // For simple operators with fixed types
96
+ acceptedTypes?: Array<{
97
+ left: string;
98
+ right: string;
99
+ result: string;
100
+ }> | 'any';
101
+
102
+ // For complex operators that need custom logic
103
+ returnType?: (
104
+ leftType: TypeRef | undefined,
105
+ rightType: TypeRef | undefined,
106
+ provider: ModelProvider
107
+ ) => TypeRef | undefined;
108
+
109
+ returnsSingleton: boolean | ((left: boolean, right: boolean) => boolean);
110
+ }
111
+
112
+ /**
113
+ * Type analysis mode
114
+ */
115
+ export enum AnalysisMode {
116
+ Strict = 'strict', // Type mismatches are errors
117
+ Lenient = 'lenient' // Type mismatches are warnings, continue with Any
118
+ }
119
+
120
+ /**
121
+ * Type analysis result
122
+ */
123
+ export interface TypeAnalysisResult {
124
+ // Annotated AST with type information
125
+ ast: import('../parser/ast').ASTNode;
126
+
127
+ // Any errors or warnings encountered
128
+ diagnostics: TypeDiagnostic[];
129
+
130
+ // Overall result type
131
+ resultType?: TypeRef;
132
+ resultIsSingleton?: boolean;
133
+ }
134
+
135
+ /**
136
+ * Type diagnostic (error or warning)
137
+ */
138
+ export interface TypeDiagnostic {
139
+ severity: 'error' | 'warning';
140
+ message: string;
141
+ position?: import('../parser/ast').Position;
142
+ }
@@ -0,0 +1,148 @@
1
+ import type {
2
+ FHIRPathBuilder,
3
+ FHIRPathAPI,
4
+ ModelProvider,
5
+ CustomFunction,
6
+ FHIRPathExpression,
7
+ CompiledExpression,
8
+ AnalysisResult,
9
+ EvaluationContext,
10
+ CompileOptions,
11
+ AnalyzeOptions
12
+ } from './types';
13
+ import { parse, evaluate, compile, analyze } from './index';
14
+ import { PublicRegistryAPI } from './registry';
15
+ import { Registry } from '../registry/registry';
16
+ import { invalidArgument } from './errors';
17
+
18
+ export class FHIRPath {
19
+ static builder(): FHIRPathBuilder {
20
+ return new Builder();
21
+ }
22
+ }
23
+
24
+ class Builder implements FHIRPathBuilder {
25
+ private modelProvider?: ModelProvider;
26
+ private customFunctions: Map<string, CustomFunction> = new Map();
27
+ private variables: Map<string, any> = new Map();
28
+
29
+ withModelProvider(provider: ModelProvider): this {
30
+ this.modelProvider = provider;
31
+ return this;
32
+ }
33
+
34
+ withCustomFunction(name: string, fn: CustomFunction): this {
35
+ // Validate function name
36
+ if (!name || typeof name !== 'string') {
37
+ throw invalidArgument('Function name must be a non-empty string');
38
+ }
39
+
40
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
41
+ throw invalidArgument(`Invalid function name: ${name}. Must start with letter or underscore, followed by letters, numbers, or underscores`);
42
+ }
43
+
44
+ // Check for conflicts with built-ins
45
+ if (Registry.get(name)) {
46
+ throw invalidArgument(`Cannot override built-in operation: ${name}`);
47
+ }
48
+
49
+ this.customFunctions.set(name, fn);
50
+ return this;
51
+ }
52
+
53
+ withVariable(name: string, value: any): this {
54
+ if (!name || typeof name !== 'string') {
55
+ throw invalidArgument('Variable name must be a non-empty string');
56
+ }
57
+
58
+ this.variables.set(name, value);
59
+ return this;
60
+ }
61
+
62
+ build(): FHIRPathAPI {
63
+ return new BuiltAPI(
64
+ this.modelProvider,
65
+ this.customFunctions,
66
+ this.variables
67
+ );
68
+ }
69
+ }
70
+
71
+ class BuiltAPI implements FHIRPathAPI {
72
+ public readonly registry: PublicRegistryAPI;
73
+
74
+ constructor(
75
+ private readonly modelProvider: ModelProvider | undefined,
76
+ private readonly customFunctions: Map<string, CustomFunction>,
77
+ private readonly defaultVariables: Map<string, any>
78
+ ) {
79
+ // Create a registry wrapper that includes custom functions
80
+ this.registry = new BuiltRegistryAPI(this.customFunctions);
81
+ }
82
+
83
+ parse(expression: string): FHIRPathExpression {
84
+ return parse(expression);
85
+ }
86
+
87
+ evaluate(expression: string | FHIRPathExpression, input?: any): any[] {
88
+ const context = this.createContext();
89
+ return evaluate(expression, input, context);
90
+ }
91
+
92
+ compile(expression: string | FHIRPathExpression, options?: CompileOptions): CompiledExpression {
93
+ // TODO: Integrate custom functions into compiled code
94
+ return compile(expression, options);
95
+ }
96
+
97
+ analyze(expression: string | FHIRPathExpression, options?: AnalyzeOptions): AnalysisResult {
98
+ const mergedOptions: AnalyzeOptions = {
99
+ ...options,
100
+ modelProvider: options?.modelProvider || this.modelProvider
101
+ };
102
+ return analyze(expression, mergedOptions);
103
+ }
104
+
105
+ private createContext(): EvaluationContext {
106
+ const context: EvaluationContext = {};
107
+
108
+ if (this.modelProvider) {
109
+ context.modelProvider = this.modelProvider;
110
+ }
111
+
112
+ if (this.defaultVariables.size > 0) {
113
+ context.variables = {};
114
+ for (const [name, value] of this.defaultVariables) {
115
+ context.variables[name] = value;
116
+ }
117
+ }
118
+
119
+ if (this.customFunctions.size > 0) {
120
+ context.customFunctions = {};
121
+ for (const [name, fn] of this.customFunctions) {
122
+ context.customFunctions[name] = fn;
123
+ }
124
+ }
125
+
126
+ return context;
127
+ }
128
+ }
129
+
130
+ // Extended registry that includes custom functions
131
+ class BuiltRegistryAPI extends PublicRegistryAPI {
132
+ constructor(private customFunctions: Map<string, CustomFunction>) {
133
+ super();
134
+ }
135
+
136
+ override hasFunction(name: string): boolean {
137
+ return super.hasFunction(name) || this.customFunctions.has(name);
138
+ }
139
+
140
+ override hasOperation(name: string): boolean {
141
+ return super.hasOperation(name) || this.customFunctions.has(name);
142
+ }
143
+
144
+ override canRegisterFunction(name: string): boolean {
145
+ // Can't register if it conflicts with built-ins or already registered custom
146
+ return super.canRegisterFunction(name) && !this.customFunctions.has(name);
147
+ }
148
+ }
@@ -0,0 +1,134 @@
1
+ import type { Location } from './types';
2
+
3
+ export enum ErrorCode {
4
+ // Parse errors
5
+ PARSE_ERROR = 'PARSE_ERROR',
6
+ SYNTAX_ERROR = 'SYNTAX_ERROR',
7
+ UNEXPECTED_TOKEN = 'UNEXPECTED_TOKEN',
8
+ UNTERMINATED_STRING = 'UNTERMINATED_STRING',
9
+ INVALID_ESCAPE = 'INVALID_ESCAPE',
10
+
11
+ // Type errors
12
+ TYPE_ERROR = 'TYPE_ERROR',
13
+ TYPE_MISMATCH = 'TYPE_MISMATCH',
14
+ UNKNOWN_TYPE = 'UNKNOWN_TYPE',
15
+ CARDINALITY_ERROR = 'CARDINALITY_ERROR',
16
+
17
+ // Runtime errors
18
+ RUNTIME_ERROR = 'RUNTIME_ERROR',
19
+ UNDEFINED_VARIABLE = 'UNDEFINED_VARIABLE',
20
+ UNDEFINED_FUNCTION = 'UNDEFINED_FUNCTION',
21
+ INVALID_ARGUMENT = 'INVALID_ARGUMENT',
22
+ DIVISION_BY_ZERO = 'DIVISION_BY_ZERO',
23
+
24
+ // Analysis errors
25
+ ANALYSIS_ERROR = 'ANALYSIS_ERROR',
26
+ UNREACHABLE_CODE = 'UNREACHABLE_CODE',
27
+ AMBIGUOUS_TYPE = 'AMBIGUOUS_TYPE',
28
+ }
29
+
30
+ export class FHIRPathError extends Error {
31
+ constructor(
32
+ message: string,
33
+ public code: ErrorCode,
34
+ public location?: Location,
35
+ public expression?: string
36
+ ) {
37
+ super(message);
38
+ this.name = 'FHIRPathError';
39
+
40
+ // Ensure proper prototype chain
41
+ Object.setPrototypeOf(this, FHIRPathError.prototype);
42
+ }
43
+
44
+ override toString(): string {
45
+ let result = `${this.name}: ${this.message}`;
46
+
47
+ if (this.expression && this.location) {
48
+ result += `\n\n${this.expression}`;
49
+ if (this.location.offset >= 0 && this.location.length > 0) {
50
+ const indent = ' '.repeat(this.location.offset);
51
+ const marker = '^'.repeat(this.location.length);
52
+ result += `\n${indent}${marker}`;
53
+ }
54
+ }
55
+
56
+ if (this.location) {
57
+ result += `\n\nAt line ${this.location.line}, column ${this.location.column}`;
58
+ }
59
+
60
+ return result;
61
+ }
62
+ }
63
+
64
+ // Error factory functions
65
+ export function parseError(
66
+ message: string,
67
+ location?: Location,
68
+ expression?: string
69
+ ): FHIRPathError {
70
+ return new FHIRPathError(message, ErrorCode.PARSE_ERROR, location, expression);
71
+ }
72
+
73
+ export function syntaxError(
74
+ message: string,
75
+ location?: Location,
76
+ expression?: string
77
+ ): FHIRPathError {
78
+ return new FHIRPathError(message, ErrorCode.SYNTAX_ERROR, location, expression);
79
+ }
80
+
81
+ export function typeError(
82
+ message: string,
83
+ location?: Location,
84
+ expression?: string
85
+ ): FHIRPathError {
86
+ return new FHIRPathError(message, ErrorCode.TYPE_ERROR, location, expression);
87
+ }
88
+
89
+ export function runtimeError(
90
+ message: string,
91
+ location?: Location,
92
+ expression?: string
93
+ ): FHIRPathError {
94
+ return new FHIRPathError(message, ErrorCode.RUNTIME_ERROR, location, expression);
95
+ }
96
+
97
+ export function undefinedVariable(
98
+ name: string,
99
+ location?: Location,
100
+ expression?: string
101
+ ): FHIRPathError {
102
+ return new FHIRPathError(
103
+ `Undefined variable: ${name}`,
104
+ ErrorCode.UNDEFINED_VARIABLE,
105
+ location,
106
+ expression
107
+ );
108
+ }
109
+
110
+ export function undefinedFunction(
111
+ name: string,
112
+ location?: Location,
113
+ expression?: string
114
+ ): FHIRPathError {
115
+ return new FHIRPathError(
116
+ `Undefined function: ${name}`,
117
+ ErrorCode.UNDEFINED_FUNCTION,
118
+ location,
119
+ expression
120
+ );
121
+ }
122
+
123
+ export function invalidArgument(
124
+ message: string,
125
+ location?: Location,
126
+ expression?: string
127
+ ): FHIRPathError {
128
+ return new FHIRPathError(
129
+ `Invalid argument: ${message}`,
130
+ ErrorCode.INVALID_ARGUMENT,
131
+ location,
132
+ expression
133
+ );
134
+ }