ANTLR指南(v3.0)
第一章Hello World
ANTLR是ANother Tool for Language Recognition的缩写“又一个语言识别工具”。从名字上可以看出在ANTLR出现之前已经存在其它语言识别工具了(如LEX[1],YACC[2])。ANTLR的官方定义为:根据一种可以嵌入如Java, C++或C#等辅助代码段的文法,来构筑出相对该文法的识别器,编译器或翻译器的一种语言工具框架。这个定义说明了ANTLR的功能是根据给定文法自动生成编译器,其过程为先编写相应语言的文法然后生成相应语言编译器。定义提到的语言识别器,编译器和翻译器我们以后统称为语法分析器。事实上ANTLR是生成相应语言编译器的源代码,我们还需要编译它。那么ANTLR可以生成哪些方语言的语法分析器源代码语言的代码呢?这是程序员
很关心的问题。幸运的是ANTLR现在已经支持了多种当前流行的开发语言,包括Java、C#、C、C++、Objective-C、Python和 Ruby.1等。你可以根据需要生成其中任何一种语言的语法分析器。本书主要介绍java,C#两种语言,有详细的操作步骤包括如何编译、执行和如何使用ANTLRWorks开发环境编写文法等。读者可以顺利上手,避免实际操作的障碍。后面章节还会指出在Java和C#开发中应注意的细微差别,确保程序的顺利运行。
1.1开发Hello World示例
本章将开发一个简单示例让读者对ANTLR有一个初步的认识,并搭建开发环境以便后续的学习。读者在示例中遇到不懂的地方也不必担心,我们的目的是搭建开发环境学会编译运行语法分析器。用ANTLR开发一个语法分析器大致分三步,第一步:写出要分析内容的文法。第二步:用ANTLR生成相对该文法的语法分析器的代码。第三步:编译运行语法分析器。
和多数编译书籍一样,本章也用解析简单的表达式作为示例。要解析的表达式中有二种数据类型:整数 如“23”, “5” 和字符串 如“Hello World”。表达式中以算术表达式为主也包括赋值表达式。我们列举两个表达式语句:
23+4*(5+1); str=“Hello World”;
第一条语句是一个算术表达式,括号改变了运算顺序,计算结果不赋给任何变量。第二条是一个赋值表达式,将字符串赋给一个变量。后面我们要开发一个语法分析器来分析这两条语句。在开发之前先简单提一下语法树的概念,在语法分析中一般用树来表示语法结构,表达式的语法树是以操作符为根节点操作数为子节点的树形结构,23+4*(5+1)的语法树根据操作符的优先级如下。
图1.1
算术表达式先计算5+1,5+1在括号中操作符的优先级最高在语法树中的深度最大,然后是4*(5+1),最后是23+4*(5+1)。可以看出语法树的求值顺序是从下向上的,先计算深度大的操作符5+1结果为6,然后是4*6结果为24,然后是23+24表达式的结果为47。下面再看一下赋值表达式的语法树结构:
图1.2
赋值操作符“=”做为根节点变量str作为左子树,而字符串表达式“Hello World” 作为右子树。了解了语法树后我们开始录入文法源文件。ANTLR中文法文件是扩展名为“.g”的文本文件,“.g”文件就是我们的源文件。这里新建一个叫“E.g”的文法文件,在文件中输入如下文法定义:
grammar E;
options{ output=AST;}
program : statement + ;
statement: (expression | VAR '=' expression) ';' ;
expression : (multExpr (('+' |'-' ) multExpr)*) | STRING;
multExpr : atom ('*' atom)*;
atom : INT | '(' expression ')';
VAR : ('a'..'z' |'A'..'Z' )+ ;
INT : '0'..'9' + ;
STRING : '"' (('A'..'Z' | 'a'..'z' | ' ') +) '"' ;
WS : (' ' |'\t' |'\n' |'\r' )+ {skip();} ;
文件的第一行grammar E的E为文法的名称它与文件名一致。第二行是文法的设置部分options{ output=AST;},output=AST表示让语法分析器返回包含语法树的信息。第三行开始是文法定义部分,文法是用EBNF1推导式来描述的(有关EBNF会在后面章节中讲解),文法定义中分两大部分以小写字母开头的语法描述和全大写的词法描述。其中每一行都是一个规则(rule)或叫做推导式、产生式,每个规则的左边是文法中的一个名字,代表文法中的
一个抽象概念。中间用一个“:”表示推导关系,右边是该名字推导出的文法形式。下面逐行介绍文法的规则定义:
statement : (expression | VAR '=' expression) ';'
statement代表表达式语句,前面说了语句有两种,在推导式中以“|”分隔代表“或”关系。表达式本身是合法的语句,表达式也可以出现在赋值表达式中组成赋值语句,两种语句都以“;”字符结束。
expression: (multExpr (('+' |'-' ) multExpr)*) | STRING
expression代表表达式,“|”的左边是算术表达式的形式,“|”的右边是字符串表达式。我们通过规则的推导顺序可以看出,规则按操作符的优先级首先推导优先级最低的运算“+”,“-”运算。
multExpr : atom ('*' atom)*;
然后是'*'的运算。为了使示例简单,表达式中没有除法运算。
atom : '(' expression ')' | INT;
最后是“()”运算,括号中又可以是一个表达式,这样也就实现了括号的嵌套关系。以“|” 分隔与括号并列的可以出现参与运算的整型数。
VAR : ('a'..'z' |'A'..'Z' )+ ;
INT : '0'..'9' + ;
STRING : '"' (('A'..'Z' | 'a'..'z' | ' ') +) '"' ;
WS : (' ' |'\t' |'\n' |'\r' )+ {skip();} ;
以大写形式表达的是词法描述部分,VAR表示变量由一个或多个大小写字母组成,INT表示整型,整型由一个或多个0到9的数字组成,STRING表示字符串,和变量类似一个或多个大小写字母组成但要用“"”括起来。WS表示空白,它的作用是可以滤掉空格、TAB、回车换行这样的无意义字符。{skip();}的作用是跳过这些字符。
1.2下载ANTLR
本章我们的目的是运行第一个示例,后面章节会详细介绍文法定义的写法,所以暂时有不清楚的地方不必担心。这里应该注意的是:文法中单词的大小写,ANTLR文法是大小写敏感的,文法名称要和文件名一致。下面我们用ANTLR生成该文法的java分析器代码。我们先下载ANTLR的Runtime和开发环境,到/download.html ANTLR的下载页,为ANTLR的,ANTLR是一个开源项目可以免费下载。图1.1为开发环境ANTLRWorks的下载页面,图1.2为开发环境ANTLR Runtime3.01的下载页面。您的机器上需要安装JDK1.5或更高版本的java SDK。其中ANTLRWorks的下载文件名叫antlrworks-1.1.7.jar安装JDK后可以直接双击运行打开ANTLRWorks开发环境。
(图1.3)ANTLRWorks下载页面
(图1.4)ANTLR Runtime下载页面
(图1.5)ANTLRWorks IDE
1.3 Java字符串长度在线测试环境的运行
下面录入文法文件,运行ANTLRWorks点击“File – New”菜单新建文法文件,在新文件中将前面的文法录入。(我的网站中有本书所有示例源代码,但我建议您还是手工录入一遍。这样您会有更好的学习效果。)录入文法后点击“File – Save” 菜单文件名为“E.g”。然后点击“Generate–Generate Code”,如果ANTLRWorks提示“The grammar has been successfully generated in path…”说明ANTLRWorks成功生成了语法分析器的代码。会在“E.g”的当前目录中生成“ELexer.java”、“EParser.java”、“E.tokens”和“E__.g”四个文件,其中有两个java源文件。“ELexer.java”为词法分析部分的代码,“EParser.java”为语法分析部分的代码。那么为什么会生成java代码呢?ANTLR在不指定目标语言的情况下默认是java语言。我们也可以在文法文件中加入options项指定目标语言。
grammar E;
options { output=AST; language=Java; }
program : statement + ;
…
…
生成了代码后,我们来编译运行语法分析器。刚才生成的是java代码,所以先来看看java如何编译运行。首先要有一个执行程序的main方法,这个类如下:
import org.antlr.runtime.*;
import org.*;
public class run
{
public static void main(String[] args) throws Exception
{
ANTLRInputStream input = new ANTLRInputStream(System.in);
ELexer lexer = new ELexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
EParser parser = new EParser(tokens);
EParser.program_return r = parser.program();
System.out.println(((Tree()).toStringTree());
}
}
把这段代码存入run.java文件中,main方法功能是从命令行接收输入的表达式,通过词法分析和语法分析两个步骤来获得这个表达式的语法树,并以字符串的形式输出语法树的内容。
ANTLRInputStream input = new ANTLRInputStream(System.in);
ELexer lexer = new ELexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
词法分析步骤是从命令行接收输入的表达式,通过ANTLR内部ANTLRInputStream类,生成一个ANTLRInputStream类的实例input,再将input传给ELexer类。ELexer类是词法分析类,把input中的输入内容进行词法分析,词法分析后会产生词号流(token stream)交给语法分析类,为语法分析提拱前提。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论