feat: add ErrorStrategy(#230)
* refactor: rename errorHandler to errorListener * feat: add ErrorStrategy to mark context exceptions * test: errorStrategy unit tests
This commit is contained in:
		@ -18,7 +18,7 @@ export * from './lib/impala/ImpalaSqlParserVisitor';
 | 
			
		||||
export { SyntaxContextType } from './parser/common/basic-parser-types';
 | 
			
		||||
 | 
			
		||||
export type * from './parser/common/basic-parser-types';
 | 
			
		||||
export type { SyntaxError, ParseError, ErrorHandler } from './parser/common/parseErrorListener';
 | 
			
		||||
export type { SyntaxError, ParseError, ErrorListener } from './parser/common/parseErrorListener';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @deprecated legacy, will be removed.
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,8 @@ import {
 | 
			
		||||
    WordRange,
 | 
			
		||||
    TextSlice,
 | 
			
		||||
} from './basic-parser-types';
 | 
			
		||||
import ParseErrorListener, { ParseError, ErrorHandler } from './parseErrorListener';
 | 
			
		||||
import ParseErrorListener, { ParseError, ErrorListener } from './parseErrorListener';
 | 
			
		||||
import { ErrorStrategy } from './errorStrategy';
 | 
			
		||||
 | 
			
		||||
interface IParser<IParserRuleContext extends ParserRuleContext> extends Parser {
 | 
			
		||||
    // Customized in our parser
 | 
			
		||||
@ -46,7 +47,7 @@ export default abstract class BasicParser<
 | 
			
		||||
    protected _parseErrors: ParseError[] = [];
 | 
			
		||||
    /** members for cache end */
 | 
			
		||||
 | 
			
		||||
    private _errorHandler: ErrorHandler<any> = (error) => {
 | 
			
		||||
    private _errorListener: ErrorListener<any> = (error) => {
 | 
			
		||||
        this._parseErrors.push(error);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -90,7 +91,7 @@ export default abstract class BasicParser<
 | 
			
		||||
     * Create an antlr4 lexer from input.
 | 
			
		||||
     * @param input string
 | 
			
		||||
     */
 | 
			
		||||
    public createLexer(input: string, errorListener?: ErrorHandler<any>) {
 | 
			
		||||
    public createLexer(input: string, errorListener?: ErrorListener<any>) {
 | 
			
		||||
        const charStreams = CharStreams.fromString(input.toUpperCase());
 | 
			
		||||
        const lexer = this.createLexerFormCharStream(charStreams);
 | 
			
		||||
        if (errorListener) {
 | 
			
		||||
@ -104,7 +105,7 @@ export default abstract class BasicParser<
 | 
			
		||||
     * Create an antlr4 parser from input.
 | 
			
		||||
     * @param input string
 | 
			
		||||
     */
 | 
			
		||||
    public createParser(input: string, errorListener?: ErrorHandler<any>) {
 | 
			
		||||
    public createParser(input: string, errorListener?: ErrorListener<any>) {
 | 
			
		||||
        const lexer = this.createLexer(input, errorListener);
 | 
			
		||||
        const tokenStream = new CommonTokenStream(lexer);
 | 
			
		||||
        const parser = this.createParserFromTokenStream(tokenStream);
 | 
			
		||||
@ -123,9 +124,10 @@ export default abstract class BasicParser<
 | 
			
		||||
     * @param errorListener listen parse errors and lexer errors.
 | 
			
		||||
     * @returns parseTree
 | 
			
		||||
     */
 | 
			
		||||
    public parse(input: string, errorListener?: ErrorHandler<any>) {
 | 
			
		||||
    public parse(input: string, errorListener?: ErrorListener<any>) {
 | 
			
		||||
        const parser = this.createParser(input, errorListener);
 | 
			
		||||
        parser.buildParseTree = true;
 | 
			
		||||
        parser.errorHandler = new ErrorStrategy();
 | 
			
		||||
 | 
			
		||||
        return parser.program();
 | 
			
		||||
    }
 | 
			
		||||
@ -141,7 +143,7 @@ export default abstract class BasicParser<
 | 
			
		||||
        this._lexer = this.createLexerFormCharStream(this._charStreams);
 | 
			
		||||
 | 
			
		||||
        this._lexer.removeErrorListeners();
 | 
			
		||||
        this._lexer.addErrorListener(new ParseErrorListener(this._errorHandler));
 | 
			
		||||
        this._lexer.addErrorListener(new ParseErrorListener(this._errorListener));
 | 
			
		||||
 | 
			
		||||
        this._tokenStream = new CommonTokenStream(this._lexer);
 | 
			
		||||
        /**
 | 
			
		||||
@ -153,6 +155,7 @@ export default abstract class BasicParser<
 | 
			
		||||
 | 
			
		||||
        this._parser = this.createParserFromTokenStream(this._tokenStream);
 | 
			
		||||
        this._parser.buildParseTree = true;
 | 
			
		||||
        this._parser.errorHandler = new ErrorStrategy();
 | 
			
		||||
 | 
			
		||||
        return this._parser;
 | 
			
		||||
    }
 | 
			
		||||
@ -165,7 +168,7 @@ export default abstract class BasicParser<
 | 
			
		||||
     * @param errorListener listen errors
 | 
			
		||||
     * @returns parseTree
 | 
			
		||||
     */
 | 
			
		||||
    private parseWithCache(input: string, errorListener?: ErrorHandler<any>) {
 | 
			
		||||
    private parseWithCache(input: string, errorListener?: ErrorListener<any>) {
 | 
			
		||||
        // Avoid parsing the same input repeatedly.
 | 
			
		||||
        if (this._parsedInput === input && !errorListener) {
 | 
			
		||||
            return this._parseTree;
 | 
			
		||||
@ -175,7 +178,7 @@ export default abstract class BasicParser<
 | 
			
		||||
        this._parsedInput = input;
 | 
			
		||||
 | 
			
		||||
        parser.removeErrorListeners();
 | 
			
		||||
        parser.addErrorListener(new ParseErrorListener(this._errorHandler));
 | 
			
		||||
        parser.addErrorListener(new ParseErrorListener(this._errorListener));
 | 
			
		||||
 | 
			
		||||
        this._parseTree = parser.program();
 | 
			
		||||
 | 
			
		||||
@ -317,6 +320,7 @@ export default abstract class BasicParser<
 | 
			
		||||
                    const parser = this.createParserFromTokenStream(tokenStream);
 | 
			
		||||
                    parser.removeErrorListeners();
 | 
			
		||||
                    parser.buildParseTree = true;
 | 
			
		||||
                    parser.errorHandler = new ErrorStrategy();
 | 
			
		||||
 | 
			
		||||
                    sqlParserIns = parser;
 | 
			
		||||
                    c3Context = parser.program();
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										75
									
								
								src/parser/common/errorStrategy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/parser/common/errorStrategy.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
import { DefaultErrorStrategy } from 'antlr4ts/DefaultErrorStrategy';
 | 
			
		||||
import { Parser } from 'antlr4ts/Parser';
 | 
			
		||||
import { InputMismatchException } from 'antlr4ts/InputMismatchException';
 | 
			
		||||
import { IntervalSet } from 'antlr4ts/misc/IntervalSet';
 | 
			
		||||
import { ParserRuleContext } from 'antlr4ts/ParserRuleContext';
 | 
			
		||||
import { RecognitionException } from 'antlr4ts/RecognitionException';
 | 
			
		||||
import { Token } from 'antlr4ts/Token';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base on DefaultErrorStrategy.
 | 
			
		||||
 * The difference is that it assigns exception to the context.exception when it encounters error.
 | 
			
		||||
 */
 | 
			
		||||
export class ErrorStrategy extends DefaultErrorStrategy {
 | 
			
		||||
    public recover(recognizer: Parser, e: RecognitionException): void {
 | 
			
		||||
        // Mark the context as an anomaly
 | 
			
		||||
        for (
 | 
			
		||||
            let context: ParserRuleContext | undefined = recognizer.context;
 | 
			
		||||
            context;
 | 
			
		||||
            context = context.parent
 | 
			
		||||
        ) {
 | 
			
		||||
            context.exception = e;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Error recovery
 | 
			
		||||
        if (
 | 
			
		||||
            this.lastErrorIndex === recognizer.inputStream.index &&
 | 
			
		||||
            this.lastErrorStates &&
 | 
			
		||||
            this.lastErrorStates.contains(recognizer.state)
 | 
			
		||||
        ) {
 | 
			
		||||
            recognizer.consume();
 | 
			
		||||
        }
 | 
			
		||||
        this.lastErrorIndex = recognizer.inputStream.index;
 | 
			
		||||
        if (!this.lastErrorStates) {
 | 
			
		||||
            this.lastErrorStates = new IntervalSet();
 | 
			
		||||
        }
 | 
			
		||||
        this.lastErrorStates.add(recognizer.state);
 | 
			
		||||
        let followSet: IntervalSet = this.getErrorRecoverySet(recognizer);
 | 
			
		||||
        this.consumeUntil(recognizer, followSet);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public recoverInline(recognizer: Parser): Token {
 | 
			
		||||
        let e: RecognitionException;
 | 
			
		||||
        if (this.nextTokensContext === undefined) {
 | 
			
		||||
            e = new InputMismatchException(recognizer);
 | 
			
		||||
        } else {
 | 
			
		||||
            e = new InputMismatchException(
 | 
			
		||||
                recognizer,
 | 
			
		||||
                this.nextTokensState,
 | 
			
		||||
                this.nextTokensContext
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Mark the context as an anomaly
 | 
			
		||||
        for (
 | 
			
		||||
            let context: ParserRuleContext | undefined = recognizer.context;
 | 
			
		||||
            context;
 | 
			
		||||
            context = context.parent
 | 
			
		||||
        ) {
 | 
			
		||||
            context.exception = e;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Error recovery
 | 
			
		||||
        let matchedSymbol = this.singleTokenDeletion(recognizer);
 | 
			
		||||
        if (matchedSymbol) {
 | 
			
		||||
            recognizer.consume();
 | 
			
		||||
            return matchedSymbol;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.singleTokenInsertion(recognizer)) {
 | 
			
		||||
            return this.getMissingSymbol(recognizer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw e;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -25,16 +25,16 @@ export interface SyntaxError<T> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ErrorHandler will be invoked when it encounters a parsing error.
 | 
			
		||||
 * ErrorListener will be invoked when it encounters a parsing error.
 | 
			
		||||
 * Includes lexical errors and parsing errors.
 | 
			
		||||
 */
 | 
			
		||||
export type ErrorHandler<T> = (parseError: ParseError, originalError: SyntaxError<T>) => void;
 | 
			
		||||
export type ErrorListener<T> = (parseError: ParseError, originalError: SyntaxError<T>) => void;
 | 
			
		||||
 | 
			
		||||
export default class ParseErrorListener implements ANTLRErrorListener<Token> {
 | 
			
		||||
    private _errorHandler;
 | 
			
		||||
    private _errorListener;
 | 
			
		||||
 | 
			
		||||
    constructor(errorListener: ErrorHandler<Token>) {
 | 
			
		||||
        this._errorHandler = errorListener;
 | 
			
		||||
    constructor(errorListener: ErrorListener<Token>) {
 | 
			
		||||
        this._errorListener = errorListener;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    syntaxError(
 | 
			
		||||
@ -49,8 +49,8 @@ export default class ParseErrorListener implements ANTLRErrorListener<Token> {
 | 
			
		||||
        if (offendingSymbol && offendingSymbol.text !== null) {
 | 
			
		||||
            endCol = charPositionInLine + offendingSymbol.text.length;
 | 
			
		||||
        }
 | 
			
		||||
        if (this._errorHandler) {
 | 
			
		||||
            this._errorHandler(
 | 
			
		||||
        if (this._errorListener) {
 | 
			
		||||
            this._errorListener(
 | 
			
		||||
                {
 | 
			
		||||
                    startLine: line,
 | 
			
		||||
                    endLine: line,
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user