例3:計算器—double類型加法
下面我們對上個例子的代碼進行進一步的修改,使得代碼具有簡單的四則運算的功能。
第一步修改,我們將打印出每一行的值,使得計算器更具交互性。一開始,我們只是把數字加起來,然后再關注其他運算,比如減法、乘法和除法。
1.Options和class聲明塊
描述文件calculator0.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)
Calculator類中的previousValue屬性,用於存儲上一行的計算結果的,我們將在另一個版本中使用到該值,到時可以使用美元符號來表示它。import導入語句聲明說明了在PARSER_BEGIN和PARSER_END之間可能有import導入聲明;這些代碼都會被原樣復制到生成的語法解析類和token管理類中去。同樣還可以有包package的聲明,package的聲明將會被復制到最后生成的所有java類中去。
2.詞法描述文件
詞法分析器的描述文件在這里將會發生一些變化,首先一行的結束符也被聲明為了token,並給這些行結束符命名為EOL,這樣一來這個token也可以被傳遞給語法分析器了。
SKIP : { " " }
TOKEN : { < EOL : "\n" | "\r" | "\r\n" > }
TOKEN : { < PLUS : "+" > }
接下來,我們要定義合適的token使得允許輸入中的數值有小數點。為此我們修改NUMBER這個token的定義,使得它可以識別decimal類型的數值。當數值中有小數點,它可以有如下的4中類型,我們分別用豎線分隔開來了,這4中類型分別是:整型,沒有小數點、小數點在中間、小數點在末尾和小數點在開頭。滿足此需求的描述串如下:
TOKEN { < NUMBER : (["0"-"9"])+
| (["0"-"9"])+ "." (["0"-"9"])+
| (["0"-"9"])+ "."
| "." (["0"-"9"])+ >
}
有時候,同樣的規則表達式可能會出現多次。為了更好的可讀性,最好是給這些重復出現的表達式起一個名字。對於那些只在詞法描述文件中使用到,但又不是token的規則表達式,我們創建了一個特殊的標識來表示它:#。因此,對於上面的詞法描述,可以替換成如下:
TOKEN : { < NUMBER : <DIGITS>
| <DIGITS> "." <DIGITS>
| <DIGITS> "."
| "."<DIGITS> >
}
TOKEN : { < #DIGITS : (["0"-"9"])+ > }
可以看到,我們把([”0”-”9”])+這串規則表達式提取了出來,並將其命名為了DIGITS。但是要注意到,DIGITS這個並不是token,這意味着在后面生成的Token類中,將不會有DIGITS對應的屬性,而在語法分析器中也無法使用DIGITS。
3.語法描述文件
語法分析器的輸入由零行或多行組成。每行包含一個表達式。通過使用BNF符號表達式,語法分析器可以寫成如下:
Start -->(Expression EOL) * EOF
由此我們可以得出BNF生產式如下:
void Start() :
{}
{
(
Expression()
<EOL>
)*
<EOF>
}
我們在上面的BNF生產式中填充上java代碼,使得它具備接收入參、記錄並打印每一行的計算結果:
void Start(PrintStream printStream) throws NumberFormatException :
{}
{
(
previousValue = Expression()
<EOL> { printStream.println( previousValue ) ; }
)*
<EOF>
}
每個表達式由一個或多個數字組成,這些數字目前用加號隔開。用BNF符號表達式如下:
Expression --> Primary(PLUS Primary)*
在這里的Primary,我們暫時用它來表示數值。
上面的BNF符號表達式用JavaCC表示出來如下所示:
double Expression() throws NumberFormatException : {
double i ;
double value ;
}
{
value = Primary()
(
<PLUS>
i = Primary()
{ value += i ; }
)*
{ return value ; }
}
這個跟我們前面例子中的Start BNF生產式差不多,我們只是將數值的類型由int修改成了double類型而已。至於Primary(),跟上面例子也非常類似,它用BNF符號表達式來表示:
Primary --> NUMBER
Primary()對應的JavaCC描述文件其實也差不多,只不過在這里它是對double精度的數值進行的轉換計算:
double Primary() throws NumberFormatException :
{
Token t ;
}
{
t = <NUMBER>
{ return Double.parseDouble( t.image ) ; }
}
下面我們用BNF符號表達式將語法分析器的邏輯表示出來:
Start --> (Expression EOL) * EOF
Expression --> Primary (PLUS Primary)*
Primary --> NUMBER
至此,我們就把calculator0.jj描述文件都修改完了,接下來可以跑幾個例子進行測試。
4.測試
4.1 calculator0.jj
經過上面的修改,最后得到的.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 : { < 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 = Primary()
(
<PLUS>
i = Primary()
{ value += i ; }
)*
{ return value ; }
}
double Primary() throws NumberFormatException :
{
Token t ;
}
{
t = <NUMBER>
{ return Double.parseDouble( t.image ) ; }
}
4.2 運行
我們首先計算1+2,如下所示:
接下來計算1+2.,即小數點在末尾的情形:
接下來計算1 + 2.3,即小數點在中間的情形:
接下來計算1 + .2,即小數點在開頭的情形: