我从以下几个问题入手介绍ANTLR的相关知识。
1 ANTLR是什么?
ANTLR, ANother Tool for Language Recognition, 是一个可以接受含有语法描述的语言描述符并且生成程序能够识别这些语言所产生的句子。作为一个翻译程序的 一部分,你可以给你的语法附上简单的操作符和行为并且告诉ANTLR如何构造AST并且如何输出它们。ANTLR知道如何使用Java,C++,C#或者Python来生成它们。
ANTLR知道如何构造识别程序并且将语法结构应用到三种不同的输入上:(i) 字符流, (ii) 标记(token)流,(iii) 二维树结构。本质上这些对应了词法分析器(Lexer),解析器(Parser)和Tree Walker。用于制定这些语法(Grammar)的句法(Syntax),被称 为meta-language,在所有情况下是独立的。
一旦你适应了ANTLR或者相应的工具,你将会以另一种眼光来看待编程。很多任务期待一种不同于传统编程语言流派的语言解决方案。举个例子,这些课程的笔记就是用TML编写 的,Terence's Markup Language。我讨厌输入HTML所以我用ANTLR编写了一个简单的翻译程序来转换文本成为HTML或者PDF或者其他我讨厌直接编写的东西。
(以上摘自http://www.cs.usfca.edu/~parrt/course/652/lectures/antlr.html)
2 ANTLR能做些什么?
ANTLR仅仅是一个工具!它帮你通过自动生成单调乏味的组件来构造程序,但并不试图让你创造一个完整的编译器,举个例子,单行的描述。
3 ANTLR怎么搭建环境?
我安装的是插件形式的ANTLR,它的版本为2.7.6.
当其他插件安装一样,比较简单。
或者:
1 、从 http://www.antlr.org/ 上下载 antlr-x.x.x.jar
2 、配置环境变量: classpath=.;x:jdklib ools.jar;x:antlr-x.x.x.jar
3 、在命令提示符环境里在 t.g 所在目录下执行:
java antlr.Tool t.g
4 ANTLR语法文件怎么写?
4.1 Antlr 的主要类:
Antlr 中有主要类有两种(其实还有一种 TreeLexer )
Lexer: 文法分析器类。主要用于把读入的字节流根据规则分段。既把长面条根据你要的尺寸切成一段一段:)并不对其作任何修改。
Parser: 解析器类。主要用于处理经过 Lexer 处理后的各段。一些具体的操作都在这里。
4.2 Antlr 文法文件形式:
Antlr 文件是 *.g 形式,即以 g 为后缀名。
例如: t.g
class P extends Parser;
startRule
: n:NAME
{System.out.println("Hi there, "+n.getText());}
class L extends Lexer;
// one-or-more letters followed by a newline
NAME: ( 'a'……'z'|'A'……'Z' )+ NEWLINE
NEWLINE
: '
' '
' // DOS
| '
' // UNIX 网管u家u.bitsCN.com
;
4.3 具体成分分析:
1 、总体结构
Class P extends Parser
Class L extends Lexer
两行同 JAVA 继承一样, P 继承 Parser 类; L 继承 Lexer 类。每个 .g 文件只能各有一个。
2 、 Lexer 类分析
一般按照:
类型名:匹配的具体规则的形式构成。是分隔字节流的依据。同时可以看到里面可以互相引用。如本例中的类型名 NEWLINE 出现在 NEW 的匹配规则中。
4.4 Parser 类分析
一般按照
起始规则名:
规则实例名:类型名或规则名
{Java 语句……; };
……
的形式构成。
起始规则名:任意。
规则实例名:就象 Java 中“ String s ;”的 s 一样。规则实例名用于在之后的 JAVA 语句中调用。
类型名或规则名:可以是在 Lexer 中定义的类型名,也可以是 Parser 中定义的规则名。感觉就像是 int 与 Integer 的区别。
Java 语句:指当满足当前规则时所执行的语句。 Antlr 会自动嵌入生成的 java 类中。
三、生成 Java 类
会在当前目录下生成如下文件:
L.java : Lexer 文法分析器 java 类。
P.java : Parser 解析器 java 类。
PTokenTypes.java : Lexer 中定义的类型具体化,供 Parser 解析器调用。
PTokenTypes.txt :当外部的(如 t2.g )要调用当前的类型或规则时要用到本文件。
4.5 执行
1 、编写 Main 类
import java.io.*;
class Main {
public static void main(String[] args) {
try {
L lexer = new L(new DataInputStream(System.in));
P parser = new P(lexer);
parser.startRule();
} catch(Exception e) {
System.err.println("exception: "+e);
}
5 再从编写一个表达式解析器文件介绍一下ANTLR语法:
表达式是几乎所有高级编程语言中,都会出现的重要组成部分。因此,如何准确的理解一个表达式,可以说是各种不同的语言所共同面临的问题。表达式千变万化,真正要想正确解释C/C++那样的复杂表达式,是非常困难的,我们这里只从最简单的表达式做起。
假设一个表达式中,只有常量,没有变量。所有的运算只有:“+”、“-”、“*”、“/”、“^”、“%”六种,而且没有括号,只有整数,没有小数。就这么简单,要解释这样的表达式,我们如果要想自己写个程序来解释,只怕也是非常麻烦的吧,而且要写一个功能全面且BUG很少的解释器势必要大费周折。
现在有了ANTLR,我们只需要将定义写清楚,程序就会自动帮我们生成了。可以先下载一个人家现成的文件来看看:expression.g,然后再antlr expression.g生成一堆java文件。
接下来的步骤和前面的也差不多,建一个Main.java,
import java.io.*;
import antlr.CommonAST;
import antlr.collections.AST;
import antlr.debug.misc.ASTFrame;
public class Main {
public static void main(String args[]) {
try {
DataInputStream input = new DataInputStream(System.in);
ExpressionLexer lexer = new ExpressionLexer(input);
ExpressionParser parser = new ExpressionParser(lexer);
parser.expr();
CommonAST parseTree = (CommonAST)parser.getAST();
System.out.println(parseTree.toStringList());
ASTFrame frame = new ASTFrame("The tree", parseTree);
frame.setVisible(true);
ExpressionTreeWalker walker = new ExpressionTreeWalker();
double r = walker.expr(parseTree);
System.out.println("Value: "+r);
} catch(Exception e) { System.err.println("Exception: "+e); }
}
}
执行这个Main.class,输入个表达式给它试一试,比如: 1+2-3*4/5^6;系统应该就能给出正确的答案了:
( - ( + 1 2 ) ( / ( * 3 4 ) ( ^ 5 6 ) ) ) ;
Value: 2.999232
咱们来一句一句的解释一下这个expression.g文件。还是从最简单的几行开始:
class ExpressionLexer extends Lexer; //这是用来声明一个词法分析器,名字叫
//ExpressionLexer
PLUS : '+' ; //加号
MINUS : '-' ; //减号
MUL : '*' ; //乘号
DIV : '/' ; //除号
MOD : '%' ; //求余
POW : '^' ; //开方
SEMI : ';' ; //结束号
//上面这些都太简单了,简直就不需要说明
protected DIGIT : '0'..'9' ; //数字,这是一个受保护的单词,只能被
//词法分析器内部使用
INT : (DIGIT)+ ; //出现了一次以上的数字的词,就是整数,
//它通过受保护的单词:“数字”来定义自己。
//如果DIGIT不是被保护的单词,则词法分析器就会
//无法分辨究竟是数字还是整数了
接下来看语法分析器的代码:
class ExpressionParser extends Parser; //定义一个语法分析器,名字叫ExpressionParser
options { buildAST=true; } //告诉ANTLR,要帮我生成一棵抽象语法树,
//留着以后有用
//接下来的部分就非常复杂了,主要是多出来了两个特殊的符号“!”、“^”
//这两个符号,不是EBNF原有的,而是ANTLR为了生成AST而增加的符号
//“!”,是告诉AST生成程序,不要把自己算进去
//“^”,是告诉AST生成程序,把这个符号,放在一颗树的根部,或者一颗子树的根部
//另外,“*”表示出现0次以上,“+”表示出现一次以上,“?”表示出现0或1次
/*以下为定义一个语法的规则,一般采用递归的形式来规定。
即就是它描述了一个表达式的表达形式。解析和计算表达式的方式有很多种。在使用递归下降的解析器时,表达式被视为递归的数据结构—— 表达式由其本身来定义。假定表达式只能使用+、-、*、/和圆括号,那么所有的表达式可以用下面的规则来定义:
表达式 项 [+项] [–项]
项因数[*因数] [/因数]
因数变量、数字或者(表达式)
方括号里面表示可选元素,而箭头表示箭头前面的元素由箭头后面的元素定义产生。实际上,该规则通常被称为表达式的生成规则。因此,对于项的定义可以这样表述:“项由因数乘以因数或者因数除以因数产生。”需要注意的是,运算符的优先级已经隐含在表达式的定义中。 */
expr : sumExpr SEMI!; //“;”作为结束符,不放入AST
sumExpr : prodExpr ((PLUS^|MINUS^) prodExpr)* ; //“+”“-”作为计算符号
//放在树的顶部
prodExpr : powExpr ((MUL^|DIV^|MOD^) powExpr)* ; //剩下的就不解释了,都能明白的
powExpr : atom (POW^ atom)? ;
atom : INT ;
再来看AST计算器的代码。这“AST计算器”是我起的名字,也就是通过对一个生成的抽象语法树,递归求值,得到最后的结果。
{import java.lang.Math;} //ExpressionTreeWalker要用到的
class ExpressionTreeWalker extends TreeParser; //声明一个树计算器
expr returns [double r] //有一个方法叫expr
//它的返回值是double类型
{double a,b; r=0; } //嵌入的代码,后面要用到
: #(PLUS a=expr b=expr) { r=a+b; } //以下就是计算各种算符,不用多说了
| #(MINUS a=expr b=expr) { r=a-b; }
| #(MUL a=expr b=expr) { r=a*b; }
| #(DIV a=expr b=expr) { r=a/b; }
| #(MOD a=expr b=expr) { r=a%b; }
| #(POW a=expr b=expr) { r=Math.pow(a,b); }
| i:INT { r=(double)Integer.parseInt(i.getText()); }
;
5.1 完整的expression.g文件:
class ExpressionParser extends Parser;
options { buildAST=true; }
expr : sumExpr SEMI;
sumExpr : prodExpr ((PLUS^|MINUS^) prodExpr)*;
prodExpr : powExpr ((MUL^|DIV^|MOD^) powExpr)* ;
powExpr : atom (POW^ atom)? ;
atom : INT ;
class ExpressionLexer extends Lexer;
PLUS : '+' ;
MINUS : '-' ;
MUL : '*' ;
DIV : '/' ;
MOD : '%' ;
POW : '^' ;
SEMI : ';' ;
protected DIGIT : '0'..'9' ;
INT : (DIGIT)+ ;
{import java.lang.Math;}
class ExpressionTreeWalker extends TreeParser;
expr returns [double r]
{ double a,b; r=0; }
: #(PLUS a=expr b=expr) { r=a+b; }
| #(MINUS a=expr b=expr) { r=a-b; }
| #(MUL a=expr b=expr) { r=a*b; }
| #(DIV a=expr b=expr) { r=a/b; }
| #(MOD a=expr b=expr) { r=a%b; }
| #(POW a=expr b=expr) { r=Math.pow(a,b); }
| i:INT { r=(double)Integer.parseInt(i.getText()); }
6 ANTLR使用中的一些使用心得:
1):在此法分析中,比如要描述一个“>=”与”>”时,如果用
BEQUAL:('>''=');
BIGER:”>”;
当语法文件进行此法分析的时,当扫描到一个”>”形式时,不知道是将其当BEQUAL还是当BIGER符号处理,即出现了冲突,那么可以采用以下这种形式定义:
BEQUAL:('>''=')=>('>''=')|'>'{ $setType(BIGER); };//它的形式为: (...)=>(...)|(...)。这相当于一般语言中的三元表达式:(1)?(2):(3)。如果式1为真,则返回式2的值,否则返回式3的值。
2):在ANTLR中一个规则相当与JAVA语言中的一个函数,因此它可以有传参和返回值,例如:
expr[HashMap hm] returns [String s]//即相当于JAVA中的:
public String expr(HashMap hm){…}
3):ANTLR中可以内嵌生成的目标语言 如:{import java.lang.Math;}