@atomic-ehr/fhirpath 0.0.2 → 0.0.3
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 +716 -238
- package/dist/index.d.ts +225 -119
- package/dist/index.js +10911 -5600
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
- package/src/analyzer/augmentor.ts +242 -0
- package/src/analyzer/cursor-services.ts +75 -0
- package/src/analyzer/scope-manager.ts +57 -0
- package/src/analyzer/trivia-indexer.ts +58 -0
- package/src/analyzer/type-compat.ts +157 -0
- package/src/analyzer/utils.ts +132 -0
- package/src/analyzer.ts +921 -1208
- package/src/completion-provider.ts +209 -191
- package/src/{quantity-value.ts → complex-types/quantity-value.ts} +112 -22
- package/src/complex-types/temporal.ts +1737 -0
- package/src/errors.ts +25 -3
- package/src/index.ts +17 -104
- package/src/inspect.ts +4 -4
- package/src/{boxing.ts → interpreter/boxing.ts} +1 -1
- package/src/interpreter/navigator.ts +94 -0
- package/src/interpreter/runtime-context.ts +273 -0
- package/src/interpreter.ts +435 -469
- package/src/lexer.ts +188 -210
- package/src/model-provider.ts +71 -43
- package/src/operations/abs-function.ts +1 -1
- package/src/operations/aggregate-function.ts +84 -5
- package/src/operations/all-function.ts +4 -3
- package/src/operations/allFalse-function.ts +2 -1
- package/src/operations/allTrue-function.ts +2 -1
- package/src/operations/and-operator.ts +2 -1
- package/src/operations/anyFalse-function.ts +2 -1
- package/src/operations/anyTrue-function.ts +2 -1
- package/src/operations/as-function.ts +58 -0
- package/src/operations/as-operator.ts +57 -19
- package/src/operations/ceiling-function.ts +1 -1
- package/src/operations/children-function.ts +14 -5
- package/src/operations/combine-function.ts +6 -3
- package/src/operations/combine-operator.ts +6 -7
- package/src/operations/comparison.ts +692 -0
- package/src/operations/contains-function.ts +1 -1
- package/src/operations/contains-operator.ts +2 -1
- package/src/operations/convertsToBoolean-function.ts +78 -0
- package/src/operations/convertsToDecimal-function.ts +82 -0
- package/src/operations/convertsToInteger-function.ts +71 -0
- package/src/operations/convertsToLong-function.ts +89 -0
- package/src/operations/convertsToQuantity-function.ts +116 -0
- package/src/operations/convertsToString-function.ts +88 -0
- package/src/operations/count-function.ts +2 -1
- package/src/operations/dateOf-function.ts +69 -0
- package/src/operations/dayOf-function.ts +66 -0
- package/src/operations/decimal-boundaries.ts +133 -0
- package/src/operations/defineVariable-function.ts +130 -17
- package/src/operations/distinct-function.ts +1 -1
- package/src/operations/div-operator.ts +1 -1
- package/src/operations/divide-operator.ts +12 -7
- package/src/operations/dot-operator.ts +1 -1
- package/src/operations/empty-function.ts +30 -21
- package/src/operations/endsWith-function.ts +6 -1
- package/src/operations/equal-operator.ts +23 -32
- package/src/operations/equivalent-operator.ts +13 -53
- package/src/operations/exclude-function.ts +2 -1
- package/src/operations/exists-function.ts +4 -3
- package/src/operations/first-function.ts +1 -1
- package/src/operations/floor-function.ts +1 -1
- package/src/operations/greater-operator.ts +20 -3
- package/src/operations/greater-or-equal-operator.ts +20 -3
- package/src/operations/highBoundary-function.ts +120 -0
- package/src/operations/hourOf-function.ts +66 -0
- package/src/operations/iif-function.ts +186 -7
- package/src/operations/implies-operator.ts +1 -1
- package/src/operations/in-operator.ts +2 -1
- package/src/operations/index.ts +41 -0
- package/src/operations/indexOf-function.ts +1 -1
- package/src/operations/intersect-function.ts +1 -1
- package/src/operations/is-function.ts +59 -0
- package/src/operations/is-operator.ts +20 -9
- package/src/operations/isDistinct-function.ts +2 -1
- package/src/operations/join-function.ts +1 -1
- package/src/operations/last-function.ts +1 -1
- package/src/operations/lastIndexOf-function.ts +85 -0
- package/src/operations/length-function.ts +1 -1
- package/src/operations/less-operator.ts +20 -3
- package/src/operations/less-or-equal-operator.ts +20 -3
- package/src/operations/less-than.ts +2 -2
- package/src/operations/lowBoundary-function.ts +120 -0
- package/src/operations/lower-function.ts +1 -1
- package/src/operations/matches-function.ts +86 -0
- package/src/operations/matchesFull-function.ts +96 -0
- package/src/operations/millisecondOf-function.ts +66 -0
- package/src/operations/minus-operator.ts +69 -4
- package/src/operations/minuteOf-function.ts +66 -0
- package/src/operations/mod-operator.ts +1 -1
- package/src/operations/monthOf-function.ts +66 -0
- package/src/operations/multiply-operator.ts +27 -3
- package/src/operations/not-equal-operator.ts +24 -30
- package/src/operations/not-equivalent-operator.ts +13 -53
- package/src/operations/not-function.ts +1 -1
- package/src/operations/ofType-function.ts +8 -12
- package/src/operations/or-operator.ts +2 -1
- package/src/operations/plus-operator.ts +71 -7
- package/src/operations/power-function.ts +35 -10
- package/src/operations/repeat-function.ts +169 -0
- package/src/operations/replace-function.ts +1 -1
- package/src/operations/replaceMatches-function.ts +120 -0
- package/src/operations/round-function.ts +1 -1
- package/src/operations/secondOf-function.ts +66 -0
- package/src/operations/select-function.ts +66 -5
- package/src/operations/single-function.ts +1 -1
- package/src/operations/skip-function.ts +1 -1
- package/src/operations/split-function.ts +1 -1
- package/src/operations/sqrt-function.ts +15 -8
- package/src/operations/startsWith-function.ts +1 -1
- package/src/operations/subsetOf-function.ts +6 -2
- package/src/operations/substring-function.ts +1 -1
- package/src/operations/supersetOf-function.ts +6 -2
- package/src/operations/tail-function.ts +1 -1
- package/src/operations/take-function.ts +1 -1
- package/src/operations/temporal-functions.ts +555 -0
- package/src/operations/timeOf-function.ts +67 -0
- package/src/operations/timezoneOffsetOf-function.ts +69 -0
- package/src/operations/toBoolean-function.ts +27 -8
- package/src/operations/toChars-function.ts +56 -0
- package/src/operations/toDecimal-function.ts +27 -8
- package/src/operations/toInteger-function.ts +15 -3
- package/src/operations/toLong-function.ts +98 -0
- package/src/operations/toQuantity-function.ts +181 -0
- package/src/operations/toString-function.ts +45 -3
- package/src/operations/trace-function.ts +1 -1
- package/src/operations/trim-function.ts +1 -1
- package/src/operations/truncate-function.ts +1 -1
- package/src/operations/unary-minus-operator.ts +2 -2
- package/src/operations/unary-plus-operator.ts +1 -1
- package/src/operations/union-function.ts +1 -1
- package/src/operations/union-operator.ts +16 -26
- package/src/operations/upper-function.ts +1 -1
- package/src/operations/where-function.ts +3 -3
- package/src/operations/xor-operator.ts +1 -1
- package/src/operations/yearOf-function.ts +66 -0
- package/src/{cursor-nodes.ts → parser/cursor-nodes.ts} +10 -7
- package/src/parser.ts +248 -501
- package/src/registry.ts +53 -42
- package/src/types.ts +128 -16
- package/src/utils/pprint.ts +151 -0
package/src/parser.ts
CHANGED
|
@@ -2,23 +2,21 @@ import { Lexer, TokenType, Channel } from './lexer';
|
|
|
2
2
|
import type { Token, LexerOptions } from './lexer';
|
|
3
3
|
import { registry } from './registry';
|
|
4
4
|
import { NodeType } from './types';
|
|
5
|
-
import type { AnyCursorNode } from './cursor-nodes';
|
|
6
5
|
import {
|
|
7
|
-
CursorContext,
|
|
8
6
|
createCursorOperatorNode,
|
|
9
7
|
createCursorIdentifierNode,
|
|
10
8
|
createCursorArgumentNode,
|
|
11
9
|
createCursorIndexNode,
|
|
12
10
|
createCursorTypeNode,
|
|
13
|
-
} from './cursor-nodes';
|
|
11
|
+
} from './parser/cursor-nodes';
|
|
14
12
|
import type {
|
|
15
13
|
Position,
|
|
16
14
|
Range,
|
|
17
15
|
BaseASTNode,
|
|
18
16
|
ASTNode,
|
|
19
17
|
IdentifierNode,
|
|
20
|
-
TypeOrIdentifierNode,
|
|
21
18
|
LiteralNode,
|
|
19
|
+
TemporalLiteralNode,
|
|
22
20
|
BinaryNode,
|
|
23
21
|
UnaryNode,
|
|
24
22
|
FunctionNode,
|
|
@@ -34,7 +32,11 @@ import type {
|
|
|
34
32
|
ParseResult,
|
|
35
33
|
ParseError
|
|
36
34
|
} from './types';
|
|
37
|
-
import { Errors
|
|
35
|
+
import { Errors } from './errors';
|
|
36
|
+
import { parseTemporalLiteral } from './complex-types/temporal';
|
|
37
|
+
import { augment } from './analyzer/augmentor';
|
|
38
|
+
import { findNodeAtPosition, getCompletions as lspGetCompletions, getExpectedTokens as lspGetExpectedTokens } from './analyzer/cursor-services';
|
|
39
|
+
import { computeTriviaSpans } from './analyzer/trivia-indexer';
|
|
38
40
|
|
|
39
41
|
// Re-export types for backward compatibility
|
|
40
42
|
export {
|
|
@@ -44,8 +46,8 @@ export {
|
|
|
44
46
|
type Range,
|
|
45
47
|
type ASTNode,
|
|
46
48
|
type IdentifierNode,
|
|
47
|
-
type TypeOrIdentifierNode,
|
|
48
49
|
type LiteralNode,
|
|
50
|
+
type TemporalLiteralNode,
|
|
49
51
|
type BinaryNode,
|
|
50
52
|
type UnaryNode,
|
|
51
53
|
type FunctionNode,
|
|
@@ -61,6 +63,7 @@ export {
|
|
|
61
63
|
type ParseResult,
|
|
62
64
|
type ParseError
|
|
63
65
|
} from './types';
|
|
66
|
+
export { pprint } from './utils/pprint';
|
|
64
67
|
|
|
65
68
|
// Parser options
|
|
66
69
|
export interface ParserOptions {
|
|
@@ -80,13 +83,14 @@ export class Parser {
|
|
|
80
83
|
protected current = 0;
|
|
81
84
|
private mode: 'simple' | 'lsp';
|
|
82
85
|
private options: ParserOptions;
|
|
86
|
+
private preserveTriviaEffective = false;
|
|
83
87
|
private errors?: ParseError[];
|
|
84
|
-
private nodeIdCounter?: number;
|
|
85
|
-
private nodeIndex?: Map<string, ASTNode>;
|
|
86
|
-
private nodesByType?: Map<NodeType | 'Error', ASTNode[]>;
|
|
87
|
-
private identifierIndex?: Map<string, ASTNode[]>;
|
|
88
|
-
private currentParent?: ASTNode | null;
|
|
89
88
|
private input: string;
|
|
89
|
+
// Trivia and token indexes for LSP mode with trivia preservation
|
|
90
|
+
private leadingTriviaByTokenStart?: Map<number, TriviaInfo[]>;
|
|
91
|
+
private trailingTriviaByTokenEnd?: Map<number, TriviaInfo[]>;
|
|
92
|
+
private tokenByStart?: Map<number, Token>;
|
|
93
|
+
private tokenByEnd?: Map<number, Token>;
|
|
90
94
|
|
|
91
95
|
// Synchronization tokens for error recovery
|
|
92
96
|
private readonly synchronizationTokens = new Set([
|
|
@@ -103,73 +107,75 @@ export class Parser {
|
|
|
103
107
|
trackPosition: true,
|
|
104
108
|
preserveTrivia: mode === 'lsp' ? true : (options.preserveTrivia ?? false)
|
|
105
109
|
};
|
|
110
|
+
this.preserveTriviaEffective = !!lexerOptions.preserveTrivia;
|
|
106
111
|
|
|
107
112
|
this.lexer = new Lexer(input, lexerOptions);
|
|
108
113
|
this.tokens = this.lexer.tokenize();
|
|
109
114
|
|
|
110
|
-
//
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
// If preserving trivia, capture leading/trailing trivia spans before filtering
|
|
116
|
+
if (this.preserveTriviaEffective) {
|
|
117
|
+
const spans = computeTriviaSpans(this.tokens);
|
|
118
|
+
this.leadingTriviaByTokenStart = spans.leadingByStart;
|
|
119
|
+
this.trailingTriviaByTokenEnd = spans.trailingByEnd;
|
|
120
|
+
this.tokenByStart = spans.tokenByStart;
|
|
121
|
+
this.tokenByEnd = spans.tokenByEnd;
|
|
122
|
+
// Then filter hidden-channel tokens out for parsing
|
|
123
|
+
this.tokens = this.tokens.filter(token => token.channel === undefined || token.channel === Channel.DEFAULT);
|
|
115
124
|
}
|
|
116
125
|
|
|
126
|
+
// Make mode/options/input available before cursor injection decisions
|
|
127
|
+
this.input = input;
|
|
128
|
+
this.mode = mode;
|
|
129
|
+
this.options = options;
|
|
130
|
+
|
|
117
131
|
// Inject cursor token if cursor position is provided
|
|
118
132
|
if (options.cursorPosition !== undefined) {
|
|
119
133
|
this.tokens = this.injectCursorToken(this.tokens, options.cursorPosition);
|
|
120
134
|
}
|
|
121
135
|
|
|
122
|
-
this.input = input;
|
|
123
|
-
this.mode = mode;
|
|
124
|
-
this.options = options;
|
|
125
|
-
|
|
126
136
|
// Initialize LSP features only if needed
|
|
127
137
|
if (this.mode === 'lsp') {
|
|
128
138
|
this.errors = [];
|
|
129
|
-
|
|
130
|
-
this.nodeIndex = new Map();
|
|
131
|
-
this.nodesByType = new Map();
|
|
132
|
-
this.identifierIndex = new Map();
|
|
133
|
-
this.currentParent = null;
|
|
139
|
+
// indexes are now built by the augmentor
|
|
134
140
|
}
|
|
135
141
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (this.peek().type === TokenType.CURSOR) {
|
|
139
|
-
return null; // Will be handled contextually
|
|
140
|
-
}
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
142
|
+
|
|
143
|
+
// removed unused checkCursor(); cursor handling is contextual in parse methods
|
|
143
144
|
|
|
144
145
|
private injectCursorToken(tokens: Token[], cursorPosition: number): Token[] {
|
|
145
146
|
// Find the position to inject the cursor token
|
|
146
147
|
let insertIndex = 0;
|
|
147
|
-
|
|
148
|
+
|
|
148
149
|
for (let i = 0; i < tokens.length; i++) {
|
|
149
150
|
const token = tokens[i];
|
|
150
151
|
if (!token) continue;
|
|
151
|
-
|
|
152
|
+
|
|
152
153
|
// Skip EOF token
|
|
153
154
|
if (token.type === TokenType.EOF) {
|
|
154
155
|
break;
|
|
155
156
|
}
|
|
156
|
-
|
|
157
|
+
|
|
157
158
|
// Check if cursor is before this token
|
|
158
159
|
if (cursorPosition <= token.start) {
|
|
159
160
|
insertIndex = i;
|
|
160
161
|
break;
|
|
161
162
|
}
|
|
162
|
-
|
|
163
|
+
|
|
163
164
|
// Check if cursor is within this token (we ignore mid-token cursors)
|
|
164
165
|
if (cursorPosition > token.start && cursorPosition < token.end) {
|
|
165
|
-
//
|
|
166
|
-
|
|
166
|
+
// Only materialize mid-token cursor in LSP mode; otherwise ignore
|
|
167
|
+
if (this.mode === 'lsp') {
|
|
168
|
+
insertIndex = i;
|
|
169
|
+
break;
|
|
170
|
+
} else {
|
|
171
|
+
return tokens;
|
|
172
|
+
}
|
|
167
173
|
}
|
|
168
|
-
|
|
174
|
+
|
|
169
175
|
// Cursor is after this token
|
|
170
176
|
insertIndex = i + 1;
|
|
171
177
|
}
|
|
172
|
-
|
|
178
|
+
|
|
173
179
|
// Create cursor token
|
|
174
180
|
const cursorToken: Token = {
|
|
175
181
|
type: TokenType.CURSOR,
|
|
@@ -183,14 +189,14 @@ export class Parser {
|
|
|
183
189
|
end: { line: 0, character: cursorPosition, offset: cursorPosition }
|
|
184
190
|
}
|
|
185
191
|
};
|
|
186
|
-
|
|
192
|
+
|
|
187
193
|
// Insert cursor token
|
|
188
194
|
const result = [...tokens];
|
|
189
195
|
result.splice(insertIndex, 0, cursorToken);
|
|
190
|
-
|
|
196
|
+
|
|
191
197
|
return result;
|
|
192
198
|
}
|
|
193
|
-
|
|
199
|
+
|
|
194
200
|
private getRangeFromToken(token: Token): Range {
|
|
195
201
|
return token.range || {
|
|
196
202
|
start: { line: 0, character: 0, offset: token.start },
|
|
@@ -257,9 +263,7 @@ export class Parser {
|
|
|
257
263
|
private parseLSP(): ParseResult {
|
|
258
264
|
// Clear indexes for fresh parse
|
|
259
265
|
this.errors = [];
|
|
260
|
-
|
|
261
|
-
this.nodesByType!.clear();
|
|
262
|
-
this.identifierIndex!.clear();
|
|
266
|
+
// indexes will be built by augmentor
|
|
263
267
|
|
|
264
268
|
let ast: ASTNode;
|
|
265
269
|
|
|
@@ -270,6 +274,8 @@ export class Parser {
|
|
|
270
274
|
const token = this.peek();
|
|
271
275
|
this.addError(Errors.unexpectedToken(token.value || TokenType[token.type], this.getRangeFromToken(token)).message, token);
|
|
272
276
|
}
|
|
277
|
+
|
|
278
|
+
// No transform here; augmentor handles cursor-specific transforms
|
|
273
279
|
} catch (error) {
|
|
274
280
|
// In LSP mode, create error node on fatal errors
|
|
275
281
|
if (error instanceof Error) {
|
|
@@ -279,28 +285,37 @@ export class Parser {
|
|
|
279
285
|
}
|
|
280
286
|
}
|
|
281
287
|
|
|
288
|
+
// Augment AST for LSP consumers
|
|
289
|
+
const aug = augment(ast, {
|
|
290
|
+
input: this.input,
|
|
291
|
+
preserveTrivia: this.preserveTriviaEffective,
|
|
292
|
+
trivia: this.preserveTriviaEffective ? {
|
|
293
|
+
leadingByStart: this.leadingTriviaByTokenStart,
|
|
294
|
+
trailingByEnd: this.trailingTriviaByTokenEnd,
|
|
295
|
+
} : undefined,
|
|
296
|
+
cursorPosition: this.options.cursorPosition,
|
|
297
|
+
});
|
|
298
|
+
|
|
282
299
|
const result: ParseResult = {
|
|
283
|
-
ast,
|
|
300
|
+
ast: aug.ast,
|
|
284
301
|
errors: this.errors!,
|
|
285
|
-
indexes:
|
|
286
|
-
nodeById: this.nodeIndex!,
|
|
287
|
-
nodesByType: this.nodesByType!,
|
|
288
|
-
identifiers: this.identifierIndex!
|
|
289
|
-
}
|
|
302
|
+
indexes: aug.indexes,
|
|
290
303
|
};
|
|
291
304
|
|
|
292
305
|
// Add cursor context if partial parsing
|
|
293
306
|
if (this.options.partialParse) {
|
|
294
|
-
const nodeAtCursor =
|
|
307
|
+
const nodeAtCursor = findNodeAtPosition(aug.ast, this.options.partialParse.cursorPosition);
|
|
295
308
|
result.cursorContext = {
|
|
296
309
|
node: nodeAtCursor,
|
|
297
|
-
expectedTokens:
|
|
298
|
-
availableCompletions:
|
|
310
|
+
expectedTokens: lspGetExpectedTokens(nodeAtCursor),
|
|
311
|
+
availableCompletions: lspGetCompletions(nodeAtCursor, aug.indexes.identifiers)
|
|
299
312
|
};
|
|
300
313
|
}
|
|
301
314
|
|
|
302
315
|
return result;
|
|
303
316
|
}
|
|
317
|
+
|
|
318
|
+
// Trivia population moved to augmentor
|
|
304
319
|
|
|
305
320
|
// Shared expression parsing with precedence climbing
|
|
306
321
|
protected expression(): ASTNode {
|
|
@@ -329,8 +344,15 @@ export class Parser {
|
|
|
329
344
|
this.current++; // inline advance()
|
|
330
345
|
// Check for cursor in indexer
|
|
331
346
|
if (this.peek().type === TokenType.CURSOR) {
|
|
332
|
-
this.
|
|
333
|
-
|
|
347
|
+
if (this.mode === 'lsp') {
|
|
348
|
+
const cursorTok = this.advance();
|
|
349
|
+
const cursorIndex = createCursorIndexNode(cursorTok.start) as any;
|
|
350
|
+
this.consume(TokenType.RBRACKET, "Expected ']'");
|
|
351
|
+
left = this.createIndexNode(left, cursorIndex, token);
|
|
352
|
+
} else {
|
|
353
|
+
this.advance();
|
|
354
|
+
left = createCursorIndexNode(this.previous().start) as any;
|
|
355
|
+
}
|
|
334
356
|
} else {
|
|
335
357
|
const index = this.expression();
|
|
336
358
|
this.consume(TokenType.RBRACKET, "Expected ']'");
|
|
@@ -340,13 +362,8 @@ export class Parser {
|
|
|
340
362
|
}
|
|
341
363
|
|
|
342
364
|
if (token.type === TokenType.LPAREN && this.isFunctionCall(left)) {
|
|
343
|
-
// Function calls - always bind tightly
|
|
344
|
-
this.
|
|
345
|
-
const args = this.parseArgumentList();
|
|
346
|
-
this.consume(TokenType.RPAREN, "Expected ')'");
|
|
347
|
-
// For function calls, we need to find the start token from the name node
|
|
348
|
-
const startToken = this.tokens[this.current - args.length - 2] || token;
|
|
349
|
-
left = this.createFunctionNode(left, args, startToken);
|
|
365
|
+
// Function calls - always bind tightly, handled via shared helper
|
|
366
|
+
left = this.parseFunctionCall(left);
|
|
350
367
|
continue;
|
|
351
368
|
}
|
|
352
369
|
|
|
@@ -419,6 +436,18 @@ export class Parser {
|
|
|
419
436
|
return createCursorOperatorNode(token.start) as any;
|
|
420
437
|
}
|
|
421
438
|
|
|
439
|
+
// Quantity literal emitted by lexer
|
|
440
|
+
if (token.type === TokenType.QUANTITY) {
|
|
441
|
+
this.current++;
|
|
442
|
+
const raw = token.value;
|
|
443
|
+
const quoteIdx = raw.indexOf("'");
|
|
444
|
+
const numberPart = quoteIdx >= 0 ? raw.slice(0, quoteIdx).trim() : raw;
|
|
445
|
+
const unitPart = quoteIdx >= 0 ? raw.slice(quoteIdx) : '';
|
|
446
|
+
const numberValue = Number(numberPart);
|
|
447
|
+
const unit = unitPart ? this.parseStringValue(unitPart) : '';
|
|
448
|
+
return this.createQuantityNode(numberValue, unit, token, token);
|
|
449
|
+
}
|
|
450
|
+
|
|
422
451
|
if (token.type === TokenType.NUMBER) {
|
|
423
452
|
this.current++; // inline advance()
|
|
424
453
|
const numberValue = parseFloat(token.value);
|
|
@@ -428,7 +457,7 @@ export class Parser {
|
|
|
428
457
|
if (nextToken.type === TokenType.STRING) {
|
|
429
458
|
this.advance();
|
|
430
459
|
const unit = this.parseStringValue(nextToken.value);
|
|
431
|
-
return this.createQuantityNode(numberValue, unit,
|
|
460
|
+
return this.createQuantityNode(numberValue, unit, token, nextToken);
|
|
432
461
|
}
|
|
433
462
|
|
|
434
463
|
// Check if next token is a calendar duration identifier
|
|
@@ -438,7 +467,7 @@ export class Parser {
|
|
|
438
467
|
'second', 'seconds', 'millisecond', 'milliseconds'];
|
|
439
468
|
if (calendarUnits.includes(nextToken.value)) {
|
|
440
469
|
this.advance();
|
|
441
|
-
return this.createQuantityNode(numberValue, nextToken.value,
|
|
470
|
+
return this.createQuantityNode(numberValue, nextToken.value, token, nextToken);
|
|
442
471
|
}
|
|
443
472
|
}
|
|
444
473
|
|
|
@@ -461,16 +490,22 @@ export class Parser {
|
|
|
461
490
|
return this.createLiteralNode(null, 'null', token);
|
|
462
491
|
}
|
|
463
492
|
|
|
493
|
+
if (token.type === TokenType.DATE) {
|
|
494
|
+
this.advance();
|
|
495
|
+
const value = token.value.substring(1); // Remove @
|
|
496
|
+
return this.createTemporalLiteralNode(value, 'date', token);
|
|
497
|
+
}
|
|
498
|
+
|
|
464
499
|
if (token.type === TokenType.DATETIME) {
|
|
465
500
|
this.advance();
|
|
466
501
|
const value = token.value.substring(1); // Remove @
|
|
467
|
-
return this.
|
|
502
|
+
return this.createTemporalLiteralNode(value, 'datetime', token);
|
|
468
503
|
}
|
|
469
504
|
|
|
470
505
|
if (token.type === TokenType.TIME) {
|
|
471
506
|
this.advance();
|
|
472
507
|
const value = token.value.substring(1); // Remove @
|
|
473
|
-
return this.
|
|
508
|
+
return this.createTemporalLiteralNode(value, 'time', token);
|
|
474
509
|
}
|
|
475
510
|
|
|
476
511
|
if (token.type === TokenType.SPECIAL_IDENTIFIER) {
|
|
@@ -490,6 +525,15 @@ export class Parser {
|
|
|
490
525
|
}
|
|
491
526
|
|
|
492
527
|
if (token.type === TokenType.IDENTIFIER) {
|
|
528
|
+
// If cursor is exactly at end of identifier and no whitespace at cursor, treat as identifier context
|
|
529
|
+
if (this.options.cursorPosition !== undefined && this.options.cursorPosition === token.end) {
|
|
530
|
+
const ch = this.input[this.options.cursorPosition];
|
|
531
|
+
const isWs = ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r' || ch === undefined;
|
|
532
|
+
if (!isWs) {
|
|
533
|
+
this.advance();
|
|
534
|
+
return createCursorIdentifierNode(this.options.cursorPosition, token.value) as any;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
493
537
|
this.advance();
|
|
494
538
|
const name = this.parseIdentifierValue(token.value);
|
|
495
539
|
return this.createIdentifierNode(name, token);
|
|
@@ -519,7 +563,7 @@ export class Parser {
|
|
|
519
563
|
const tokenStr = token.value || TokenType[token.type];
|
|
520
564
|
const range = this.getRangeFromToken(token);
|
|
521
565
|
const error = Errors.unexpectedToken(tokenStr, range);
|
|
522
|
-
return this.
|
|
566
|
+
return this.handleAstError(error.message, token);
|
|
523
567
|
}
|
|
524
568
|
|
|
525
569
|
protected parseInvocation(): ASTNode {
|
|
@@ -533,17 +577,19 @@ export class Parser {
|
|
|
533
577
|
|
|
534
578
|
// Allow identifiers and keywords that can be used as member names
|
|
535
579
|
if (token.type === TokenType.IDENTIFIER) {
|
|
580
|
+
// Check for cursor at the end of an identifier
|
|
581
|
+
if (this.options.cursorPosition !== undefined && this.options.cursorPosition === token.end) {
|
|
582
|
+
this.advance();
|
|
583
|
+
return createCursorIdentifierNode(this.options.cursorPosition, token.value) as any;
|
|
584
|
+
}
|
|
585
|
+
|
|
536
586
|
this.advance();
|
|
537
587
|
const name = this.parseIdentifierValue(token.value);
|
|
538
588
|
const node = this.createIdentifierNode(name, token);
|
|
539
589
|
|
|
540
590
|
// Check if this is a function call
|
|
541
591
|
if (this.check(TokenType.LPAREN)) {
|
|
542
|
-
this.
|
|
543
|
-
const args = this.parseArgumentList();
|
|
544
|
-
this.consume(TokenType.RPAREN, "Expected ')'");
|
|
545
|
-
const startToken = this.tokens[this.current - args.length - 2] || this.previous();
|
|
546
|
-
return this.createFunctionNode(node, args, startToken);
|
|
592
|
+
return this.parseFunctionCall(node);
|
|
547
593
|
}
|
|
548
594
|
|
|
549
595
|
return node;
|
|
@@ -558,17 +604,17 @@ export class Parser {
|
|
|
558
604
|
const tokenStr = token.value || TokenType[token.type];
|
|
559
605
|
const range = this.getRangeFromToken(token);
|
|
560
606
|
const error = Errors.expectedIdentifier('.', tokenStr, range);
|
|
561
|
-
return this.
|
|
607
|
+
return this.handleAstError(error.message, token);
|
|
562
608
|
}
|
|
563
609
|
|
|
564
|
-
protected parseArgumentList(): ASTNode[] {
|
|
610
|
+
protected parseArgumentList(functionName?: string): ASTNode[] {
|
|
565
611
|
const args: ASTNode[] = [];
|
|
566
612
|
|
|
567
613
|
// Check for cursor at start of arguments
|
|
568
614
|
if (this.peek().type === TokenType.CURSOR) {
|
|
569
615
|
this.advance();
|
|
570
|
-
|
|
571
|
-
args.push(createCursorArgumentNode(this.previous().start,
|
|
616
|
+
const fn = functionName ?? '';
|
|
617
|
+
args.push(createCursorArgumentNode(this.previous().start, fn, 0) as any);
|
|
572
618
|
return args;
|
|
573
619
|
}
|
|
574
620
|
|
|
@@ -582,7 +628,8 @@ export class Parser {
|
|
|
582
628
|
// Check for cursor after comma
|
|
583
629
|
if (this.peek().type === TokenType.CURSOR) {
|
|
584
630
|
this.advance();
|
|
585
|
-
|
|
631
|
+
const fn = functionName ?? '';
|
|
632
|
+
args.push(createCursorArgumentNode(this.previous().start, fn, args.length) as any);
|
|
586
633
|
return args;
|
|
587
634
|
}
|
|
588
635
|
args.push(this.expression());
|
|
@@ -591,20 +638,29 @@ export class Parser {
|
|
|
591
638
|
return args;
|
|
592
639
|
}
|
|
593
640
|
|
|
641
|
+
// Shared function-call parser for both standalone and dotted calls
|
|
642
|
+
protected parseFunctionCall(nameNode: ASTNode): ASTNode {
|
|
643
|
+
// Current token is '(' per caller contract
|
|
644
|
+
this.advance();
|
|
645
|
+
const fnName = (nameNode as any)?.name && (typeof (nameNode as any).name === 'string')
|
|
646
|
+
? (nameNode as any).name as string
|
|
647
|
+
: undefined;
|
|
648
|
+
const args = this.parseArgumentList(fnName);
|
|
649
|
+
this.consume(TokenType.RPAREN, "Expected ')'");
|
|
650
|
+
return this.createFunctionNode(nameNode, args);
|
|
651
|
+
}
|
|
652
|
+
|
|
594
653
|
protected parseCollectionElements(): ASTNode[] {
|
|
595
654
|
const elements: ASTNode[] = [];
|
|
596
655
|
|
|
597
656
|
if (this.peek().type === TokenType.RBRACE) {
|
|
598
|
-
return elements;
|
|
657
|
+
return elements; // Empty collection {} is valid
|
|
599
658
|
}
|
|
600
659
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
return elements;
|
|
660
|
+
// Any other token is an error - braces can only contain empty collections
|
|
661
|
+
const unexpectedToken = this.peek();
|
|
662
|
+
const message = `Unexpected token '${unexpectedToken.value}', expected '}'. Braces can only be used for empty collections. Use parentheses and pipe operators for non-empty collections: (1 | 2 | 3)`;
|
|
663
|
+
return this.handleAstError(message, unexpectedToken) as any;
|
|
608
664
|
}
|
|
609
665
|
|
|
610
666
|
protected parseTypeName(): string {
|
|
@@ -613,8 +669,12 @@ export class Parser {
|
|
|
613
669
|
const tokenStr = token.value || TokenType[token.type];
|
|
614
670
|
const range = this.getRangeFromToken(token);
|
|
615
671
|
const error = Errors.expectedTypeName(tokenStr, range);
|
|
616
|
-
this.
|
|
617
|
-
|
|
672
|
+
if (this.mode === 'lsp' && this.options.errorRecovery) {
|
|
673
|
+
this.reportError(error.message, token);
|
|
674
|
+
return '';
|
|
675
|
+
}
|
|
676
|
+
this.throwSyntax(error.message, token);
|
|
677
|
+
return '';
|
|
618
678
|
}
|
|
619
679
|
return this.parseIdentifierValue(token.value);
|
|
620
680
|
}
|
|
@@ -648,36 +708,10 @@ export class Parser {
|
|
|
648
708
|
|
|
649
709
|
// Shared utility methods
|
|
650
710
|
protected isFunctionCall(node: ASTNode): boolean {
|
|
651
|
-
return (node as any).type === NodeType.Identifier
|
|
711
|
+
return (node as any).type === NodeType.Identifier;
|
|
652
712
|
}
|
|
653
713
|
|
|
654
|
-
//
|
|
655
|
-
protected isBinaryOperatorToken(token: Token): boolean {
|
|
656
|
-
if (token.type === TokenType.OPERATOR || token.type === TokenType.DOT) {
|
|
657
|
-
return registry.isBinaryOperator(token.value);
|
|
658
|
-
}
|
|
659
|
-
if (token.type === TokenType.IDENTIFIER) {
|
|
660
|
-
return registry.isKeywordOperator(token.value);
|
|
661
|
-
}
|
|
662
|
-
return false;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
protected isKeywordAllowedAsMember(token: Token): boolean {
|
|
666
|
-
// Keywords that can be used as member names
|
|
667
|
-
if (token.type !== TokenType.IDENTIFIER) return false;
|
|
668
|
-
|
|
669
|
-
const keywordsAllowed = [
|
|
670
|
-
'contains', 'and', 'or', 'xor', 'implies',
|
|
671
|
-
'as', 'is', 'div', 'mod', 'in', 'true', 'false'
|
|
672
|
-
];
|
|
673
|
-
|
|
674
|
-
return keywordsAllowed.includes(token.value.toLowerCase());
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
protected isKeywordAllowedAsIdentifier(token: Token): boolean {
|
|
678
|
-
// Keywords that can be used as identifiers in certain contexts
|
|
679
|
-
return this.isKeywordAllowedAsMember(token);
|
|
680
|
-
}
|
|
714
|
+
// removed unused: isBinaryOperatorToken, isKeywordAllowedAsMember, isKeywordAllowedAsIdentifier
|
|
681
715
|
|
|
682
716
|
// Helper methods
|
|
683
717
|
protected peek(): Token {
|
|
@@ -713,48 +747,58 @@ export class Parser {
|
|
|
713
747
|
}
|
|
714
748
|
|
|
715
749
|
protected consume(type: TokenType, message: string): Token {
|
|
716
|
-
if (this.check(type))
|
|
717
|
-
|
|
718
|
-
|
|
750
|
+
if (this.check(type)) {
|
|
751
|
+
return this.advance();
|
|
752
|
+
}
|
|
753
|
+
// If we are in recovery mode (LSP + errorRecovery), report and return a synthetic token
|
|
754
|
+
if (this.mode === 'lsp' && this.options.errorRecovery) {
|
|
755
|
+
const tok = this.peek();
|
|
756
|
+
this.reportError(message, tok);
|
|
757
|
+
// Create a zero-width synthetic token of the expected type at the current position
|
|
758
|
+
const pos = this.isAtEnd() ? this.input.length : tok.start;
|
|
759
|
+
const synthetic: Token = {
|
|
760
|
+
type,
|
|
761
|
+
value: '',
|
|
762
|
+
start: pos,
|
|
763
|
+
end: pos,
|
|
764
|
+
line: tok?.line ?? 0,
|
|
765
|
+
column: tok?.column ?? 0,
|
|
766
|
+
range: {
|
|
767
|
+
start: { line: 0, character: pos, offset: pos },
|
|
768
|
+
end: { line: 0, character: pos, offset: pos }
|
|
769
|
+
}
|
|
770
|
+
} as Token;
|
|
771
|
+
return synthetic;
|
|
772
|
+
}
|
|
773
|
+
// Be lenient when cursor is provided and we're at EOF (legacy behavior for simple mode cursor tests)
|
|
719
774
|
if (this.options.cursorPosition !== undefined && this.isAtEnd()) {
|
|
720
|
-
|
|
721
|
-
|
|
775
|
+
const pos = this.input.length;
|
|
776
|
+
const synthetic: Token = {
|
|
722
777
|
type,
|
|
723
778
|
value: '',
|
|
724
|
-
start:
|
|
725
|
-
end:
|
|
779
|
+
start: pos,
|
|
780
|
+
end: pos,
|
|
781
|
+
line: 0,
|
|
782
|
+
column: 0,
|
|
783
|
+
range: {
|
|
784
|
+
start: { line: 0, character: pos, offset: pos },
|
|
785
|
+
end: { line: 0, character: pos, offset: pos }
|
|
786
|
+
}
|
|
726
787
|
} as Token;
|
|
788
|
+
return synthetic;
|
|
727
789
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const tokenStr = token.value || TokenType[token.type];
|
|
731
|
-
const range = this.getRangeFromToken(token);
|
|
732
|
-
const error = Errors.expectedToken(TokenType[type], tokenStr, range);
|
|
733
|
-
return this.handleError(error.message, token) as any;
|
|
790
|
+
// Simple mode: throw
|
|
791
|
+
this.throwSyntax(message, this.peek());
|
|
734
792
|
}
|
|
735
793
|
|
|
736
794
|
// Implement node creation methods
|
|
737
795
|
protected createIdentifierNode(name: string, token: Token): ASTNode {
|
|
738
796
|
const range = this.getRangeFromToken(token);
|
|
739
|
-
const isType = name[0] && name[0] >= 'A' && name[0] <= 'Z';
|
|
740
|
-
const nodeType = isType ? NodeType.TypeOrIdentifier : NodeType.Identifier;
|
|
741
|
-
|
|
742
797
|
const node: ASTNode = {
|
|
743
|
-
type:
|
|
798
|
+
type: NodeType.Identifier,
|
|
744
799
|
name,
|
|
745
800
|
range
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
// Add LSP features if in LSP mode
|
|
749
|
-
if (this.mode === 'lsp') {
|
|
750
|
-
this.enrichNodeForLSP(node, token);
|
|
751
|
-
|
|
752
|
-
// Index identifier
|
|
753
|
-
const identifiers = this.identifierIndex!.get(name) || [];
|
|
754
|
-
identifiers.push(node);
|
|
755
|
-
this.identifierIndex!.set(name, identifiers);
|
|
756
|
-
}
|
|
757
|
-
|
|
801
|
+
} as any;
|
|
758
802
|
return node;
|
|
759
803
|
}
|
|
760
804
|
|
|
@@ -766,9 +810,24 @@ export class Parser {
|
|
|
766
810
|
range: this.getRangeFromToken(token)
|
|
767
811
|
};
|
|
768
812
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
813
|
+
// LSP enrichment handled by augmentor
|
|
814
|
+
|
|
815
|
+
return node;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
protected createTemporalLiteralNode(rawValue: string, valueType: TemporalLiteralNode['valueType'], token: Token): TemporalLiteralNode {
|
|
819
|
+
// Parse temporal value immediately
|
|
820
|
+
const temporalValue = parseTemporalLiteral('@' + rawValue);
|
|
821
|
+
|
|
822
|
+
const node: TemporalLiteralNode = {
|
|
823
|
+
type: NodeType.TemporalLiteral,
|
|
824
|
+
value: temporalValue,
|
|
825
|
+
valueType,
|
|
826
|
+
rawValue,
|
|
827
|
+
range: this.getRangeFromToken(token)
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
// LSP enrichment handled by augmentor
|
|
772
831
|
|
|
773
832
|
return node;
|
|
774
833
|
}
|
|
@@ -782,19 +841,7 @@ export class Parser {
|
|
|
782
841
|
range: this.getRangeFromNodes(left, right)
|
|
783
842
|
};
|
|
784
843
|
|
|
785
|
-
|
|
786
|
-
// For binary nodes, we need to find the actual start and end tokens
|
|
787
|
-
const startToken = this.tokens.find(t => t.start === left.range.start.offset) || token;
|
|
788
|
-
const endToken = this.tokens.find(t => t.end === right.range.end.offset) || token;
|
|
789
|
-
this.enrichNodeForLSP(node, startToken, endToken);
|
|
790
|
-
|
|
791
|
-
// Set up parent-child relationships
|
|
792
|
-
if (node.id) {
|
|
793
|
-
left.parent = node;
|
|
794
|
-
right.parent = node;
|
|
795
|
-
node.children = [left, right];
|
|
796
|
-
}
|
|
797
|
-
}
|
|
844
|
+
// LSP enrichment handled by augmentor
|
|
798
845
|
|
|
799
846
|
return node;
|
|
800
847
|
}
|
|
@@ -808,20 +855,12 @@ export class Parser {
|
|
|
808
855
|
range: { start: startPos, end: operand.range.end }
|
|
809
856
|
};
|
|
810
857
|
|
|
811
|
-
|
|
812
|
-
const endToken = this.tokens.find(t => t.end === operand.range.end.offset) || token;
|
|
813
|
-
this.enrichNodeForLSP(node, token, endToken);
|
|
814
|
-
|
|
815
|
-
if (node.id) {
|
|
816
|
-
operand.parent = node;
|
|
817
|
-
node.children = [operand];
|
|
818
|
-
}
|
|
819
|
-
}
|
|
858
|
+
// LSP enrichment handled by augmentor
|
|
820
859
|
|
|
821
860
|
return node;
|
|
822
861
|
}
|
|
823
862
|
|
|
824
|
-
protected createFunctionNode(name: ASTNode, args: ASTNode[]
|
|
863
|
+
protected createFunctionNode(name: ASTNode, args: ASTNode[]): FunctionNode {
|
|
825
864
|
const endNode = args.length > 0 ? args[args.length - 1]! : name;
|
|
826
865
|
const node: FunctionNode = {
|
|
827
866
|
type: NodeType.Function,
|
|
@@ -830,17 +869,7 @@ export class Parser {
|
|
|
830
869
|
range: this.getRangeFromNodes(name, endNode)
|
|
831
870
|
};
|
|
832
871
|
|
|
833
|
-
|
|
834
|
-
const startTok = this.tokens.find(t => t.start === name.range.start.offset) || startToken;
|
|
835
|
-
const endToken = this.tokens.find(t => t.end === endNode.range.end.offset) || startToken;
|
|
836
|
-
this.enrichNodeForLSP(node, startTok, endToken);
|
|
837
|
-
|
|
838
|
-
if (node.id) {
|
|
839
|
-
name.parent = node;
|
|
840
|
-
args.forEach(arg => { arg.parent = node; });
|
|
841
|
-
node.children = [name, ...args];
|
|
842
|
-
}
|
|
843
|
-
}
|
|
872
|
+
// LSP enrichment handled by augmentor
|
|
844
873
|
|
|
845
874
|
return node;
|
|
846
875
|
}
|
|
@@ -852,9 +881,7 @@ export class Parser {
|
|
|
852
881
|
range: this.getRangeFromToken(token)
|
|
853
882
|
};
|
|
854
883
|
|
|
855
|
-
|
|
856
|
-
this.enrichNodeForLSP(node, token);
|
|
857
|
-
}
|
|
884
|
+
// LSP enrichment handled by augmentor
|
|
858
885
|
|
|
859
886
|
return node;
|
|
860
887
|
}
|
|
@@ -867,64 +894,38 @@ export class Parser {
|
|
|
867
894
|
range: this.getRangeFromNodes(expression, index)
|
|
868
895
|
};
|
|
869
896
|
|
|
870
|
-
|
|
871
|
-
const startTok = this.tokens.find(t => t.start === expression.range.start.offset) || startToken;
|
|
872
|
-
const endToken = this.tokens.find(t => t.end === index.range.end.offset) || startToken;
|
|
873
|
-
this.enrichNodeForLSP(node, startTok, endToken);
|
|
874
|
-
|
|
875
|
-
if (node.id) {
|
|
876
|
-
expression.parent = node;
|
|
877
|
-
index.parent = node;
|
|
878
|
-
node.children = [expression, index];
|
|
879
|
-
}
|
|
880
|
-
}
|
|
897
|
+
// LSP enrichment handled by augmentor
|
|
881
898
|
|
|
882
899
|
return node;
|
|
883
900
|
}
|
|
884
901
|
|
|
885
902
|
|
|
886
903
|
protected createMembershipTestNode(expression: ASTNode, targetType: string, startToken: Token): MembershipTestNode {
|
|
887
|
-
// The range should extend from expression to the end of the type name
|
|
904
|
+
// The range should extend from expression start to the end of the type name
|
|
888
905
|
const endToken = this.previous(); // Should be the type identifier
|
|
889
906
|
const node: MembershipTestNode = {
|
|
890
907
|
type: NodeType.MembershipTest,
|
|
891
908
|
expression,
|
|
892
909
|
targetType,
|
|
893
|
-
range: this.
|
|
910
|
+
range: { start: expression.range.start, end: this.getRangeFromToken(endToken).end }
|
|
894
911
|
};
|
|
895
912
|
|
|
896
|
-
|
|
897
|
-
const startTok = this.tokens.find(t => t.start === expression.range.start.offset) || startToken;
|
|
898
|
-
this.enrichNodeForLSP(node, startTok, endToken);
|
|
899
|
-
|
|
900
|
-
if (node.id) {
|
|
901
|
-
expression.parent = node;
|
|
902
|
-
node.children = [expression];
|
|
903
|
-
}
|
|
904
|
-
}
|
|
913
|
+
// LSP enrichment handled by augmentor
|
|
905
914
|
|
|
906
915
|
return node;
|
|
907
916
|
}
|
|
908
917
|
|
|
909
918
|
protected createTypeCastNode(expression: ASTNode, targetType: string, startToken: Token): TypeCastNode {
|
|
910
|
-
// The range should extend from expression to the end of the type name
|
|
919
|
+
// The range should extend from expression start to the end of the type name
|
|
911
920
|
const endToken = this.previous(); // Should be the type identifier
|
|
912
921
|
const node: TypeCastNode = {
|
|
913
922
|
type: NodeType.TypeCast,
|
|
914
923
|
expression,
|
|
915
924
|
targetType,
|
|
916
|
-
range: this.
|
|
925
|
+
range: { start: expression.range.start, end: this.getRangeFromToken(endToken).end }
|
|
917
926
|
};
|
|
918
927
|
|
|
919
|
-
|
|
920
|
-
const startTok = this.tokens.find(t => t.start === expression.range.start.offset) || startToken;
|
|
921
|
-
this.enrichNodeForLSP(node, startTok, endToken);
|
|
922
|
-
|
|
923
|
-
if (node.id) {
|
|
924
|
-
expression.parent = node;
|
|
925
|
-
node.children = [expression];
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
+
// LSP enrichment handled by augmentor
|
|
928
929
|
|
|
929
930
|
return node;
|
|
930
931
|
}
|
|
@@ -937,80 +938,52 @@ export class Parser {
|
|
|
937
938
|
range: this.getRangeFromTokens(startToken, endToken)
|
|
938
939
|
};
|
|
939
940
|
|
|
940
|
-
|
|
941
|
-
this.enrichNodeForLSP(node, startToken, endToken);
|
|
942
|
-
|
|
943
|
-
if (node.id) {
|
|
944
|
-
elements.forEach(elem => { elem.parent = node; });
|
|
945
|
-
node.children = elements;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
941
|
+
// LSP enrichment handled by augmentor
|
|
948
942
|
|
|
949
943
|
return node;
|
|
950
944
|
}
|
|
951
945
|
|
|
952
|
-
protected createQuantityNode(value: number, unit: string,
|
|
946
|
+
protected createQuantityNode(value: number, unit: string, startToken: Token, endToken: Token): QuantityNode {
|
|
953
947
|
const node: QuantityNode = {
|
|
954
948
|
type: NodeType.Quantity,
|
|
955
949
|
value,
|
|
956
950
|
unit,
|
|
957
|
-
isCalendarUnit,
|
|
958
951
|
range: this.getRangeFromTokens(startToken, endToken)
|
|
959
952
|
};
|
|
960
953
|
|
|
961
|
-
|
|
962
|
-
this.enrichNodeForLSP(node, startToken, endToken);
|
|
963
|
-
}
|
|
954
|
+
// LSP enrichment handled by augmentor
|
|
964
955
|
|
|
965
956
|
return node;
|
|
966
957
|
}
|
|
967
958
|
|
|
968
|
-
|
|
959
|
+
// AST-level error handler: returns an ErrorNode in recovery, throws in simple mode
|
|
960
|
+
protected handleAstError(message: string, token?: Token): ErrorNode {
|
|
969
961
|
if (this.mode === 'lsp' && this.options.errorRecovery) {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
// Try to synchronize
|
|
962
|
+
this.reportError(message, token);
|
|
963
|
+
// Attempt to synchronize so the parse can continue
|
|
974
964
|
this.synchronize();
|
|
975
|
-
|
|
976
|
-
// Return error node (cast to never to satisfy type system)
|
|
977
|
-
return this.createErrorNode(message, token) as never;
|
|
965
|
+
return this.createErrorNode(message, token);
|
|
978
966
|
}
|
|
979
|
-
|
|
980
|
-
|
|
967
|
+
this.throwSyntax(message, token);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Record an error (LSP path)
|
|
971
|
+
private reportError(message: string, token?: Token): void {
|
|
972
|
+
this.addError(message, token);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Throw a syntax error (simple mode path)
|
|
976
|
+
private throwSyntax(message: string, token?: Token): never {
|
|
981
977
|
const range = token ? this.getRangeFromToken(token) : undefined;
|
|
978
|
+
if (message.includes('Unexpected token:')) {
|
|
979
|
+
const tokenMatch = message.match(/Unexpected token: (.+)/);
|
|
980
|
+
const tokenValue = tokenMatch?.[1] ?? 'unknown';
|
|
981
|
+
throw Errors.unexpectedToken(tokenValue, range);
|
|
982
|
+
}
|
|
982
983
|
throw Errors.invalidSyntax(message, range);
|
|
983
984
|
}
|
|
984
985
|
|
|
985
|
-
// LSP
|
|
986
|
-
private enrichNodeForLSP(node: ASTNode, startToken: Token, endToken?: Token): void {
|
|
987
|
-
if (this.mode !== 'lsp') return;
|
|
988
|
-
|
|
989
|
-
// Add unique ID
|
|
990
|
-
node.id = `node_${this.nodeIdCounter!++}`;
|
|
991
|
-
|
|
992
|
-
// Add raw source text
|
|
993
|
-
const start = startToken.start;
|
|
994
|
-
const end = endToken ? endToken.end : startToken.end;
|
|
995
|
-
node.raw = this.input.substring(start, end);
|
|
996
|
-
|
|
997
|
-
// Add trivia if preserving
|
|
998
|
-
if (this.options.preserveTrivia) {
|
|
999
|
-
node.leadingTrivia = []; // TODO: Implement trivia collection
|
|
1000
|
-
node.trailingTrivia = [];
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
// Set parent relationship
|
|
1004
|
-
if (this.currentParent) {
|
|
1005
|
-
node.parent = this.currentParent;
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
// Index node
|
|
1009
|
-
this.nodeIndex!.set(node.id, node);
|
|
1010
|
-
const nodesByType = this.nodesByType!.get(node.type) || [];
|
|
1011
|
-
nodesByType.push(node);
|
|
1012
|
-
this.nodesByType!.set(node.type, nodesByType);
|
|
1013
|
-
}
|
|
986
|
+
// LSP enrichment moved to augmentor
|
|
1014
987
|
|
|
1015
988
|
private createErrorNode(message: string, token?: Token): ErrorNode {
|
|
1016
989
|
const range = token ? this.getRangeFromToken(token) : {
|
|
@@ -1024,9 +997,7 @@ export class Parser {
|
|
|
1024
997
|
range
|
|
1025
998
|
};
|
|
1026
999
|
|
|
1027
|
-
|
|
1028
|
-
this.enrichNodeForLSP(node, token || this.peek());
|
|
1029
|
-
}
|
|
1000
|
+
// LSP enrichment handled by augmentor
|
|
1030
1001
|
|
|
1031
1002
|
return node;
|
|
1032
1003
|
}
|
|
@@ -1063,90 +1034,8 @@ export class Parser {
|
|
|
1063
1034
|
}
|
|
1064
1035
|
}
|
|
1065
1036
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
if (offset < root.range.start.offset! || offset > root.range.end.offset!) {
|
|
1069
|
-
return null;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
// Check children if they exist
|
|
1073
|
-
if ('children' in root && Array.isArray(root.children)) {
|
|
1074
|
-
for (const child of root.children) {
|
|
1075
|
-
const found = this.findNodeAtPosition(child, offset);
|
|
1076
|
-
if (found) return found;
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
// Check specific node types
|
|
1081
|
-
if (root.type === NodeType.Binary) {
|
|
1082
|
-
const binaryNode = root as BinaryNode;
|
|
1083
|
-
const leftResult = this.findNodeAtPosition(binaryNode.left, offset);
|
|
1084
|
-
if (leftResult) return leftResult;
|
|
1085
|
-
const rightResult = this.findNodeAtPosition(binaryNode.right, offset);
|
|
1086
|
-
if (rightResult) return rightResult;
|
|
1087
|
-
} else if (root.type === NodeType.Unary) {
|
|
1088
|
-
const unaryNode = root as UnaryNode;
|
|
1089
|
-
return this.findNodeAtPosition(unaryNode.operand, offset);
|
|
1090
|
-
} else if (root.type === NodeType.Function) {
|
|
1091
|
-
const funcNode = root as FunctionNode;
|
|
1092
|
-
const nameResult = this.findNodeAtPosition(funcNode.name, offset);
|
|
1093
|
-
if (nameResult) return nameResult;
|
|
1094
|
-
for (const arg of funcNode.arguments) {
|
|
1095
|
-
const argResult = this.findNodeAtPosition(arg, offset);
|
|
1096
|
-
if (argResult) return argResult;
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
return root;
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
private getExpectedTokens(node: ASTNode | null): TokenType[] {
|
|
1104
|
-
if (!node) return this.getExpectedTokensForError();
|
|
1105
|
-
|
|
1106
|
-
// Context-specific expectations
|
|
1107
|
-
switch (node.type) {
|
|
1108
|
-
case NodeType.Binary:
|
|
1109
|
-
return [TokenType.DOT, TokenType.LBRACKET];
|
|
1110
|
-
case NodeType.Identifier:
|
|
1111
|
-
case NodeType.TypeOrIdentifier:
|
|
1112
|
-
return [TokenType.DOT, TokenType.LPAREN, TokenType.LBRACKET];
|
|
1113
|
-
default:
|
|
1114
|
-
return this.getExpectedTokensForError();
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
private getExpectedTokensForError(): TokenType[] {
|
|
1119
|
-
// Common continuations
|
|
1120
|
-
return [
|
|
1121
|
-
TokenType.EOF,
|
|
1122
|
-
TokenType.DOT,
|
|
1123
|
-
TokenType.LBRACKET,
|
|
1124
|
-
TokenType.LPAREN,
|
|
1125
|
-
TokenType.OPERATOR,
|
|
1126
|
-
TokenType.IDENTIFIER
|
|
1127
|
-
];
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
private getCompletions(node: ASTNode | null): string[] {
|
|
1131
|
-
if (!node) return [];
|
|
1132
|
-
|
|
1133
|
-
const completions: string[] = [];
|
|
1134
|
-
|
|
1135
|
-
// Add all identifiers seen so far
|
|
1136
|
-
if (this.identifierIndex) {
|
|
1137
|
-
for (const name of Array.from(this.identifierIndex.keys())) {
|
|
1138
|
-
completions.push(name);
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// Add common FHIRPath functions
|
|
1143
|
-
completions.push(
|
|
1144
|
-
'where', 'select', 'first', 'last', 'tail',
|
|
1145
|
-
'skip', 'take', 'count', 'empty', 'exists'
|
|
1146
|
-
);
|
|
1147
|
-
|
|
1148
|
-
return completions;
|
|
1149
|
-
}
|
|
1037
|
+
// Cursor-specific transforms moved to augmentor
|
|
1038
|
+
// Cursor services moved to lsp/cursor-services
|
|
1150
1039
|
}
|
|
1151
1040
|
|
|
1152
1041
|
export function parse(input: string, options?: ParserOptions): ParseResult {
|
|
@@ -1160,146 +1049,4 @@ export function parse(input: string, options?: ParserOptions): ParseResult {
|
|
|
1160
1049
|
* @param indent - Current indentation level
|
|
1161
1050
|
* @returns Lisp-style string representation
|
|
1162
1051
|
*/
|
|
1163
|
-
|
|
1164
|
-
const spaces = ' '.repeat(indent);
|
|
1165
|
-
|
|
1166
|
-
switch (node.type) {
|
|
1167
|
-
case NodeType.Literal: {
|
|
1168
|
-
const lit = node as LiteralNode;
|
|
1169
|
-
if (lit.valueType === 'string') {
|
|
1170
|
-
return `"${lit.value}"`;
|
|
1171
|
-
} else if (lit.valueType === 'null') {
|
|
1172
|
-
return 'null';
|
|
1173
|
-
}
|
|
1174
|
-
return String(lit.value);
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
case NodeType.Identifier:
|
|
1178
|
-
case NodeType.TypeOrIdentifier: {
|
|
1179
|
-
const id = node as IdentifierNode | TypeOrIdentifierNode;
|
|
1180
|
-
return id.name;
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
case NodeType.Variable: {
|
|
1184
|
-
const v = node as VariableNode;
|
|
1185
|
-
return v.name;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
case NodeType.Binary: {
|
|
1189
|
-
const bin = node as BinaryNode;
|
|
1190
|
-
const op = bin.operator;
|
|
1191
|
-
|
|
1192
|
-
// For simple expressions, put on one line
|
|
1193
|
-
const leftStr = pprint(bin.left, 0);
|
|
1194
|
-
const rightStr = pprint(bin.right, 0);
|
|
1195
|
-
|
|
1196
|
-
if (leftStr.length + rightStr.length + op.length + 4 < 60 &&
|
|
1197
|
-
!leftStr.includes('\n') && !rightStr.includes('\n')) {
|
|
1198
|
-
return `(${op} ${leftStr} ${rightStr})`;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
// For complex expressions, use multiple lines
|
|
1202
|
-
return `(${op}\n${spaces} ${pprint(bin.left, indent + 2)}\n${spaces} ${pprint(bin.right, indent + 2)})`;
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
case NodeType.Unary: {
|
|
1206
|
-
const un = node as UnaryNode;
|
|
1207
|
-
const operandStr = pprint(un.operand, 0);
|
|
1208
|
-
|
|
1209
|
-
if (operandStr.length < 40 && !operandStr.includes('\n')) {
|
|
1210
|
-
return `(${un.operator} ${operandStr})`;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
return `(${un.operator}\n${spaces} ${pprint(un.operand, indent + 2)})`;
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
case NodeType.Function: {
|
|
1217
|
-
const fn = node as FunctionNode;
|
|
1218
|
-
const nameStr = pprint(fn.name, 0);
|
|
1219
|
-
|
|
1220
|
-
if (fn.arguments.length === 0) {
|
|
1221
|
-
return `(${nameStr})`;
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
const argStrs = fn.arguments.map(arg => pprint(arg, 0));
|
|
1225
|
-
const totalLen = nameStr.length + argStrs.reduce((sum, s) => sum + s.length + 1, 0) + 2;
|
|
1226
|
-
|
|
1227
|
-
if (totalLen < 60 && argStrs.every(s => !s.includes('\n'))) {
|
|
1228
|
-
return `(${nameStr} ${argStrs.join(' ')})`;
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
// Multi-line format
|
|
1232
|
-
const argLines = fn.arguments.map(arg => `${spaces} ${pprint(arg, indent + 2)}`);
|
|
1233
|
-
return `(${nameStr}\n${argLines.join('\n')})`;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
case NodeType.Index: {
|
|
1237
|
-
const idx = node as IndexNode;
|
|
1238
|
-
const exprStr = pprint(idx.expression, 0);
|
|
1239
|
-
const indexStr = pprint(idx.index, 0);
|
|
1240
|
-
|
|
1241
|
-
if (exprStr.length + indexStr.length < 50 &&
|
|
1242
|
-
!exprStr.includes('\n') && !indexStr.includes('\n')) {
|
|
1243
|
-
return `([] ${exprStr} ${indexStr})`;
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
return `([]\n${spaces} ${pprint(idx.expression, indent + 2)}\n${spaces} ${pprint(idx.index, indent + 2)})`;
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
case NodeType.MembershipTest: {
|
|
1250
|
-
const mt = node as MembershipTestNode;
|
|
1251
|
-
const exprStr = pprint(mt.expression, 0);
|
|
1252
|
-
|
|
1253
|
-
if (exprStr.length + mt.targetType.length < 50 && !exprStr.includes('\n')) {
|
|
1254
|
-
return `(is ${exprStr} ${mt.targetType})`;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
return `(is\n${spaces} ${pprint(mt.expression, indent + 2)}\n${spaces} ${mt.targetType})`;
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
case NodeType.TypeCast: {
|
|
1261
|
-
const tc = node as TypeCastNode;
|
|
1262
|
-
const exprStr = pprint(tc.expression, 0);
|
|
1263
|
-
|
|
1264
|
-
if (exprStr.length + tc.targetType.length < 50 && !exprStr.includes('\n')) {
|
|
1265
|
-
return `(as ${exprStr} ${tc.targetType})`;
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
return `(as\n${spaces} ${pprint(tc.expression, indent + 2)}\n${spaces} ${tc.targetType})`;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
case NodeType.Collection: {
|
|
1272
|
-
const coll = node as CollectionNode;
|
|
1273
|
-
|
|
1274
|
-
if (coll.elements.length === 0) {
|
|
1275
|
-
return '{}';
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
const elemStrs = coll.elements.map(e => pprint(e, 0));
|
|
1279
|
-
const totalLen = elemStrs.reduce((sum, s) => sum + s.length + 1, 2);
|
|
1280
|
-
|
|
1281
|
-
if (totalLen < 60 && elemStrs.every(s => !s.includes('\n'))) {
|
|
1282
|
-
return `{${elemStrs.join(' ')}}`;
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
const elemLines = coll.elements.map(e => `${spaces} ${pprint(e, indent + 2)}`);
|
|
1286
|
-
return `{\n${elemLines.join('\n')}\n${spaces}}`;
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
case NodeType.TypeReference: {
|
|
1290
|
-
const tr = node as TypeReferenceNode;
|
|
1291
|
-
return `Type[${tr.typeName}]`;
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
case NodeType.Quantity: {
|
|
1295
|
-
const q = node as QuantityNode;
|
|
1296
|
-
if (q.isCalendarUnit) {
|
|
1297
|
-
return `${q.value} ${q.unit}`;
|
|
1298
|
-
}
|
|
1299
|
-
return `${q.value} '${q.unit}'`;
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
default:
|
|
1303
|
-
return `<unknown:${node.type}>`;
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1052
|
+
// moved to src/utils/pprint
|