一、开始我们的grammar文件
让我们开始第一个antlr的grammar文件,最常见antlr grammar 是一个结合词法分析规则(lexer)、语法分析规则(parser)的表达式。这些规则指定了一个表达式的语法结构和词法结构。
举例说明:一个赋值表达式(x=y)是由一个标识(这里是x) ,紧跟着一个赋值符号(=),再紧跟一个表达式(y),最后以换行符结束。
定义antlr grammar:我们通过使用grammar关键词来定义 antlr grammar
grammar helloworld;
<rules>//下面是具体规则
然后保存到helloworld.g文件,注意文件名必须与grammar 名字一致。
规则定义:符号":"表示开始规则定义,符号"|"表示这条规则还有其他形式
prog:stat+;
stat:expr NEWLINE
| ID '=' expr NEWLINE
| NEWLINE;
从上面定义可以看出,一个语法规则(prog、stat)可以有一个或多个形式。这里prog是一个stat组。stat有三种形式:
#1、expr 紧跟一个换行符
#2、id = expr 换行符
#3、换行符
现在,我们需要定义expr表达式。这表明,这里有一个语法设计模型(a grammar design pattern)来描述“算法表达式”。这个模型
规定了一系列规则,一个来描述运算优先级,一个来描述最低级别的原子表达式(比如整数)。这里expr代表了这个完整的表达式。expr规则将匹配最低优先级操作(+、-),当子表达式匹配到下一个最高优先级的操作时,将与之结合。在这里,高级别操作是multiply。我们称之为multiply规则
expr: multExpr (('+' |'-' ) multExpr)*
;
multExpr
: atom ('*' atom)*
;
atom: INT
| ID
| '(' expr ')'
;
现在我们来定义词法规则,词法规则必须以大写开始,而且代表字面意思而不是一个符号。下面使我们这个例子需要的词法规则定义
ID : ('a'..'z' |'A'..'Z' )+ ;
INT : '0'..'9' + ;
NEWLINE:'\r' ? '\n' ;
WS : (' ' |'\t' |'\n' |'\r' )+ {skip();} ;
WS是这里唯一一个使用了action的规则,skip()告诉antlr丢弃WS匹配的继续需找下一个词
最方便的方式使用antlr grammar是使用ANTLRWorks工具,他提供精致的开发环境。
二、生成可执行文件
现在最关键的是,我们没哟java代码可以执行。我们现在有的只是ANTLR gramm文件。我们需要执行一些命令,将gramm文件转换为代码(请保证,antlr-3.0.jar, antlr-2.7.7, stringtemplate-3.0.jar are 在java虚拟机的 CLASSPATH目录中)。
执行命令:java org.antlr.Tool Expr.g,Antlr将生成一个词法解析器(lexer)和一个语法解析器(parser),这个例子中我们使用的语言是java。更好的事,antlr生成的代码,可读性很高。强烈建议你阅读这些代码,这回帮助阐明Antlr。Antlr将会生成下述文件。
ExprParser.java:递归下降的语法解析器。
Expr.tokens:列出了词名、此类型(INT=6,INT是词名,6是词类型)
ExprLexer.java:递归下降的词法解析器
如果你阅读ExprParser.java文件,你会发现为每个rule都提供了一个方法。为 multiExpr规则生成的代码,如下所示
void multExpr() {
try {
atom();
while ( «next input symbol is * » ) {
match('*' );
atom();
}
}
catch (RecognitionException re) {
reportError(re); // automatic error reporting and recovery
recover(input,re);
}
}
void atom() {
try {
// predict which alternative will succeed
// by looking at next (lookahead) symbol: input.LA(1)
int alt=3;
switch ( «next input symbol » ) {
case INT: alt=1; break;
case ID: alt=2; break;
case '(' : alt=3; break;
default: «throw NoViableAltException»
}
// now we know which alt will succeed, jump to it
switch (alt) {
case 1 : match(INT); break;
case 2 : match(ID); break;
case 3 :
match('(' );
expr(); // invoke rule expr
match(')' );
break;
}
}
catch (RecognitionException re) {
reportError(re); // automatic error reporting and recovery
recover(input,re);
}
}
可以发现,语法规则引用被转换成了方法调用,而词法规则引用被转换成了match(TOKEN) 调用
所有的FOLLOW_multExpr_in_expr160变量,以及pushFollow方法调用都是错误恢复机制的一部分。当出现少词、多词的情况,recognizer将重新同步,通过跳过词直到发现一个词出现在适当的位置。
ANTLR产生了Expr.tokens文件,是为了方便其他grammar需要使用同样的tockens.
测试:
import org.antlr.runtime.*;
public class Test {
public static void main(String[] args) throws Exception {
// Create an input character stream from standard in
ANTLRInputStream input = new ANTLRInputStream(System.in);
// Create an ExprLexer that feeds from that stream
ExprLexer lexer = new ExprLexer(input);
// Create a stream of tokens fed by the lexer
CommonTokenStream tokens = new CommonTokenStream(lexer);
// Create a parser that feeds off the token stream
ExprParser parser = new ExprParser(tokens);
// Begin parsing at rule prog
parser.prog();
}
}
三、向grammar语法中加入执行动作
@header {
import java.util.HashMap;
}
@members {
/** Map variable name to Integer object holding value */
HashMap memory = new HashMap();
}
prog: stat+ ;
stat: // evaluate expr and emit result
// $expr.value is return attribute 'value' from expr call
expr NEWLINE {System.out.println($expr.value);}
// match assignment and stored value
// $ID.text is text property of token matched for ID reference
| ID '=' expr NEWLINE
{memory.put($ID.text, new Integer($expr.value));}
// do nothing: empty statement
| NEWLINE
;
对于需要计算表达式值的规则,通过加入action可以很方便的返回表达式值。所以,每个规则,都要匹配表达式,然后返回表达式结果。我们先以atom为例,这个最简单
atom returns [int value]
: // value of an INT is the int computed from char sequence
INT {$value = Integer.parseInt($INT.text);}
| ID // variable reference
{
// look up value of variable
Integer v = (Integer)memory.get($ID.text);
// if found, set return value else error
if ( v!=null ) $value = v.intValue();
else System.err.println("undefined variable "+$ID.text);
}
// value of parenthesized expression is just the expr value
| '(' expr ')' {$value = $expr.value;}
;
INT atom的结果就是INT词的转换。对ID,我们需要查找他在memory里面的值,如果有值的话,返回整数值,没有,打印错误。atom第三种可能形式递归调用了expr,所以这里不需要计算,只要返回expr()的值。正如你所见,$value是returns语句中定义的返回值变量。$expr.value 是调用expr,expr返回结果。
/** return the value of an atom or, if '*' present, return
* multiplication of results from both atom references.
* $value is the return value of this method, $e.value
* is the return value of the rule labeled with e.
*/
multExpr returns [int value]
: e=atom {$value = $e.value;} ('*' e=atom {$value *= $e.value;})*
;
multExpr规则随意匹配一个atom,后面有一个*操作符 以及一个atom操作数。如果没有*操作符,那么 multExpr的值就是atom的值。如果后面有乘法操作,我们需要更新multExpr的返回值。
/** return value of multExpr or, if '+'|'-' present, return
* multiplication of results from both multExpr references.
*/
expr returns [int value]
: e=multExpr {$value = $e.value;}
( '+' e=multExpr {$value += $e.value;}
| '-' e=multExpr {$value -= $e.value;}
)*
;
四、action在生成的代码中如何体现的?
int value = 0; // rule return value, $value
...
return value;
}
五、通过AST中间形式,来计算表达式值