让我们开始第一个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;
#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(); } }
@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;} ;
/** 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;})* ;
/** 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;} )* ;
五、通过AST中间形式,来计算表达式值