JavaCC.docx

上传人:b****5 文档编号:7962878 上传时间:2023-01-27 格式:DOCX 页数:28 大小:62.59KB
下载 相关 举报
JavaCC.docx_第1页
第1页 / 共28页
JavaCC.docx_第2页
第2页 / 共28页
JavaCC.docx_第3页
第3页 / 共28页
JavaCC.docx_第4页
第4页 / 共28页
JavaCC.docx_第5页
第5页 / 共28页
点击查看更多>>
下载资源
资源描述

JavaCC.docx

《JavaCC.docx》由会员分享,可在线阅读,更多相关《JavaCC.docx(28页珍藏版)》请在冰豆网上搜索。

JavaCC.docx

JavaCC

JavaCC

JavaCC简介简介

TheodoreS.Norvell写的一本《TheJavaCCTutorial》的第一章IntroductiontoJavaCC

JavaCC入门1.      JavaCC和分析器生成程序JavaCC是一个能生成语法和词法分析器的生成程序。

语法和词法分析器是字符串处理软件的重要组件。

编译器和解释器集成了词法和语法分析器来解释那些含有程序的文件,不管怎样,词法和预防分析器被广泛用于各种应用,所以我希望这本书中的示例能阐述清楚。

那么什么是词法和语法分析器呢?

词法分析器能把一串字符切分成一溜叫做token的子串并把它们归类。

看一个用C语言写的小程序。

intmain(){

return0;

}

C编译器的词法分析器会把上面的程序切割成下面的一串token

“int”,“”,“main”,“(”,“)”,

“”,“{”,“\n”,“\t”,“return”

“”,“0”,“”,“;”,“\n”,

“}”,“\n”,“”.

词法分析器还会识别每个token的类型;在这个例子中这个token串的类型可能是

KWINT,SPACE,ID,OPAR,CPAR,

SPACE,OBRACE,SPACE,SPACE,KWRETURN,

SPACE,OCTALCONST,SPACE,SEMICOLON,SPACE,

CBRACE,SPACE,EOF.

EOF这种token表示输入文件结束。

Token流将会传给分析器。

在这个C语言的例子中,分析器并不需要所有的token;本例那些被归为SPACE的token不会传给分析器。

分析器将会分析token流以决定程序的结构。

通常在编译器中,分析器输出一棵代表程序结构的树。

这棵树将会作为编译器语法分析和代码生成的输入的一部分。

考虑某个程序里的一个语句:

fahrenheit=32.0+9.0*celcius/5.0;

分析器根据语法规则分析这个表达式并生成一棵树:

图1fahrenheit=32.0+9.0*celcius/5.0(译注:

原文缺失该图,此图为译者根据前后语境所画)

如果输入不遵循语言的词法或句法规则,词法分析器同时也负责产生出错信息。

JavaCC本身并不是一个词法分析器或是语法分析器,而是一个分析程序生成器。

这就是说它可以根据输入的语言规范输出一个词法和语法分析器。

JavaCC生成的是用Java写成的词法和语法分析器。

见下图TBD:

DIAGRAMTBD

词法和语法分析器本身就是一个冗长而又复杂的组件。

手工编写一个这样的程序需要仔细考虑各种语法规则的相互影响。

比如在一个C的词法分析器中,处理整数的代码和处理浮点常量的代码不能相互独立,因为整数和浮点数的开头都是数字。

而使用像JavaCC这一点分析程序生成器时,处理整数的规则和处理浮点数的是分开书写的,而它们之间公共的代码在生成器处理时被抽取出来了。

模块性的增加意味着语言规范比手写的Java程序更容易编写、阅读和修改。

通过使用JavaCC这样的分析程序生成器,使软件工程师节约了不少时间,同时也增强了所编写软件的质量。

2.      第一个例子——整数相加作为第一个例子,我们把一串数字加起来,像这样

99+42+0+15

我们忽略所有数字和符号间的空格和换行符,除此之外,我们不接受除了10个数字和加号之外的其他字符。

本节后面的代码都出自一个叫“adder.jj”的文件。

这个文件含有符合JavaCC规范的词法和语法说明,用来作为JavaCC的输入。

   

2.1.  选项和类声明文件的第一部分

/*adder.jjAddingupnumbers*/

options{

STATIC=false;

}

PARSERBEGIN(Adder)

classAdder{

publicstaticvoidmain(String[]args)

throwsParseException,TokenMgrError{

Adderparser=newAdder(System.in);

parser.Start();}

}

PARSEREND(Adder)

在第一个注释之后的是选项段;除了STATIC这一项(缺省为true),所有其他的JavaCC选都为默认值。

