给出使用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.java
import java_cup.runtime.*;
import java.io.*;
//import sym;
public class scanner implements java_cup.runtime.Scanner {
/**//* single lookahead character */
protected static int next_char;
// since cup v11 we use SymbolFactories rather than Symbols
private SymbolFactory sf = new DefaultSymbolFactory();
private static FileReader fileReader;
public scanner(FileReader fr){
this.fileReader=fr;
}
/**//* advance input by one character */
protected static void advance()
throws java.io.IOException
{ next_char = fileReader.read(); }
/**//* initialize the scanner */
public static void init()
throws java.io.IOException
{ advance(); }
/**//* recognize and return the next complete token */
public Symbol next_token()
throws java.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 */
int i_val = 0;
do {
i_val = i_val * 10 + (next_char - '0');
advance();
} while (next_char >= '0' && next_char <= '9');
return sf.newSymbol("NUMBER",sym.NUMBER, new Integer(i_val));
case ';': advance(); return sf.newSymbol("SEMI",sym.SEMI);
case '+': advance(); return sf.newSymbol("PLUS",sym.PLUS);
case '-': advance(); return sf.newSymbol("MINUS",sym.MINUS);
case '*': advance(); return sf.newSymbol("TIMES",sym.TIMES);
case '/': advance(); return sf.newSymbol("DIVIDE",sym.DIVIDE);
case '%': advance(); return sf.newSymbol("MOD",sym.MOD);
case '(': advance(); return sf.newSymbol("LPAREN",sym.LPAREN);
case ')': advance(); return sf.newSymbol("RPAREN",sym.RPAREN);
case -1: return sf.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
//用户代码段
import java_cup.runtime.*;
import java.io.*;
%%
//参数设置和声明段
%class scanner
%line
%column
%cup
%unicode
%{
public static void init(){}/**//* Just为了兼容手写版*/
private Symbol symbol(int type){
return new Symbol(type,yyline,yycolumn);
}
private Symbol symbol(int type,Object value){
return new Symbol(type,yyline,yycolumn,value);
}
%}
digit=[0-9]
number={digit}+
LineTerminator=/r|/n|/r/n
WhiteSpace={LineTerminator}|[ /t/f]
%%
//词法规则段
{
";" { return symbol(sym.SEMI); /**//*case ";"*/}
"+" { return symbol(sym.PLUS); /**//*case "+"*/}
"-" { return symbol(sym.MINUS); /**//*case "-"*/}
"*" { return symbol(sym.TIMES); /**//*case "*"*/}
"/" { return symbol(sym.DIVIDE); /**//*case "/"*/}
"%" { return symbol(sym.MOD); /**//*case "%"*/}
"(" { return symbol(sym.LPAREN); /**//*case "("*/}
")" { return symbol(sym.RPAREN); /**//*case ")"*/}
{number} { return symbol(sym.NUMBER,new Integer(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.cup
import java_cup.runtime.*;
/**//* Preliminaries to set up and use the scanner. */
init with {: scanner.init(); :};
scan with {: return getScanner().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 = new Integer(e1.intValue() + e2.intValue()); :}
|
expr:e1 MINUS expr:e2
{: RESULT = new Integer(e1.intValue() - e2.intValue()); :}
|
expr:e1 TIMES expr:e2
{: RESULT = new Integer(e1.intValue() * e2.intValue()); :}
|
expr:e1 DIVIDE expr:e2
{: RESULT = new Integer(e1.intValue() / e2.intValue()); :}
|
expr:e1 MOD expr:e2
{: RESULT = new Integer(e1.intValue() % e2.intValue()); :}
|
NUMBER:n
{: RESULT = n; :}
|
MINUS expr:e
{: RESULT = new Integer(0 - e.intValue()); :}
%prec UMINUS
|
LPAREN expr:e RPAREN
{: RESULT = e; :}
;
现在你需要用JavaCUP来分析你的cup文件,请在命令行下输入
java -jar java-cup-11a.jar parser.cup
如果屏幕出现以下输出,就说明你已经成功了,这时javaCUP自动生成了parser.java和sym.java两个文件。
现在你的scanner.java也可以成功通过编译了。
------- CUP v0.11a beta 20060608 Parser Generation Summary -------
0 errors and 0 warnings
12 terminals, 4 non-terminals, and 13 productions declared,
producing 24 unique parse states.
0 terminals declared but not used.
0 non-terminals declared but not used.
0 productions never reduced.
0 conflicts detected (0 expected).
Code written to "parser.java", and "sym.java".
---------------------------------------------------- (v0.11a beta 20060608)
4、编写主函数。
现在你的计算器的语法分析器已经做好,你还要做的就是编写一个主函数来调用这个分析器。请在work/
下新建一个Calc.java,然后输入以下代码:
Calc.java
//Calc.java
import java.io.*;
public class Calc{
public static void main(String argv[])throws Exception{
parser p=new parser(new scanner(new FileReader(argv[0])));
p.parse();
}
}
5、测试用例。
你还需要设计一些测试例子来检查你是否已经成功完成了这个计算器。在work/下新建一个test.txt,
输入一些数值表达式,例如:
test.txt
2 * 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.flexif errorlevel 1 goto EXITjava -jar cup.jar parser.cupif errorlevel 1 goto EXITjavac scanner.javaif errorlevel 1 goto EXITjavac parser.javaif errorlevel 1 goto EXITjavac Calc.javaif errorlevel 1 goto EXITjava Calc test.txt:EXIT这样你只要轻轻的输入run,然后回车,就可以自动调用Jflex、JavaCUP生成代码、编译、运行了,并且当其中一步出错时,其后所有的步骤都不会被执行。是不是很好玩?