Java中的ANTLR基本示例

时间:2012-03-20 15:38:34

标签: java compiler-construction antlr

过去几个小时我一直在网上搜索,试图学习一个使用ANTLR的简单例子。但是我很难理解这些例子。是否有任何正文可以在Java中输出:

如果我输入的话 printf("Hello World");

输出应为:

Hello World

如果我的输入是

inx = 1;

它应该给出错误信息。

我正在尝试使用java创建一个c ++编译器(从词汇开始直到语义部分),我真的想知道我应该做什么。

2 个答案:

答案 0 :(得分:5)

这是一种几乎可以满足你想要的语法:

grammar PrintLang;

sentence 
    :    statement
    ;

statement 
    :   functionCall '(' argument ')' ';'
    { 
      if ($functionCall.funName.equals("printf")) {
        System.out.println($argument.arg);
      }
    }
    ;

functionCall returns [String funName]
    :    ID 
    { $funName = $ID.text; }
    ;

argument returns [String arg]
    :   STRING
    { $arg = $STRING.text; }
    ;

ID  :   ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
    ;

WS  :   ( ' '
        | '\t'
        | '\r'
        | '\n'
        ) {$channel=HIDDEN;}
    ;

STRING
    :  '"' ( ESC_SEQ | ~('\\'|'"') )* '"'
    ;

fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;

fragment
ESC_SEQ
    :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
    |   UNICODE_ESC
    |   OCTAL_ESC
    ;

fragment
OCTAL_ESC
    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7')
    ;

fragment
UNICODE_ESC
    :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
    ;

我在AntlrWorks中生成了这个。所有令牌规则都是为我生成的。

这是测试它的java文件。

import org.antlr.runtime.*;


public class PrintIt {
  public static void main(String args[]) {
    String inputString = "printf(\"HelloWorld\");";

    // Create an input character stream from standard in
    ANTLRStringStream input = new ANTLRStringStream(inputString); 
    // Create an ExprLexer that feeds from that stream 
    PrintLangLexer lexer = new PrintLangLexer(input);
    // Create a stream of tokens fed by the lexer 
    CommonTokenStream tokens = new CommonTokenStream(lexer); 
    // Create a parser that feeds off the token stream 
    PrintLangParser plParser = new PrintLangParser(tokens);
    try {
        plParser.sentence();
    } catch (Exception e) {
        e.printStackTrace();
    }
  }
}

你会注意到这个java代码几乎是来自Antlr网站示例的逐字复制/粘贴(我不相信我甚至改变了评论,这就是为什么评论引用标准,但代码实际使用一个字符串)。这是我过去使用的命令行。

bash$ java -cp ./antlr-3.4-complete.jar org.antlr.Tool PrintLang.g
bash$ javac -cp ./:./antlr-3.4-complete.jar PrintIt.java 
bash$ java -cp antlr-3.4-complete.jar:. PrintIt
"HelloWorld"

糟糕,我忘记了我想要打印的字符串不是匹配的标记(“HelloWorld”,包括引号),它是引号中的字符串。

另外,您会注意到我将printf的查找硬编码为字符串比较。实际上,您需要一个包含在给定范围内可访问的符号的环境(相关,请参阅antlr的“范围”构造。更难,但有时有用:创建一个传递给每个解析规则的环境)。

最重要的是:通过搜索SO寻找更多的问题来找到Bart Kiers的答案。他发布了优秀的示例。

答案 1 :(得分:2)

ANTLR这里是解析(和评估)表达式的简单例子。

grammar Expr;

@header {
package test;
import java.util.HashMap;
}

@lexer::header {package test;}

@members {
/** Map variable name to Integer object holding value */
HashMap memory = new HashMap();
}

prog:   stat+ ;

stat:   expr NEWLINE {System.out.println($expr.value);}
    |   ID '=' expr NEWLINE
        {memory.put($ID.text, new Integer($expr.value));}
    |   NEWLINE
    ;

expr returns [int value]
    :   e=multExpr {$value = $e.value;}
        (   '+' e=multExpr {$value += $e.value;}
        |   '-' e=multExpr {$value -= $e.value;}
        )*
    ;

multExpr returns [int value]
    :   e=atom {$value = $e.value;} ('*' e=atom {$value *= $e.value;})*
    ; 

atom returns [int value]
    :   INT {$value = Integer.parseInt($INT.text);}
    |   ID
        {
        Integer v = (Integer)memory.get($ID.text);
        if ( v!=null ) $value = v.intValue();
        else System.err.println("undefined variable "+$ID.text);
        }
    |   '(' e=expr ')' {$value = $e.value;}
    ;

    ID  :   ('a'..'z'|'A'..'Z')+ ;
    INT :   '0'..'9'+ ;
    NEWLINE:'\r'? '\n' ;
    WS  :   (' '|'\t')+ {skip();} ;

但正如我在评论中提到的,C ++很难正确解析。有许多含糊之处,需要*前瞻(ANTLR确实提供)。所以以任何有效的形式这样做是很复杂的。这就是为什么我建议实现像PL/0这样的东西,它是为学生编写的第一个编译器而设计的。 Tiny BASIC也是一个好的开始。通过执行recursive descent,可以在不使用ANTLR等工具的情况下实现这两个功能。我已经在1000行以下实现了(分别用C ++和C#)。

ANTLR虽然是一个很棒的工具,但是一旦你的脑袋绕着递归下降,你可能想要升级到更强大的解析器。我推荐Terrence Parr的书籍ANTLR ReferenceLanguage Implementation Patterns。 ANTLR书将告诉你想要了解的关于ANTLR的一切(加上一些)。第二本书将教你所有解析器和编译器,从递归下降到黑魔法回溯。

可以找到来自类似SO问题的更多资源here。如果您使用的是Lisp或Scheme,则可以查看JScheme,它是用Java编写的(我相信不到1000行)。