8.JavaCC官方入門指南-例3


例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,即小數點在開頭的情形:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM