用ANTLR3实现规则解析----2-grammar概览

一、开始我们的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语法中加入执行动作

此时,我们已经取得了明显的进展--没有写任何java代码,生成了语法解析器、词法解析器。正如你所见,编写一个grammar比自己写解析代码容易多了。你可以认为ANTLR 是一门特定领域的语言,被用来创建“识别器”、“转换器”。
        将识别器变成“转换器”或者“解析器”,我们需要在gramm中加入动作(action).  加什么动作在哪些地方加?。在这个例子中,我们需要如下动作:
       #1 定义一个字典,名叫memory用来存储  key-value 映射。
       #2 对于规则表达式,需要打印他的结果
       #3 对于赋值运算,计算右边表达式的值,存储左边id 到 值的映射
       #4 INT 词法规则,返回他的整数值
       #5 对于ID词法规则,返回memory存储的值。如果变量不存在,发出一个错误信息
       #6 对于括号限定的表达式,返回括号内子表达式的运算值
       #7 如果是两个atom相乘,返回相乘结果
       #8 如果是两个乘法表达式相加,返回加法的结果值
       #9 如果是两个乘法表达式相减,返回减法的结果值
现在我们来用java语言实现这些动作。首先在grammar中定义memory存储变量名到变量值的映射。
@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返回结果。
继续关注规则multExpr::
/** 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的返回值。

expr规则,作为最外层的规则表达式,其结果要么是multExpr规则的结果,或者时多个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在生成的代码中如何体现的?

ANTLR简单的将action插入到 当前执行grammar元素后----------解析器在匹配grammar元素后,必须执行内嵌的action。ANTLR将逐字的将action翻译出来,除非有特殊的属性,或者template reference translations。
ANTLR翻译规则返回一个值,如下方式:
public int multExpr() throws RecognitionException {
int value = 0; // rule return value, $value
...
return value;
}

ANTLR翻译一个队规则引用的标签,比如e=multExpr,为int e = 0;e=multExpr();对于规则返回值的引用,比如$e.value,直接用e替代,当规则只有一个返回值时,当有多个返回值时,用e.value表示。

五、通过AST中间形式,来计算表达式值



你可能感兴趣的:(用ANTLR3实现规则解析----2-grammar概览)