6.JavaCC官方入門指南-例1

例1:整數加法運算

  在這個例子中,咱們將判斷以下輸入的式子是不是一個合法的加法運算:java

99 + 42 + 0 + 15

  而且在輸入上面式子的時候,數字與加號之間的任何位置,都是能夠有空格或者換行符的,也就說,即便咱們輸入的式子是下面這種形式,咱們所編寫的詞法和語法分析器也應該要能判斷出來它是一個合法的加法運算表示形式:正則表達式

99      + 42 + 0
+ 15

  (注:上面輸入的式子中既有空格,也有製表符,還有換行符)windows

1.Option塊和class聲明塊

  語法描述文件的第一部分是:函數

/* adder.jj Adding up numbers */
options {
    STATIC = false ;
    }

PARSER_BEGIN(Adder)
    class Adder {
        public static void main( String[] args ) throws ParseException, TokenMgrError {
            Adder parser = new Adder( System.in );
            parser.Start();
        }
    }
PARSER_END(Adder)

  上面的代碼能夠分爲兩個部分,一個是options塊,另外一個是PARSER_BEGIN(XXX)…… PARSER_END(XXX)塊。編碼

  • 在options中,幾乎全部配置項的默認值都適用於本例子,除了 STATIC選項,STATIC默認是true,這裏要將其修改成false,使得生成的函數不是static 的。
  • 接下來是ARSER_BEGIN(XXX)……PARSER_END(XXX)塊,這裏定義了一個名爲 Adder的類,固然在這個塊中定義的並不是是Adder類的所有,JavaCC會根據.jj描述文件的其餘部分的描述,來生成Adder的其餘聲明信息。另外注意到,在該類的main方法聲明中, 拋出了兩個異常類,分別爲ParseException和TokenMgrError,這兩個異常類會在使用javacc 命令編譯當前.jj描述文件的時候生成。)

2.詞法描述器

  咱們在後面還會再講到main方法。這裏讓咱們先看看詞法描述器。當前例子中所需的詞法描述器經過以下的四行來進行描述:3d

SKIP : { " "}
SKIP : { "\n" | "\r" | "\r\n" }
TOKEN : { < PLUS : "+" > }
TOKEN : { < NUMBER : (["0"-"9"])+ > }
  • 第一行是SKIP,表示將會被詞法分析器忽略的部分:空格。被忽略掉意味着,它們將不會被傳給語法分析器。
  • 第二行也是SKIP,定義了將會被忽略的部分:換行符。之因此會有幾個,是由於在不一樣的系統中,換行符有不一樣的表示方式——在Unix/Linux系統中,換行符是"\n";在Windows系統中,換行符是"\r";在mac系統中,換行符則是"\r\n"。這幾個換行符用一個豎槓分隔,表示「或」的意思。
  • 第三行定義了一個名爲PLUS的token,用它來表示加號"+"。
  • 第四行定義了一個名爲NUMBET的token,用它來表示([」0」-」9」])+,即全部的正整數序列。能夠注意到([」0」-」9」])+是一個正則表達式。
      這四行描述均可以被稱爲表達生產式。
      事實上,詞法分析器中還能夠生成一種token,這種token用EOF表示,用來表明輸入序列的末尾。可是沒有必要在詞法分析器部分顯式的定義EOF這個token,由於JavaCC會自動處理文件的結束符了。
      假設有以下的輸入:
「123 + 456\n」

  詞法分析器將解析的7個token,依次是NUMBER、空格、PLUS、空格、NUMBER、換行符、EOF。在解析出來的這些token中,被標記爲SKIP的token將不會被往下傳遞給語法分析器。詞法分析器在分析完成以後,只會將如下token傳遞給語法分析器:NUMBER, PLUS, NUMBER, EOF。
  再假設一種不合法的輸入,以下:code

