锁定老帖子 主题:小型桌面计算器的实现(javacc)
精华帖 (3) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
作者 | 正文 | ||||||||||||||
发表时间:2009-04-01
最后修改:2009-04-07
从开始学计算理论,就对形式语言,编译原理很感兴趣,所以大学对这门课学的也算是最好了。自己也实现过一些简单的词法分析器之类的东西,不过也都是学习目的的,质量一般 后来一直在Linux学习,对lex/yacc研究过一点时间,设计过一个小的脚本引擎,可以做一些比较复杂的数学运算,这个可以参考我的这篇博客<hoc>。工作以后,平台 变成了java,跟C渐渐的离得远了,也喜欢上java这个提供语言级别的面向对象的语言以前的一些东西用顺手了,一时习惯还改不过来,于是就开始找lex/yacc的替代品。
研究过一段时间的antlr,觉得还行,特别是那个GUI做的非常好,但是跟lex/yacc在用法上还是有些差别的,最近才遇到了javacc,觉得跟lex/yacc的写法相当像,就试着
写了一个简单的桌面计算器(desktop-calculator),大家可以参考一下。 (从下载到javacc.zip到写出这个计算器,大约花了我一个下午的时间,所以你可以很快
的学会javacc,并在实际中应用。)用这类工具的好处是,只要你理解形式语言,那么就可以很方便的做出一个分析起来,而不用关心其实现细节(如词法分析等部分是十分
一来这是一个比较经典的例子,通过它的学习,可以迅速掌握一个编译器生成器,或者叫语言解析器生成器。二来最近项目中用到了一些类似的东西,正好趁此机会记下来 以便将来参考。
四则运算的计算器的规则非常清晰,可以看一下这个巴克斯范式(BNF) expr ::= term ((+|-) term)* term ::= factor ((*|/) factor)* factor ::= number | expr number ::= [0-9]+...
TOKEN:{ <ADD : "+"> | <SUB : "-"> | <MUL : "*"> | <DIV : "/"> | <LPAREN : "("> | <RPAREN : ")"> | <NUMBER : ["0"-"9"] (["0"-"9"])* | (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)? | "." (["0"-"9"])+ (<EXPONENT>)? | (["0"-"9"])+ <EXPONENT> > | <#EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ > } javacc的记号描述很简单,就是普通的正则表达式,当然用引号引起来的简单单词也算是正则表达式,如记号ADD,就是"+",这样,当解析器遇到"+"时就返回一个ADD记号,方便在内部使用。
number ::= [0-9]([0-9])* | [0-9]+ '.' ([0-9])+ exponent? | '.' ([0-9])+ exponent? | ([0-9])+ exponent? exponent ::= [e,E]([+,-])?([0-9])+
可以看到,形式语言在描述这种抽象概念上比自然语言要好得多。一旦掌握了形式语言就可以很容易的理解各种复杂的规则。 SKIP:{ " " | "\t" | "\r" | "\n" }
有了BNF,我们看看在javacc中,如何表现这些规则: void expr():{} { term()((<ADD>|<SUB>)term())* } void term():{} { factor()((<MUL>|<DIV>)factor())* } void factor():{} { <NUMBER>| <LPAREN>expr()<RPAREN> }
double expr(): { double temp = 0; double first, second; } { //你可以在非终结符前插入变量,等号等,在其后可以插入普通的java代码 //插入代码后看起来可能不够清晰,可以参看上边的形式定义 first=term(){temp = first;} (<ADD>second=term(){temp = first + second;}| <SUB>second=term(){temp = first - second;})* {return temp;}//最后,应当返回某值 } double term(): { double temp = 0; double first, second; } { first=factor(){temp = first;} (<MUL>second=factor(){temp = first * second;}| <DIV>second=factor(){temp = first / second;})* {return temp;} } double factor(): { double temp = 0; Token token; } { token=<NUMBER>{ return Double.parseDouble(token.image); } | <LPAREN> temp=expr() <RPAREN>{ return temp; } }
PARSER_BEGIN(CalcParser) import java.io.StringReader; import java.io.Reader; public class CalcParser{ public CalcParser(String expr){ this((Reader)(new StringReader(expr))); } public static void main(String[] args){ try{ CalcParser parser = new CalcParser(args[0]); System.out.println(parser.expr()); }catch(Exception e){ System.out.println("error : "+e.getMessage()); } } } PARSER_END(CalcParser)
可以看到有类似的输出; 写道
Java Compiler Compiler Version 4.2 (Parser Generator)
(type "javacc" with no arguments for help) Reading from file CalcParser.jj . . . File "TokenMgrError.java" does not exist. Will create one. File "ParseException.java" does not exist. Will create one. File "Token.java" does not exist. Will create one. File "SimpleCharStream.java" does not exist. Will create one. Parser generated successfully.
PARSER_BEGIN(CalcParser) import java.io.StringReader; import java.io.Reader; public class CalcParser{ public CalcParser(String expr){ this((Reader)(new StringReader(expr))); } public static void main(String[] args){ try{ CalcParser parser = new CalcParser(args[0]); System.out.println(parser.elexpr()); }catch(Exception e){ System.out.println("error : "+e.getMessage()); } } } PARSER_END(CalcParser) //声明到此结束 SKIP:{ " " | "\t" | "\r" | "\n" } TOKEN:{ <ADD : "+"> | <SUB : "-"> | <MUL : "*"> | <DIV : "/"> | <MOD : "%"> | <LPAREN : "("> | <RPAREN : ")"> | <NUMBER : ["0"-"9"] (["0"-"9"])* | (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)? | "." (["0"-"9"])+ (<EXPONENT>)? | (["0"-"9"])+ <EXPONENT> > | <#EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ > | <EXPRPREFIX: "${"> | <EXPRSUFFIX: "}"> | <SIN: "sin"> | <COS: "cos"> } //记号部分声明到此结束,下面是语法声明,包括语义解释 double elexpr(): { double temp = 0; } { <EXPRPREFIX>temp=expr()<EXPRSUFFIX> {return temp;} } double expr(): { double temp = 0; double first, second; } { first=term(){temp = first;} (<ADD>second=term(){temp = first + second;}| <SUB>second=term(){temp = first - second;})* {return temp;} } double term(): { double temp = 0; double first, second; } { first=factor(){temp = first;} (<MUL>second=factor(){temp = first * second;}| <DIV>second=factor(){temp = first / second;})* {return temp;} } double factor(): { double temp = 0; Token token; } { token=<NUMBER>{ return Double.parseDouble(token.image); } | <LPAREN> temp=expr() <RPAREN>{ return temp; } | <SIN><LPAREN>temp=expr()<RPAREN>{ return java.lang.Math.sin(temp); } | <COS><LPAREN>temp=expr()<RPAREN>{ return java.lang.Math.sin(temp); } //如果有兴趣,可以加入更多的java.lang.Math中的数学函数, //当然,你也可以加入自己实现的一些方法,如返回前一个运算结果 //记录历史信息等等。 }
演示计算过程,如: [juntao@juntao CalcParser]$ java CalcParser '${sin(1/2)*cos(1/4) + 12.3}'
12.418611776418413 [juntao@juntao CalcParser]$ java CalcParser '${(12+45)*(3-23)}' -1140.0 [juntao@juntao CalcParser]$ java CalcParser '${3e-5}' 3.0E-5 [juntao@juntao CalcParser]$ java CalcParser '${sin(3/4)/2}' 0.34081938001166706
关于JJTree后边再说吧,最重要的还是上边提到的形式语言,它是一切的基础。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2009-04-02
谢谢分享,期待jjtree帖子
|
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2009-05-15
你好!我最近也在学习javacc的内容,有不懂的问题想请教一下:
对于下面这两行要解析的代码 /TITLE Multivariate Software Program mvIRT 1.0 我定义了 TOKEN: { < STRING: <LETTER>(<LETTER>|<DIGIT>|"."|"_"|" "|"\t")+ > } 其中LETTER就是字母,DIGIT是数字。 void Title():{} { <title>String() }这是title结点,其中<title:"/TITLE"> void String():{} { <STRING> } 可是我一运行的时候就提示说: /TITLE WO SHI LITING //前两行蓝色是我自己的输入字符串, Exception in thread "main" javac.ParseException: Encountered "\r\n WO SHI LITING\r\n. Was expecting: <STRING> ... \r\n在SKIP中已经跳过了,为什么这边会这样提示啊?谢谢您了!! |
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2009-05-15
没看到你对\r\n跳过的规则,没贴出来吗?
一般定义跳过的是这样: SKIP : /* WHITE SPACE */ { " " | "\t" | "\n" | "\r" | "\f" } |
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2009-05-15
对啊 对啊 我就是这样跳过的 和您定义的一样的
还试过 SKIP : { " " | "\t" | "\n" | "\r" |"\r\n" | "\f" } 但是结果还是出错 提示跟上面一样的错误 |
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2009-05-15
一般STRING可以定义成这样:
<code> TOKEN : /* IDENTIFIERS */ { < STRING: <LETTER> (<LETTER>|<DIGIT>)* > | < #LETTER: [ "a"-"z", "A"-"Z" ] > | < #DIGIT: [ "0"-"9"] > } </code> 如果需要加别的终结符,可以添加到(<LETTER>|<DIGIT>这里)* 而且,如果只是解析的话不需要规约成节点. |
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2009-05-15
TOKEN:
{ < STRING: <LETTER>(<LETTER>|<DIGIT>|"."|"_"|" "|"\t")+> } TOKEN: { <#DIGIT:["0"-"9"]> } TOKEN:/*定义字母*/ { <#LETTER:["A"-"Z","a"-"z"]> } 这是我jj文件中的定义,因为我要解析的文件中有 /TITLE Multivariate Software Program mvIRT 1.0 //这行字符串其中不止有字母和数字还有空格等,所以我就像上面那样定义了…… 我一直不明白的是为什么会提示说期望的是<STRING>? Encountered "\r\n WO SHI LITING\r\n. 我一直以为这里的\r\n已经在SKIP中跳过了,是不是我对SKIP的理解有错误呢? |
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2009-05-16
你要解析的文本的BNF规则呢?贴出来我看看,大概是这个样子。
<TITLE : "/TITLE"> <STRING : ...> void expr(){ Token t; }{ <TITLE> t=<STRING>{System.out.println("title = "+t.image);} } |
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2009-05-16
我要解析的东西一共有六个结点,我先贴出来两个:
/TITLE Multivariate Software Program mvIRT 1.0 /SPECIFICATIONS DATA='C:\mvIRT\females.ess'; VARIABLES=14;CASES=2000; USE=Q46-Q375; 我的想法是 开始:procedure()->title()specifications() title()-><TITLE>String() specifications-><SPECIFICATIONS>data_value()var_value()cases_value()use_value() data_value()-><DATA><EQUAL><QUOTATION>address()<QUOTATION><SEMICOLON> var_value()-><VARIABLES><EQUAL>integer()<SEMICOLON> 以下几个类似这样子的,我就不写出来了 其中 <TITLE:"/TITLE"> <SPECIFICATIONS:"/SPECIFICATIONS"> <DATA:"DATA">等等,因为DATA,VARIABLES,CASES,USE这些变量名都是不变,我就这样声明了 address()代表C:\mvIRT\females.ess这样的字符串 <QUOTATION:"\'"> <EQUAL:"="> <SEMICOLON:";"> 今天我去海淀图书城想找介绍javacc书,可是都没有找到 |
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2009-05-16
现在网上这么多资料,还买书?难道还是学生?
javacc的包中就有很多个例子,可以用来学习,再者,这篇文章你仔细读一读,特别是正则表达式部分(关于规则的定义)。写一个你提到的这种应该是很容易的,呵呵。如果明天有时间的话,我可以写一个给你参考。 |
|||||||||||||||
返回顶楼 | |||||||||||||||