@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.
Files changed (85) hide show
  1. package/README.md +473 -0
  2. package/dist/index.d.ts +462 -0
  3. package/dist/index.js +10307 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +58 -0
  6. package/src/analyzer/analyzer.ts +499 -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 +157 -0
  12. package/src/api/errors.ts +145 -0
  13. package/src/api/expression.ts +156 -0
  14. package/src/api/index.ts +122 -0
  15. package/src/api/inspect.ts +99 -0
  16. package/src/api/registry.ts +128 -0
  17. package/src/api/types.ts +210 -0
  18. package/src/compiler/compiler.ts +546 -0
  19. package/src/compiler/index.ts +2 -0
  20. package/src/compiler/prototype-context-adapter.ts +99 -0
  21. package/src/compiler/types.ts +24 -0
  22. package/src/index.ts +107 -0
  23. package/src/interpreter/README.md +78 -0
  24. package/src/interpreter/interpreter.ts +475 -0
  25. package/src/interpreter/types.ts +108 -0
  26. package/src/lexer/char-tables.ts +37 -0
  27. package/src/lexer/errors.ts +31 -0
  28. package/src/lexer/index.ts +5 -0
  29. package/src/lexer/lexer.ts +745 -0
  30. package/src/lexer/token.ts +104 -0
  31. package/src/lexer2/index.md +232 -0
  32. package/src/lexer2/index.perf.test.ts +68 -0
  33. package/src/lexer2/index.test.ts +549 -0
  34. package/src/lexer2/index.ts +1251 -0
  35. package/src/lexer2/notes.md +173 -0
  36. package/src/lexer2/optimization-summary.md +718 -0
  37. package/src/parser/ast-factory.ts +220 -0
  38. package/src/parser/ast.ts +144 -0
  39. package/src/parser/collection-parser.ts +89 -0
  40. package/src/parser/diagnostic-messages.ts +216 -0
  41. package/src/parser/diagnostics.ts +85 -0
  42. package/src/parser/error-reporter.ts +230 -0
  43. package/src/parser/index.ts +3 -0
  44. package/src/parser/literal-parser.ts +103 -0
  45. package/src/parser/parse-error.ts +16 -0
  46. package/src/parser/parser-error-factory.ts +141 -0
  47. package/src/parser/parser-state.ts +134 -0
  48. package/src/parser/parser.ts +1272 -0
  49. package/src/parser/pprint.ts +169 -0
  50. package/src/parser/precedence-manager.ts +64 -0
  51. package/src/parser/source-mapper.ts +248 -0
  52. package/src/parser/special-constructs.ts +142 -0
  53. package/src/parser/token-navigator.ts +110 -0
  54. package/src/parser/types.ts +60 -0
  55. package/src/parser2/index.md +177 -0
  56. package/src/parser2/index.perf.test.ts +184 -0
  57. package/src/parser2/index.test.ts +305 -0
  58. package/src/parser2/index.ts +578 -0
  59. package/src/parser2/optimization-summary.md +176 -0
  60. package/src/registry/default-analyzers.ts +257 -0
  61. package/src/registry/default-compilers.ts +31 -0
  62. package/src/registry/index.ts +96 -0
  63. package/src/registry/operations/arithmetic.ts +506 -0
  64. package/src/registry/operations/collection.ts +425 -0
  65. package/src/registry/operations/comparison.ts +432 -0
  66. package/src/registry/operations/existence.ts +703 -0
  67. package/src/registry/operations/filtering.ts +358 -0
  68. package/src/registry/operations/literals.ts +341 -0
  69. package/src/registry/operations/logical.ts +439 -0
  70. package/src/registry/operations/math.ts +128 -0
  71. package/src/registry/operations/membership.ts +132 -0
  72. package/src/registry/operations/navigation.ts +52 -0
  73. package/src/registry/operations/string.ts +507 -0
  74. package/src/registry/operations/subsetting.ts +174 -0
  75. package/src/registry/operations/type-checking.ts +162 -0
  76. package/src/registry/operations/type-conversion.ts +404 -0
  77. package/src/registry/operations/type-operators.ts +308 -0
  78. package/src/registry/operations/utility.ts +644 -0
  79. package/src/registry/registry.ts +146 -0
  80. package/src/registry/types.ts +161 -0
  81. package/src/registry/utils/evaluation-helpers.ts +93 -0
  82. package/src/registry/utils/index.ts +3 -0
  83. package/src/registry/utils/type-system.ts +173 -0
  84. package/src/runtime/context.ts +158 -0
  85. 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
+ }