「123 - 456\n」

  詞法分析器在對上面的輸入進行解析時,解析到的第一個token是NUMBER,第二個token是空格,接下來,它就遇到了一個減號字符——由於在咱們上面的詞法描述器中沒有定義減號這個token,所以就沒法對其進行解析,此時詞法分析器就拋出一個異常:TokenMgrError。
  再看另一種輸入狀況:對象

「123 ++ 456\n」

  這時詞法分析器能夠對其進行解析,並給語法分析器傳遞以下的token序列:NUMBER, PLUS, PLUS, NUMBER, EOF。
  很明顯,這不是一個合法的「加運算」的輸入它連續出現了兩個PLUS token。可是,詞法分析器的任務是將輸入解析成一個個的token,而不是判斷token的順序是否正確。判斷tokens序列的順序是否正確是語法分析器的任務。接下來將會介紹到的語法分析器,它在分析到第二個PLUS token的時候,將會檢測到錯誤,一旦檢測到錯誤,它就中止從詞法分析器請求tokens了,因此,實際上真正傳遞給語法分析器的tokens序列只有NUMBER, PLUS, PLUS。blog

3.語法分析器

  語法分析器的描述由BNF生產式構成。能夠看到,語法分析器的描述看起來跟java的方法定義形式有點類似。token

void Start() :
{}
{
    <NUMBER>
    (
        <PLUS>
        <NUMBER>
    )*
    <EOF>
}

  上面的BNF生產式指定了合法的token序列的規則:必須以NUMBER token開頭,以EOF token結尾,而在NUMBER和EOF中間,能夠是0至多個PLUS和NUMBER的token,並且必須是PLUS後跟着NUMBER。
  根據上面的語法描述器語法,解析器將只檢測輸入序列是否無錯誤,它不會把數字加起來。咱們接下來將很快修改解析器描述文件以更正此問題,可是首先,讓咱們生成Java組件並運行它們。

4.生成詞法描述器和語法描述器

  將前面部分提到的幾個部分都合併起來,保存成adder.jj文件:

/* adder.jj Adding up numbers */
options {
    STATIC = false ;
    }

PARSER_BEGIN(Adder)
    class Adder {
        public static void main( String[] args ) throws ParseException, TokenMgrError {
            Adder parser = new Adder( System.in );
            parser.Start();
        }
    }
PARSER_END(Adder)

SKIP : { " "}
SKIP : { "\n" | "\r" | "\r\n" }
TOKEN : { < PLUS : "+" > }
TOKEN : { < NUMBER : (["0"-"9"])+ > }

void Start() :
{}
{
    <NUMBER>
    (
        <PLUS>
        <NUMBER>
    )*
    <EOF>
}

  而後在上面調用javacc命令。下面是在windows系統上的演示。

  執行完以後,會生成7個java文件。以下所示:

  其中:

  • TokenMgrError 是一個簡單的定義錯誤的類,它是Throwable類的子類,用於定義在詞法分析階段檢測到的錯誤。
  • ParseException是另外一個定義錯誤的類。它是Exception 和Throwable的子類,用於定義在語法分析階段檢測到的錯誤。
  • Token類是一個用於表示token的類。咱們在.jj文件中定義的每個token(PLUS, NUMBER, or EOF),在Token類中都有對應的一個整數屬性來表示,此外每個token都有名爲image的string類型的屬性,用來表示token所表明的從輸入中獲取到的真實值。
  • SimpleCharStream是一個轉接器類,用於把字符傳遞給語法分析器。
  • AdderConstants是一個接口,裏面定義了一些詞法分析器和語法分析器中都會用到的常量。
  • AdderTokenManager 是詞法分析器。
  • Adder 是語法分析器。

  接下來咱們對這些java文件進行編譯:

  編譯完成以後,可獲得對應的class文件:

5.執行程序

  如今,讓咱們來看看Adder類中的main方法:

