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

时间:2013-02-13 22:51:24

标签: sockets stream eof antlr4

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个令牌 - 其中没有一个是已知的!)

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

然后 - ' ox '

上没有任何内容 新行(unix)上的

:访问令牌' brown '

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

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

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

谢谢!

3 个答案:

答案 0 :(得分:5)

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

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

  1. 获取所有可用(或以前未解析)的输入,并将其放在Stringchar[]
  2. 为缓冲区创建ANTLRInputStream
  3. 尝试lex / parse这个流,最后会有一个隐含的EOF。
  4. 解析的结果将告诉您是否将结果丢弃到该点,或者在有更多数据可用时保持重试:

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

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

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

答案 1 :(得分:3)

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

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

Streaming.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();
    }
}

下面是输入和输出的混合,因为它们是从上面程序中的词法分析器和解析器提供的。每行输出都以->为前缀。我会解释为什么事情就像那之后的那样。

输入&amp;输出

-> 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在课堂上的评论:

  

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

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

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

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


  

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

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

答案 2 :(得分:2)

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