7

Antlr 是否适合在要解析的文本之后从没有 EOF 的流中解析数据?根据我的观察,词法分析器在收到下一个标记的第一个字符之前不会发出当前标记。最重要的是 - 解析器似乎不会发出规则,直到收到下一条规则的第一个令牌。这是我尝试过的一个简单语法:

fox: 'quick' 'brown' 'fox' '\r'? '\n' ;

然后我将生成的解析器与 UnbufferedCharStream 和 UnbufferedTokenStream 一起使用:

  CharStream input = new UnbufferedCharStream(is);
  MyLexer lex = new MyLexer(input);
  lex.setTokenFactory(new CommonTokenFactory(true));
  TokenStream tokens = new UnbufferedTokenStream(lex);
  MyParser parser = new MyParser(tokens);
  MyParser.FoxContext fox = parser.fox();

当流变得“快速”时 - 没有任何反应。

当' b '进来-进入规则' fox '

然后是 ' roun ' - 什么都没有(流中有 2 个标记 - 还没有一个标记是 leser 的!)

只有在“ f ”之后,侦听器才访问第一个标记:“ quick

然后 - “”上什么都没有

在新行(unix)上:访问令牌'棕色'

现在流中包含所有数据(4 个标记),但只有 2 个标记被识别。

我发现为了通过系统推送这些标记,流可以发出 2 个标记,即语法已知的任何标记。它可能是 2 个额外的新行,或者我们说“狐狸”和“棕色”。只有这样标记“ fox ”和“ \n ”才被访问,解析器退出规则“ fox ”并完成解析。

这是一个错误还是一个功能?有没有办法消除这种滞后?

谢谢!

4

3 回答 3

7

ANTLR 4 书原本打算包含一个解析流输入的示例,但我反对它,因为使用自适应无限前瞻解析器将不可避免地产生严重的复杂性。

ANTLR 4 没有保证的前瞻界限(也没有办法告诉它寻找甚至尝试强制执行),因此任何在阻塞流上运行的实现都有可能在不返回有关导致该点的解析的信息的情况下死锁. 除非我首先看到一个中间缓冲区,否则我什至不会接受解析流输入的可能性

  1. 获取所有可用的(或以前未解析的)输入并将其放在 aStringchar[].
  2. 为缓冲区创建一个ANTLRInputStream
  3. 尝试对这个流进行 lex/parse,它最后会有一个隐式的 EOF。

解析的结果将告诉您是放弃该点的结果,还是在有更多数据可用时保留它们以重试:

  • 如果没有出现语法错误,则说明输入已成功解析,您可以在稍后可用时解析输入的下一部分。

  • 如果在使用 EOF 令牌之前报告了语法错误,则实际输入中会出现语法错误,因此您需要处理它(将其报告给用户等)。

  • 如果在使用 EOF 令牌时报告了语法错误,那么额外的输入可能会解决问题 - 忽略当前解析的结果,然后在输入流中有更多数据可用时重试。

于 2013-02-18T03:58:55.007 回答
4

我认为您正确使用了无缓冲流,您看到的是使用这些流的预期结果。但我想你可能对他们有期望,他们没有义务满足。

下面是我们用棍子戳的测试代码。我正在使用System.in输入,所以我修改了语法以说明单词标记之间的换行符。

流媒体.g

grammar Streaming;

fox   : 'quick' NL 'brown' NL 'fox' NL DONE NL;
DONE  : 'done';
NL    : '\r'? '\n';

StreamingTest.java

import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.CommonTokenFactory;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.UnbufferedCharStream;
import org.antlr.v4.runtime.UnbufferedTokenStream;
import org.antlr.v4.runtime.tree.TerminalNode;

public class StreamingTest {
    public static void main(String[] args) throws Exception {
        lex();
        parse();
    }

