9
9
ParseTreeListener ,
10
10
PredictionMode ,
11
11
ANTLRErrorListener ,
12
+ Parser ,
12
13
} from 'antlr4ng' ;
13
14
import { CandidatesCollection , CodeCompletionCore } from 'antlr4-c3' ;
14
15
import { SQLParserBase } from '../../lib/SQLParserBase' ;
@@ -211,6 +212,28 @@ export abstract class BasicSQL<
211
212
return this . _parseTree ;
212
213
}
213
214
215
+ /**
216
+ * Get the parseTree of the input string.
217
+ * @param input source string
218
+ * @returns parse and parserTree
219
+ */
220
+ private parserWithNewInput ( inputSlice : string ) {
221
+ const lexer = this . createLexer ( inputSlice ) ;
222
+ lexer . removeErrorListeners ( ) ;
223
+ const tokenStream = new CommonTokenStream ( lexer ) ;
224
+ tokenStream . fill ( ) ;
225
+ const parser = this . createParserFromTokenStream ( tokenStream ) ;
226
+ parser . interpreter . predictionMode = PredictionMode . SLL ;
227
+ parser . removeErrorListeners ( ) ;
228
+ parser . buildParseTrees = true ;
229
+ parser . errorHandler = new ErrorStrategy ( ) ;
230
+
231
+ return {
232
+ sqlParserIns : parser ,
233
+ parseTree : parser . program ( ) ,
234
+ } ;
235
+ }
236
+
214
237
/**
215
238
* Validate input string and return syntax errors if exists.
216
239
* @param input source string
@@ -263,8 +286,8 @@ export abstract class BasicSQL<
263
286
if ( errors . length || ! this . _parseTree ) {
264
287
return null ;
265
288
}
266
- const splitListener = this . splitListener ;
267
289
290
+ const splitListener = this . splitListener ;
268
291
this . listen ( splitListener , this . _parseTree ) ;
269
292
270
293
const res = splitListener . statementsContext
@@ -277,35 +300,30 @@ export abstract class BasicSQL<
277
300
}
278
301
279
302
/**
280
- * Get a minimum boundary parser near tokenIndex .
281
- * @param input source string.
282
- * @param tokenIndex start from which index to minimize the boundary.
283
- * @param originParseTree the parse tree need to be minimized, default value is the result of parsing `input`.
284
- * @returns minimum parser info
303
+ * Get the minimum input string that can be parsed successfully by c3 .
304
+ * @param input source string
305
+ * @param caretTokenIndex tokenIndex of caretPosition
306
+ * @param originParseTree origin parseTree
307
+ * @returns MinimumInputInfo
285
308
*/
286
- public getMinimumParserInfo (
309
+ public getMinimumInputInfo (
287
310
input : string ,
288
- tokenIndex : number ,
289
- originParseTree ?: ParserRuleContext | null
290
- ) {
291
- if ( arguments . length <= 2 ) {
292
- this . parseWithCache ( input ) ;
293
- originParseTree = this . _parseTree ;
294
- }
295
-
311
+ caretTokenIndex : number ,
312
+ originParseTree : ParserRuleContext | undefined
313
+ ) : { input : string ; tokenIndexOffset : number ; statementCount : number } | null {
296
314
if ( ! originParseTree || ! input ?. length ) return null ;
315
+ let inputSlice = input ;
297
316
298
- const splitListener = this . splitListener ;
299
317
/**
300
318
* Split sql by statement.
301
319
* Try to collect candidates in as small a range as possible.
302
320
*/
321
+ const splitListener = this . splitListener ;
303
322
this . listen ( splitListener , originParseTree ) ;
323
+
304
324
const statementCount = splitListener . statementsContext ?. length ;
305
325
const statementsContext = splitListener . statementsContext ;
306
326
let tokenIndexOffset = 0 ;
307
- let sqlParserIns = this . _parser ;
308
- let parseTree = originParseTree ;
309
327
310
328
// If there are multiple statements.
311
329
if ( statementCount > 1 ) {
@@ -330,14 +348,14 @@ export abstract class BasicSQL<
330
348
const isNextCtxValid =
331
349
index === statementCount - 1 || ! statementsContext [ index + 1 ] ?. exception ;
332
350
333
- if ( ctx . stop && ctx . stop . tokenIndex < tokenIndex && isPrevCtxValid ) {
351
+ if ( ctx . stop && ctx . stop . tokenIndex < caretTokenIndex && isPrevCtxValid ) {
334
352
startStatement = ctx ;
335
353
}
336
354
337
355
if (
338
356
ctx . start &&
339
357
! stopStatement &&
340
- ctx . start . tokenIndex > tokenIndex &&
358
+ ctx . start . tokenIndex > caretTokenIndex &&
341
359
isNextCtxValid
342
360
) {
343
361
stopStatement = ctx ;
@@ -347,41 +365,67 @@ export abstract class BasicSQL<
347
365
348
366
// A boundary consisting of the index of the input.
349
367
const startIndex = startStatement ?. start ?. start ?? 0 ;
350
- const stopIndex = stopStatement ?. stop ?. stop ?? input . length - 1 ;
368
+ const stopIndex = stopStatement ?. stop ?. stop ?? inputSlice . length - 1 ;
351
369
352
370
/**
353
371
* Save offset of the tokenIndex in the range of input
354
372
* compared to the tokenIndex in the whole input
355
373
*/
356
374
tokenIndexOffset = startStatement ?. start ?. tokenIndex ?? 0 ;
357
- tokenIndex = tokenIndex - tokenIndexOffset ;
375
+ inputSlice = inputSlice . slice ( startIndex , stopIndex ) ;
376
+ }
358
377
359
- /**
360
- * Reparse the input fragment,
361
- * and c3 will collect candidates in the newly generated parseTree.
362
- */
363
- const inputSlice = input . slice ( startIndex , stopIndex ) ;
378
+ return {
379
+ input : inputSlice ,
380
+ tokenIndexOffset,
381
+ statementCount,
382
+ } ;
383
+ }
364
384
365
- const lexer = this . createLexer ( inputSlice ) ;
366
- lexer . removeErrorListeners ( ) ;
367
- const tokenStream = new CommonTokenStream ( lexer ) ;
368
- tokenStream . fill ( ) ;
385
+ /**
386
+ * Get a minimum boundary parser near caretTokenIndex.
387
+ * @param input source string.
388
+ * @param caretTokenIndex start from which index to minimize the boundary.
389
+ * @param originParseTree the parse tree need to be minimized, default value is the result of parsing `input`.
390
+ * @returns minimum parser info
391
+ */
392
+ public getMinimumParserInfo (
393
+ input : string ,
394
+ caretTokenIndex : number ,
395
+ originParseTree : ParserRuleContext | undefined
396
+ ) : {
397
+ parser : Parser ;
398
+ parseTree : ParserRuleContext ;
399
+ tokenIndexOffset : number ;
400
+ newTokenIndex : number ;
401
+ } | null {
402
+ if ( ! originParseTree || ! input ?. length ) return null ;
369
403
370
- const parser = this . createParserFromTokenStream ( tokenStream ) ;
371
- parser . interpreter . predictionMode = PredictionMode . SLL ;
372
- parser . removeErrorListeners ( ) ;
373
- parser . buildParseTrees = true ;
374
- parser . errorHandler = new ErrorStrategy ( ) ;
404
+ const inputInfo = this . getMinimumInputInfo ( input , caretTokenIndex , originParseTree ) ;
405
+ if ( ! inputInfo ) return null ;
406
+ const { input : inputSlice , tokenIndexOffset } = inputInfo ;
407
+ caretTokenIndex = caretTokenIndex - tokenIndexOffset ;
375
408
376
- sqlParserIns = parser ;
377
- parseTree = parser . program ( ) ;
409
+ let sqlParserIns = this . _parser ;
410
+ let parseTree = originParseTree ;
411
+
412
+ /**
413
+ * Reparse the input fragment,
414
+ * and c3 will collect candidates in the newly generated parseTree when input changed.
415
+ */
416
+ if ( inputSlice !== input ) {
417
+ const { sqlParserIns : _sqlParserIns , parseTree : _parseTree } =
418
+ this . parserWithNewInput ( inputSlice ) ;
419
+
420
+ sqlParserIns = _sqlParserIns ;
421
+ parseTree = _parseTree ;
378
422
}
379
423
380
424
return {
381
425
parser : sqlParserIns ,
382
426
parseTree,
383
427
tokenIndexOffset,
384
- newTokenIndex : tokenIndex ,
428
+ newTokenIndex : caretTokenIndex ,
385
429
} ;
386
430
}
387
431
@@ -396,32 +440,41 @@ export abstract class BasicSQL<
396
440
caretPosition : CaretPosition
397
441
) : Suggestions | null {
398
442
this . parseWithCache ( input ) ;
399
-
400
443
if ( ! this . _parseTree ) return null ;
401
444
402
- const allTokens = this . getAllTokens ( input ) ;
445
+ let allTokens = this . getAllTokens ( input ) ;
403
446
let caretTokenIndex = findCaretTokenIndex ( caretPosition , allTokens ) ;
404
-
405
447
if ( ! caretTokenIndex && caretTokenIndex !== 0 ) return null ;
406
448
407
- const minimumParser = this . getMinimumParserInfo ( input , caretTokenIndex ) ;
449
+ const inputInfo = this . getMinimumInputInfo ( input , caretTokenIndex , this . _parseTree ) ;
450
+ if ( ! inputInfo ) return null ;
451
+ const { input : _input , tokenIndexOffset } = inputInfo ;
452
+ caretTokenIndex = caretTokenIndex - tokenIndexOffset ;
453
+ let inputSlice = _input ;
408
454
409
- if ( ! minimumParser ) return null ;
455
+ let sqlParserIns = this . _parser ;
456
+ let parseTree = this . _parseTree ;
457
+
458
+ /**
459
+ * Reparse the input fragment,
460
+ * and c3 will collect candidates in the newly generated parseTree when input changed.
461
+ */
462
+ if ( inputSlice !== input ) {
463
+ const { sqlParserIns : _sqlParserIns , parseTree : _parseTree } =
464
+ this . parserWithNewInput ( inputSlice ) ;
465
+
466
+ sqlParserIns = _sqlParserIns ;
467
+ parseTree = _parseTree ;
468
+ }
410
469
411
- const {
412
- parser : sqlParserIns ,
413
- tokenIndexOffset,
414
- newTokenIndex,
415
- parseTree : c3Context ,
416
- } = minimumParser ;
417
470
const core = new CodeCompletionCore ( sqlParserIns ) ;
418
471
core . preferredRules = this . preferredRules ;
419
472
420
- const candidates = core . collectCandidates ( newTokenIndex , c3Context ) ;
473
+ const candidates = core . collectCandidates ( caretTokenIndex , parseTree ) ;
421
474
const originalSuggestions = this . processCandidates (
422
475
candidates ,
423
476
allTokens ,
424
- newTokenIndex ,
477
+ caretTokenIndex ,
425
478
tokenIndexOffset
426
479
) ;
427
480
0 commit comments