JavaCCWord文件下载.docx
- 文档编号:21098925
- 上传时间:2023-01-27
- 格式:DOCX
- 页数:28
- 大小:62.59KB
JavaCCWord文件下载.docx
《JavaCCWord文件下载.docx》由会员分享,可在线阅读,更多相关《JavaCCWord文件下载.docx(28页珍藏版)》请在冰豆网上搜索。
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:
{””}
{”\n”|”\r”|”\r\n”}
TOKEN:
{<
PLUS:
”+”>
NUMBER:
([”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():
{}
{
<
NUMBER>
(
PLUS>
)*
EOF>
这个BNF范式声明了一个正确的输入序列的模式。
我们解释一下它的意思:
它以NUMBER开头的序列,以EOF结束,中间存在零个或多个由一个PLUS后面跟一个NUMBER组成的子序列。
正如所见,语法分析器只会检查一个输入序列是否合法,而并没有真的把数字加起来。
待会儿我们还会修改这个语法分析器,但现在我们先让它生成Java组件,然后run起来。
2.4.
生成一个解析器和一个词法分析器我们现在用JavaCC根据我们写好的adder.jj文件生成分析器。
具体怎么做依赖于操作系统。
下面是在WindowsNT,2000和XP上完成的。
首先使用“命令提示符”程序(CMD.EXE)运行JavaCC:
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.
ParseException.java"
Token.java"
SimpleCharStream.java"
Parsergeneratedsuccessfully.
这个操作生成了七个Java类,每一个在独立的文件中:
l
TokenMgrError是一个简单的错误类;
词法分析器用它来侦测错误,父类是Throwable.
ParserException是另一个错误类;
解析器用它侦测错误,父类是Exception,因此也是Throwable的子类。
Token是一个表示token的类。
每个Token对象都有一个整数域kind表示token的类型(PLUS,NUMBER,或者EOF),和一个String域image,存储token所代表的内容。
SimpleCharStream是一个把字符串提交给词法分析器的接口转换类。
AdderConstants是一个接口,定义了一组在词法分析器和解析器中都要用到的类。
AdderTokenManager就是词法分析器。
Adder是解析器。
现在我们可以用一个Java编译器编译这些类了:
javac*.java
2.5.
让它跑起来现在我们换个角度来看Adder类的main方法。
staticvoidmain(String[]args)
最先注意到main可能会抛出继承自Throwable的两个子类(译注:
TokenMgrError和ParserException)中的任意一个。
这风格不是很好,我们应该捕捉这些异常。
但是为了保持第一个例子简洁(译注:
为了让读者能迅速把握要点,而不是陷入无穷的细节之中),我们忽略了这些东西。
第一个语句创建了一个解析器实例,构建函数使用了自动生成的接受一个java.io.InputStream的重载。
其实还有一个(更好的)接受Reader实例的重载(java建议在处理字符串时尽量使用Reader(Writer)而不是InputStream(OutputStream),这样能更好的避免字符编码带来的问题——译者如是说)。
这个构建函数创建了一个SimpleCharStream对象和一个词法分析器AdderTokenManager的实例。
这样,词法分析器通过SimpleCharStream顺利地获取到了我们的输入。
第二句调用了一个由JavaCC生成的方法Start()。
对语法规范中的每个BNF产生式,JavaCC都会生成一个对应的方法。
这个方法负责尝试在输入序列中寻找符合模式的输入。
例如,调用Start时会使解析器试图寻找一个匹配下面模式的输入序列:
(<
)*<
我们可以准备一个合适的输入然后运行这条命令
javaAdder<
input.txt
我们运行程序,输入表达式以后,会出现以下三种不同的情况:
1.
出现词法错误。
本例中,词法错误只出现在遇到未知字符时。
我们可以通过下面的输入引发一个词法错误:
“123-456\n”
这种情况下,程序会抛出一个TokenMrgError异常。
这个异常的message域是:
Exceptioninthread“main”TokenMgrError:
Lexicalerroratline1,column5.Encountered:
“-“(45),after:
“”
出现一个解析错误。
这发生在输入序列不符合Start的BNF范式时。
例如
5楼
或者
“\n”
这时,程序会扔出一个ParseException异常。
这种异常的第一条信息分别是:
Exceptioninthread“main”ParseException:
Encountered”+”atline1,column6.
Wasexpecting:
...
3.
输入串符合Start的定义。
这时,程序不抛出任何异常,只会默默的停止。
由于解析器除了挑错什么都不做,所有现在这个程序除了检查输入合法性以外什么都做不了。
在下一节,我们将会做一些改变让它更有用。
2.6.
生成的代码为了了解JavaCC生成的代码是如何工作的,最好的办法是看看它生成的代码。
finalpublicvoidStart()throwsParseException{
jjconsumetoken(NUMBER);
label1:
while(true){
jjconsumetoken(PLUS);
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;
{value+=i;
{returnvalue;
首先,我们定义了BNF产生式的返回类型,这样生成的方法就从void变为int。
然后还声明了NumberFormatException可能会在运行时抛出。
我们定义了三个变量。
变量t是一个Token,Token是一个生成的类用来表示token;
Token类的image域记录了匹配的字符串。
当一个token匹配上了一个BNF产生式,我们就能通过赋上一个引用来记下这个Token对象。
像这样
我们可以在BNF产生式的大括号里添加任意的Java语句,这些语句会原封不动的copy到生产的代码里面。
由于更改了Start的返回类型,我们有必要更改一下我们的main函数:
throwsParseException,TokenMgrError,NumberFormatException{
intval=parser.Start();
System.out.println(val);
在结束这个例子前,我们再做一点小小的改进。
下面的代码在start中出现了两次:
{i=Integer.parseInt(t.image);
}
虽然在这个例子中不会引起太大的差异,仅仅涉及两行代码,但这种重复会导致维护的问题。
所以我们把这两行提出来作为另一个BNF产生式,叫做Primary。
最新的修改依旧用黑体标出。
value=Primary()
<
6楼
i=Primary()
{value+=i;
intPrimary()throwsNumberFormatException:
Tokent;
t=<
{returnInteger.parseInt(t.image);
这时我们再来看看JavaCC所生成的代码:
finalpublicintStart()throwsParseException,NumberFormatException{
inti;
intvalue;
value=Primary();
switch((jjntk==-1)?
jjntk():
jjntk){
i=Primary();
value+=i;
{if(true)returnvalue;
thrownewError(”Missingreturnstatementinfunction”);
finalpublicintPrimary()throwsParseException,NumberFormatException{
t=jjconsumetoken(NUMBER);
{if(true)returnInteger.parseInt(t.image);
待会儿我们还能看到如何向BNF产生式传递参数。
第二个例子:
运算器接下来,我们继续改进我们的adder,使它成为一个简易的四则运算计算器。
第一步,我们让它能够和我们进行交互,把每行作为一个单独的表达式,并计算输出。
稍后,我们会考虑加法之外的其他操作,减法,乘法和除法。
3.1.
选项和类定义calculator0.jj的开头如下:
/*calculator0.jjAninteractivecalculator.*/
PARSERBEGIN(Calculator)
importjava.io.PrintStream;
classCalculator{
Calculatorparser=newCalculator(System.in);
parser.Start(System.out);
doublepreviousValue=0.0;
PARSEREND(Calculator)
类Calculator的previousValue域用于保存前一行的计算结果,我们的下一版本将允许在表达式中使用美元符号($)表示这个值。
import语句可以写在PARSER_BEGIN和PARSER_END之间,他们将被复制到生成的类文件中,包定义同样也在这时声明。
3.2.
词法定义词法定义的改变不大,
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- JavaCC
![提示](https://static.bdocx.com/images/bang_tan.gif)