@atomic-ehr/fhirpath 0.0.2 → 0.0.3-canary.2be66fb.20250905161900

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 (147) hide show
  1. package/README.md +716 -238
  2. package/dist/index.d.ts +226 -120
  3. package/dist/index.js +11552 -5580
  4. package/dist/index.js.map +1 -1
  5. package/package.json +12 -5
  6. package/src/analyzer/augmentor.ts +242 -0
  7. package/src/analyzer/cursor-services.ts +75 -0
  8. package/src/analyzer/scope-manager.ts +57 -0
  9. package/src/analyzer/trivia-indexer.ts +58 -0
  10. package/src/analyzer/type-compat.ts +157 -0
  11. package/src/analyzer/utils.ts +132 -0
  12. package/src/analyzer.ts +939 -1204
  13. package/src/completion-provider.ts +209 -191
  14. package/src/complex-types/quantity-value.ts +410 -0
  15. package/src/complex-types/temporal.ts +1776 -0
  16. package/src/errors.ts +25 -3
  17. package/src/index.ts +17 -104
  18. package/src/inspect.ts +4 -4
  19. package/src/{boxing.ts → interpreter/boxing.ts} +1 -1
  20. package/src/interpreter/navigator.ts +94 -0
  21. package/src/interpreter/runtime-context.ts +273 -0
  22. package/src/interpreter.ts +506 -468
  23. package/src/lexer.ts +192 -211
  24. package/src/model-provider.ts +71 -43
  25. package/src/operations/abs-function.ts +1 -1
  26. package/src/operations/aggregate-function.ts +84 -5
  27. package/src/operations/all-function.ts +4 -3
  28. package/src/operations/allFalse-function.ts +2 -1
  29. package/src/operations/allTrue-function.ts +2 -1
  30. package/src/operations/and-operator.ts +2 -1
  31. package/src/operations/anyFalse-function.ts +2 -1
  32. package/src/operations/anyTrue-function.ts +2 -1
  33. package/src/operations/as-function.ts +99 -0
  34. package/src/operations/as-operator.ts +57 -19
  35. package/src/operations/ceiling-function.ts +1 -1
  36. package/src/operations/children-function.ts +14 -5
  37. package/src/operations/combine-function.ts +6 -3
  38. package/src/operations/combine-operator.ts +6 -7
  39. package/src/operations/comparison.ts +744 -0
  40. package/src/operations/contains-function.ts +1 -1
  41. package/src/operations/contains-operator.ts +2 -1
  42. package/src/operations/convertsToBoolean-function.ts +78 -0
  43. package/src/operations/convertsToDecimal-function.ts +82 -0
  44. package/src/operations/convertsToInteger-function.ts +71 -0
  45. package/src/operations/convertsToLong-function.ts +89 -0
  46. package/src/operations/convertsToQuantity-function.ts +132 -0
  47. package/src/operations/convertsToString-function.ts +88 -0
  48. package/src/operations/count-function.ts +2 -1
  49. package/src/operations/dateOf-function.ts +69 -0
  50. package/src/operations/dayOf-function.ts +66 -0
  51. package/src/operations/decimal-boundaries.ts +133 -0
  52. package/src/operations/defineVariable-function.ts +130 -17
  53. package/src/operations/distinct-function.ts +1 -1
  54. package/src/operations/div-operator.ts +1 -1
  55. package/src/operations/divide-operator.ts +12 -7
  56. package/src/operations/dot-operator.ts +1 -1
  57. package/src/operations/empty-function.ts +30 -21
  58. package/src/operations/endsWith-function.ts +6 -1
  59. package/src/operations/equal-operator.ts +23 -32
  60. package/src/operations/equivalent-operator.ts +13 -53
  61. package/src/operations/exclude-function.ts +2 -1
  62. package/src/operations/exists-function.ts +4 -3
  63. package/src/operations/extension-function.ts +84 -0
  64. package/src/operations/first-function.ts +1 -1
  65. package/src/operations/floor-function.ts +1 -1
  66. package/src/operations/greater-operator.ts +7 -9
  67. package/src/operations/greater-or-equal-operator.ts +7 -9
  68. package/src/operations/highBoundary-function.ts +120 -0
  69. package/src/operations/hourOf-function.ts +66 -0
  70. package/src/operations/iif-function.ts +193 -8
  71. package/src/operations/implies-operator.ts +2 -1
  72. package/src/operations/in-operator.ts +2 -1
  73. package/src/operations/index.ts +43 -0
  74. package/src/operations/indexOf-function.ts +1 -1
  75. package/src/operations/intersect-function.ts +1 -1
  76. package/src/operations/is-function.ts +70 -0
  77. package/src/operations/is-operator.ts +176 -13
  78. package/src/operations/isDistinct-function.ts +2 -1
  79. package/src/operations/join-function.ts +1 -1
  80. package/src/operations/last-function.ts +1 -1
  81. package/src/operations/lastIndexOf-function.ts +85 -0
  82. package/src/operations/length-function.ts +1 -1
  83. package/src/operations/less-operator.ts +8 -9
  84. package/src/operations/less-or-equal-operator.ts +7 -9
  85. package/src/operations/less-than.ts +8 -13
  86. package/src/operations/lowBoundary-function.ts +120 -0
  87. package/src/operations/lower-function.ts +1 -1
  88. package/src/operations/matches-function.ts +86 -0
  89. package/src/operations/matchesFull-function.ts +96 -0
  90. package/src/operations/millisecondOf-function.ts +66 -0
  91. package/src/operations/minus-operator.ts +76 -4
  92. package/src/operations/minuteOf-function.ts +66 -0
  93. package/src/operations/mod-operator.ts +8 -2
  94. package/src/operations/monthOf-function.ts +66 -0
  95. package/src/operations/multiply-operator.ts +27 -3
  96. package/src/operations/not-equal-operator.ts +24 -30
  97. package/src/operations/not-equivalent-operator.ts +13 -53
  98. package/src/operations/not-function.ts +10 -3
  99. package/src/operations/ofType-function.ts +43 -12
  100. package/src/operations/or-operator.ts +2 -1
  101. package/src/operations/plus-operator.ts +71 -7
  102. package/src/operations/power-function.ts +35 -10
  103. package/src/operations/precision-function.ts +146 -0
  104. package/src/operations/repeat-function.ts +169 -0
  105. package/src/operations/replace-function.ts +1 -1
  106. package/src/operations/replaceMatches-function.ts +125 -0
  107. package/src/operations/round-function.ts +1 -1
  108. package/src/operations/secondOf-function.ts +66 -0
  109. package/src/operations/select-function.ts +66 -5
  110. package/src/operations/single-function.ts +1 -1
  111. package/src/operations/skip-function.ts +1 -1
  112. package/src/operations/split-function.ts +1 -1
  113. package/src/operations/sqrt-function.ts +15 -8
  114. package/src/operations/startsWith-function.ts +1 -1
  115. package/src/operations/subsetOf-function.ts +6 -2
  116. package/src/operations/substring-function.ts +1 -1
  117. package/src/operations/supersetOf-function.ts +6 -2
  118. package/src/operations/tail-function.ts +1 -1
  119. package/src/operations/take-function.ts +1 -1
  120. package/src/operations/temporal-functions.ts +555 -0
  121. package/src/operations/timeOf-function.ts +67 -0
  122. package/src/operations/timezoneOffsetOf-function.ts +69 -0
  123. package/src/operations/toBoolean-function.ts +27 -8
  124. package/src/operations/toChars-function.ts +56 -0
  125. package/src/operations/toDecimal-function.ts +27 -8
  126. package/src/operations/toInteger-function.ts +15 -3
  127. package/src/operations/toLong-function.ts +98 -0
  128. package/src/operations/toQuantity-function.ts +181 -0
  129. package/src/operations/toString-function.ts +78 -15
  130. package/src/operations/trace-function.ts +1 -1
  131. package/src/operations/trim-function.ts +1 -1
  132. package/src/operations/truncate-function.ts +1 -1
  133. package/src/operations/unary-minus-operator.ts +2 -2
  134. package/src/operations/unary-plus-operator.ts +1 -1
  135. package/src/operations/union-function.ts +1 -1
  136. package/src/operations/union-operator.ts +16 -26
  137. package/src/operations/upper-function.ts +1 -1
  138. package/src/operations/where-function.ts +3 -3
  139. package/src/operations/xor-operator.ts +1 -1
  140. package/src/operations/yearOf-function.ts +66 -0
  141. package/src/{cursor-nodes.ts → parser/cursor-nodes.ts} +10 -7
  142. package/src/parser.ts +262 -503
  143. package/src/registry.ts +53 -42
  144. package/src/types.ts +129 -17
  145. package/src/utils/decimal.ts +76 -0
  146. package/src/utils/pprint.ts +151 -0
  147. package/src/quantity-value.ts +0 -198
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, FHIRPathError, toDiagnostic } from './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
- // Filter out trivia tokens if they exist (tokens on hidden channel)
111
- if (lexerOptions.preserveTrivia) {
112
- this.tokens = this.tokens.filter(token =>
113
- token.channel === undefined || token.channel === Channel.DEFAULT
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
- this.nodeIdCounter = 0;
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
- private checkCursor(): AnyCursorNode | null {
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
- // Cursor is mid-token, ignore it
166
- return tokens;
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
- this.nodeIndex!.clear();
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 = this.findNodeAtPosition(ast, this.options.partialParse.cursorPosition);
307
+ const nodeAtCursor = findNodeAtPosition(aug.ast, this.options.partialParse.cursorPosition);
295
308
  result.cursorContext = {
296
309
  node: nodeAtCursor,
297
- expectedTokens: this.getExpectedTokens(nodeAtCursor),
298
- availableCompletions: this.getCompletions(nodeAtCursor)
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.advance();
333
- left = createCursorIndexNode(this.previous().start) as any;
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.current++; // inline advance()
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, false, token, nextToken);
460
+ return this.createQuantityNode(numberValue, unit, token, nextToken);
432
461
  }
433
462
 
434
463
  // Check if next token is a calendar duration identifier
@@ -438,11 +467,14 @@ 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, true, token, nextToken);
470
+ return this.createQuantityNode(numberValue, nextToken.value, token, nextToken);
442
471
  }
443
472
  }
444
473
 
445
- return this.createLiteralNode(numberValue, 'number', token);
474
+ // Determine if this is an integer or decimal based on the original token text
475
+ // If the token contains a decimal point, it's a decimal even if the value is a whole number
476
+ const isDecimal = token.value.includes('.');
477
+ return this.createLiteralNode(numberValue, isDecimal ? 'decimal' : 'number', token);
446
478
  }
447
479
 
448
480
  if (token.type === TokenType.STRING) {
@@ -461,16 +493,22 @@ export class Parser {
461
493
  return this.createLiteralNode(null, 'null', token);
462
494
  }
463
495
 
496
+ if (token.type === TokenType.DATE) {
497
+ this.advance();
498
+ const value = token.value.substring(1); // Remove @
499
+ return this.createTemporalLiteralNode(value, 'date', token);
500
+ }
501
+
464
502
  if (token.type === TokenType.DATETIME) {
465
503
  this.advance();
466
504
  const value = token.value.substring(1); // Remove @
467
- return this.createLiteralNode(value, 'datetime', token);
505
+ return this.createTemporalLiteralNode(value, 'datetime', token);
468
506
  }
469
507
 
470
508
  if (token.type === TokenType.TIME) {
471
509
  this.advance();
472
510
  const value = token.value.substring(1); // Remove @
473
- return this.createLiteralNode(value, 'time', token);
511
+ return this.createTemporalLiteralNode(value, 'time', token);
474
512
  }
475
513
 
476
514
  if (token.type === TokenType.SPECIAL_IDENTIFIER) {
@@ -490,6 +528,15 @@ export class Parser {
490
528
  }
491
529
 
492
530
  if (token.type === TokenType.IDENTIFIER) {
531
+ // If cursor is exactly at end of identifier and no whitespace at cursor, treat as identifier context
532
+ if (this.options.cursorPosition !== undefined && this.options.cursorPosition === token.end) {
533
+ const ch = this.input[this.options.cursorPosition];
534
+ const isWs = ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r' || ch === undefined;
535
+ if (!isWs) {
536
+ this.advance();
537
+ return createCursorIdentifierNode(this.options.cursorPosition, token.value) as any;
538
+ }
539
+ }
493
540
  this.advance();
494
541
  const name = this.parseIdentifierValue(token.value);
495
542
  return this.createIdentifierNode(name, token);
@@ -519,7 +566,7 @@ export class Parser {
519
566
  const tokenStr = token.value || TokenType[token.type];
520
567
  const range = this.getRangeFromToken(token);
521
568
  const error = Errors.unexpectedToken(tokenStr, range);
522
- return this.handleError(error.message, token);
569
+ return this.handleAstError(error.message, token);
523
570
  }
524
571
 
525
572
  protected parseInvocation(): ASTNode {
@@ -533,17 +580,19 @@ export class Parser {
533
580
 
534
581
  // Allow identifiers and keywords that can be used as member names
535
582
  if (token.type === TokenType.IDENTIFIER) {
583
+ // Check for cursor at the end of an identifier
584
+ if (this.options.cursorPosition !== undefined && this.options.cursorPosition === token.end) {
585
+ this.advance();
586
+ return createCursorIdentifierNode(this.options.cursorPosition, token.value) as any;
587
+ }
588
+
536
589
  this.advance();
537
590
  const name = this.parseIdentifierValue(token.value);
538
591
  const node = this.createIdentifierNode(name, token);
539
592
 
540
593
  // Check if this is a function call
541
594
  if (this.check(TokenType.LPAREN)) {
542
- this.advance();
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);
595
+ return this.parseFunctionCall(node);
547
596
  }
548
597
 
549
598
  return node;
@@ -558,17 +607,17 @@ export class Parser {
558
607
  const tokenStr = token.value || TokenType[token.type];
559
608
  const range = this.getRangeFromToken(token);
560
609
  const error = Errors.expectedIdentifier('.', tokenStr, range);
561
- return this.handleError(error.message, token);
610
+ return this.handleAstError(error.message, token);
562
611
  }
563
612
 
564
- protected parseArgumentList(): ASTNode[] {
613
+ protected parseArgumentList(functionName?: string): ASTNode[] {
565
614
  const args: ASTNode[] = [];
566
615
 
567
616
  // Check for cursor at start of arguments
568
617
  if (this.peek().type === TokenType.CURSOR) {
569
618
  this.advance();
570
- // Need to get function name from context - for now use empty string
571
- args.push(createCursorArgumentNode(this.previous().start, '', 0) as any);
619
+ const fn = functionName ?? '';
620
+ args.push(createCursorArgumentNode(this.previous().start, fn, 0) as any);
572
621
  return args;
573
622
  }
574
623
 
@@ -582,7 +631,8 @@ export class Parser {
582
631
  // Check for cursor after comma
583
632
  if (this.peek().type === TokenType.CURSOR) {
584
633
  this.advance();
585
- args.push(createCursorArgumentNode(this.previous().start, '', args.length) as any);
634
+ const fn = functionName ?? '';
635
+ args.push(createCursorArgumentNode(this.previous().start, fn, args.length) as any);
586
636
  return args;
587
637
  }
588
638
  args.push(this.expression());
@@ -591,20 +641,29 @@ export class Parser {
591
641
  return args;
592
642
  }
593
643
 
644
+ // Shared function-call parser for both standalone and dotted calls
645
+ protected parseFunctionCall(nameNode: ASTNode): ASTNode {
646
+ // Current token is '(' per caller contract
647
+ this.advance();
648
+ const fnName = (nameNode as any)?.name && (typeof (nameNode as any).name === 'string')
649
+ ? (nameNode as any).name as string
650
+ : undefined;
651
+ const args = this.parseArgumentList(fnName);
652
+ this.consume(TokenType.RPAREN, "Expected ')'");
653
+ return this.createFunctionNode(nameNode, args);
654
+ }
655
+
594
656
  protected parseCollectionElements(): ASTNode[] {
595
657
  const elements: ASTNode[] = [];
596
658
 
597
659
  if (this.peek().type === TokenType.RBRACE) {
598
- return elements;
599
- }
600
-
601
- elements.push(this.expression());
602
-
603
- while (this.match(TokenType.COMMA)) {
604
- elements.push(this.expression());
660
+ return elements; // Empty collection {} is valid
605
661
  }
606
662
 
607
- return elements;
663
+ // Any other token is an error - braces can only contain empty collections
664
+ const unexpectedToken = this.peek();
665
+ 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)`;
666
+ return this.handleAstError(message, unexpectedToken) as any;
608
667
  }
609
668
 
610
669
  protected parseTypeName(): string {
@@ -613,8 +672,12 @@ export class Parser {
613
672
  const tokenStr = token.value || TokenType[token.type];
614
673
  const range = this.getRangeFromToken(token);
615
674
  const error = Errors.expectedTypeName(tokenStr, range);
616
- this.handleError(error.message, token);
617
- return ''; // For TypeScript, though handleError should throw/return error node
675
+ if (this.mode === 'lsp' && this.options.errorRecovery) {
676
+ this.reportError(error.message, token);
677
+ return '';
678
+ }
679
+ this.throwSyntax(error.message, token);
680
+ return '';
618
681
  }
619
682
  return this.parseIdentifierValue(token.value);
620
683
  }
@@ -622,7 +685,14 @@ export class Parser {
622
685
  protected parseStringValue(raw: string): string {
623
686
  // Remove quotes and handle escape sequences
624
687
  const content = raw.slice(1, -1);
625
- return content.replace(/\\(.)/g, (_, char) => {
688
+
689
+ // Handle unicode escapes first (\uXXXX)
690
+ let result = content.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => {
691
+ return String.fromCharCode(parseInt(hex, 16));
692
+ });
693
+
694
+ // Then handle other escape sequences
695
+ result = result.replace(/\\(.)/g, (_, char) => {
626
696
  switch (char) {
627
697
  case 'n': return '\n';
628
698
  case 'r': return '\r';
@@ -636,6 +706,8 @@ export class Parser {
636
706
  default: return char;
637
707
  }
638
708
  });
709
+
710
+ return result;
639
711
  }
640
712
 
641
713
  protected parseIdentifierValue(raw: string): string {
@@ -648,36 +720,10 @@ export class Parser {
648
720
 
649
721
  // Shared utility methods
650
722
  protected isFunctionCall(node: ASTNode): boolean {
651
- return (node as any).type === NodeType.Identifier || (node as any).type === NodeType.TypeOrIdentifier;
652
- }
653
-
654
- // Helper method to check if a token is a binary operator
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());
723
+ return (node as any).type === NodeType.Identifier;
675
724
  }
676
725
 
677
- protected isKeywordAllowedAsIdentifier(token: Token): boolean {
678
- // Keywords that can be used as identifiers in certain contexts
679
- return this.isKeywordAllowedAsMember(token);
680
- }
726
+ // removed unused: isBinaryOperatorToken, isKeywordAllowedAsMember, isKeywordAllowedAsIdentifier
681
727
 
682
728
  // Helper methods
683
729
  protected peek(): Token {
@@ -713,48 +759,58 @@ export class Parser {
713
759
  }
714
760
 
715
761
  protected consume(type: TokenType, message: string): Token {
716
- if (this.check(type)) return this.advance();
717
-
718
- // Be lenient when cursor is present and we're at EOF
762
+ if (this.check(type)) {
763
+ return this.advance();
764
+ }
765
+ // If we are in recovery mode (LSP + errorRecovery), report and return a synthetic token
766
+ if (this.mode === 'lsp' && this.options.errorRecovery) {
767
+ const tok = this.peek();
768
+ this.reportError(message, tok);
769
+ // Create a zero-width synthetic token of the expected type at the current position
770
+ const pos = this.isAtEnd() ? this.input.length : tok.start;
771
+ const synthetic: Token = {
772
+ type,
773
+ value: '',
774
+ start: pos,
775
+ end: pos,
776
+ line: tok?.line ?? 0,
777
+ column: tok?.column ?? 0,
778
+ range: {
779
+ start: { line: 0, character: pos, offset: pos },
780
+ end: { line: 0, character: pos, offset: pos }
781
+ }
782
+ } as Token;
783
+ return synthetic;
784
+ }
785
+ // Be lenient when cursor is provided and we're at EOF (legacy behavior for simple mode cursor tests)
719
786
  if (this.options.cursorPosition !== undefined && this.isAtEnd()) {
720
- // Return a synthetic token to allow parsing to continue
721
- return {
787
+ const pos = this.input.length;
788
+ const synthetic: Token = {
722
789
  type,
723
790
  value: '',
724
- start: this.input.length,
725
- end: this.input.length
791
+ start: pos,
792
+ end: pos,
793
+ line: 0,
794
+ column: 0,
795
+ range: {
796
+ start: { line: 0, character: pos, offset: pos },
797
+ end: { line: 0, character: pos, offset: pos }
798
+ }
726
799
  } as Token;
800
+ return synthetic;
727
801
  }
728
-
729
- const token = this.peek();
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;
802
+ // Simple mode: throw
803
+ this.throwSyntax(message, this.peek());
734
804
  }
735
805
 
736
806
  // Implement node creation methods
737
807
  protected createIdentifierNode(name: string, token: Token): ASTNode {
738
808
  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
809
  const node: ASTNode = {
743
- type: nodeType,
810
+ type: NodeType.Identifier,
744
811
  name,
745
812
  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
-
813
+ } as any;
758
814
  return node;
759
815
  }
760
816
 
@@ -766,9 +822,24 @@ export class Parser {
766
822
  range: this.getRangeFromToken(token)
767
823
  };
768
824
 
769
- if (this.mode === 'lsp') {
770
- this.enrichNodeForLSP(node, token);
771
- }
825
+ // LSP enrichment handled by augmentor
826
+
827
+ return node;
828
+ }
829
+
830
+ protected createTemporalLiteralNode(rawValue: string, valueType: TemporalLiteralNode['valueType'], token: Token): TemporalLiteralNode {
831
+ // Parse temporal value immediately
832
+ const temporalValue = parseTemporalLiteral('@' + rawValue);
833
+
834
+ const node: TemporalLiteralNode = {
835
+ type: NodeType.TemporalLiteral,
836
+ value: temporalValue,
837
+ valueType,
838
+ rawValue,
839
+ range: this.getRangeFromToken(token)
840
+ };
841
+
842
+ // LSP enrichment handled by augmentor
772
843
 
773
844
  return node;
774
845
  }
@@ -782,19 +853,7 @@ export class Parser {
782
853
  range: this.getRangeFromNodes(left, right)
783
854
  };
784
855
 
785
- if (this.mode === 'lsp') {
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
- }
856
+ // LSP enrichment handled by augmentor
798
857
 
799
858
  return node;
800
859
  }
@@ -808,20 +867,12 @@ export class Parser {
808
867
  range: { start: startPos, end: operand.range.end }
809
868
  };
810
869
 
811
- if (this.mode === 'lsp') {
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
- }
870
+ // LSP enrichment handled by augmentor
820
871
 
821
872
  return node;
822
873
  }
823
874
 
824
- protected createFunctionNode(name: ASTNode, args: ASTNode[], startToken: Token): FunctionNode {
875
+ protected createFunctionNode(name: ASTNode, args: ASTNode[]): FunctionNode {
825
876
  const endNode = args.length > 0 ? args[args.length - 1]! : name;
826
877
  const node: FunctionNode = {
827
878
  type: NodeType.Function,
@@ -830,17 +881,7 @@ export class Parser {
830
881
  range: this.getRangeFromNodes(name, endNode)
831
882
  };
832
883
 
833
- if (this.mode === 'lsp') {
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
- }
884
+ // LSP enrichment handled by augmentor
844
885
 
845
886
  return node;
846
887
  }
@@ -852,9 +893,7 @@ export class Parser {
852
893
  range: this.getRangeFromToken(token)
853
894
  };
854
895
 
855
- if (this.mode === 'lsp') {
856
- this.enrichNodeForLSP(node, token);
857
- }
896
+ // LSP enrichment handled by augmentor
858
897
 
859
898
  return node;
860
899
  }
@@ -867,64 +906,38 @@ export class Parser {
867
906
  range: this.getRangeFromNodes(expression, index)
868
907
  };
869
908
 
870
- if (this.mode === 'lsp') {
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
- }
909
+ // LSP enrichment handled by augmentor
881
910
 
882
911
  return node;
883
912
  }
884
913
 
885
914
 
886
915
  protected createMembershipTestNode(expression: ASTNode, targetType: string, startToken: Token): MembershipTestNode {
887
- // The range should extend from expression to the end of the type name
916
+ // The range should extend from expression start to the end of the type name
888
917
  const endToken = this.previous(); // Should be the type identifier
889
918
  const node: MembershipTestNode = {
890
919
  type: NodeType.MembershipTest,
891
920
  expression,
892
921
  targetType,
893
- range: this.getRangeFromTokens(startToken, endToken)
922
+ range: { start: expression.range.start, end: this.getRangeFromToken(endToken).end }
894
923
  };
895
924
 
896
- if (this.mode === 'lsp') {
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
- }
925
+ // LSP enrichment handled by augmentor
905
926
 
906
927
  return node;
907
928
  }
908
929
 
909
930
  protected createTypeCastNode(expression: ASTNode, targetType: string, startToken: Token): TypeCastNode {
910
- // The range should extend from expression to the end of the type name
931
+ // The range should extend from expression start to the end of the type name
911
932
  const endToken = this.previous(); // Should be the type identifier
912
933
  const node: TypeCastNode = {
913
934
  type: NodeType.TypeCast,
914
935
  expression,
915
936
  targetType,
916
- range: this.getRangeFromTokens(startToken, endToken)
937
+ range: { start: expression.range.start, end: this.getRangeFromToken(endToken).end }
917
938
  };
918
939
 
919
- if (this.mode === 'lsp') {
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
- }
940
+ // LSP enrichment handled by augmentor
928
941
 
929
942
  return node;
930
943
  }
@@ -937,80 +950,52 @@ export class Parser {
937
950
  range: this.getRangeFromTokens(startToken, endToken)
938
951
  };
939
952
 
940
- if (this.mode === 'lsp') {
941
- this.enrichNodeForLSP(node, startToken, endToken);
942
-
943
- if (node.id) {
944
- elements.forEach(elem => { elem.parent = node; });
945
- node.children = elements;
946
- }
947
- }
953
+ // LSP enrichment handled by augmentor
948
954
 
949
955
  return node;
950
956
  }
951
957
 
952
- protected createQuantityNode(value: number, unit: string, isCalendarUnit: boolean, startToken: Token, endToken: Token): QuantityNode {
958
+ protected createQuantityNode(value: number, unit: string, startToken: Token, endToken: Token): QuantityNode {
953
959
  const node: QuantityNode = {
954
960
  type: NodeType.Quantity,
955
961
  value,
956
962
  unit,
957
- isCalendarUnit,
958
963
  range: this.getRangeFromTokens(startToken, endToken)
959
964
  };
960
965
 
961
- if (this.mode === 'lsp') {
962
- this.enrichNodeForLSP(node, startToken, endToken);
963
- }
966
+ // LSP enrichment handled by augmentor
964
967
 
965
968
  return node;
966
969
  }
967
970
 
968
- protected handleError(message: string, token?: Token): never {
971
+ // AST-level error handler: returns an ErrorNode in recovery, throws in simple mode
972
+ protected handleAstError(message: string, token?: Token): ErrorNode {
969
973
  if (this.mode === 'lsp' && this.options.errorRecovery) {
970
- // In LSP mode with error recovery, add error and try to recover
971
- this.addError(message, token);
972
-
973
- // Try to synchronize
974
+ this.reportError(message, token);
975
+ // Attempt to synchronize so the parse can continue
974
976
  this.synchronize();
975
-
976
- // Return error node (cast to never to satisfy type system)
977
- return this.createErrorNode(message, token) as never;
977
+ return this.createErrorNode(message, token);
978
978
  }
979
-
980
- // In simple mode, throw error
979
+ this.throwSyntax(message, token);
980
+ }
981
+
982
+ // Record an error (LSP path)
983
+ private reportError(message: string, token?: Token): void {
984
+ this.addError(message, token);
985
+ }
986
+
987
+ // Throw a syntax error (simple mode path)
988
+ private throwSyntax(message: string, token?: Token): never {
981
989
  const range = token ? this.getRangeFromToken(token) : undefined;
990
+ if (message.includes('Unexpected token:')) {
991
+ const tokenMatch = message.match(/Unexpected token: (.+)/);
992
+ const tokenValue = tokenMatch?.[1] ?? 'unknown';
993
+ throw Errors.unexpectedToken(tokenValue, range);
994
+ }
982
995
  throw Errors.invalidSyntax(message, range);
983
996
  }
984
997
 
985
- // LSP mode helper methods
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
- }
998
+ // LSP enrichment moved to augmentor
1014
999
 
1015
1000
  private createErrorNode(message: string, token?: Token): ErrorNode {
1016
1001
  const range = token ? this.getRangeFromToken(token) : {
@@ -1024,9 +1009,7 @@ export class Parser {
1024
1009
  range
1025
1010
  };
1026
1011
 
1027
- if (this.mode === 'lsp') {
1028
- this.enrichNodeForLSP(node, token || this.peek());
1029
- }
1012
+ // LSP enrichment handled by augmentor
1030
1013
 
1031
1014
  return node;
1032
1015
  }
@@ -1063,90 +1046,8 @@ export class Parser {
1063
1046
  }
1064
1047
  }
1065
1048
 
1066
- private findNodeAtPosition(root: ASTNode, offset: number): ASTNode | null {
1067
- // DFS to find the most specific node containing the position
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
- }
1049
+ // Cursor-specific transforms moved to augmentor
1050
+ // Cursor services moved to lsp/cursor-services
1150
1051
  }
1151
1052
 
1152
1053
  export function parse(input: string, options?: ParserOptions): ParseResult {
@@ -1160,146 +1061,4 @@ export function parse(input: string, options?: ParserOptions): ParseResult {
1160
1061
  * @param indent - Current indentation level
1161
1062
  * @returns Lisp-style string representation
1162
1063
  */
1163
- export function pprint(node: ASTNode, indent: number = 0): string {
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
- }
1064
+ // moved to src/utils/pprint