下面咱們對上個例子的代碼進行進一步的修改,使得代碼具備簡單的四則運算的功能。
第一步修改,咱們將打印出每一行的值,使得計算器更具交互性。一開始,咱們只是把數字加起來,而後再關注其餘運算,好比減法、乘法和除法。java
描述文件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類中去。code
詞法分析器的描述文件在這裏將會發生一些變化,首先一行的結束符也被聲明爲了token,並給這些行結束符命名爲EOL,這樣一來這個token也能夠被傳遞給語法分析器了。orm
SKIP : { " " } TOKEN : { < EOL : "\n" | "\r" | "\r\n" > } TOKEN : { < PLUS : "+" > }
接下來,咱們要定義合適的token使得容許輸入中的數值有小數點。爲此咱們修改NUMBER這個token的定義,使得它能夠識別decimal類型的數值。當數值中有小數點,它能夠有以下的4中類型,咱們分別用豎線分隔開來了,這4中類型分別是:整型,沒有小數點、小數點在中間、小數點在末尾和小數點在開頭。知足此需求的描述串以下:blog
TOKEN { < NUMBER : (["0"-"9"])+ | (["0"-"9"])+ "." (["0"-"9"])+ | (["0"-"9"])+ "." | "." (["0"-"9"])+ > }
有時候,一樣的規則表達式可能會出現屢次。爲了更好的可讀性,最好是給這些重複出現的表達式起一個名字。對於那些只在詞法描述文件中使用到,但又不是token的規則表達式,咱們建立了一個特殊的標識來表示它:#。所以,對於上面的詞法描述,能夠替換成以下:token
TOKEN : { < NUMBER : <DIGITS> | <DIGITS> "." <DIGITS> | <DIGITS> "." | "."<DIGITS> > } TOKEN : { < #DIGITS : (["0"-"9"])+ > }
能夠看到,咱們把([」0」-」9」])+這串規則表達式提取了出來,並將其命名爲了DIGITS。可是要注意到,DIGITS這個並非token,這意味着在後面生成的Token類中,將不會有DIGITS對應的屬性,而在語法分析器中也沒法使用DIGITS。ci
語法分析器的輸入由零行或多行組成。每行包含一個表達式。經過使用BNF符號表達式,語法分析器能夠寫成以下:io
Start -->(Expression EOL) * EOF
由此咱們能夠得出BNF生產式以下:class
void Start() : {} { ( Expression() <EOL> )* <EOF> }
咱們在上面的BNF生產式中填充上java代碼,使得它具有接收入參、記錄並打印每一行的計算結果:import
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描述文件都修改完了,接下來能夠跑幾個例子進行測試。
通過上面的修改,最後獲得的.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 ) ; } }
咱們首先計算1+2,以下所示:
接下來計算1+2.,即小數點在末尾的情形:
接下來計算1 + 2.3,即小數點在中間的情形:
接下來計算1 + .2,即小數點在開頭的情形: