亲和的ANTLR语法的介绍
通过例子来逐渐学习ANTLR是最好的。 一个简单计算器常被用来入门,原因很简单:它简单易懂。这有许多给ANTLR的相似例子和教程,但是我会使用我自己的语言来描述一个计算器。首先我们会创建一些可以直接对简单表达式求值的程序。然后,我会生成树结构,并计算这些树来得到同样的答案。
当你知道最终你需要将一个字符输入流分解成多个记号时,则好的开始就是去思考一个表达式的文法结构。
语法向导执行
语言识别
我们接受了包含+,-和*的算术表达式,如3+4*5-1,或是可以增强求值顺序的括号表达式,如(3+4)*5。
全部ANTLR语法都是lexer,语法解析程序和语法树解析程序的子类。既然你应该开始在文法层面思考问题,你因该创建一个文法解析程序的子类。在类的声明之后,你可以使用扩展巴柯斯范式符号指定规则:
class ExprParser extends Parser;
expr: mexpr ((PLUS|MINUS) mexpr)*
;
mexpr
: atom (STAR atom)*
;
atom: INT
| LPAREN expr RPAREN
;
lexer遵从相似的模式,它只需要定义一些操作符和空白符。把lexer放进相同的文件,如expr.g,是要做的最容易的事情:
class ExprLexer extends Lexer;
options {
k=2; // needed for newline junk
charVocabulary='/u0000'..'/u007F'; // allow ascii
}
LPAREN: '(' ;
RPAREN: ')' ;
PLUS : '+' ;
MINUS : '-' ;
STAR : '*' ;
INT : ('0'..'9')+ ;
WS : ( ' '
| '/r' '/n'
| '/n'
| '/t'
)
{$setType(Token.SKIP);}
;
从这个语法定义文件expr.g生成程序(Java),可以运行ANTLR如下:
$ java antlr.Tool expr.g
ANTLR Parser Generator Version 2.7.2 1989-2003 jGuru.com
$
ANTLR 产生什么?
当发现没有必要完成这个教程时,你可能看到ANTLR在识别程序文件里面生成了什么,并发现这很有启发。ANTLR生成了识别程序,这些程序模拟你通过手工递归下推的语法分析程序来创建的东西;而另一方面,yacc及其朋友在模拟下推自动机的时候生成满是整形数的表。
ANTLR 将产生下列文件:
ExprLexer.java
ExprParser.java
ExprParserTokenTypes.java
ExprParserTokenTypes.txt
如果你看一下里面的内容,比如ExprParser.java,你会看到它为文件expr.g中有解析语法定义的每条规则生成一个方法。比如,mexpr合atom规则的代码看起来类似如下代码:
public void mexpr() {
atom();
while ( LA(1)==STAR ) {
match(STAR);
atom();
}
}
public void atom() {
switch ( LA(1) ) { // switch on lookahead token type
case INT :
match(INT);
break;
case LPAREN :
match(LPAREN);
expr();
match(RPAREN);
break;
default :
// error
}
}
注意到规则定义被翻译成了方法调用,而记号定义则被译成match(TOKEN)函数调用。则关于创建一种语法parser中仅有的难事就是计算前瞻信息。
记号类别类定义了你的词法分析程序(lexer)和parser所使用到的所有记号类别数字常量:
// $ANTLR 2.7.2: "expr.g" -> "ExprParser.java"$
public interface ExprParserTokenTypes {
int EOF = 1;
int NULL_TREE_LOOKAHEAD = 3;
int PLUS = 4;
int MINUS = 5;
int STAR = 6;
int INT = 7;
int LPAREN = 8;
int RPAREN = 9;
int WS = 10;
}
测试lexer/parser
为了在实际中使用在ExprParser.java中作为结果的parser,须像如下所示来使用main()函数:
import antlr.*;
public class Main {
public static void main(String[] args) throws Exception {
ExprLexer lexer = new ExprLexer(System.in);
ExprParser parser = new ExprParser(lexer);
parser.expr();
}
}
$ java Main
3+(4*5)
$
给错误的输入:
$ java Main
3++
line 1:3: unexpected token: +
$
或者
$ java Main
3+(4
line 1:6: expecting RPAREN, found 'null'
$