Java 交互式Antlr

Java 交互式Antlr,java,parsing,antlr,Java,Parsing,Antlr,我正在尝试使用antlr编写一个简单的交互式(使用System.in作为源代码)语言,但我对它有一些问题。我在web上找到的示例都使用每行循环,例如: while(readline) result = parse(line) doStuff(result) 但是如果我写的东西像pascal/smtp/etc,第一行看起来像X requirement呢?我知道它可以在doStuff中检查,但我认为逻辑上它是语法的一部分 或者,如果一个命令被拆分为多行怎么办?我可以试试 while(rea

我正在尝试使用antlr编写一个简单的交互式(使用System.in作为源代码)语言,但我对它有一些问题。我在web上找到的示例都使用每行循环,例如:

while(readline)
  result = parse(line)
  doStuff(result)
但是如果我写的东西像pascal/smtp/etc,第一行看起来像X requirement呢?我知道它可以在doStuff中检查,但我认为逻辑上它是语法的一部分

或者,如果一个命令被拆分为多行怎么办?我可以试试

while(readline)
  lines.add(line)
  try
    result = parse(lines)
    lines = []
    doStuff(result)
  catch
    nop
但有了这个,我也隐藏了真正的错误

或者我每次都可以重新分析所有行,但是:

  • 会很慢的
  • 有些说明我不想运行两次

  • 这可以用ANTLR来完成吗?如果不是,可以用其他方法来完成吗?

    如果您使用System.in作为源,这是一个输入流,为什么不让ANTLR在读取时标记输入流,然后解析标记呢?

    您必须将它放在doStuff中

    例如,如果您正在声明一个函数,解析将返回一个函数,对吗?没有身体,那很好,因为身体会晚一点来。你会做大多数人都会做的事

    杜托写道:

    或者我每次都可以重新分析所有行,但是:

    会很慢的 有些说明我不想运行两次 这可以用ANTLR来完成吗?如果不能,可以用其他方法来完成吗

    是的,ANTLR可以做到这一点。也许不是开箱即用的,但是有一点自定义代码,这当然是可能的。您也不需要为它重新解析整个令牌流

    假设您想逐行解析一种非常简单的语言,其中每一行要么是
    程序
    声明,要么是
    使用
    声明,要么是
    语句

    它应该总是以
    程序
    声明开始,然后是零个或多个
    使用
    声明,然后是零个或多个
    语句
    s<代码>使用声明不能在
    语句
    s之后,并且不能有多个
    程序
    声明

    为简单起见,
    语句
    只是一个简单的赋值:
    a=4
    b=a

    这种语言的ANTLR语法可以如下所示:

    grammar REPL;
    
    parse
      :  programDeclaration EOF
      |  usesDeclaration EOF
      |  statement EOF
      ;
    
    programDeclaration
      :  PROGRAM ID
      ;
    
    usesDeclaration
      :  USES idList
      ;
    
    statement
      :  ID '=' (INT | ID)
      ;
    
    idList
      :  ID (',' ID)*
      ;
    
    PROGRAM : 'program';
    USES    : 'uses';
    ID      : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*;
    INT     : '0'..'9'+;
    SPACE   : (' ' | '\t' | '\r' | '\n') {skip();};
    
    但是,我们当然需要加几张支票。此外,默认情况下,解析器在其构造函数中获取令牌流,但由于我们计划在解析器中逐行滴入令牌,因此我们需要在解析器中创建一个新的构造函数。通过将自定义成员分别放在
    @parser::members{…}
    @lexer::members{…}
    节中,可以在lexer或parser类中添加自定义成员。我们还将添加两个布尔标志,以跟踪
    程序
    声明是否已经发生,以及是否允许
    使用
    声明。最后,我们将添加一个
    process(stringsource)
    方法,该方法为每一行创建一个lexer,该lexer将被提供给解析器

    所有这些看起来都像:

    @parser::members {
    
      boolean programDeclDone;
      boolean usesDeclAllowed;
    
      public REPLParser() {
        super(null);
        programDeclDone = false;
        usesDeclAllowed = true;
      }
    
      public void process(String source) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream(source);
        REPLLexer lexer = new REPLLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        super.setTokenStream(tokens);
        this.parse(); // the entry point of our parser
      } 
    }
    
    现在在语法中,我们将检查几个是否以正确的顺序解析声明。在解析某个声明或语句之后,我们希望从那时起翻转某些布尔标志以允许或不允许声明。这些布尔标志的翻转是通过每个规则的
    @after{…}
    部分完成的,该部分在匹配来自该解析器规则的标记后执行(毫不奇怪)

    现在,您的最终语法文件如下所示(包括一些用于调试的
    System.out.println
    ):

    要运行此测试类,请执行以下操作:

    # generate a lexer and parser:
    java -cp antlr-3.2.jar org.antlr.Tool REPL.g
    
    # compile all .java source files:
    javac -cp antlr-3.2.jar *.java
    
    # run the main class on Windows:
    java -cp .;antlr-3.2.jar Main 
    # or on Linux/Mac:
    java -cp .:antlr-3.2.jar Main
    

    如您所见,您只能声明一次
    程序

    > program A
                             program <- A
    
    > program B
    line 1:0 rule programDeclaration failed predicate: {!programDeclDone}?
    

    您必须从
    程序
    声明开始:

    > uses foo
    line 1:0 rule parse failed predicate: {programDeclDone}?
    

    下面是一个如何解析来自
    系统的输入的示例。在
    中,无需首先一次手动解析一行,也无需在语法上做出重大妥协。我使用的是ANTLR 3.4。ANTLR 4可能已经解决了这个问题。不过,我仍然在使用Antlr3,也许其他有这个问题的人也在使用

    在进入解决方案之前,我遇到了一些障碍,这些障碍使这个看似微不足道的问题难以解决:

    • CharStream
      派生的内置ANTLR类预先消耗整个数据流。显然,交互模式(或任何其他不确定长度的流源)不能提供所有数据
    • 内置的
      BufferedTokenStream
      和派生类不会以跳过的或非通道令牌结束。在交互式设置中,这意味着当前语句不能结束(因此不能执行),直到在使用其中一个类时使用下一个语句或
      EOF
      的第一个标记
    • 在下一个语句开始之前,语句本身的结尾可能是不确定的
    考虑一个简单的例子:

    statement: 'verb' 'noun' ('and' 'noun')*
             ;
    WS: //etc...
    
    无法以交互方式解析单个
    语句
    (并且只能解析单个
    语句
    )。必须启动下一个
    语句
    (即在输入中点击“动词”),或者必须修改语法以标记语句的结尾,例如使用
    ;”

    • 我还没有找到用我的解决方案管理多通道lexer的方法。它不会伤害我,因为我可以用
      skip()
      替换我的
      $channel=HIDDEN
      ,但它仍然是一个值得一提的限制
    • 语法可能需要一个新规则来简化交互式解析
    例如,我的语法的正常切入点是以下规则:

    script    
        : statement* EOF -> ^(STMTS statement*) 
        ;
    
    我的交互式会话无法在
    脚本
    规则下启动,因为它在
    EOF
    之前不会结束。但是它也不能从
    语句
    开始,因为我的树解析器可能会使用
    STMTS

    因此,我专门为您介绍了以下规则
    > uses foo
    line 1:0 rule parse failed predicate: {programDeclDone}?
    
    statement: 'verb' 'noun' ('and' 'noun')*
             ;
    WS: //etc...
    
    script    
        : statement* EOF -> ^(STMTS statement*) 
        ;
    
    interactive
        : statement -> ^(STMTS statement)
        ;
    
    interactive_start
        : first_line
        ;
    
    public class MyInputStream extends ANTLRStringStream {
        private InputStream in;
    
        public MyInputStream(InputStream in) {
            super(new char[0], 0);
            this.in = in;
        }
    
        @Override
        // copied almost verbatim from ANTLRStringStream
        public void consume() {
            if (p < n) {
                charPositionInLine++;
                if (dataAt(p) == '\n') {
                    line++;
                    charPositionInLine = 0;
                }
                p++;
            }
        }
    
        @Override
        // copied almost verbatim from ANTLRStringStream
        public int LA(int i) {
            if (i == 0) {
                return 0; // undefined
            }
            if (i < 0) {
                i++; // e.g., translate LA(-1) to use offset i=0; then data[p+0-1]
                if ((p + i - 1) < 0) {
                    return CharStream.EOF; // invalid; no char before first char
                }
            }
    
            // Read ahead
            return dataAt(p + i - 1);
        }
    
        @Override
        public String substring(int start, int stop) {
            if (stop >= n) {
                //Read ahead.
                dataAt(stop);
            }
            return new String(data, start, stop - start + 1);
        }
    
        private int dataAt(int i) {
            ensureRead(i);
    
            if (i < n) {
                return data[i];
            } else {
                // Nothing to read at that point.
                return CharStream.EOF;
            }
        }
    
        private void ensureRead(int i) {
            if (i < n) {
                // The data has been read.
                return;
            }
    
            int distance = i - n + 1;
    
            ensureCapacity(n + distance);
    
            // Crude way to copy from the byte stream into the char array.
            for (int pos = 0; pos < distance; ++pos) {
                int read;
                try {
                    read = in.read();
                } catch (IOException e) {
                    // TODO handle this better.
                    throw new RuntimeException(e);
                }
    
                if (read < 0) {
                    break;
                } else {
                    data[n++] = (char) read;
                }
            }
        }
    
        private void ensureCapacity(int capacity) {
            if (capacity > n) {
                char[] newData = new char[capacity];
                System.arraycopy(data, 0, newData, 0, n);
                data = newData;
            }
        }
    }
    
        MyLexer lex = new MyLexer(new MyInputStream(System.in));
        TokenStream tokens = new UnbufferedTokenStream(lex);
    
        //Handle "first line" parser rule(s) here.
    
        while (true) {
            MyParser parser = new MyParser(tokens);
            //Set up the parser here.
    
            MyParser.interactive_return r = parser.interactive();
    
            //Do something with the return value.
            //Break on some meaningful condition.
        }