撰写本文的目的:
给出使用JFlex、JavaCUP来为一个计算器建立分析器的示例的完整代码,使读者能充分领会JavaCUP的使用方法。虽然本文仅仅给出了计算器的代码,但只要你会写你的语言的翻译模式,则只要照抄这个模版,并改改相应动作就可以了。
引用到的资料:
《CUP User's Manual》,作者:Scott E. Hudson地址为李老师那里下载下来的JavaCUP-11a.rar\CUP-develop.tar.gz\develop\manual.html,有详细的英文说明和示例代码,但有很多错。本文中简称为《手册》。
《使用CUP进行语法分析》,摘自Apollo的博客,貎似是转载的(竟然不注明[转]和真实出处?!,BS之~),作者待考。有详尽的解释,但缺乏示例代码。本文中简称为《语法分析》。
详细步骤:
1、准备工作。
JavaCUP和JFlex一样,压缩包里边有许多的文件夹和文件,我不知道正统的做法是否要求使用javaCUP也像Jflex一样要设置一堆path啊、classpath啊、jflex_home之类的环境变量,但如果你像我一样只打算用它几次,你只要执行下述的两个简单步骤就可以了,它并不需要你设置任何的环境变量(以下假设你的工作目录是work\):
1) 将JavaCUP压缩包里的java-cup-11a.jar解压到work\下。
2) 将JavaCUP压缩包里的CUP-develop.tar.gz\develop\src下的java_cup文件夹整个解压到work\下。
现在你可以使用JavaCUP了。
2、为这个计算器写一个词法分析器。或者用JFlex生成一个词法分析器
两种方法都可以生成词法分析器,其中,直接写分析器的代码如下:
scanner.java//Simple Example Scanner Class
//scanner.javaimportjava_cup.runtime.*;
importjava.io.*;
//import sym;
publicclassscannerimplementsjava_cup.runtime.Scanner{
/**//*single lookahead character*/protectedstaticintnext_char;
//since cup v11 we use SymbolFactories rather than SymbolsprivateSymbolFactory sf=newDefaultSymbolFactory();
privatestaticFileReader fileReader;
publicscanner(FileReader fr){
this.fileReader=fr;
}
/**//*advance input by one character*/protectedstaticvoidadvance()
throwsjava.io.IOException
{ next_char=fileReader.read(); }
/**//*initialize the scanner*/publicstaticvoidinit()
throwsjava.io.IOException
{ advance(); }
/**//*recognize and return the next complete token*/publicSymbol next_token()
throwsjava.io.IOException
{
for(;;)
switch(next_char)
{
case'0':case'1':case'2':case'3':case'4':
case'5':case'6':case'7':case'8':case'9':
/**//*parse a decimal integer*/inti_val=0;
do{
i_val=i_val*10+(next_char-'0');
advance();
}while(next_char>='0'&&next_char<='9');
returnsf.newSymbol("NUMBER",sym.NUMBER,newInteger(i_val));
case';': advance();returnsf.newSymbol("SEMI",sym.SEMI);
case'+': advance();returnsf.newSymbol("PLUS",sym.PLUS);
case'-': advance();returnsf.newSymbol("MINUS",sym.MINUS);
case'*': advance();returnsf.newSymbol("TIMES",sym.TIMES);
case'/': advance();returnsf.newSymbol("DIVIDE",sym.DIVIDE);
case'%': advance();returnsf.newSymbol("MOD",sym.MOD);
case'(': advance();returnsf.newSymbol("LPAREN",sym.LPAREN);
case')': advance();returnsf.newSymbol("RPAREN",sym.RPAREN);
case-1:returnsf.newSymbol("EOF",sym.EOF);
default:
/**//*in this simple scanner we just ignore everything else*/ advance();
break;
} }};
以上代码来自《手册》的附录B,但有以下修改:
修改概要注释掉第4行的import sym;
原文第6行改成public class CalcLex implements java_cup.runtime.Scanner {
因为语法分析器要求其词法分析器必须派生自Scanner类。
删去原文第23行的static。因为其超类Scanner的next_token()方法不是静态的。
删掉原文第48行其中一个return(无聊的语法错误!)
新增加了一个构造函数scanner(FileReader)和静态属性FileReader fr,(当然要import System.io.*;)它们之后将会用到。
修改了advance()的定义
这时scanner.java还未能通过编译的,因为其需要引用到的sym类还未生成,不用管它,继续下一步。
如果用JFlex来生成一个词法分析器,则要先写一个scanner.flex,代码如下:
scanner.flex//scanner.flex
//用户代码段importjava_cup.runtime.*;
importjava.io.*;
%%//参数设置和声明段%classscanner
%line
%column
%cup
%unicode
%{
publicstaticvoidinit(){}/**//*Just为了兼容手写版*/
privateSymbol symbol(inttype){
returnnewSymbol(type,yyline,yycolumn);
}
privateSymbol symbol(inttype,Object value){
returnnewSymbol(type,yyline,yycolumn,value);
}%}digit=[0-9]
number={digit}+LineTerminator=\r|\n|\r\n
WhiteSpace={LineTerminator}|[ \t\f]
%%//词法规则段
{
";"{returnsymbol(sym.SEMI);/**//*case ";"*/}
"+"{returnsymbol(sym.PLUS);/**//*case "+"*/}
"-"{returnsymbol(sym.MINUS);/**//*case "-"*/}
"*"{returnsymbol(sym.TIMES);/**//*case "*"*/}
"/"{returnsymbol(sym.DIVIDE);/**//*case "/"*/}
"%"{returnsymbol(sym.MOD);/**//*case "%"*/}
"("{returnsymbol(sym.LPAREN);/**//*case "("*/}
")"{returnsymbol(sym.RPAREN);/**//*case ")"*/}
{number}{returnsymbol(sym.NUMBER,newInteger(yytext()));/**//*case {number}*/}
{WhiteSpace}{/**//*case {WhiteSpace}: do nothing*/}
}
.{
System.out.println("Error:"+yytext()+"is illegal!");
}
3、使用javaCUP生成一个语法分析器。
在这一步里,你需要写一个parser.cup文件,代码如下:
parser.cup//CUP specification for a simple expression evaluator (w/ actions)
//parser.cupimportjava_cup.runtime.*;
/**//*Preliminaries to set up and use the scanner.*/
init with{: scanner.init(); :};
scan with{:returngetScanner().next_token(); :};
/**//*Terminals (tokens returned by the scanner).*/terminal SEMI, PLUS, MINUS, TIMES, DIVIDE, MOD;
terminal UMINUS, LPAREN, RPAREN;
terminal Integer NUMBER;
/**//*Non-terminals*/non terminal expr_list, expr_part;
non terminal Integer expr;
/**//*Precedences*/precedence left PLUS, MINUS;
precedence left TIMES, DIVIDE, MOD;
precedence left UMINUS;
/**//*The grammar*/expr_list ::=expr_list expr_part
| expr_part;
expr_part ::=expr:e
{: System.out.println("="+e); :} SEMI
;
expr ::=expr:e1 PLUS expr:e2
{: RESULT=newInteger(e1.intValue()+e2.intValue()); :}| expr:e1 MINUS expr:e2
{: RESULT=newInteger(e1.intValue()-e2.intValue()); :}| expr:e1 TIMES expr:e2
{: RESULT=newInteger(e1.intValue()*e2.intValue()); :}| expr:e1 DIVIDE expr:e2
{: RESULT=newInteger(e1.intValue()/e2.intValue()); :}| expr:e1 MOD expr:e2
{: RESULT=newInteger(e1.intValue()%e2.intValue()); :}| NUMBER:n
{: RESULT=n; :}| MINUS expr:e
{: RESULT=newInteger(0-e.intValue()); :}%prec UMINUS
| LPAREN expr:e RPAREN
{: RESULT=e; :} ;
现在你需要用JavaCUP来分析你的cup文件,请在命令行下输入
java-jar java-cup-11a.jarparser.cup
如果屏幕出现以下输出,就说明你已经成功了,这时javaCUP自动生成了parser.java和sym.java两个文件。现在你的scanner.java也可以成功通过编译了。
-------CUP v0.11a beta20060608Parser Generation Summary-------0errors and0warnings
12terminals,4non-terminals, and13productions declared,
producing24unique parse states.
0terminals declared but not used.
0non-terminals declared but not used.
0productions never reduced.
0conflicts detected (0expected).
Code written to"parser.java", and"sym.java".
----------------------------------------------------(v0.11a beta20060608)
4、编写主函数。
现在你的计算器的语法分析器已经做好,你还要做的就是编写一个主函数来调用这个分析器。请在work\下新建一个Calc.java,然后输入以下代码:
Calc.java//Calc.javaimportjava.io.*;
publicclassCalc{
publicstaticvoidmain(String argv[])throwsException{
parser p=newparser(newscanner(newFileReader(argv[0])));
p.parse();
}}
5、测试用例。
你还需要设计一些测试例子来检查你是否已经成功完成了这个计算器。在work\下新建一个test.txt,输入一些数值表达式,例如:
test.txt2*4+6;
7*(5+3);
(5-3)/(2*4+3);
然后在命令行输入:
java Calc test.txt
如果屏幕输出:
=14=56=0
这就表示你已经大功告成了
~~
补充两点:
自我贴出这篇博文后,有很多朋友跟我说在输入java Calc test.txt时出现以下输出:
Exception in thread "main" java.lang.NoClassDefFoundError: calc
甚至
Exception in thread "main" java.lang.NoSuchMethodError: calc
但此前所有的代码生成、编译工作都是成功的。
经过我的分析,这是因为.java文件和.class文件版本不匹配造成的。通常是你用jflex或javaCUP生成了新的.java文件,却没有对它重新编译,因此.class里边的是旧的.java文件的内容,因此在运行的时候就会产生错误。解决方法是在命令行输入del *.class并重新编译,这时java会重新生成所有.class文件。
觉得每次都要手工输入命令去生成代码、编译、运行很慢很麻烦?呵呵~~像我这么懒的人当然不会这么笨啦
~~你可以写一个bat文件去自动帮你完成所有的工作,自然也可以解决上面那个“.NoClassDefFoundError”问题。不懂bat?去google一下“批处理文件”吧。这里再教你一个技巧,就是如何在bat中使用分支:
run.batcall jflex scanner.flex
if errorlevel 1 goto EXIT
java -jar cup.jar parser.cup
if errorlevel 1 goto EXIT
javac scanner.java
if errorlevel 1 goto EXIT
javac parser.java
if errorlevel 1 goto EXIT
javac Calc.java
if errorlevel 1 goto EXIT
java Calc test.txt
:EXIT
这样你只要轻轻的输入run,然后回车,就可以自动调用Jflex、JavaCUP生成代码、编译、运行了,并且当其中一步出错时,其后所有的步骤都不会被执行。是不是很好玩?
posted on 2007-05-09 15:35 踏雪赤兔 阅读(9171) 评论(24) 编辑 收藏 引用 所属分类: 玩转编程