当前位置: 首页 > 知识库问答 >
问题:

为什么ANTLR省略了最后一个令牌*而*不产生错误?

尤俊誉
2023-03-14
grammar Query;

startExpression
  : WS? expression WS? EOF
  ;

expression
  | maybeDefaultBooleanExpression
  ;

maybeDefaultBooleanExpression
  : defaultBooleanExpression
  | queryFragment
  ;

defaultBooleanExpression
  : nested += queryFragment (WS nested += queryFragment)+
  ;

queryFragment
  : unquotedQuery
  | quotedQuery
  ;

unquotedQuery
  : UNQUOTED
  ;

quotedQuery
  : QUOTED
  ;

UNQUOTED
  : UnquotedStartChar
    UnquotedChar*
  ;

fragment
UnquotedStartChar
  : EscapeSequence
  | ~( ' ' | '\r' | '\t' | '\u000C' | '\n' | '\\' | ':'
     | '"' | '\u201C' | '\u201D'    // DoubleQuote
     | '\'' | '\u2018' | '\u2019'   // SingleQuote
     | '(' | ')' | '[' | ']' | '{' | '}' | '~'
     | '&' | '|' | '!' | '^' | '?' | '*' | '/' | '+' | '-' | '$' )
  ;

fragment
UnquotedChar
  : EscapeSequence
  | ~( ' ' | '\r' | '\t' | '\u000C' | '\n' | '\\' | ':'
     | '"' | '\u201C' | '\u201D'    // DoubleQuote
     | '\'' | '\u2018' | '\u2019'   // SingleQuote
     | '(' | ')' | '[' | ']' | '{' | '}' | '~'
     | '&' | '|' | '!' | '^' | '?' | '*' )
  ;

QUOTED
  : '"'
    QuotedChar*
    '"'
  ;

fragment
QuotedChar
  : ~( '\\'
     |  | '\u201C' | '\u201D'      // DoubleQuote
     | '\r' | '\n' | '?' | '*' )
  ;


WS : ( ' ' | '\r' | '\t' | '\u000C' | '\n' )+;
    CharStream input = CharStreams.fromString("A \"");
    QueryLexer lexer = new QueryLexer(input);
    lexer.removeErrorListeners();
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    System.out.println(tokens.LT(0));
    System.out.println(tokens.LT(1));
    System.out.println(tokens.LT(2));
    System.out.println(tokens.LT(3));
java.lang.StringIndexOutOfBoundsException: String index out of range: 4

    at java.lang.String.checkBounds(String.java:385)
    at java.lang.String.<init>(String.java:462)
    at org.antlr.v4.runtime.CodePointCharStream$CodePoint8BitCharStream.getText(CodePointCharStream.java:160)
    at org.antlr.v4.runtime.Lexer.notifyListeners(Lexer.java:360)
    at org.antlr.v4.runtime.Lexer.nextToken(Lexer.java:144)
    at org.antlr.v4.runtime.BufferedTokenStream.fetch(BufferedTokenStream.java:169)
    at org.antlr.v4.runtime.BufferedTokenStream.sync(BufferedTokenStream.java:152)
    at org.antlr.v4.runtime.CommonTokenStream.LT(CommonTokenStream.java:100)
        QueryParser parser = new QueryParser(tokens);
        parser.removeErrorListeners();
        parser.addErrorListener(LoggingErrorListener.get());
        parser.setErrorHandler(new BailErrorStrategy());

        // Performance hack as per the ANTLR v4 FAQ
        parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
        ParseTree expression;
        try
        {
            expression = parser.startExpression();
        }
        catch (Exception e)
        {
            // It catches a StringIndexOutOfBoundsException here.

            parser.reset();
            parser.getInterpreter().setPredictionMode(PredictionMode.LL);
            expression = parser.startExpression();
        }

我得到:

tokens = {org.antlr.v4.runtime.CommonTokenStream@1811} 
 channel = 0
 tokenSource = {MyQueryLexer@1810} 
 tokens = {java.util.ArrayList@1816}  size = 3
  0 = {org.antlr.v4.runtime.CommonToken@1818} "[@0,0:0='A',<13>,1:0]"
  1 = {org.antlr.v4.runtime.CommonToken@1819} "[@1,1:1=' ',<32>,1:1]"
  2 = {org.antlr.v4.runtime.CommonToken@1820} "[@2,3:2='<EOF>',<-1>,1:3]"
 p = 2
 fetchedEOF = true

expression = {MyQueryParser$StartExpressionContext@1813} "[]"
 children = {java.util.ArrayList@1827}  size = 3
  0 = {MyQueryParser$ExpressionContext@1831} "[87]"
  1 = {org.antlr.v4.runtime.tree.TerminalNodeImpl@1832} " "
  2 = {org.antlr.v4.runtime.tree.TerminalNodeImpl@1833} "<EOF>"

在这里,我本来希望得到一个recognitionexception,但不知何故解析成功了,并且在末尾丢失了令牌数据的无效位。

问题是:

然后我进一步钻探,发现令牌#NextToken()抛出了一个异常,文档使它看起来不应该发生这种情况,所以我最终为此提交了一张罚单。

共有1个答案

曹浩波
2023-03-14