Pubic static void main( String[] args ) throws ParseException, TokenMgrError {
    Adder parser = new Adder( System.in );
    parser.Start();
}

  首先注意到main方法有可能拋出兩個異常錯誤類:ParseException和TokenMgrError,它們都是Throwable類的子類。這並非一種好的編碼習慣,理論上咱們應該對着兩異常進行try-catch捕獲,可是在本例中咱們暫且將其拋出,以使得代碼簡潔易懂。
  main方法的第一行代碼new了一個parser對象,使用的是Adder類的默認構造器,它接收一個InputStream類型對象做爲輸入。此外Adder類還有一個構造器,這個構造器接收的入參是一個Reader對象。構造函數依次建立一個SimpleCharacterStream類實例和一個AdderTokenManager類的實例(即詞法分析器對象)。所以,最後的效果是,詞法分析器經過SimpleCharacterStream實例對象從System.in中讀取字符,而語法分析器則是從語法分析器中讀取tokens。
  第二行代碼則是調用了語法分析器的一個名爲Start()的方法。對於在.jj文件中的每個BNF生產式,javacc在parser類中都會生成相應的方法。此方法負責嘗試在其輸入流中找到與輸入描述匹配的項。好比,在本例中,調用Start()方法將會使得語法分析器嘗試從輸入中符合下面描述的tokens序列:

<NUMBER> (<PLUS> <NUMBER>)* <EOF>

  咱們能夠事先準備一個input.txt文件,裏面的內容下面會說到。在準備了輸入文件以後,接下來就能夠用下面的命令來執行程序了。

  執行的結果有可能爲如下3中狀況之一:

  1. 程序報一個詞法錯誤。好比,詞法錯誤只有在詞法分析器沒法解析出輸入的字符時纔會拋出。假設input.txt文件中的內容是123 – 456,在這種狀況下,程序就會拋出一個TokenMgrError的錯誤,報錯的信息是:Exception in thread 」main」 TokenMgrError: Lexical error at line 1,column 5. Encountered: 」-」 (45), after : 」」 。即:詞法分析器不認識「-」減號,由於在.jj文件中,咱們並無對「-」定義相應的token。
  2. 程序報一個語法錯誤。當語法分析器接收到的tokens序列不匹配Start()方法中的規範時,就會拋出該錯誤。好比,若input.txt文件中的內容是123 ++ 456或123 456又或者什麼都不寫,此時程序就會拋出一個ParseException.異常,對於123 ++ 456來講,拋出的異常就是:Exception in thread 」main」 ParseException: Encountered 」+」 at line 1, column 6. Was expecting: ...
  3. 若輸入中的tokens序列跟Start()方法中的規範匹配時,將不拋出任何錯誤異常,程序正常結束。
      可是從咱們的代碼能夠看出,當咱們的輸入是合法的時候,語法分析器什麼也不幹,它僅僅用於檢查咱們的輸入是否符合相應的表達式規範而已。在下一小節中,咱們將對.jj文件進行一些修改,使得生成的語法分析器有更多的用處。

6.生成的代碼解析

  想要知道JavaCC生成的語法分析器是如何工做的,咱們須要看一些它生成的代碼。

final public void Start() throws ParseException {
    jj_consume_token(NUMBER);
    label_1:
    while (true) {
        jj_consume_token(PLUS);
        jj_consume_token(NUMBER);
        switch ((jj_ntk == -1) ? jj_ntk() : jj_ntk) {
        case PLUS:
            ;
            break;
        default:
            jj_la1[0] = jj_gen;
            break label_1;
        }
    }
    jj_consume_token(0);
}

  jj_consume_token方法以token類型做爲入參,並試圖從語法分析器中獲取指定類型的token,若是下一個獲取到的token跟語法分析器中定義的不一樣,此時就會拋出一個異常。看下面的表達式:

(jj_ntk == -1) ? jj_ntk() : jj_ntk

  該表達式計算下一個未讀的token。   程序的最後一行是要獲取一個爲0的token。JavaCC老是使用0來表示EOF的token。

相關文章
相關標籤/搜索