例6:計算器--添加括號、一元運算符和歷史記錄
1.calculator3.jj
我們只需要再添加一些特色,就可以得到一個可用的四則運算計算器。在這一版的修改中 ,我們將使得程序可以接收括號、負值,並且還可以通過$符號來引用上一次計算的結果。
對詞法描述文件的修改如下所示,我們只添加下面3行:
TOKEN : { < OPEN_PAR : "(" > }
TOKEN : { < CLOSE_PAR : ")" > }
TOKEN : { < PREVIOUS : "$" > }
我們沒有必要專門為負號創建一個token,因為我們已經定義了MINUS這個token了。
對於語法描述部分的修改,則都是體現在了Primary當中,在Primary當中有4中可能的值:一個數值(跟之前的例子一樣)、一個$符號、帶有括號的表達式、一個負號然后跟着前3個的任一種。BNF符號表達式如下:
Primary --> NUMBER
| PREVIOUS
| OPEN_PAR Expression CLOSE_PAR
| MINUS Primary
這個BNF生產式中有兩個遞歸。最后一種選擇是直接遞歸,倒數第二個選擇是間接遞歸,因為Expression最終還是依賴於Primary。在BNF生產式中使用遞歸是沒有任何問題的,當然也有一些限制,我們將在后面談論到。
考慮下面的表達式:
- - 22
那么Primary就是下圖中的方塊部分:
在語法分析器執行到上面的輸入時,對於每一個方塊,都會調用一次Primary。相似的,對於下面的輸入:
12 * ( 42 + 19 )
我們把Primary框出來,可得到如下所示:
相互嵌套的框框,其實就表示了相互遞歸調用的Primary方法。
下面是JavaCC中Primary的生產式:
double Primary() throws NumberFormatException :
{
Token t ;
double d ;
}
{
t=<NUMBER>
{ return Double.parseDouble( t.image ) ; }
| <PREVIOUS>
{ return previousValue ; }
| <OPEN_PAR> d=Expression() <CLOSE_PAR>
{ return d ; }
| <MINUS> d=Primary()
{ return -d ; }
}
2. 測試
根據上面的修改,得到完整的calculator3.jj文件如下:
/* calculator0.jj An interactive calculator. */
options {
STATIC = false ;
}
PARSER_BEGIN(Calculator)
import java.io.PrintStream ;
class Calculator {
public static void main( String[] args )
throws ParseException, TokenMgrError, NumberFormatException {
Calculator parser = new Calculator( System.in ) ;
parser.Start( System.out ) ;
}
double previousValue = 0.0 ;
}
PARSER_END(Calculator)
SKIP : { " " }
TOKEN : { < EOL : "\n" | "\r" | "\r\n" > }
TOKEN : { < PLUS : "+" > }
TOKEN : { < MINUS : "-" > }
TOKEN : { < TIMES : "*" > }
TOKEN : { < DIVIDE : "/" > }
TOKEN : { < OPEN_PAR : "(" > }
TOKEN : { < CLOSE_PAR : ")" > }
TOKEN : { < PREVIOUS : "$" > }
TOKEN : { < NUMBER : <DIGITS>
| <DIGITS> "." <DIGITS>
| <DIGITS> "."
| "."<DIGITS> >
}
TOKEN : { < #DIGITS : (["0"-"9"])+ > }
void Start(PrintStream printStream) throws NumberFormatException :
{}
{
(
previousValue = Expression()
<EOL> { printStream.println( previousValue ) ; }
)*
<EOF>
}
double Expression() throws NumberFormatException :
{
double i ;
double value ;
}
{
value = Term()
(
<PLUS>
i = Term()
{ value += i ; }
| <MINUS>
i = Term()
{ value -= i ; }
)*
{ return value ; }
}
double Term() throws NumberFormatException :
{
double i ;
double value ;
}
{
value = Primary()
(
<TIMES>
i = Primary()
{ value *= i ; }
| <DIVIDE>
i = Primary()
{ value /= i ; }
)*
{ return value ; }
}
double Primary() throws NumberFormatException :
{
Token t ;
double d ;
}
{
t=<NUMBER>
{ return Double.parseDouble( t.image ) ; }
| <PREVIOUS>
{ return previousValue ; }
| <OPEN_PAR> d=Expression() <CLOSE_PAR>
{ return d ; }
| <MINUS> d=Primary()
{ return -d ; }
}
計算(1+2)*-3,如下所示,正確計算得到結果-9:
計算器示例到此結束。官方文檔中其實還有一個“文本處理”的例子,時間有限就不翻譯了。可以直接去閱讀英文原版,其實還是比較容易懂的。上面的示例通過由淺入深的引導,讓我們大致知道JavaCC能干什么以及是怎么工作的。如果想要使用JavaCC做更多的事情,建議還是把最后一個例子的英文原版看完看懂,並多加練習。