直到最近的构建,ANTLR4的自适应机制都具有这样的“特性”:如果令牌流中只有一个可行的替代方案,ANTLR4的自适应机制能够从单个令牌丢失和单个额外令牌解析中恢复。现在最近,显然这种行为已经改变了。因此,如果您像我一样使用旧的构建,您仍然会看到自适应解析。也许帕尔和哈威尔会解决这个问题。

像您一样,我认识到需要一个完美的输入流和零解析错误,不管是否被“忽略”。要创建一个“严格的解析器”,请遵循以下步骤:

>

  • 创建一个名为“StricTerrorStrategy that inherit from/extended DefaulTerrorStrategy”的类。您需要重写Recover、RecoverInline和Sync方法。这里的底线是,我们会对任何出错的地方抛出异常,并且在额外的/丢失的令牌后不尝试重新同步代码。下面是我的C#代码,您的java看起来非常相似:

    public class StrictErrorStrategy : DefaultErrorStrategy
    {
        public override void Recover(Parser recognizer, RecognitionException e)
        {
        IToken token = recognizer.CurrentToken;
        string message = string.Format("parse error at line {0}, position {1} right before {2} ", token.Line, token.Column, GetTokenErrorDisplay(token));
        throw new Exception(message, e);
        }
    
    public override IToken RecoverInline(Parser recognizer)
    {
        IToken token = recognizer.CurrentToken;
        string message = string.Format("parse error at line {0}, position {1} right before {2} ", token.Line, token.Column, GetTokenErrorDisplay(token));
        throw new Exception(message, new InputMismatchException(recognizer));
    }
    
    public override void Sync(Parser recognizer) { /* do nothing to resync */}
    }
    

    创建一个实现单个方法的新lexer:

    public class StrictLexer : <YourGeneratedLexerNameHere>
    {
       public override void Recover(LexerNoViableAltException e)
        {
            string message = string.Format("lex error after token {0} at position {1}", _lasttoken.Text, e.StartIndex);
            throw new ParseCanceledException(BasicEnvironment.SyntaxError);
        }
    }
    

    使用你的lexer和策略:

      AntlrInputStream inputStream = new AntlrInputStream(stream);
      StrictLexer lexer = new BailLexer(inputStream);
      CommonTokenStream tokenStream = new CommonTokenStream(lexer);
      LISBASICParser parser = new LISBASICParser(tokenStream);
      parser.RemoveErrorListeners();
      parser.ErrorHandler = new StrictErrorStrategy();
    

    这是很好的,来自我的一个项目的实际代码,它对语法错误有一个“零容忍规则”。我从特伦斯·帕尔关于ANTLR4的伟大著作中获得了代码和想法。

  •  类似资料:
    • 我希望以下Java代码将字符串拆分为三个项: 然而,我只得到两件。 我必须承认,我并没有深入分析这一点,但对我来说,这似乎违反直觉。Python和C#都在Python中生成三项,如下所示: 在C#中: 我错过了什么? 这是Java 1.8.0\u 101。

    • 问题内容: 关于此的几个问题: 这是好习惯吗? 它将大规模地缩短加载时间吗? 会导致浏览器“崩溃”吗? JavaScript(/ jQuery)中的最后一个函数是否也是如此? 我的意思是这样的: 问题答案: 这是好习惯吗? 手动排除分号不是一个好习惯。这纯粹是因为添加更多样式时很容易忽略,尤其是在团队中工作时: 假设您从以下内容开始: 然后有人添加了一些样式: 突然,另一个开发人员在浪费时间弄清楚

    • 我正在尝试使用授权代码流,而不是我当前使用的隐式授权。下面是这两个流的文档。我正在使用来自我的角度2应用程序的斑点API。前三个步骤进展顺利,我从回调调用中获取代码。但是当我向 https://accounts.spotify.com/api/token 发出帖子呼叫时,我得到CORS异常,因为呼叫返回浏览器禁止的网站。返回页面与单击此链接 https://accounts.spotify.com

    • 2 3+ 我错过了什么,导致了下面的错误? 线程“main”Java.lang.NumberFormatException:空字符串 位于sun.misc.FloatingDecimal.ReadJavaFormatString(FloatingDecimal.Java:1842)位于sun.misc.FloatingDecimal.ParseDouble(FloatingDecimal.Java

    • 问题内容: 我发布此消息是因为该主题刚刚在另一个问题/答案中提出,并且该行为没有得到很好的记录。 考虑数据框 我想获取由column定义的每个组的第一行和最后一行。 我试过了 但是,这并没有给我我所期望的。 如何获得每个组中的实际第一个和最后一个值? 问题答案: 一种选择是使用该方法: 但是,我还没有找到一种将它们整齐地聚合的方法。当然,总是可以使用构造函数: 注意:我明确使用了该属性,否则您必须

    • 我有以下ANTLR 4的语法: 我试图解析以下字符串 代码解析出左侧的ab cd,在我的应用程序中,它将被视为文本字符串。然后,它将解析为一个字符集,在这种情况下,该字符集将转换为任何数字。我的语法对我来说很有用,但我不喜欢将语法作为语法分析器规则(CHAR | DASH),因为它只是被当作一个标记。我希望lexer创建一个字符串,并给我以下标记: 而不是这些 我看了其他的例子,但还没有弄明白。通