关于JavaCC选项的更多信息,请参考JavaCC的文档、本书的以后的章节和FAQ。

接下来定义了一个叫做Adder的Java类,但在这你所看到的不是Adder类的全部;JavaCC会在处理时为这个类添加其他代码。

main方法宣称可能在运行时隐式的抛出两个异常:

ParseException  和TokenMgrError;这些类都会由JavaCC生成。

2010-12-1319:

23

回复

叶流征

41位粉丝

铁杆会员

8

3楼

2.2.  详述词法分析器我们待会儿再看那个main函数,现在我们首先来定义一个词法分析器。

在这个简单的例子中,词法分析器的定义只有4行:

SKIP:

  {””}

SKIP:

  {”\n”|”\r”|”\r\n”}

TOKEN:

{

”+”>}

TOKEN:

{

([”0”-”9”])+>}

第一行说明了空格是一个token,但是会被忽略。

所以解析器并不会收到任何单独的空格。

第二行也说了差不多的事情,只不过被忽略的是换行符,换行符会因操作系统而不同。

Unix/Linux采用LF(linefeed)字符;DOS和Windows则用CR+LF(carriage+linefeed),在老的Macintoshes机子上,就用一个回车表示。

我们要告之JavaCC所有的可能,就如上面用一个小竖线”|”把不同的匹配模式隔开。

第三行告诉JavaCC一个单独的加号是一个token,而且给这个Token取了一个名字:

PLUS。

最后一行告诉JavaCC数字的语法并为它们取名为NUMBER。

如果你熟悉Perl或者Java的正则表达式包,就不难明白这些式子的含义。

让我们仔细看一下这个表达式([“0”-“9”])+。

圆括号中的[“0”-“9”]是一个匹配任意数字的正则表达式,这表明unicode编码中的0-9之间的字符都能被匹配。

一个形如(x)+的正则式可以匹配任意重复的x串。

所以表达式([“0”-“9”])+就可以匹配任意连续数字串。

这四行每一行都是一个正则表达式实例(regularexpressionproduction)。

还有一种由词法分析器生成的token,它的名字是EOF,正如其名,它代表了输入的终止。

不能,也不需要任何对EOF的匹配,JavaCC会自动生成它们。

考虑一个包含如下字符串的输入文件:

“123+456\n”

我们定义的词法分析器将会找到7个token:

NUMBER,空格,PLUS,又一个空格,另一个数字,一个换行,然后是EOF。

当然,标记了SKIP的token不会被传到解析器。

所以,解析器只会看到这些东西:

NUMBER,PLUS,NUMBER,EOF

设想一个包含未定义字符的输入文件,例如:

“123–456\n”

在处理完第一个空格之后,我们的可爱的词法分析器将遇到一个不认识的字符:

减号。

由于没有任何token的定义是以减号打头,词法分析器会扔出一个TokenMgrError异常。

现在我们看看另一种情况:

“123++456\n”

我们的词法分析器会提交一个这样的串:

NUMBER,PLUS,PLUS,NUMBER,EOF

词法分析器还没有智能到判断一个token序列是否有意义,这通常是语法分析器的工作。

我们接下来要讨论的解析器会在词法分析器提交第二个PLUS之后发觉这个错误,然后拒绝处理之后的任何token。

所以解析器实际上处理的只有:

NUMBER,PLUS,PLUS

同时,跳过(skip)一个token并不代表忽略(ignore)它。

考虑下列输入:

“123456\n”

词法分析器会识别出3个token:

两个NUMBER和夹在它们中间的空格;然后报错。

2.3.  详述语法分析器语法分析器的定义使用了一种叫BNF范式的东西,这看起来有点像Java的方法定义:

voidStart():

{}

{

)*

}

这个BNF范式声明了一个正确的输入序列的模式。

我们解释一下它的意思:

它以NUMBER开头的序列,以EOF结束,中间存在零个或多个由一个PLUS后面跟一个NUMBER组成的子序列。

正如所见,语法分析器只会检查一个输入序列是否合法,而并没有真的把数字加起来。

待会儿我们还会修改这个语法分析器,但现在我们先让它生成Java组件,然后run起来。

2.4.  生成一个解析器和一个词法分析器我们现在用JavaCC根据我们写好的adder.jj文件生成分析器。

具体怎么做依赖于操作系统。

下面是在WindowsNT,2000和XP上完成的。

首先使用“命令提示符”程序(CMD.EXE)运行JavaCC:

2010-12-1319:

23

回复

叶流征

41位粉丝

铁杆会员

8

4楼

D:

\home\JavaCC-Book\adder>javaccadder.jj

