使用Antlr解析永无止境的流中的数据

And*_*eyP 6 sockets stream eof antlr4

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

fox: 'quick' 'brown' 'fox' '\r'? '\n' ;
Run Code Online (Sandbox Code Playgroud)

然后我使用生成的解析器与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();
Run Code Online (Sandbox Code Playgroud)

当流" 快速 "时 - 没有任何反应.

当' b '进来时 - 进入规则' 狐狸 '

然后' roun ' - 什么都没有(2个令牌在流中 - 没有人知道它们!)

只有在' f '之后,听众才会访问第一个令牌:' quick '

然后 - ' ' 没什么

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

现在,流具有所有数据(4个令牌),但只能识别2个令牌.

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

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

谢谢!

Sam*_*ell 6

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

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

  1. 取所有可用(或先前未解除的)输入并将其放入Stringchar[].
  2. ANTLRInputStream为缓冲区创建一个.
  3. 尝试lex/parse这个流,最后会有一个隐含的EOF.

解析的结果将告诉您是否将结果丢弃到该点,或者在有更多数据可用时保持它们重试:

  • 如果没有语法错误发生,则输入已成功解析,并且您可以在稍后可用时解析输入的下一部分.

  • 如果报告一个语法错误之前的EOF令牌被消耗,然后会出现语法错误,在实际输入,所以你要处理它(报告给用户,等...).

  • 如果在使用EOF令牌的位置报告语法错误,则可以使用其他输入来解决问题 - 忽略当前解析的结果,然后再次从输入流重试数据.

  • 我应该指出,当“交互式”是一个更好的术语时,Sam 正在使用“流媒体”。ANTLR v4 可以毫无问题地处理永无休止的套接字数据。我们只是决定无法使其适用于交互式计算器等。 (2认同)

use*_*210 4

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

下面是我们用棍子戳的测试代码。我用于System.in输入,因此我修改了语法以考虑单词标记之间的换行符。

流媒体.g

grammar Streaming;

fox   : 'quick' NL 'brown' NL 'fox' NL DONE NL;
DONE  : 'done';
NL    : '\r'? '\n';
Run Code Online (Sandbox Code Playgroud)

流测试.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();
    }
}
Run Code Online (Sandbox Code Playgroud)

下面是上面程序中的词法分析器和解析器提供/接收的输入和输出的混合。每行输出都以->. 之后我会解释为什么事情会变成这样。

输入输出

-> 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
Run Code Online (Sandbox Code Playgroud)

我注意到的第一件事是词法分析器立即收到quick NL输入,但只提供了 的令牌quick。造成这种差异的原因是,UnbufferedCharStream又预读了一个字符(尽管它已经NL为我准备好了一个完美的标记!),因为它不会位于空的预读字符缓冲区上。唉,未缓冲的流已被缓冲。根据类本身的 Javadoc 注释:

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

这一额外的读取转化为等待流上的更多输入,这解释了为什么词法分析器比其余输入落后一个标记。

现在进入解析器。为什么它比词法分析器的标记落后两个标记?简单:因为UnbufferedTokenStream也不会坐在空的前瞻缓冲区上。但它无法创建下一个标记,直到 a) 它从词法分析器获得了备用标记,并且 b) 词法分析器UnbufferedCharStream读取了自己的先行字符。实际上,它是词法分析器的单字符“滞后”加上单标记“滞后”。

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


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

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

  • 您的代码直接使用 ANTLR 4 的阻塞输入流(“System.in”),我***强烈***不鼓励这样做,因为几乎不可能确定生成的代码是否会以可靠的方式运行。请参阅我的回答以获取更多信息。 (3认同)