@atomic-ehr/fhirpath 0.0.1-canary.0c6931e.20250727185306
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 +473 -0
- package/dist/index.d.ts +462 -0
- package/dist/index.js +10307 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
- package/src/analyzer/analyzer.ts +499 -0
- package/src/analyzer/model-provider.ts +244 -0
- package/src/analyzer/schemas/index.ts +2 -0
- package/src/analyzer/schemas/types.ts +40 -0
- package/src/analyzer/types.ts +142 -0
- package/src/api/builder.ts +157 -0
- package/src/api/errors.ts +145 -0
- package/src/api/expression.ts +156 -0
- package/src/api/index.ts +122 -0
- package/src/api/inspect.ts +99 -0
- package/src/api/registry.ts +128 -0
- package/src/api/types.ts +210 -0
- package/src/compiler/compiler.ts +546 -0
- package/src/compiler/index.ts +2 -0
- package/src/compiler/prototype-context-adapter.ts +99 -0
- package/src/compiler/types.ts +24 -0
- package/src/index.ts +107 -0
- package/src/interpreter/README.md +78 -0
- package/src/interpreter/interpreter.ts +475 -0
- package/src/interpreter/types.ts +108 -0
- package/src/lexer/char-tables.ts +37 -0
- package/src/lexer/errors.ts +31 -0
- package/src/lexer/index.ts +5 -0
- package/src/lexer/lexer.ts +745 -0
- package/src/lexer/token.ts +104 -0
- package/src/lexer2/index.md +232 -0
- package/src/lexer2/index.perf.test.ts +68 -0
- package/src/lexer2/index.test.ts +549 -0
- package/src/lexer2/index.ts +1251 -0
- package/src/lexer2/notes.md +173 -0
- package/src/lexer2/optimization-summary.md +718 -0
- package/src/parser/ast-factory.ts +220 -0
- package/src/parser/ast.ts +144 -0
- package/src/parser/collection-parser.ts +89 -0
- package/src/parser/diagnostic-messages.ts +216 -0
- package/src/parser/diagnostics.ts +85 -0
- package/src/parser/error-reporter.ts +230 -0
- package/src/parser/index.ts +3 -0
- package/src/parser/literal-parser.ts +103 -0
- package/src/parser/parse-error.ts +16 -0
- package/src/parser/parser-error-factory.ts +141 -0
- package/src/parser/parser-state.ts +134 -0
- package/src/parser/parser.ts +1272 -0
- package/src/parser/pprint.ts +169 -0
- package/src/parser/precedence-manager.ts +64 -0
- package/src/parser/source-mapper.ts +248 -0
- package/src/parser/special-constructs.ts +142 -0
- package/src/parser/token-navigator.ts +110 -0
- package/src/parser/types.ts +60 -0
- package/src/parser2/index.md +177 -0
- package/src/parser2/index.perf.test.ts +184 -0
- package/src/parser2/index.test.ts +305 -0
- package/src/parser2/index.ts +578 -0
- package/src/parser2/optimization-summary.md +176 -0
- package/src/registry/default-analyzers.ts +257 -0
- package/src/registry/default-compilers.ts +31 -0
- package/src/registry/index.ts +96 -0
- package/src/registry/operations/arithmetic.ts +506 -0
- package/src/registry/operations/collection.ts +425 -0
- package/src/registry/operations/comparison.ts +432 -0
- package/src/registry/operations/existence.ts +703 -0
- package/src/registry/operations/filtering.ts +358 -0
- package/src/registry/operations/literals.ts +341 -0
- package/src/registry/operations/logical.ts +439 -0
- package/src/registry/operations/math.ts +128 -0
- package/src/registry/operations/membership.ts +132 -0
- package/src/registry/operations/navigation.ts +52 -0
- package/src/registry/operations/string.ts +507 -0
- package/src/registry/operations/subsetting.ts +174 -0
- package/src/registry/operations/type-checking.ts +162 -0
- package/src/registry/operations/type-conversion.ts +404 -0
- package/src/registry/operations/type-operators.ts +308 -0
- package/src/registry/operations/utility.ts +644 -0
- package/src/registry/registry.ts +146 -0
- package/src/registry/types.ts +161 -0
- package/src/registry/utils/evaluation-helpers.ts +93 -0
- package/src/registry/utils/index.ts +3 -0
- package/src/registry/utils/type-system.ts +173 -0
- package/src/runtime/context.ts +158 -0
- package/src/runtime/debug-context.ts +135 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { Token } from '../lexer/token';
|
|
2
|
+
import { TokenType } from '../lexer/token';
|
|
3
|
+
import type { Position } from './ast';
|
|
4
|
+
import {
|
|
5
|
+
type ASTNode,
|
|
6
|
+
type BinaryNode,
|
|
7
|
+
type UnaryNode,
|
|
8
|
+
type LiteralNode,
|
|
9
|
+
type IdentifierNode,
|
|
10
|
+
type TypeOrIdentifierNode,
|
|
11
|
+
type FunctionNode,
|
|
12
|
+
type VariableNode,
|
|
13
|
+
type IndexNode,
|
|
14
|
+
type UnionNode,
|
|
15
|
+
type MembershipTestNode,
|
|
16
|
+
type TypeCastNode,
|
|
17
|
+
type CollectionNode,
|
|
18
|
+
type TypeReferenceNode,
|
|
19
|
+
type ErrorNode,
|
|
20
|
+
type IncompleteNode,
|
|
21
|
+
NodeType
|
|
22
|
+
} from './ast';
|
|
23
|
+
import type { ParseDiagnostic as Diagnostic } from './types';
|
|
24
|
+
import type { Operation } from '../registry/types';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Factory for creating AST nodes with consistent structure
|
|
28
|
+
*/
|
|
29
|
+
export class ASTFactory {
|
|
30
|
+
static createLiteral(
|
|
31
|
+
value: any,
|
|
32
|
+
valueType: 'string' | 'number' | 'boolean' | 'date' | 'time' | 'datetime' | 'null',
|
|
33
|
+
position: Position,
|
|
34
|
+
raw?: string,
|
|
35
|
+
operation?: Operation
|
|
36
|
+
): LiteralNode {
|
|
37
|
+
return {
|
|
38
|
+
type: NodeType.Literal,
|
|
39
|
+
value,
|
|
40
|
+
valueType,
|
|
41
|
+
raw,
|
|
42
|
+
operation,
|
|
43
|
+
position
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static createIdentifier(name: string, position: Position): IdentifierNode {
|
|
48
|
+
return {
|
|
49
|
+
type: NodeType.Identifier,
|
|
50
|
+
name,
|
|
51
|
+
position
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static createTypeOrIdentifier(name: string, position: Position): TypeOrIdentifierNode {
|
|
56
|
+
return {
|
|
57
|
+
type: NodeType.TypeOrIdentifier,
|
|
58
|
+
name,
|
|
59
|
+
position
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static createVariable(name: string, isSpecial: boolean, position: Position): VariableNode {
|
|
64
|
+
return {
|
|
65
|
+
type: NodeType.Variable,
|
|
66
|
+
name,
|
|
67
|
+
position
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static createBinary(
|
|
72
|
+
operator: TokenType,
|
|
73
|
+
left: ASTNode,
|
|
74
|
+
right: ASTNode,
|
|
75
|
+
position: Position,
|
|
76
|
+
operation?: Operation
|
|
77
|
+
): BinaryNode {
|
|
78
|
+
return {
|
|
79
|
+
type: NodeType.Binary,
|
|
80
|
+
operator,
|
|
81
|
+
operation,
|
|
82
|
+
left,
|
|
83
|
+
right,
|
|
84
|
+
position
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static createUnary(
|
|
89
|
+
operator: TokenType,
|
|
90
|
+
operand: ASTNode,
|
|
91
|
+
position: Position,
|
|
92
|
+
operation?: Operation
|
|
93
|
+
): UnaryNode {
|
|
94
|
+
return {
|
|
95
|
+
type: NodeType.Unary,
|
|
96
|
+
operator,
|
|
97
|
+
operation,
|
|
98
|
+
operand,
|
|
99
|
+
position
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static createFunction(
|
|
104
|
+
name: ASTNode,
|
|
105
|
+
args: ASTNode[],
|
|
106
|
+
position: Position,
|
|
107
|
+
operation?: Operation
|
|
108
|
+
): FunctionNode {
|
|
109
|
+
return {
|
|
110
|
+
type: NodeType.Function,
|
|
111
|
+
name,
|
|
112
|
+
arguments: args,
|
|
113
|
+
operation,
|
|
114
|
+
position
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static createUnion(operands: ASTNode[], position: Position): UnionNode {
|
|
119
|
+
return {
|
|
120
|
+
type: NodeType.Union,
|
|
121
|
+
operands,
|
|
122
|
+
position
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static createIndex(expression: ASTNode, index: ASTNode, position: Position): IndexNode {
|
|
127
|
+
return {
|
|
128
|
+
type: NodeType.Index,
|
|
129
|
+
expression,
|
|
130
|
+
index,
|
|
131
|
+
position
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static createCollection(elements: ASTNode[], position: Position): CollectionNode {
|
|
136
|
+
return {
|
|
137
|
+
type: NodeType.Collection,
|
|
138
|
+
elements,
|
|
139
|
+
position
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static createMembershipTest(
|
|
144
|
+
expression: ASTNode,
|
|
145
|
+
targetType: string,
|
|
146
|
+
position: Position
|
|
147
|
+
): MembershipTestNode {
|
|
148
|
+
return {
|
|
149
|
+
type: NodeType.MembershipTest,
|
|
150
|
+
expression,
|
|
151
|
+
targetType,
|
|
152
|
+
position
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static createTypeCast(
|
|
157
|
+
expression: ASTNode,
|
|
158
|
+
targetType: string,
|
|
159
|
+
position: Position
|
|
160
|
+
): TypeCastNode {
|
|
161
|
+
return {
|
|
162
|
+
type: NodeType.TypeCast,
|
|
163
|
+
expression,
|
|
164
|
+
targetType,
|
|
165
|
+
position
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static createTypeReference(name: string, position: Position): TypeReferenceNode {
|
|
170
|
+
return {
|
|
171
|
+
type: NodeType.TypeReference,
|
|
172
|
+
typeName: name,
|
|
173
|
+
position
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
static createError(
|
|
178
|
+
position: Position,
|
|
179
|
+
expectedTokens: TokenType[],
|
|
180
|
+
diagnostic: Diagnostic
|
|
181
|
+
): ErrorNode {
|
|
182
|
+
return {
|
|
183
|
+
type: NodeType.Error,
|
|
184
|
+
position,
|
|
185
|
+
expectedTokens,
|
|
186
|
+
diagnostic
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
static createIncomplete(
|
|
191
|
+
partialNode: ASTNode | undefined,
|
|
192
|
+
missingParts: string[],
|
|
193
|
+
position: Position
|
|
194
|
+
): IncompleteNode {
|
|
195
|
+
return {
|
|
196
|
+
type: NodeType.Incomplete,
|
|
197
|
+
partialNode,
|
|
198
|
+
missingParts,
|
|
199
|
+
position
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Infer literal type from value
|
|
205
|
+
*/
|
|
206
|
+
static inferLiteralType(value: any): 'string' | 'number' | 'boolean' | 'date' | 'time' | 'datetime' | 'null' {
|
|
207
|
+
if (value === null) return 'null';
|
|
208
|
+
if (typeof value === 'boolean') return 'boolean';
|
|
209
|
+
if (typeof value === 'number') return 'number';
|
|
210
|
+
if (typeof value === 'string') return 'string';
|
|
211
|
+
if (value instanceof Date) {
|
|
212
|
+
// Check if it has time component
|
|
213
|
+
const hasTime = value.getHours() !== 0 || value.getMinutes() !== 0 || value.getSeconds() !== 0;
|
|
214
|
+
return hasTime ? 'datetime' : 'date';
|
|
215
|
+
}
|
|
216
|
+
// Check for time-only values (stored as strings like "14:30:00")
|
|
217
|
+
if (typeof value === 'object' && value.type === 'time') return 'time';
|
|
218
|
+
return 'string'; // default
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { TokenType, Token } from '../lexer/token';
|
|
2
|
+
import type { TextRange } from './types';
|
|
3
|
+
import type { ParseDiagnostic } from './types';
|
|
4
|
+
|
|
5
|
+
// Core AST Node Interface
|
|
6
|
+
export interface ASTNode {
|
|
7
|
+
type: NodeType;
|
|
8
|
+
position: Position;
|
|
9
|
+
range?: TextRange; // Full text range (populated in diagnostic modes)
|
|
10
|
+
// Type analysis fields (optional - added by analyzer)
|
|
11
|
+
resultType?: unknown; // Opaque type reference
|
|
12
|
+
isSingleton?: boolean; // Whether this expression returns a single value
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Position {
|
|
16
|
+
line: number;
|
|
17
|
+
column: number;
|
|
18
|
+
offset: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Node types
|
|
22
|
+
export enum NodeType {
|
|
23
|
+
// Navigation
|
|
24
|
+
Identifier,
|
|
25
|
+
TypeOrIdentifier, // Uppercase identifiers that could be types (Patient, Observation)
|
|
26
|
+
|
|
27
|
+
// Operators
|
|
28
|
+
Binary, // All binary operators including dot
|
|
29
|
+
Unary, // unary +, -, not
|
|
30
|
+
Union, // | operator (special handling for multiple operands)
|
|
31
|
+
|
|
32
|
+
// Functions
|
|
33
|
+
Function, // Function calls
|
|
34
|
+
|
|
35
|
+
// Literals
|
|
36
|
+
Literal, // numbers, strings, booleans, dates, null
|
|
37
|
+
Variable, // $this, $index, $total, %var
|
|
38
|
+
Collection, // {} empty collection or {expr1, expr2, ...}
|
|
39
|
+
|
|
40
|
+
// Type operations
|
|
41
|
+
MembershipTest, // 'is' operator
|
|
42
|
+
TypeCast, // 'as' operator
|
|
43
|
+
TypeReference, // Type name in ofType()
|
|
44
|
+
|
|
45
|
+
// Special
|
|
46
|
+
Index, // [] indexing
|
|
47
|
+
|
|
48
|
+
// Error recovery
|
|
49
|
+
Error = 'Error', // Error recovery node
|
|
50
|
+
Incomplete = 'Incomplete' // Incomplete expression node
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Specific node types
|
|
54
|
+
export interface IdentifierNode extends ASTNode {
|
|
55
|
+
type: NodeType.Identifier;
|
|
56
|
+
name: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface TypeOrIdentifierNode extends ASTNode {
|
|
60
|
+
type: NodeType.TypeOrIdentifier;
|
|
61
|
+
name: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface LiteralNode extends ASTNode {
|
|
65
|
+
type: NodeType.Literal;
|
|
66
|
+
value: any;
|
|
67
|
+
valueType: 'string' | 'number' | 'boolean' | 'date' | 'time' | 'datetime' | 'null';
|
|
68
|
+
raw?: string; // Raw string representation
|
|
69
|
+
operation?: any; // Operation from registry (using any to avoid circular dependency)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface BinaryNode extends ASTNode {
|
|
73
|
+
type: NodeType.Binary;
|
|
74
|
+
operator: TokenType;
|
|
75
|
+
operation?: any; // Operation from registry (using any to avoid circular dependency)
|
|
76
|
+
left: ASTNode;
|
|
77
|
+
right: ASTNode;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface UnaryNode extends ASTNode {
|
|
81
|
+
type: NodeType.Unary;
|
|
82
|
+
operator: TokenType;
|
|
83
|
+
operation?: any; // Operation from registry (using any to avoid circular dependency)
|
|
84
|
+
operand: ASTNode;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface FunctionNode extends ASTNode {
|
|
88
|
+
type: NodeType.Function;
|
|
89
|
+
name: ASTNode; // Usually an identifier
|
|
90
|
+
arguments: ASTNode[];
|
|
91
|
+
operation?: any; // Operation from registry (using any to avoid circular dependency)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface VariableNode extends ASTNode {
|
|
95
|
+
type: NodeType.Variable;
|
|
96
|
+
name: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface IndexNode extends ASTNode {
|
|
100
|
+
type: NodeType.Index;
|
|
101
|
+
expression: ASTNode;
|
|
102
|
+
index: ASTNode;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface UnionNode extends ASTNode {
|
|
106
|
+
type: NodeType.Union;
|
|
107
|
+
operands: ASTNode[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface MembershipTestNode extends ASTNode {
|
|
111
|
+
type: NodeType.MembershipTest;
|
|
112
|
+
expression: ASTNode;
|
|
113
|
+
targetType: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface TypeCastNode extends ASTNode {
|
|
117
|
+
type: NodeType.TypeCast;
|
|
118
|
+
expression: ASTNode;
|
|
119
|
+
targetType: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface CollectionNode extends ASTNode {
|
|
123
|
+
type: NodeType.Collection;
|
|
124
|
+
elements: ASTNode[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface TypeReferenceNode extends ASTNode {
|
|
128
|
+
type: NodeType.TypeReference;
|
|
129
|
+
typeName: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Error recovery nodes
|
|
133
|
+
export interface ErrorNode extends ASTNode {
|
|
134
|
+
type: NodeType.Error;
|
|
135
|
+
expectedTokens?: TokenType[];
|
|
136
|
+
actualToken?: Token;
|
|
137
|
+
diagnostic: ParseDiagnostic;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface IncompleteNode extends ASTNode {
|
|
141
|
+
type: NodeType.Incomplete;
|
|
142
|
+
partialNode?: ASTNode;
|
|
143
|
+
missingParts: string[];
|
|
144
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Token } from '../lexer/token';
|
|
2
|
+
import { TokenType } from '../lexer/token';
|
|
3
|
+
import type { ASTNode } from './ast';
|
|
4
|
+
import { ASTFactory } from './ast-factory';
|
|
5
|
+
import type { TokenNavigator } from './token-navigator';
|
|
6
|
+
import { ParseError } from './parse-error';
|
|
7
|
+
import { ErrorCode } from '../api/errors';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handles parsing of collections and parenthesized expressions
|
|
11
|
+
*/
|
|
12
|
+
export class CollectionParser {
|
|
13
|
+
/**
|
|
14
|
+
* Parse collection literal {...}
|
|
15
|
+
*/
|
|
16
|
+
static parseCollection(
|
|
17
|
+
navigator: TokenNavigator,
|
|
18
|
+
parseExpression: () => ASTNode,
|
|
19
|
+
errorCallback: (msg: string, code?: ErrorCode) => ParseError,
|
|
20
|
+
errorRecovery: boolean,
|
|
21
|
+
createErrorNode: (token: Token, msg: string) => ASTNode
|
|
22
|
+
): ASTNode {
|
|
23
|
+
const lbrace = navigator.previous();
|
|
24
|
+
const elements: ASTNode[] = [];
|
|
25
|
+
|
|
26
|
+
// Empty collection
|
|
27
|
+
if (navigator.match(TokenType.RBRACE)) {
|
|
28
|
+
return ASTFactory.createCollection([], lbrace.position);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Parse elements
|
|
32
|
+
do {
|
|
33
|
+
// Check for trailing comma
|
|
34
|
+
if (navigator.check(TokenType.RBRACE)) {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
elements.push(parseExpression());
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error instanceof ParseError && errorRecovery) {
|
|
42
|
+
elements.push(createErrorNode(error.token, error.message));
|
|
43
|
+
// Skip to next comma or closing brace
|
|
44
|
+
navigator.skipWhile(token =>
|
|
45
|
+
token.type !== TokenType.COMMA &&
|
|
46
|
+
token.type !== TokenType.RBRACE
|
|
47
|
+
);
|
|
48
|
+
} else {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} while (navigator.match(TokenType.COMMA));
|
|
53
|
+
|
|
54
|
+
// Check for trailing comma error
|
|
55
|
+
if (elements.length > 0 && navigator.previous().type === TokenType.COMMA) {
|
|
56
|
+
throw errorCallback("Expected expression after ','", ErrorCode.EXPECTED_EXPRESSION);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
navigator.consume(TokenType.RBRACE, () =>
|
|
60
|
+
errorCallback("Expected '}' after collection elements", ErrorCode.UNCLOSED_BRACE)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return ASTFactory.createCollection(elements, lbrace.position);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parse parenthesized expression
|
|
68
|
+
*/
|
|
69
|
+
static parseParenthesized(
|
|
70
|
+
navigator: TokenNavigator,
|
|
71
|
+
parseExpression: () => ASTNode,
|
|
72
|
+
errorCallback: (msg: string, code?: ErrorCode) => ParseError
|
|
73
|
+
): ASTNode {
|
|
74
|
+
const lparen = navigator.previous();
|
|
75
|
+
|
|
76
|
+
// Handle empty parentheses ()
|
|
77
|
+
if (navigator.check(TokenType.RPAREN)) {
|
|
78
|
+
throw errorCallback("Empty parentheses are not allowed", ErrorCode.EXPECTED_EXPRESSION);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const expr = parseExpression();
|
|
82
|
+
|
|
83
|
+
navigator.consume(TokenType.RPAREN, () =>
|
|
84
|
+
errorCallback("Expected ')' after expression", ErrorCode.UNCLOSED_PARENTHESIS)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return expr;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { Token } from '../lexer/token';
|
|
2
|
+
import type { SourceMapper } from './source-mapper';
|
|
3
|
+
import type { ParseDiagnostic, TextRange, RelatedInformation } from './types';
|
|
4
|
+
import type { FunctionNode } from './ast';
|
|
5
|
+
import { ErrorCode } from '../api/errors';
|
|
6
|
+
import { DiagnosticSeverity } from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Factory for creating common FHIRPath diagnostic messages
|
|
10
|
+
*/
|
|
11
|
+
export class FHIRPathDiagnostics {
|
|
12
|
+
static unclosedParenthesis(openParen: Token, mapper: SourceMapper): ParseDiagnostic {
|
|
13
|
+
const range = mapper.tokenToRange(openParen);
|
|
14
|
+
return {
|
|
15
|
+
range,
|
|
16
|
+
severity: DiagnosticSeverity.Error,
|
|
17
|
+
code: ErrorCode.UNCLOSED_PARENTHESIS,
|
|
18
|
+
message: "Unclosed parenthesis - missing ')' to close function call",
|
|
19
|
+
source: 'fhirpath-parser'
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static unclosedBracket(openBracket: Token, mapper: SourceMapper): ParseDiagnostic {
|
|
24
|
+
const range = mapper.tokenToRange(openBracket);
|
|
25
|
+
return {
|
|
26
|
+
range,
|
|
27
|
+
severity: DiagnosticSeverity.Error,
|
|
28
|
+
code: ErrorCode.UNCLOSED_BRACKET,
|
|
29
|
+
message: "Expected ']' after index expression",
|
|
30
|
+
source: 'fhirpath-parser'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static unclosedBrace(openBrace: Token, mapper: SourceMapper): ParseDiagnostic {
|
|
35
|
+
const range = mapper.tokenToRange(openBrace);
|
|
36
|
+
return {
|
|
37
|
+
range,
|
|
38
|
+
severity: DiagnosticSeverity.Error,
|
|
39
|
+
code: ErrorCode.UNCLOSED_BRACE,
|
|
40
|
+
message: "Expected '}' to close collection literal",
|
|
41
|
+
source: 'fhirpath-parser'
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static missingFunctionArguments(
|
|
46
|
+
functionName: string,
|
|
47
|
+
position: TextRange,
|
|
48
|
+
functionDefinitionLocation?: TextRange
|
|
49
|
+
): ParseDiagnostic {
|
|
50
|
+
const diagnostic: ParseDiagnostic = {
|
|
51
|
+
range: position,
|
|
52
|
+
severity: DiagnosticSeverity.Error,
|
|
53
|
+
code: ErrorCode.MISSING_ARGUMENTS,
|
|
54
|
+
message: `Function '${functionName}' requires arguments`,
|
|
55
|
+
source: 'fhirpath-parser'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (functionDefinitionLocation) {
|
|
59
|
+
diagnostic.relatedInformation = [{
|
|
60
|
+
location: functionDefinitionLocation,
|
|
61
|
+
message: `Function '${functionName}' defined here`
|
|
62
|
+
}];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return diagnostic;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static doubleDotOperator(firstDot: Token, secondDot: Token, mapper: SourceMapper): ParseDiagnostic {
|
|
69
|
+
const startPos = mapper.tokenToRange(firstDot).start;
|
|
70
|
+
const endPos = mapper.tokenToRange(secondDot).end;
|
|
71
|
+
const range: TextRange = { start: startPos, end: endPos };
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
range,
|
|
75
|
+
severity: DiagnosticSeverity.Error,
|
|
76
|
+
code: ErrorCode.INVALID_OPERATOR,
|
|
77
|
+
message: "Invalid '..' operator - use single '.' for navigation",
|
|
78
|
+
source: 'fhirpath-parser'
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static unexpectedToken(
|
|
83
|
+
token: Token,
|
|
84
|
+
expected: string[],
|
|
85
|
+
mapper: SourceMapper
|
|
86
|
+
): ParseDiagnostic {
|
|
87
|
+
const range = mapper.tokenToRange(token);
|
|
88
|
+
const expectedText = expected.length === 1
|
|
89
|
+
? expected[0]
|
|
90
|
+
: expected.slice(0, -1).join(', ') + ' or ' + expected[expected.length - 1];
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
range,
|
|
94
|
+
severity: DiagnosticSeverity.Error,
|
|
95
|
+
code: ErrorCode.UNEXPECTED_TOKEN,
|
|
96
|
+
message: `Expected ${expectedText}, found '${token.value}'`,
|
|
97
|
+
source: 'fhirpath-parser'
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static invalidEscapeSequence(
|
|
102
|
+
escapeToken: Token,
|
|
103
|
+
escapeSequence: string,
|
|
104
|
+
mapper: SourceMapper
|
|
105
|
+
): ParseDiagnostic {
|
|
106
|
+
const range = mapper.tokenToRange(escapeToken);
|
|
107
|
+
return {
|
|
108
|
+
range,
|
|
109
|
+
severity: DiagnosticSeverity.Error,
|
|
110
|
+
code: ErrorCode.INVALID_ESCAPE,
|
|
111
|
+
message: `Invalid escape sequence '${escapeSequence}'`,
|
|
112
|
+
source: 'fhirpath-parser'
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static unterminatedString(stringStart: Token, mapper: SourceMapper): ParseDiagnostic {
|
|
117
|
+
const range = mapper.tokenToRange(stringStart);
|
|
118
|
+
return {
|
|
119
|
+
range,
|
|
120
|
+
severity: DiagnosticSeverity.Error,
|
|
121
|
+
code: ErrorCode.UNTERMINATED_STRING,
|
|
122
|
+
message: "Unterminated string literal",
|
|
123
|
+
source: 'fhirpath-parser'
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static invalidCharacter(position: TextRange, character: string): ParseDiagnostic {
|
|
128
|
+
return {
|
|
129
|
+
range: position,
|
|
130
|
+
severity: DiagnosticSeverity.Error,
|
|
131
|
+
code: ErrorCode.INVALID_CHARACTER,
|
|
132
|
+
message: `Invalid character '${character}'`,
|
|
133
|
+
source: 'fhirpath-parser'
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
static trailingComma(commaToken: Token, mapper: SourceMapper): ParseDiagnostic {
|
|
138
|
+
const range = mapper.tokenToRange(commaToken);
|
|
139
|
+
return {
|
|
140
|
+
range,
|
|
141
|
+
severity: DiagnosticSeverity.Error,
|
|
142
|
+
code: ErrorCode.EXPECTED_EXPRESSION,
|
|
143
|
+
message: "Expected expression after ','",
|
|
144
|
+
source: 'fhirpath-parser'
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static missingOperand(
|
|
149
|
+
operatorToken: Token,
|
|
150
|
+
side: 'left' | 'right',
|
|
151
|
+
mapper: SourceMapper
|
|
152
|
+
): ParseDiagnostic {
|
|
153
|
+
const range = mapper.tokenToRange(operatorToken);
|
|
154
|
+
return {
|
|
155
|
+
range,
|
|
156
|
+
severity: DiagnosticSeverity.Error,
|
|
157
|
+
code: ErrorCode.EXPECTED_EXPRESSION,
|
|
158
|
+
message: `Expected expression ${side === 'left' ? 'before' : 'after'} '${operatorToken.value}'`,
|
|
159
|
+
source: 'fhirpath-parser'
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static emptyBrackets(openBracket: Token, mapper: SourceMapper): ParseDiagnostic {
|
|
164
|
+
const range = mapper.tokenToRange(openBracket);
|
|
165
|
+
return {
|
|
166
|
+
range,
|
|
167
|
+
severity: DiagnosticSeverity.Error,
|
|
168
|
+
code: ErrorCode.EXPECTED_EXPRESSION,
|
|
169
|
+
message: "Expected expression in index",
|
|
170
|
+
source: 'fhirpath-parser'
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
static spaceInDotNavigation(dotToken: Token, mapper: SourceMapper): ParseDiagnostic {
|
|
175
|
+
const range = mapper.tokenToRange(dotToken);
|
|
176
|
+
return {
|
|
177
|
+
range,
|
|
178
|
+
severity: DiagnosticSeverity.Error,
|
|
179
|
+
code: ErrorCode.EXPECTED_IDENTIFIER,
|
|
180
|
+
message: "Expected property name immediately after '.'",
|
|
181
|
+
source: 'fhirpath-parser'
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static incompleteTypeCast(asToken: Token, mapper: SourceMapper): ParseDiagnostic {
|
|
186
|
+
const range = mapper.tokenToRange(asToken);
|
|
187
|
+
return {
|
|
188
|
+
range,
|
|
189
|
+
severity: DiagnosticSeverity.Error,
|
|
190
|
+
code: ErrorCode.EXPECTED_IDENTIFIER,
|
|
191
|
+
message: "Expected type name after 'as'",
|
|
192
|
+
source: 'fhirpath-parser'
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
static incompleteMembershipTest(isToken: Token, mapper: SourceMapper): ParseDiagnostic {
|
|
197
|
+
const range = mapper.tokenToRange(isToken);
|
|
198
|
+
return {
|
|
199
|
+
range,
|
|
200
|
+
severity: DiagnosticSeverity.Error,
|
|
201
|
+
code: ErrorCode.EXPECTED_IDENTIFIER,
|
|
202
|
+
message: "Expected type name after 'is'",
|
|
203
|
+
source: 'fhirpath-parser'
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
static multipleErrors(errorCount: number, position: TextRange): ParseDiagnostic {
|
|
208
|
+
return {
|
|
209
|
+
range: position,
|
|
210
|
+
severity: DiagnosticSeverity.Error,
|
|
211
|
+
code: ErrorCode.MULTIPLE_ERRORS,
|
|
212
|
+
message: `Expression contains ${errorCount} errors`,
|
|
213
|
+
source: 'fhirpath-parser'
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|