JavaCompilerCompilerVersion2.1(ParserGenerator)

Copyright(c)1996-2001SunMicrosystems,Inc.

Copyright(c)1997-2001WebGain,Inc.

(type"javacc"withnoargumentsforhelp)

Readingfromfileadder.jj...

File"TokenMgrError.java"doesnotexist.Willcreateone.

File"ParseException.java"doesnotexist.Willcreateone.

File"Token.java"doesnotexist.Willcreateone.

File"SimpleCharStream.java"doesnotexist.Willcreateone.

Parsergeneratedsuccessfully.

这个操作生成了七个Java类,每一个在独立的文件中:

l  TokenMgrError是一个简单的错误类;词法分析器用它来侦测错误,父类是Throwable.

l  ParserException是另一个错误类;解析器用它侦测错误,父类是Exception,因此也是Throwable的子类。

l  Token是一个表示token的类。

每个Token对象都有一个整数域kind表示token的类型(PLUS,NUMBER,或者EOF),和一个String域image,存储token所代表的内容。

l  SimpleCharStream是一个把字符串提交给词法分析器的接口转换类。

l  AdderConstants是一个接口,定义了一组在词法分析器和解析器中都要用到的类。

l  AdderTokenManager就是词法分析器。

l  Adder是解析器。

现在我们可以用一个Java编译器编译这些类了:

D:

\home\JavaCC-Book\adder>javac*.java

2.5.  让它跑起来现在我们换个角度来看Adder类的main方法。

staticvoidmain(String[]args)

throwsParseException,TokenMgrError{

Adderparser=newAdder(System.in);

parser.Start();

}

最先注意到main可能会抛出继承自Throwable的两个子类(译注:

TokenMgrError和ParserException)中的任意一个。

这风格不是很好,我们应该捕捉这些异常。

但是为了保持第一个例子简洁(译注:

为了让读者能迅速把握要点,而不是陷入无穷的细节之中),我们忽略了这些东西。

第一个语句创建了一个解析器实例,构建函数使用了自动生成的接受一个java.io.InputStream的重载。

其实还有一个(更好的)接受Reader实例的重载(java建议在处理字符串时尽量使用Reader(Writer)而不是InputStream(OutputStream),这样能更好的避免字符编码带来的问题——译者如是说)。

这个构建函数创建了一个SimpleCharStream对象和一个词法分析器AdderTokenManager的实例。

这样,词法分析器通过SimpleCharStream顺利地获取到了我们的输入。

第二句调用了一个由JavaCC生成的方法Start()。

对语法规范中的每个BNF产生式,JavaCC都会生成一个对应的方法。

这个方法负责尝试在输入序列中寻找符合模式的输入。

例如,调用Start时会使解析器试图寻找一个匹配下面模式的输入序列:

)*

我们可以准备一个合适的输入然后运行这条命令

D:

\home\JavaCC-Book\adder>javaAdder

我们运行程序,输入表达式以后,会出现以下三种不同的情况:

1.         出现词法错误。

本例中,词法错误只出现在遇到未知字符时。

我们可以通过下面的输入引发一个词法错误:

“123-456\n”

这种情况下,程序会抛出一个TokenMrgError异常。

这个异常的message域是:

Exceptioninthread“main”TokenMgrError:

Lexicalerroratline1,column5.Encountered:

“-“(45),after:

“”

2.         出现一个解析错误。

这发生在输入序列不符合Start的BNF范式时。

例如

2010-12-1319:

23

回复

叶流征

41位粉丝

铁杆会员

8

5楼

“123++456\n”

或者

“123456\n”

或者

“\n”

这时,程序会扔出一个ParseException异常。

这种异常的第一条信息分别是:

Exceptioninthread“main”ParseException:

Encountered”+”atline1,column6.

Wasexpecting:

...

3.         输入串符合Start的定义。

这时,程序不抛出任何异常,只会默默的停止。

由于解析器除了挑错什么都不做,所有现在这个程序除了检查输入合法性以外什么都做不了。

在下一节,我们将会做一些改变让它更有用。

2.6.  生成的代码为了了解JavaCC生成的代码是如何工作的,最好的办法是看看它生成的代码。

finalpublicvoidStart()throwsParseException{

jjconsumetoken(NUMBER);

label1:

while(true){

jjconsumetoken(PLUS);

jjconsumetoken(NUMBER);

switch((jjntk==-1)?

jjntk():

jjntk){

casePLUS:

;

break;

default:

jjla1[0]=jjgen;

breaklabel1;

}

}

jjconsumetoken(0);

}

