@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,85 @@
1
+ import type { ParseDiagnostic, DiagnosticSeverity, TextRange } from './types';
2
+ import type { ErrorCode } from '../api/errors';
3
+
4
+ export class DiagnosticCollector {
5
+ private diagnostics: ParseDiagnostic[] = [];
6
+ private errorCount = 0;
7
+ private warningCount = 0;
8
+ private maxErrors: number;
9
+
10
+ constructor(maxErrors: number = Number.MAX_SAFE_INTEGER) {
11
+ this.maxErrors = maxErrors;
12
+ }
13
+
14
+ addError(range: TextRange, message: string, code: ErrorCode): void {
15
+ if (this.errorCount >= this.maxErrors) {
16
+ return;
17
+ }
18
+
19
+ this.diagnostics.push({
20
+ range,
21
+ severity: 1 as DiagnosticSeverity.Error,
22
+ code,
23
+ message,
24
+ source: 'fhirpath-parser'
25
+ });
26
+ this.errorCount++;
27
+ }
28
+
29
+ addWarning(range: TextRange, message: string, code: ErrorCode): void {
30
+ this.diagnostics.push({
31
+ range,
32
+ severity: 2 as DiagnosticSeverity.Warning,
33
+ code,
34
+ message,
35
+ source: 'fhirpath-parser'
36
+ });
37
+ this.warningCount++;
38
+ }
39
+
40
+ addInfo(range: TextRange, message: string, code: ErrorCode): void {
41
+ this.diagnostics.push({
42
+ range,
43
+ severity: 3 as DiagnosticSeverity.Information,
44
+ code,
45
+ message,
46
+ source: 'fhirpath-parser'
47
+ });
48
+ }
49
+
50
+ addHint(range: TextRange, message: string, code: ErrorCode): void {
51
+ this.diagnostics.push({
52
+ range,
53
+ severity: 4 as DiagnosticSeverity.Hint,
54
+ code,
55
+ message,
56
+ source: 'fhirpath-parser'
57
+ });
58
+ }
59
+
60
+ getDiagnostics(): ParseDiagnostic[] {
61
+ return [...this.diagnostics];
62
+ }
63
+
64
+ hasErrors(): boolean {
65
+ return this.errorCount > 0;
66
+ }
67
+
68
+ hasWarnings(): boolean {
69
+ return this.warningCount > 0;
70
+ }
71
+
72
+ getErrorCount(): number {
73
+ return this.errorCount;
74
+ }
75
+
76
+ getWarningCount(): number {
77
+ return this.warningCount;
78
+ }
79
+
80
+ clear(): void {
81
+ this.diagnostics = [];
82
+ this.errorCount = 0;
83
+ this.warningCount = 0;
84
+ }
85
+ }
@@ -0,0 +1,230 @@
1
+ import { TokenType, type Token } from '../lexer/token';
2
+ import type { SourceMapper } from './source-mapper';
3
+ import type { DiagnosticCollector } from './diagnostics';
4
+ import { ErrorCode } from '../api/errors';
5
+
6
+ export enum ParseContext {
7
+ Expression = 'Expression',
8
+ FunctionCall = 'FunctionCall',
9
+ IndexExpression = 'IndexExpression',
10
+ BinaryExpression = 'BinaryExpression',
11
+ UnaryExpression = 'UnaryExpression',
12
+ CollectionLiteral = 'CollectionLiteral',
13
+ TypeCast = 'TypeCast',
14
+ MembershipTest = 'MembershipTest'
15
+ }
16
+
17
+ export class ContextualErrorReporter {
18
+ constructor(
19
+ private sourceMapper: SourceMapper,
20
+ private collector: DiagnosticCollector
21
+ ) {}
22
+
23
+ reportExpectedToken(
24
+ expected: TokenType[],
25
+ actual: Token,
26
+ context: ParseContext
27
+ ): void {
28
+ const message = this.buildContextualMessage(expected, actual, context);
29
+ const range = this.sourceMapper.tokenToRange(actual);
30
+
31
+ this.collector.addError(range, message, ErrorCode.UNEXPECTED_TOKEN);
32
+ }
33
+
34
+ reportUnclosedDelimiter(openToken: Token, expectedClose: string): void {
35
+ const range = this.sourceMapper.tokenToRange(openToken);
36
+ let message: string;
37
+ let code: ErrorCode;
38
+
39
+ switch (expectedClose) {
40
+ case ')':
41
+ code = ErrorCode.UNCLOSED_PARENTHESIS;
42
+ message = "Unclosed parenthesis - missing ')' to close function call";
43
+ break;
44
+ case ']':
45
+ code = ErrorCode.UNCLOSED_BRACKET;
46
+ message = "Expected ']' after index expression";
47
+ break;
48
+ case '}':
49
+ code = ErrorCode.UNCLOSED_BRACE;
50
+ message = "Expected '}' to close collection literal";
51
+ break;
52
+ default:
53
+ code = ErrorCode.SYNTAX_ERROR;
54
+ message = `Unclosed delimiter - missing '${expectedClose}' to close '${openToken.value}'`;
55
+ }
56
+
57
+ this.collector.addError(range, message, code);
58
+ }
59
+
60
+ reportInvalidOperator(operator: Token, message: string): void {
61
+ const range = this.sourceMapper.tokenToRange(operator);
62
+ this.collector.addError(range, message, ErrorCode.INVALID_OPERATOR);
63
+ }
64
+
65
+ reportMissingExpression(token: Token, context: ParseContext): void {
66
+ const range = this.sourceMapper.tokenToRange(token);
67
+ const contextDesc = this.getContextDescription(context);
68
+ const message = `Expected expression ${contextDesc}`;
69
+
70
+ this.collector.addError(range, message, ErrorCode.EXPECTED_EXPRESSION);
71
+ }
72
+
73
+ reportMissingIdentifier(token: Token, context: string): void {
74
+ const range = this.sourceMapper.tokenToRange(token);
75
+ const message = `Expected identifier ${context}`;
76
+
77
+ this.collector.addError(range, message, ErrorCode.EXPECTED_IDENTIFIER);
78
+ }
79
+
80
+ reportMissingArguments(functionName: string, token: Token): void {
81
+ const range = this.sourceMapper.tokenToRange(token);
82
+ const message = `Function '${functionName}' requires arguments`;
83
+
84
+ this.collector.addError(range, message, ErrorCode.MISSING_ARGUMENTS);
85
+ }
86
+
87
+ private buildContextualMessage(
88
+ expected: TokenType[],
89
+ actual: Token,
90
+ context: ParseContext
91
+ ): string {
92
+ const expectedDesc = this.describeTokens(expected);
93
+ const actualDesc = this.describeToken(actual);
94
+
95
+ switch (context) {
96
+ case ParseContext.FunctionCall:
97
+ return `Expected ${expectedDesc} in function call, found ${actualDesc}`;
98
+ case ParseContext.IndexExpression:
99
+ return `Expected ${expectedDesc} in index expression, found ${actualDesc}`;
100
+ case ParseContext.BinaryExpression:
101
+ return `Expected ${expectedDesc} after operator, found ${actualDesc}`;
102
+ case ParseContext.CollectionLiteral:
103
+ return `Expected ${expectedDesc} in collection literal, found ${actualDesc}`;
104
+ case ParseContext.TypeCast:
105
+ return `Expected ${expectedDesc} after 'as', found ${actualDesc}`;
106
+ case ParseContext.MembershipTest:
107
+ return `Expected ${expectedDesc} after 'is', found ${actualDesc}`;
108
+ default:
109
+ return `Expected ${expectedDesc}, found ${actualDesc}`;
110
+ }
111
+ }
112
+
113
+ private getContextDescription(context: ParseContext): string {
114
+ switch (context) {
115
+ case ParseContext.FunctionCall:
116
+ return "in function call";
117
+ case ParseContext.IndexExpression:
118
+ return "in index expression";
119
+ case ParseContext.BinaryExpression:
120
+ return "after operator";
121
+ case ParseContext.CollectionLiteral:
122
+ return "in collection literal";
123
+ case ParseContext.TypeCast:
124
+ return "after 'as'";
125
+ case ParseContext.MembershipTest:
126
+ return "after 'is'";
127
+ default:
128
+ return "";
129
+ }
130
+ }
131
+
132
+ private describeTokens(tokens: TokenType[]): string {
133
+ if (tokens.length === 0) return "token";
134
+ if (tokens.length === 1) return this.getTokenDescription(tokens[0]!);
135
+
136
+ const descriptions = tokens.map(t => this.getTokenDescription(t));
137
+ const last = descriptions.pop();
138
+ return descriptions.join(', ') + ' or ' + last;
139
+ }
140
+
141
+ private describeToken(token: Token): string {
142
+ if (token.type === TokenType.IDENTIFIER) {
143
+ return `identifier '${token.value}'`;
144
+ }
145
+ if (token.type === TokenType.NUMBER || token.type === TokenType.STRING) {
146
+ return `${this.getTokenDescription(token.type)} '${token.value}'`;
147
+ }
148
+ if (token.type === TokenType.EOF) {
149
+ return "end of expression";
150
+ }
151
+ return `'${token.value}'`;
152
+ }
153
+
154
+ private getTokenDescription(tokenType: TokenType): string {
155
+ switch (tokenType) {
156
+ case TokenType.IDENTIFIER:
157
+ return "identifier";
158
+ case TokenType.NUMBER:
159
+ return "number";
160
+ case TokenType.STRING:
161
+ return "string";
162
+ case TokenType.LPAREN:
163
+ return "'('";
164
+ case TokenType.RPAREN:
165
+ return "')'";
166
+ case TokenType.LBRACKET:
167
+ return "'['";
168
+ case TokenType.RBRACKET:
169
+ return "']'";
170
+ case TokenType.LBRACE:
171
+ return "'{'";
172
+ case TokenType.RBRACE:
173
+ return "'}'";
174
+ case TokenType.COMMA:
175
+ return "','";
176
+ case TokenType.DOT:
177
+ return "'.'";
178
+ case TokenType.PLUS:
179
+ return "'+'";
180
+ case TokenType.MINUS:
181
+ return "'-'";
182
+ case TokenType.STAR:
183
+ return "'*'";
184
+ case TokenType.SLASH:
185
+ return "'/'";
186
+ case TokenType.MOD:
187
+ return "'mod'";
188
+ case TokenType.EQ:
189
+ return "'='";
190
+ case TokenType.NEQ:
191
+ return "'!='";
192
+ case TokenType.LT:
193
+ return "'<'";
194
+ case TokenType.GT:
195
+ return "'>'";
196
+ case TokenType.LTE:
197
+ return "'<='";
198
+ case TokenType.GTE:
199
+ return "'>='";
200
+ case TokenType.AND:
201
+ return "'and'";
202
+ case TokenType.OR:
203
+ return "'or'";
204
+ case TokenType.XOR:
205
+ return "'xor'";
206
+ case TokenType.IMPLIES:
207
+ return "'implies'";
208
+ case TokenType.NOT:
209
+ return "'not'";
210
+ case TokenType.IS:
211
+ return "'is'";
212
+ case TokenType.AS:
213
+ return "'as'";
214
+ case TokenType.IN:
215
+ return "'in'";
216
+ case TokenType.CONTAINS:
217
+ return "'contains'";
218
+ case TokenType.TRUE:
219
+ return "'true'";
220
+ case TokenType.FALSE:
221
+ return "'false'";
222
+ case TokenType.NULL:
223
+ return "'null'";
224
+ case TokenType.EOF:
225
+ return "end of expression";
226
+ default:
227
+ return "token";
228
+ }
229
+ }
230
+ }
@@ -0,0 +1,3 @@
1
+ export { FHIRPathParser, ParseError } from './parser';
2
+ export * from './ast';
3
+ export { pprint } from './pprint';
@@ -0,0 +1,103 @@
1
+ import type { Token } from '../lexer/token';
2
+ import { TokenType } from '../lexer/token';
3
+ import type { ASTNode, LiteralNode, VariableNode } from './ast';
4
+ import { ASTFactory } from './ast-factory';
5
+ import type { TokenNavigator } from './token-navigator';
6
+
7
+ /**
8
+ * Handles parsing of literals and variables
9
+ */
10
+ export class LiteralParser {
11
+ /**
12
+ * Parse all types of literals
13
+ */
14
+ static parseLiteral(navigator: TokenNavigator): ASTNode | null {
15
+ // Handle registry-based literals
16
+ if (navigator.match(TokenType.LITERAL)) {
17
+ const token = navigator.previous();
18
+ return ASTFactory.createLiteral(
19
+ token.literalValue ?? token.value,
20
+ ASTFactory.inferLiteralType(token.literalValue ?? token.value),
21
+ token.position,
22
+ token.value,
23
+ token.operation
24
+ );
25
+ }
26
+
27
+ // Handle legacy literals
28
+ if (navigator.match(TokenType.NUMBER)) {
29
+ const token = navigator.previous();
30
+ return ASTFactory.createLiteral(
31
+ parseFloat(token.value),
32
+ 'number',
33
+ token.position
34
+ );
35
+ }
36
+
37
+ if (navigator.match(TokenType.STRING)) {
38
+ const token = navigator.previous();
39
+ return ASTFactory.createLiteral(
40
+ token.value,
41
+ 'string',
42
+ token.position
43
+ );
44
+ }
45
+
46
+ if (navigator.match(TokenType.TRUE, TokenType.FALSE)) {
47
+ const token = navigator.previous();
48
+ return ASTFactory.createLiteral(
49
+ token.type === TokenType.TRUE,
50
+ 'boolean',
51
+ token.position
52
+ );
53
+ }
54
+
55
+ if (navigator.match(TokenType.NULL)) {
56
+ const token = navigator.previous();
57
+ return ASTFactory.createLiteral(
58
+ null,
59
+ 'null',
60
+ token.position
61
+ );
62
+ }
63
+
64
+ // Date/time literals
65
+ if (navigator.match(TokenType.DATE, TokenType.TIME, TokenType.DATETIME)) {
66
+ const token = navigator.previous();
67
+ return ASTFactory.createLiteral(
68
+ token.value,
69
+ token.type === TokenType.DATE ? 'date' :
70
+ token.type === TokenType.TIME ? 'time' : 'datetime',
71
+ token.position
72
+ );
73
+ }
74
+
75
+ return null;
76
+ }
77
+
78
+ /**
79
+ * Parse special variables
80
+ */
81
+ static parseVariable(navigator: TokenNavigator): ASTNode | null {
82
+ if (navigator.match(TokenType.THIS)) {
83
+ return ASTFactory.createVariable('$this', true, navigator.previous().position);
84
+ }
85
+
86
+ if (navigator.match(TokenType.INDEX)) {
87
+ return ASTFactory.createVariable('$index', true, navigator.previous().position);
88
+ }
89
+
90
+ if (navigator.match(TokenType.TOTAL)) {
91
+ return ASTFactory.createVariable('$total', true, navigator.previous().position);
92
+ }
93
+
94
+ if (navigator.match(TokenType.ENV_VAR)) {
95
+ const token = navigator.previous();
96
+ return ASTFactory.createVariable(token.value, false, token.position);
97
+ }
98
+
99
+ // Variables starting with $ or % are handled by ENV_VAR token type
100
+
101
+ return null;
102
+ }
103
+ }
@@ -0,0 +1,16 @@
1
+ import type { Token } from '../lexer/token';
2
+ import type { Position } from './ast';
3
+
4
+ /**
5
+ * Error thrown during parsing
6
+ */
7
+ export class ParseError extends Error {
8
+ constructor(
9
+ message: string,
10
+ public position: Position,
11
+ public token: Token
12
+ ) {
13
+ super(message);
14
+ this.name = 'ParseError';
15
+ }
16
+ }
@@ -0,0 +1,141 @@
1
+ import type { Token } from '../lexer/token';
2
+ import type { Position } from './ast';
3
+ import { ParseError } from './parser';
4
+ import { ErrorCode } from '../api/errors';
5
+ import type { DiagnosticCollector } from './diagnostics';
6
+ import type { SourceMapper } from './source-mapper';
7
+
8
+ /**
9
+ * Factory for creating parser errors with consistent formatting
10
+ */
11
+ export class ParserErrorFactory {
12
+ constructor(
13
+ private throwOnError: boolean,
14
+ private diagnostics?: DiagnosticCollector,
15
+ private sourceMapper?: SourceMapper
16
+ ) {}
17
+
18
+ /**
19
+ * Create a parse error with proper formatting
20
+ */
21
+ createError(message: string, token: Token, code: ErrorCode = ErrorCode.PARSE_ERROR): ParseError {
22
+ if (this.diagnostics && this.sourceMapper && !this.throwOnError) {
23
+ // In diagnostic mode, add to diagnostics and return error
24
+ const range = this.sourceMapper.tokenToRange(token);
25
+ this.diagnostics.addError(range, message, code);
26
+ }
27
+
28
+ const pos = token.position;
29
+ const fullMessage = `${message} at line ${pos.line}, column ${pos.column}`;
30
+ return new ParseError(fullMessage, pos, token);
31
+ }
32
+
33
+ /**
34
+ * Create error for unexpected token
35
+ */
36
+ unexpectedToken(token: Token, expected?: string): ParseError {
37
+ const message = expected
38
+ ? `Expected ${expected}, found '${token.value}'`
39
+ : `Unexpected token '${token.value}'`;
40
+ return this.createError(message, token, ErrorCode.UNEXPECTED_TOKEN);
41
+ }
42
+
43
+ /**
44
+ * Create error for missing token
45
+ */
46
+ missingToken(expected: string, token: Token, code: ErrorCode = ErrorCode.UNEXPECTED_TOKEN): ParseError {
47
+ return this.createError(`Expected ${expected}`, token, code);
48
+ }
49
+
50
+ /**
51
+ * Create error for unclosed delimiter
52
+ */
53
+ unclosedDelimiter(openToken: Token, expected: string): ParseError {
54
+ const code = expected === ')' ? ErrorCode.UNCLOSED_PARENTHESIS :
55
+ expected === ']' ? ErrorCode.UNCLOSED_BRACKET :
56
+ expected === '}' ? ErrorCode.UNCLOSED_BRACE :
57
+ ErrorCode.SYNTAX_ERROR;
58
+
59
+ return this.createError(
60
+ `Unclosed ${this.getDelimiterName(expected)} - missing '${expected}'`,
61
+ openToken,
62
+ code
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Create error for invalid operator
68
+ */
69
+ invalidOperator(token: Token): ParseError {
70
+ return this.createError(`Unknown operator: ${token.value}`, token, ErrorCode.INVALID_OPERATOR);
71
+ }
72
+
73
+ /**
74
+ * Create error for missing arguments
75
+ */
76
+ missingArguments(functionName: string, token: Token): ParseError {
77
+ return this.createError(
78
+ `Function '${functionName}' requires arguments`,
79
+ token,
80
+ ErrorCode.MISSING_ARGUMENTS
81
+ );
82
+ }
83
+
84
+ /**
85
+ * Create error for expected identifier
86
+ */
87
+ expectedIdentifier(token: Token, context?: string): ParseError {
88
+ const message = context
89
+ ? `Expected identifier ${context}`
90
+ : `Expected identifier`;
91
+ return this.createError(message, token, ErrorCode.EXPECTED_IDENTIFIER);
92
+ }
93
+
94
+ /**
95
+ * Create error for expected expression
96
+ */
97
+ expectedExpression(token: Token, context?: string): ParseError {
98
+ const message = context
99
+ ? `Expected expression ${context}`
100
+ : `Expected expression`;
101
+ return this.createError(message, token, ErrorCode.EXPECTED_EXPRESSION);
102
+ }
103
+
104
+ /**
105
+ * Create error for double dot operator
106
+ */
107
+ doubleDotOperator(firstDot: Token, secondDot: Token): ParseError {
108
+ const message = `Invalid '..' operator - use a single '.' for navigation`;
109
+ if (this.diagnostics && this.sourceMapper) {
110
+ const range = this.sourceMapper.mergeRanges(
111
+ this.sourceMapper.tokenToRange(firstDot),
112
+ this.sourceMapper.tokenToRange(secondDot)
113
+ );
114
+ this.diagnostics.addError(range, message, ErrorCode.INVALID_OPERATOR);
115
+ }
116
+ return this.createError(message, firstDot, ErrorCode.INVALID_OPERATOR);
117
+ }
118
+
119
+ /**
120
+ * Create error for invalid escape sequence
121
+ */
122
+ invalidEscape(token: Token, sequence: string): ParseError {
123
+ return this.createError(
124
+ `Invalid escape sequence: ${sequence}`,
125
+ token,
126
+ ErrorCode.INVALID_ESCAPE
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Get human-readable name for delimiter
132
+ */
133
+ private getDelimiterName(delimiter: string): string {
134
+ switch (delimiter) {
135
+ case ')': return 'parenthesis';
136
+ case ']': return 'bracket';
137
+ case '}': return 'brace';
138
+ default: return 'delimiter';
139
+ }
140
+ }
141
+ }