Skip to content

Commit bd83147

Browse files
committed
refactor: split getMinimumParserInfo to slice input and parser again
1 parent 042477d commit bd83147

File tree

1 file changed

+105
-52
lines changed

1 file changed

+105
-52
lines changed

src/parser/common/basicSQL.ts

+105-52
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ParseTreeListener,
1010
PredictionMode,
1111
ANTLRErrorListener,
12+
Parser,
1213
} from 'antlr4ng';
1314
import { CandidatesCollection, CodeCompletionCore } from 'antlr4-c3';
1415
import { SQLParserBase } from '../../lib/SQLParserBase';
@@ -211,6 +212,28 @@ export abstract class BasicSQL<
211212
return this._parseTree;
212213
}
213214

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+
214237
/**
215238
* Validate input string and return syntax errors if exists.
216239
* @param input source string
@@ -263,8 +286,8 @@ export abstract class BasicSQL<
263286
if (errors.length || !this._parseTree) {
264287
return null;
265288
}
266-
const splitListener = this.splitListener;
267289

290+
const splitListener = this.splitListener;
268291
this.listen(splitListener, this._parseTree);
269292

270293
const res = splitListener.statementsContext
@@ -277,35 +300,30 @@ export abstract class BasicSQL<
277300
}
278301

279302
/**
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
285308
*/
286-
public getMinimumParserInfo(
309+
public getMinimumInputInfo(
287310
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 {
296314
if (!originParseTree || !input?.length) return null;
315+
let inputSlice = input;
297316

298-
const splitListener = this.splitListener;
299317
/**
300318
* Split sql by statement.
301319
* Try to collect candidates in as small a range as possible.
302320
*/
321+
const splitListener = this.splitListener;
303322
this.listen(splitListener, originParseTree);
323+
304324
const statementCount = splitListener.statementsContext?.length;
305325
const statementsContext = splitListener.statementsContext;
306326
let tokenIndexOffset = 0;
307-
let sqlParserIns = this._parser;
308-
let parseTree = originParseTree;
309327

310328
// If there are multiple statements.
311329
if (statementCount > 1) {
@@ -330,14 +348,14 @@ export abstract class BasicSQL<
330348
const isNextCtxValid =
331349
index === statementCount - 1 || !statementsContext[index + 1]?.exception;
332350

333-
if (ctx.stop && ctx.stop.tokenIndex < tokenIndex && isPrevCtxValid) {
351+
if (ctx.stop && ctx.stop.tokenIndex < caretTokenIndex && isPrevCtxValid) {
334352
startStatement = ctx;
335353
}
336354

337355
if (
338356
ctx.start &&
339357
!stopStatement &&
340-
ctx.start.tokenIndex > tokenIndex &&
358+
ctx.start.tokenIndex > caretTokenIndex &&
341359
isNextCtxValid
342360
) {
343361
stopStatement = ctx;
@@ -347,41 +365,67 @@ export abstract class BasicSQL<
347365

348366
// A boundary consisting of the index of the input.
349367
const startIndex = startStatement?.start?.start ?? 0;
350-
const stopIndex = stopStatement?.stop?.stop ?? input.length - 1;
368+
const stopIndex = stopStatement?.stop?.stop ?? inputSlice.length - 1;
351369

352370
/**
353371
* Save offset of the tokenIndex in the range of input
354372
* compared to the tokenIndex in the whole input
355373
*/
356374
tokenIndexOffset = startStatement?.start?.tokenIndex ?? 0;
357-
tokenIndex = tokenIndex - tokenIndexOffset;
375+
inputSlice = inputSlice.slice(startIndex, stopIndex);
376+
}
358377

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+
}
364384

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;
369403

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;
375408

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;
378422
}
379423

380424
return {
381425
parser: sqlParserIns,
382426
parseTree,
383427
tokenIndexOffset,
384-
newTokenIndex: tokenIndex,
428+
newTokenIndex: caretTokenIndex,
385429
};
386430
}
387431

@@ -396,32 +440,41 @@ export abstract class BasicSQL<
396440
caretPosition: CaretPosition
397441
): Suggestions | null {
398442
this.parseWithCache(input);
399-
400443
if (!this._parseTree) return null;
401444

402-
const allTokens = this.getAllTokens(input);
445+
let allTokens = this.getAllTokens(input);
403446
let caretTokenIndex = findCaretTokenIndex(caretPosition, allTokens);
404-
405447
if (!caretTokenIndex && caretTokenIndex !== 0) return null;
406448

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;
408454

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+
}
410469

411-
const {
412-
parser: sqlParserIns,
413-
tokenIndexOffset,
414-
newTokenIndex,
415-
parseTree: c3Context,
416-
} = minimumParser;
417470
const core = new CodeCompletionCore(sqlParserIns);
418471
core.preferredRules = this.preferredRules;
419472

420-
const candidates = core.collectCandidates(newTokenIndex, c3Context);
473+
const candidates = core.collectCandidates(caretTokenIndex, parseTree);
421474
const originalSuggestions = this.processCandidates(
422475
candidates,
423476
allTokens,
424-
newTokenIndex,
477+
caretTokenIndex,
425478
tokenIndexOffset
426479
);
427480

0 commit comments

Comments
 (0)