小型桌面计算器的实现(javacc)
從開始學(xué)計(jì)算理論,就對(duì)形式語(yǔ)言,編譯原理很感興趣,所以大學(xué)對(duì)這門課學(xué)的也算是最好了。自己也實(shí)現(xiàn)過一些簡(jiǎn)單的詞法分析器之類的東西,不過也都是學(xué)習(xí)目的的,質(zhì)量一般 后來一直在Linux學(xué)習(xí),對(duì)lex/yacc研究過一點(diǎn)時(shí)間,設(shè)計(jì)過一個(gè)小的腳本引擎,可以做一些比較復(fù)雜的數(shù)學(xué)運(yùn)算,這個(gè)可以參考我的這篇博客<hoc>。工作以后,平臺(tái) 變成了java,跟C漸漸的離得遠(yuǎn)了,也喜歡上java這個(gè)提供語(yǔ)言級(jí)別的面向?qū)ο蟮恼Z(yǔ)言以前的一些東西用順手了,一時(shí)習(xí)慣還改不過來,于是就開始找lex/yacc的替代品。
研究過一段時(shí)間的antlr,覺得還行,特別是那個(gè)GUI做的非常好,但是跟lex/yacc在用法上還是有些差別的,最近才遇到了javacc,覺得跟lex/yacc的寫法相當(dāng)像,就試著 寫了一個(gè)簡(jiǎn)單的桌面計(jì)算器(desktop-calculator),大家可以參考一下。 (從下載到j(luò)avacc.zip到寫出這個(gè)計(jì)算器,大約花了我一個(gè)下午的時(shí)間,所以你可以很快 的學(xué)會(huì)javacc,并在實(shí)際中應(yīng)用。)用這類工具的好處是,只要你理解形式語(yǔ)言,那么就可以很方便的做出一個(gè)分析起來,而不用關(guān)心其實(shí)現(xiàn)細(xì)節(jié)(如詞法分析等部分是十分
枯燥且無意義的)
一來這是一個(gè)比較經(jīng)典的例子,通過它的學(xué)習(xí),可以迅速掌握一個(gè)編譯器生成器,或者叫語(yǔ)言解析器生成器。二來最近項(xiàng)目中用到了一些類似的東西,正好趁此機(jī)會(huì)記下來 以便將來參考。
四則運(yùn)算的計(jì)算器的規(guī)則非常清晰,可以看一下這個(gè)巴克斯范式(BNF)
Java代碼
expr::=term((+|-)term)*
term::=factor((*|/)factor)*
factor::=number|expr
number::=[0-9]+...
形式語(yǔ)言的好處就在于其清晰,準(zhǔn)確和強(qiáng)大。如果你對(duì)正則表達(dá)式比較收悉,那么可以很容易掌握形式語(yǔ)言。(正則表達(dá)式其實(shí)就是一個(gè)精簡(jiǎn)而強(qiáng)大的形式語(yǔ)言實(shí)例)
首先,我們需要定義在文法描述中需要用到的所有記號(hào)(token),這是分析器能認(rèn)識(shí)到的最小單元
Java代碼
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的記號(hào)描述很簡(jiǎn)單,就是普通的正則表達(dá)式,當(dāng)然用引號(hào)引起來的簡(jiǎn)單單詞也算是正則表達(dá)式,如記號(hào)ADD,就是"+",這樣,當(dāng)解析器遇到"+"時(shí)就返回一個(gè)ADD記號(hào),方便在內(nèi)部使用。
這里詳細(xì)說一下NUMBER記號(hào),數(shù)字怎么表示呢?
我們用自然語(yǔ)言可以描述成:以0到9中的任一個(gè)數(shù)字開頭,后邊可以有任意多位(包括0位)數(shù)字(此為整數(shù)),或者以至少一位數(shù)字,后邊跟一個(gè)小數(shù)點(diǎn),然后又有任意多位的數(shù)字,如果使用科學(xué) 計(jì)數(shù)法,則這個(gè)數(shù)字串后邊還可以跟一個(gè)E,E后邊又可以有正號(hào)(+)或者負(fù)號(hào)(-),也可以沒有,然后,后邊又是至少一位數(shù)字,或者……
可以看到,自然語(yǔ)言的描述冗長(zhǎng)且不容易理解,我們看看形式語(yǔ)言的描述:首先定義一些符號(hào)的意義,如
| "|" | 表示或者 |
| 0-9 | 表示0到9的一個(gè)數(shù)字 |
| [] | 表示一個(gè)區(qū)間 |
| "?" | 表示其前的區(qū)間中的元素重復(fù)一次或零次 |
| "+" | 表示其前的區(qū)間中的元素重復(fù)至少一次 |
| "*" | 表示其前的區(qū)間中的元素重復(fù)零次或多次 |
| () | 表示一個(gè)分組,是一個(gè)整 |
好了,有了這些定義,我們?cè)倏纯慈绾斡眯问秸Z(yǔ)言描述一個(gè)浮點(diǎn)數(shù)或者整數(shù):
Java代碼
number::=[0-9]([0-9])*|[0-9]+'.'([0-9])+exponent?
|'.'([0-9])+exponent?
|([0-9])+exponent?
exponent::=[e,E]([+,-])?([0-9])+
可以看到,形式語(yǔ)言在描述這種抽象概念上比自然語(yǔ)言要好得多。一旦掌握了形式語(yǔ)言就可以很容易的理解各種復(fù)雜的規(guī)則。
詞法分析器的一個(gè)功能就是剔除源碼中的空白字符,比如,空格,制表符,回車換行等
Java代碼
SKIP:{
""|"\t"|"\r"|"\n"
}
有了BNF,我們看看在javacc中,如何表現(xiàn)這些規(guī)則:
Java代碼
voidexpr():{}
{
term()((<ADD>|<SUB>)term())*
}
voidterm():{}
{
factor()((<MUL>|<DIV>)factor())*
}
voidfactor():{}
{
<NUMBER>|
<LPAREN>expr()<RPAREN>
}
javacc基本忠實(shí)的體現(xiàn)了BNF的規(guī)則定義,當(dāng)然,現(xiàn)在這個(gè)解析器的文法部分(詞法分析和語(yǔ)法分析) 已經(jīng)算是結(jié)束了,但是它還不能完成任何計(jì)算,因?yàn)樗鼪]有語(yǔ)義的定義部分,即當(dāng)發(fā)現(xiàn)了 此語(yǔ)法現(xiàn)象后該做什么是沒有定義的。
相信大家都已經(jīng)注意到,每個(gè)非終結(jié)符后邊都有兩個(gè)大括號(hào){},第一個(gè)目前為空。在javacc中,每個(gè)非終結(jié)符都最終會(huì)被翻譯成一個(gè)方法,(至于怎么翻譯的,你可以自己看看它生成的代 碼,當(dāng)年我曾痛苦在yacc生成的一堆yy_*中徜徉過一段時(shí)間,現(xiàn)在是實(shí)在看不下去了,javacc生成的代碼中到處都是jj_*,唉,一個(gè)yy,一個(gè)jj,生成的代碼是看不成了), 第一個(gè)空的大括號(hào)中即為將來你自己填寫的一些關(guān)于這個(gè)方法的一些臨時(shí)變量,包括返回值等信息。
好了,我們看看加入了語(yǔ)義解釋后的代碼:
Java代碼
doubleexpr():
{
doubletemp=0;
doublefirst,second;
}
{
//你可以在非終結(jié)符前插入變量,等號(hào)等,在其后可以插入普通的java代碼
//插入代碼后看起來可能不夠清晰,可以參看上邊的形式定義
first=term(){temp=first;}
(<ADD>second=term(){temp=first+second;}|
<SUB>second=term(){temp=first-second;})*
{returntemp;}//最后,應(yīng)當(dāng)返回某值
}
doubleterm():
{
doubletemp=0;
doublefirst,second;
}
{
first=factor(){temp=first;}
(<MUL>second=factor(){temp=first*second;}|
<DIV>second=factor(){temp=first/second;})*
{returntemp;}
}
doublefactor():
{
doubletemp=0;
Tokentoken;
}
{
token=<NUMBER>{
returnDouble.parseDouble(token.image);
}|<LPAREN>temp=expr()<RPAREN>{
returntemp;
}
}
好了,主體部分已經(jīng)建立好了,我們?cè)賮砜纯绰暶餍畔⒌?形式語(yǔ)言的學(xué)習(xí)是重點(diǎn),其余的 都比較簡(jiǎn)單易學(xué),而且不同的cc都提供大同小異的功能)
Java代碼
PARSER_BEGIN(CalcParser)
importjava.io.StringReader;
importjava.io.Reader;
publicclassCalcParser{
publicCalcParser(Stringexpr){
this((Reader)(newStringReader(expr)));
}
publicstaticvoidmain(String[]args){
try{
CalcParserparser=newCalcParser(args[0]);
System.out.println(parser.expr());
}catch(Exceptione){
System.out.println("error:"+e.getMessage());
}
}
}
PARSER_END(CalcParser)
每個(gè)分析器需要一個(gè)名字,這個(gè)名字定義在PARSER_BEGIN(xxx)中,而且應(yīng)保證與下面的類聲明保持一致:
public class xxx{}
現(xiàn)在,我們可以用javacc提供的命令行程序來生成我們的計(jì)算器,需要注意的是,javacc生成的是java源碼,且不再依賴于javacc,你可以將你的分析器源碼放在任何地方使用。
$ javacc CalcParser.jj(你看看這文件名后綴)
可以看到有類似的輸出;
寫道
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.
你現(xiàn)在可以試著輸入一些表達(dá)式讓計(jì)算器進(jìn)行計(jì)算了。(事例結(jié)果見后)
如果想要加入一些別的功能,比如,計(jì)算表達(dá)式的正弦,余弦函數(shù)?很簡(jiǎn)單,我們可以使用 java.lang.Math中提供的一些數(shù)學(xué)函數(shù)。
對(duì)規(guī)則稍事修改,即可完成我們的需求。當(dāng)然,這個(gè)計(jì)算器的還是比較簡(jiǎn)單的,比如,不能回溯(這個(gè)以后再說),不支持負(fù)數(shù),不支持冪計(jì)算。但是,如果通過此文,你對(duì)形式語(yǔ)言有了比較 好的理解的話,這些問題都是很容易解決的。
下面這些代碼是我再上邊的這個(gè)四則運(yùn)算計(jì)算器的基礎(chǔ)上加入了少量規(guī)則而成的一個(gè)微型函數(shù)計(jì)算器,其表達(dá)式格式類似于JSP中的EL表達(dá)式,你可以對(duì)其進(jìn)行擴(kuò)展,從而使之更加有趣。
Java代碼
PARSER_BEGIN(CalcParser)
importjava.io.StringReader;
importjava.io.Reader;
publicclassCalcParser{
publicCalcParser(Stringexpr){
this((Reader)(newStringReader(expr)));
}
publicstaticvoidmain(String[]args){
try{
CalcParserparser=newCalcParser(args[0]);
System.out.println(parser.elexpr());
}catch(Exceptione){
System.out.println("error:"+e.getMessage());
}
}
}
PARSER_END(CalcParser)
//聲明到此結(jié)束
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">
}
//記號(hào)部分聲明到此結(jié)束,下面是語(yǔ)法聲明,包括語(yǔ)義解釋
doubleelexpr():
{doubletemp=0;}
{
<EXPRPREFIX>temp=expr()<EXPRSUFFIX>
{returntemp;}
}
doubleexpr():
{
doubletemp=0;
doublefirst,second;
}
{
first=term(){temp=first;}
(<ADD>second=term(){temp=first+second;}|
<SUB>second=term(){temp=first-second;})*
{returntemp;}
}
doubleterm():
{
doubletemp=0;
doublefirst,second;
}
{
first=factor(){temp=first;}
(<MUL>second=factor(){temp=first*second;}|
<DIV>second=factor(){temp=first/second;})*
{returntemp;}
}
doublefactor():
{
doubletemp=0;
Tokentoken;
}
{
token=<NUMBER>{
returnDouble.parseDouble(token.image);
}|<LPAREN>temp=expr()<RPAREN>{
returntemp;
}|
<SIN><LPAREN>temp=expr()<RPAREN>{
returnjava.lang.Math.sin(temp);
}|
<COS><LPAREN>temp=expr()<RPAREN>{
returnjava.lang.Math.sin(temp);
}
//如果有興趣,可以加入更多的java.lang.Math中的數(shù)學(xué)函數(shù),
//當(dāng)然,你也可以加入自己實(shí)現(xiàn)的一些方法,如返回前一個(gè)運(yùn)算結(jié)果
//記錄歷史信息等等。
}
演示計(jì)算過程,如:
[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
關(guān)于JJTree后邊再說吧,最重要的還是上邊提到的形式語(yǔ)言,它是一切的基礎(chǔ)。
這篇文章可以算是這篇小型桌面計(jì)算器的實(shí)現(xiàn)(javacc)的續(xù)。
可以這么說,使用javacc作分析器生成器,如果沒有用到j(luò)jTree,那么就是對(duì)語(yǔ)義分析的過程理解不夠深入。如果用到了jjTree而且用好了,那么對(duì)編譯原理,BNF等的理解才算是比較到位了。
jjTree中最重要的概念是Node接口,所有的非終結(jié)符都可以規(guī)約為一個(gè)節(jié)點(diǎn)。這個(gè)節(jié)點(diǎn)一般來講是實(shí)現(xiàn)了Node接口的節(jié)點(diǎn)類
其中主要有這樣幾個(gè)方法:
Java代碼
……
/**Thismethodtellsthenodetoadditsargumenttothenode's
listofchildren.*/
publicvoidjjtAddChild(Noden,inti);
/**返回孩子節(jié)點(diǎn),下標(biāo)從0開始,從左到右*/
publicNodejjtGetChild(inti);
/**返回字節(jié)點(diǎn)個(gè)數(shù)*/
intjjtGetNumChildren();
……
在本文所舉的例子中,每個(gè)節(jié)點(diǎn)還要實(shí)現(xiàn)這樣一個(gè)接口中聲明的方法:
Java代碼
voidinterpret()
這個(gè)方法中為每個(gè)節(jié)點(diǎn)具體的計(jì)算過程。
jjTree處理的腳本(jjTree規(guī)則列表)以jjt結(jié)尾,這個(gè)文件通過jjtree工具可以生成.jj文件,然后用javacc編譯.jj文件即可生成分析器生成器的java代碼,然后于一些輔助解析的類進(jìn)行編譯,從而最終完成整個(gè)腳本引擎。
jjTree有什么用?
這個(gè)是最核心的問題了,我們都知道jjTree的作用是為了將終結(jié)符通過規(guī)則規(guī)約成非終結(jié)符節(jié)點(diǎn)。但是,規(guī)約的目的又是什么呢?其實(shí),如果你的目標(biāo)是簡(jiǎn)單的行解析器的話,根本不需要jjTree。但是,如果需要做一些比較有規(guī)模的腳本解析器,比如支持if,while等代碼塊的話,就需要解析器將這些臨時(shí)的狀態(tài)記錄下來,那就必須用到j(luò)jTree了。
加入了語(yǔ)法樹之后,解析器就需要做出一些修改了,比如需要加入全局的符號(hào)表,堆棧等數(shù)據(jù)結(jié)構(gòu),以方便規(guī)約出非終結(jié)符后再做動(dòng)作時(shí)可以取出這些數(shù)據(jù)。當(dāng)然,如果你的腳本引擎支持IO的話,這些全局的流描述符也應(yīng)該和符號(hào)表,堆棧放在一起,比如一個(gè)單獨(dú)的類中。
涉及到使用jjTree的項(xiàng)目,即使是演示目的的,一般也比較大,所以,這篇文章中給出的都是一些片段,如果需要,我可以在blog中做一個(gè)小系列來說。
比如,看一個(gè)例子:
Java代碼
/**Conditionalorexpression.*/
voidConditionalOrExpression()#void:
{}
{
ConditionalAndExpression()
("||"ConditionalAndExpression()#OrNode(2))*
}
上邊這段中 #void意思為當(dāng)遇到ConditionalOrExpression規(guī)則時(shí),不生成節(jié)點(diǎn)(不規(guī)約),而#OrNode(2)則表示如果發(fā)現(xiàn)有恰好2個(gè)ContionalAndExpression()規(guī)則,則規(guī)約到一個(gè)OrNode節(jié)點(diǎn)(需要在外部寫一個(gè)ASTOrNode類)。
而在ASTOrNode類中,會(huì)有下面的動(dòng)作定義
Java代碼
publicvoidinterpret()
{
jjtGetChild(0).interpret();
if(((Boolean)stack[top]).booleanValue())
{
stack[top]=newBoolean(true);
return;
}
jjtGetChild(1).interpret();
stack[--top]=newBoolean(((Boolean)stack[top]).booleanValue()||
((Boolean)stack[top+1]).booleanValue());
}
在語(yǔ)法解析時(shí),當(dāng)規(guī)約出非終結(jié)符后,設(shè)置ASTOrNode父類的children的數(shù)組,然后在ASTOrNode先取出這個(gè)數(shù)組中的第一個(gè)Node進(jìn)行
遞歸解析,完成后取出第二個(gè)Node進(jìn)行解析,最后,將這兩個(gè)解析后的結(jié)果進(jìn)行bool的或運(yùn)算。整個(gè)過程很自然,計(jì)算過程放在外部的單獨(dú)
的類中進(jìn)行。
再看一個(gè)例子:
Java代碼
/**Ablock.*/
voidBlock():
{}
{
"{"(Statement())*"}"
}
用花括號(hào)括起來的一些代碼表示一個(gè)塊,怎么解析這個(gè)快呢?在ASTBlock中,有這樣的運(yùn)算過程:
Java代碼
publicvoidinterpret()
{
inti,k=jjtGetNumChildren();//取出代碼塊中的代碼條數(shù)
for(i=0;i<k;i++)
jjtGetChild(i).interpret();//遞歸解析
}
取出代碼塊中的代碼條數(shù),然后依次執(zhí)行,如果遇到其他的Node,則遞歸調(diào)用這個(gè)Node上的interpret過程,從而執(zhí)行整個(gè)代碼塊。
希望這篇文章可以說明jjTree的運(yùn)行機(jī)制,但是由于規(guī)模所限,有些地方可能還是不太清晰。歡迎留言,我會(huì)盡快在blog上寫一個(gè)小的系列,謝謝!
解釋器(Interpreter)模式:
解釋器模式是類的行為模式。給定一個(gè)語(yǔ)言之后,解釋器模式可以定義出其文法的一種表示,并同時(shí)提供一個(gè)解釋器。客戶端可以使用這個(gè)解釋器
來解釋這個(gè)語(yǔ)言中的句子。
一、解釋器模式所涉及的角色
1、抽象表達(dá)式角色:聲明一個(gè)所有的具體表達(dá)式角色都需要實(shí)現(xiàn)的抽象接口。這個(gè)接口主要是一個(gè)interpret()方法,稱做解釋操作。
2、終結(jié)符表達(dá)式角色:這是一個(gè)具體角色。
(1)實(shí)現(xiàn)了抽象表達(dá)式角色所要求的接口,主要是一個(gè)interpret()方法;
(2)文法中的每一個(gè)終結(jié)符都有一個(gè)具體終結(jié)表達(dá)式與之相對(duì)應(yīng)。
3、非終結(jié)符表達(dá)式角色:這是一個(gè)具體角色。
(1)文法中的每一條規(guī)則 R=R1R2.....Rn 都需要一個(gè)具體的非終結(jié)符表達(dá)式類;
(2)對(duì)每一個(gè) R1R2.....Rn 中的符號(hào)都持有一個(gè)靜態(tài)類型為Expression的實(shí)例變量。
(3)實(shí)現(xiàn)解釋操作,即 interpret()方法。解釋操作以遞歸方式調(diào)用上面所提到的代表 R1R2.....Rn 中的各個(gè)符號(hào)的實(shí)
例變量
4、客戶端角色:代表模式的客戶端它有以下功能
(1)建造一個(gè)抽象語(yǔ)法樹(AST或者Abstract Syntax Tree)
(2)調(diào)用解釋操作interpret()。
5、環(huán)境角色:(在一般情況下,模式還需要一個(gè)環(huán)境角色)提供解釋器之外的一些全局信息,比如變量的真實(shí)量值等。
(抽象語(yǔ)法樹的每一個(gè)節(jié)點(diǎn)都代表一個(gè)語(yǔ)句,而在每一個(gè)節(jié)點(diǎn)上都可以執(zhí)行解釋方法。這個(gè)解釋方法的執(zhí)行就代表這個(gè)語(yǔ)句被解釋。
由于每一個(gè)語(yǔ)句都代表對(duì)一個(gè)問題實(shí)例的解答。)
Java代碼
//抽象角色Expression
/*
這個(gè)抽象類代表終結(jié)類和非終結(jié)類的抽象化
其中終結(jié)類和非終結(jié)類來自下面的文法
Expression::=
ExpressionANDExpression
|ExpressionORExpression
|NOTExpression
|Variable
|Constant
Variable::=....//可以打印出的非空白字符串
Constant::="true"|"false"
*/
publicabstractclassExpression{
//以環(huán)境類為準(zhǔn),本方法解釋給定的任何一個(gè)表達(dá)式
publicabstractbooleaninterpret(Contextctx);
//檢驗(yàn)兩個(gè)表達(dá)式在結(jié)構(gòu)上是否相同
publicabstractbooleanequals(Objecto);
//返回表達(dá)式的hashcode
publicabstractinthashCode();
//將表達(dá)式轉(zhuǎn)換成字符串
publicabstractStringtoString();
}
publicclassConstantextendsExpression{
privatebooleanvalue;
publicConstant(booleanvalue){
this.value=value;
}
//解釋操作
publicbooleaninterpret(Contextctx){
returnvalue;
}
//檢驗(yàn)兩個(gè)表達(dá)式在結(jié)構(gòu)上是否相同
publicbooleanequals(Objecto){
if(o!=null&&oinstanceofConstant){
returnthis.value==((Constant)o).value;
}
returnfalse;
}
//返回表達(dá)式的hashcode
publicinthashCode(){
return(this.toString()).hashCode();
}
//將表達(dá)式轉(zhuǎn)換成字符串
publicStringtoString(){
returnnewBoolean(value).toString();
}
}
publicclassVariableextendsExpression{
privateStringname;
publicVariable(Stringname){
this.name=name;
}
publicbooleaninterpret(Contextctx){
returnctx.lookup(this);
}
publicbooleanequals(Objecto){
if(o!=null&&oinstanceofVariable){
returnthis.name.equals(((Variable)o).name);
}
returnfalse;
}
publicinthashCode(){
return(this.toString()).hashCode();
}
publicStringtoString(){
returnname;
}
}
publicclassAndextendsExpression{
privateExpressionleft,right;
publicAnd(Expressionleft,Expressionright){
this.left=left;
this.right=right;
}
publicbooleaninterpret(Contextctx){
returnleft.interpret(ctx)&&
right.interpret(ctx);
}
publicbooleanequlas(Objecto){
if(o!=null&&oinstanceofAnd){
returnthis.left.equals(((And)o).left)&&
this.right.equals(((And)o).right);
}
returnfalse;
}
publicinthashCode(){
return(this.toString()).hashCode();
}
publicStringtoString(){
return"("+left.toString()+"AND"+right.toString()+")";
}
}
publicclassOrextendsExpression{
privateExpressionleft,right;
publicOr(Expressionleft,Expressionright){
this.left=left;
this.right=right;
}
publicbooleaninterpret(Contextctx){
returnleft.interpret(ctx)||right.interpret(ctx);
}
publicbooleanequals(Objecto){
if(o!=null&&oinstanceofOr){
returnthis.left.equals(((And)o).left)&&
this.right.equals(((And)o).right);
}
returnfalse;
}
publicinthashCode(){
return(this.toString()).hashCode();
}
publicStringtoString(){
return"("+left.toString()+"OR"+right.toString()+")";
}
}
publicclassNotextendsExpression{
privateExpressionexp;
publicNot(Expressionexp){
this.exp=exp;
}
publicbooleaninterpret(Contextctx){
return!exp.interpret(ctx);
}
publicbooleanequals(Objecto){
if(o!=null&&oinstanceofNot){
returnthis.exp.equals(((Not)o).exp);
}
returnfalse;
}
publicinthashCode(){
return(this.toString()).hashCode();
}
publicStringtoString(){
return"(NOT"+exp.toString()+")";
}
}
importjava.util.HashMap;
publicclassContext{
privateHashMapmap=newHashMap();
publicvoidassign(Variablevar,booleanvalue){
map.put(var,newBoolean(value));
}
publicbooleanlookup(Variablevar)throwsIllegalArgumentException{
Booleanvalue=(Boolean)map.get(var);
if(value==null){
thrownewIllegalArgumentException();
}
returnvalue.booleanValue();
}
}
//客戶端
publicclassClient{
privatestaticContextctx;
privatestaticExpressionexp;
publicstaticvoidmain(Stringargs[]){
ctx=newContext();
Variablex=newVariable("x");
Variabley=newVariable("y");
Constantc=newConstant(true);
ctx.assign(x,false);
ctx.assign(y,true);
exp=newOr(newAnd(c,x),newAnd(y,newNot(x)));
System.out.println("x="+x.interpret(ctx));
System.out.println("y="+y.interpret(ctx));
System.out.println(exp.toString()+"="+exp.interpret(ctx));
}
}
二、解釋器模式適用于以下的情況:
(1)系統(tǒng)有一個(gè)簡(jiǎn)單的語(yǔ)言可供解釋
(2)一些重復(fù)發(fā)生的問題可以用這種簡(jiǎn)單的語(yǔ)言表達(dá)
(3)效率不是主要的考慮。
?如果你喜歡本文, 請(qǐng)長(zhǎng)按二維碼,關(guān)注公眾號(hào) 分布式編程.
作者:分布式編程
出處:https://zthinker.com/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
總結(jié)
以上是生活随笔為你收集整理的小型桌面计算器的实现(javacc)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jar命令|jdt的简单使用
- 下一篇: 什么是优秀的程序员