方法jj_consume_token将试图从输入中读取一个指定类型的token,如果得到的token与期望的类型不符,则抛出一个异常。

表达式

(jj_ntk==-1)?

jj_ntk():

jj_ntk

计算下一个未读token的类型。

而最后一行则要求匹配一个类型0的token;JavaCC总是用0来编码EOF类型。

2.7.  增强解析器像上文中提到的start方法一样的,由JavaCC根据BNF文法生成的方法,在默认情况下仅仅是检查了输入是否符合规则。

但是我们可以在BNF中间夹杂Java代码,这些代码将来会被包含在生成的方法中。

JavaCC为我们提供了一个骨架,而我们要让它有血有肉。

下面我们改变adder.jj中的BNF规范,为Start添加一些声明和Java代码。

新的文件叫做adder1.jj。

添加或改变的部分用黑体标出:

intstart()throwsNumberFormatException:

{

Tokent;

inti;

intvalue;

}

{

t=

{i=Integer.parseInt(t.image);}

{value=i;}

t=

{i=Integer.parseInt(t.image);}

{value+=i;}

)*

{returnvalue;}

}

首先,我们定义了BNF产生式的返回类型,这样生成的方法就从void变为int。

然后还声明了NumberFormatException可能会在运行时抛出。

我们定义了三个变量。

变量t是一个Token,Token是一个生成的类用来表示token;Token类的image域记录了匹配的字符串。

当一个token匹配上了一个BNF产生式,我们就能通过赋上一个引用来记下这个Token对象。

像这样

t=

我们可以在BNF产生式的大括号里添加任意的Java语句,这些语句会原封不动的copy到生产的代码里面。

由于更改了Start的返回类型,我们有必要更改一下我们的main函数:

staticvoidmain(String[]args)

throwsParseException,TokenMgrError,NumberFormatException{

Adderparser=newAdder(System.in);

intval=parser.Start();

System.out.println(val);

}

在结束这个例子前,我们再做一点小小的改进。

下面的代码在start中出现了两次:

t=

{i=Integer.parseInt(t.image);}

虽然在这个例子中不会引起太大的差异,仅仅涉及两行代码,但这种重复会导致维护的问题。

所以我们把这两行提出来作为另一个BNF产生式,叫做Primary。

最新的修改依旧用黑体标出。

intstart()throwsNumberFormatException:

{

inti;

intvalue;

}

{

value=Primary()

   

2010-12-1319:

23

回复

叶流征

41位粉丝

铁杆会员

8

6楼

   i=Primary()

   {value+=i;}

)*

{returnvalue;}

}

intPrimary()throwsNumberFormatException:

{

Tokent;

}

{

t=

{returnInteger.parseInt(t.image);}

}

这时我们再来看看JavaCC所生成的代码:

finalpublicintStart()throwsParseException,NumberFormatException{

inti;

intvalue;

value=Primary();

label1:

while(true){

switch((jjntk==-1)?

jjntk():

jjntk){

casePLUS:

;

break;

default:

jjla1[0]=jjgen;

breaklabel1;

}

jjconsumetoken(PLUS);

i=Primary();

value+=i;

}

jjconsumetoken(0);

{if(true)returnvalue;}

thrownewError(”Missingreturnstatementinfunction”);

}

finalpublicintPrimary()throwsParseException,NumberFormatException{

Tokent;

t=jjconsumetoken(NUMBER);

{if(true)returnInteger.parseInt(t.image);}

thrownewError(”Missingreturnstatementinfunction”);

}

待会儿我们还能看到如何向BNF产生式传递参数。

3.      第二个例子:

运算器接下来,我们继续改进我们的adder,使它成为一个简易的四则运算计算器。

第一步,我们让它能够和我们进行交互,把每行作为一个单独的表达式,并计算输出。

稍后,我们会考虑加法之外的其他操作,减法,乘法和除法。

3.1.  选项和类定义calculator0.jj的开头如下:

/*calculator0.jjAninteractivecalculator.*/

options{

STATIC=false;

}

PARSERBEGIN(Calculator)

importjava.io.PrintStream;

classCalculator{

staticvoidmain(String[]args)

throwsParseException,TokenMgrError,NumberFormatException{

Calculatorparser=newCalculator(System.in);

parser.Start(System.out);

}

doublepreviousValue=0.0;

}

PARSEREND(Calculator)

类Calculator的previousValue域用于保存前一行的计算结果,我们的下一版本将允许在表达式中使用美元符号($)表示这个值。

import语句可以写在PARSER_BEGIN和PARSER_END之间,他们将被复制到生成的类文件中,包定义同样也在这时声明。

3.2.  词法定义词法定义的改变不大,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1