    private static void lex() {
        System.out.println("-> Reading from lexer:");
        UnbufferedCharStream input = new UnbufferedCharStream(System.in);
        StreamingLexer lexer = new StreamingLexer(input);
        lexer.setTokenFactory(new CommonTokenFactory(true));

        Token t;

        //read each token until hitting input "done"
        while ((t = lexer.nextToken()).getType() != StreamingLexer.DONE){
            if (t.getText().trim().length() == 0){
                System.out.println("-> " + StreamingLexer.tokenNames[t.getType()]);
            } else { 
                System.out.println("-> " + t.getText());
            }
        }
    }

    private static void parse() {
        System.out.println("-> Reading from parser:");
        UnbufferedCharStream input = new UnbufferedCharStream(System.in);
        StreamingLexer lexer = new StreamingLexer(input);
        lexer.setTokenFactory(new CommonTokenFactory(true));

        StreamingParser parser = new StreamingParser(new UnbufferedTokenStream<CommonToken>(lexer));
        parser.addParseListener(new StreamingBaseListener(){
            @Override
            public void visitTerminal(TerminalNode t) {
                if (t.getText().trim().length() == 0){
                    System.out.println("-> " + StreamingLexer.tokenNames[t.getSymbol().getType()]);
                } else { 
                    System.out.println("-> " + t.getText());
                }
            }
        });

        parser.fox();
    }
}

下面是输入和输出的混合,因为它们在上述程序中的词法分析器和解析器中提供/接收。每行输出都带有前缀->。我会解释为什么事情是在那之后的样子。

输入输出

-> Reading from lexer:
quick
-> quick
brown
-> NL
-> brown
fox
-> NL
-> fox
done
-> NL
-> Reading from parser:
quick
brown
-> quick
-> NL
fox
-> brown
-> NL
done
-> fox
-> NL

-> done

-> NL

我注意到的第一件事是词法分析器立即收到quick NL输入,但只为quick. 这种差异的原因是UnbufferedCharStream提前读取了一个字符(即使它NL为我准备了一个非常好的令牌!),因为它不会位于一个空的预读字符缓冲区中。唉,未缓冲的流被缓冲了。根据类本身的 Javadoc 注释:

这里的“无缓冲”是指它不缓冲所有数据,而不是按需加载char。

这种额外的读取转化为在流上等待更多输入,这解释了为什么词法分析器对于其余输入来说是一个标记。

现在转到解析器。为什么它落后于词法分析器的两个标记?简单:因为UnbufferedTokenStream也不会坐在空的前瞻缓冲区上。但是它不能创建下一个标记,直到 a) 它有一个来自词法分析器的备用标记和 b) 词法分析器UnbufferedCharStream读取它自己的前瞻字符。实际上,它是词法分析器的单字符“滞后”加上单标记“滞后”。

看来,在 ANTLR v4 中获得“无延迟”的按需数据流意味着自己编写。但在我看来,现有的流按预期工作。


Antlr 是否适合在要解析的文本之后从没有 EOF 的流中解析数据?

我还不能对 ANTLR 4 充满信心地回答这个问题。编写一个在需要之前不会提前缓冲的令牌流似乎很容易(覆盖UnbufferedTokenStream'sconsume以跳过调用sync),但是无论任何人的缓冲如何,这些字符流都会被自己提前读取的类调用。或者看起来是这样。我会尽我所能继续深入研究,但这可能需要学习官方的方法来做到这一点。

于 2013-02-15T07:21:06.063 回答
2

显然问题的根源不在 Unbuffered*Streams 中。它在解释器中,例如 LexerATNSimulator.execATN() 方法。该方法将词法分析器解释为状态机,一旦next的第一个字符从一个标签移动到另一个标签标签被消费。ParserATNSimulator 中使用了类似的算法,它处理 Lexer 识别的令牌。这就是造成双重滞后的原因。所以,现在我非常有信心现在实现的 Antlr 4 不能用于解析连续的交互式数据。与 Flex/Bison 不同,其中词法分析器在最后一个字符可能与标签匹配时立即返回标签。结果 - parse() 函数在匹配语法的数据部分到达时结束。这提供了读取确切数据量的很好的能力,当大小没有另外定义时,由数据结构确定。

于 2013-02-18T03:19:35.823 回答