迷途書童

          敏感、勤學、多思
          隨筆 - 77, 文章 - 4, 評論 - 86, 引用 - 0
          數據加載中……

          從lex&yacc說到編譯器(5.實用javacc)

          前言

          本系列的文章的宗旨是讓大家能夠寫出自己的編譯器,解釋器或者腳本引擎,所以每到理論介紹到一個程度后,我都會來討論實踐問題.理論方面,編譯原理的教材已經是夠多了,而實踐的問題卻很少討論.

          前幾節文章只討論到了詞法分析和LL文法分析,關鍵的LR文法分析這里卻還沒有講,我們先不要管復雜的LR文法和算法,讓我們使用LL算法來實際做一些東西后再說.本文將介紹一個在JAVA上廣泛使用的LL算法分析工具Javacc.(這是我唯一能找到的使用LL算法的語法分析器構造工具).這一節的文章并非只針對JAVA開發者,如果你是C/C++開發者,那么也請你來看看這個JAVA下的優秀工具,或許你將來也用得著它.

          Lex和yacc這兩個工具是經典的詞法分析和語法分析工具,但是它們都是基于C語言下面的工具,而使用JAVA的朋友們就用不上了.但是JAVA下已經有了lex和yacc的替代品javacc( Java Compiler Compiler ) .同時javacc也是使用LL算法的工具,我們也可以實踐一下前面學的LL算法.

          首先聲明我不是一個JAVA專家,我也是剛剛才接觸JAVA.Java里面或許有很多類似javacc一樣的工具,但是據我所知,javacc還是最廣泛,最標準的JAVA下的詞法語法分析器.

          Javacc的獲取

          lex和yacc一樣,javacc也是一個免費可以獲取的通用工具,它可以在很多JAVA相關的工具下載網站下載,當然,javacc所占的磁盤空間比起lex和yacc更大一些,里面有標準的文檔和examples.相對lex和yacc來說,javacc做得更人性化,更容易一些.如果你實在找不到javacc,還是可以聯系我,我這里有.現在最新的就是javacc 3.2版本.

          Javacc的原理

          Javacc可以同時完成對text的詞法分析和語法分析的工作,使用起來相當方便.同樣,它和lex和yacc一樣,先輸入一個按照它規定的格式的文件,然后javacc根據你輸入的文件來生成相應的詞法分析于語法分析程序.同時,新版本的Javacc除了常規的詞法分析和語法分析以外,還提供JJTree等工具來幫助我們建立語法樹.總之,Javacc在很多地方做得都比lex和yacc要人性化,這個在后面的輸入文件格式中也能體現出來.

          Javacc的輸入文件

          Javacc的輸入文件格式做得比較簡單.每個非終結符產生式對應一個Class中的函數,函數中可以嵌入相應的識別出該終結符文法時候的處理代碼(也叫動作).這個與YACC中是一致的.

          Javacc的輸入文件中,有一系列的系統參數,比如其中lookahead可以設置成大于1的整數,那么就是說,它可以為我們生成LL(k)算法(k>=1),而不是簡單的遞歸下降那樣的LL(1)算法了.要知道,LL(2)文法比起前面討論的LL(1)文法判斷每個非終結符時候需要看前面兩個記號而不是一個,那么對于文法形式的限制就更少.不過LL(2)的算法當然也比LL(1)算法慢了不少.作為一般的計算機程序設計語言,LL(1)算法已經是足夠了.就算不是LL(1)算法,我們也可以通過前面講的左提公因式把它變成一個LL(1)文法來處理.不過既然javacc都把lookahead選擇做出來了,那么在某些特定的情況下,我們可以直接調整一個lookahead的參數就可以,而不必糾正我們的文法.

          下面我們來看看Javacc中自帶的example中的例子.

          5.1

          這個例子可以在javacc-3.2/doc/examples/SimpleExamples/Simple1.jj看到

          PARSER_BEGIN(Simple1)

          public class Simple1 {

          public static void main(String args[]) throws ParseException {

          ??? Simple1 parser = new Simple1(System.in);

          ??? parser.Input();

          ? }

          }

          PARSER_END(Simple1)

          void Input() :

          {}

          {

          ? MatchedBraces() ("\n"|"\r")* <EOF>

          }

          void MatchedBraces() :

          {}

          {

          "{" [ MatchedBraces() ] "}"

          }

          設置好javacc的bin目錄后,在命令提示符下輸入

          javacc Simple1.jj

          然后 javacc 就會為你生成下面幾個 java 源代碼文件

          Simple1.java

          Simple1TokenManager.java

          Simple1Constants.java

          SimpleCharStream.java

          Token.java

          TokenMgrError.java

          其中Simple1就是你的語法分析器的對象,它的構造函數參數就是要分析的輸入流,這里的是System.in.

          class Simple1就定義在標記 PARSER_BEGIN(Simple1)

          PARSER_END(Simple1)之間.

          但是必須清楚的是,PARSER_BEGIN和PARSER_END中的名字必須是詞法分析器的名字(這里是Simple1).

          PARSER_END下面的定義就是文法非終結符號的定義了.

          Simple1的文法基本就是:

          Input -> MatchedBraces ("\n"|"\r")* <EOF>

          MatchedBraces -> { MatchedBraces }

          從它的定義我們可以看到 , 每個非終結符號對于一個過程 .

          比如 Input 的過程

          void Input() :

          {}

          {

          ? MatchedBraces() ("\n"|"\r")* <EOF>

          }

          在定義 void Input 后面記住需要加上一個冒號 ”:”, 然后接下來是兩個塊 {} 的定義 .

          第一個 {} 中的代碼是定義數據 , 初試化數據的代碼 . 第二個 {} 中的部分就是真正定義 Input 的產生式了 .

          每個產生式之間用 ”|” 符號連接 .

          注意 : 這里的產生式并非需要嚴格 BNF 范式文法 , 它的文法既可以是 BNF, 同時還可以是混合了正則表達式中的定義方法 . 比如上面的

          Input -> MatchedBraces ("\n"|"\r")* <EOF>

          (“\n”|”\r”)* 就是個正則表達式 , 表示的是 \n 或者 \r 0 個到無限個的重復的記號 .

          <EOF> javacc 系統定義的記號 (TOKEN), 表示文件結束符號 .

          除了 <EOF>, 無論是系統定義的 TOKEN, 還是自定義的 TOKEN, 里面的 TOKEN 都是以 <token’s name> 的方式表示 .

          每個非終結符號 (Input MatchedBraces) 都會在 javacc 生成的 Simple1.java 中形成 Class Simple1 的成員函數 . 當你在外部調用 Simple1 Input, 那么語法分析器就會開始進行語法分析了 .

          5.2

          javacc 提供的 example 里面沒有 .javacc 提供的 example 里面提供的例子中 SimpleExamples 過于簡單 , 而其它例子又過于龐大 . 下面我以我們最常見的數學四則混合運算的文法來構造一個 javacc 的文法識別器 . 這個例子是我自己寫的 , 十分簡單 ,. 其中還包括了文法識別同時嵌入的構建語法樹 Parse-Tree 的代碼 . 不過由于篇幅的原因 , 我并沒有給出全部的代碼 , 這里只給了 javacc 輸入部分相關的代碼 . Parse-tree 就是一個普通的 4 叉樹 ,3 child,1 next( 平行結點 ), 相信大家在學習數據結構的時候應該都是學過的 . 所以這里就省略過去了 .

          在大家看這些輸入代碼之前 , 我先給出它所使用的文法定義 , 好讓大家有個清楚的框架 .

          Expression -> Term{ Addop Term }
          Addop -> "+" | "-"
          Term -> Factor { Mulop Factor }
          Mulop -> "*" | "/"
          Factor -> ID | NUM | "("Expression")"

          這里的文法可能和BNF范式有點不同.{}的意思就是0次到無限次重復,它跟我們在學習正則表達式的時候的”*”符號相同,所以,在Javacc中的文法表示的時候,{…}部分的就是用(…)*來表示.

          為了讓詞法分析做得更簡單 , 我們通常都不會在文法分析的時候 , 使用 ”(”,”)“ 等字符號串來表示終結符號 , 而需要轉而使用 LPAREN , RPAREN 這樣的整型符號來表示 .

          PARSER_BEGIN(Grammar)

          public class Grammar implements NodeType {

          ? public ParseTreeNode GetParseTree(InputStream in) throws ParseException

          ? {

          ? ???? Grammar parser =new Grammar(in);

          ? ???? return parser.Expression();

          ? }

          ?

          }

          PARSER_END(Grammar)

          SKIP :

          {

          ? " " | "\t" | "\n" | "\r"

          }

          TOKEN :

          {

          ? < ID: ["a"-"z","A"-"Z","_"] ( ["a"-"z","A"-"Z","_","0"-"9"] )* >

          |? < NUM: ( ["0"-"9"] )+ >

          | ?< PLUS:?? "+" >

          | ?< MINUS:? "-" >

          | ?< TIMERS: "*" >

          | ?< OVER:?? "/" >

          | ?< LPAREN: "(" >

          | ?< RPAREN: ")" >

          }

          ParseTreeNode Expression() :

          {

          ???????? ParseTreeNode ParseTree = null;

          ???????? ParseTreeNode node;

          }

          {????????????????

          ?( node=Simple_Expression()

          ?{

          ??? if(ParseTree == null)

          ??? ???????? ParseTree =node;

          ??? else

          ??? {

          ??????? ???????? ParseTreeNode t;

          ??????? ?? t= ParseTree;

          ??????? ???????? while(t.next != null)

          ???????? ???????? t=t.next;

          ??? ?????????? t.next = node;

          ??? }

          ?}

          )*

          ? { return ParseTree;}

          ? <EOF>

          }

          ParseTreeNode Simple_Expression() :

          {

          ???????? ParseTreeNode node;

          ???????? ParseTreeNode t;

          ???????? int op;

          }

          {

          ? node=Term(){}

          ? (

          ? op=addop() t=Term()

          {

          ???????? ???????? ParseTreeNode newNode = new ParseTreeNode();

          ???????? ???????? newNode.nodetype = op;

          ???????? ???????? newNode.child[0] = node;

          ???????? ???????? newNode.child[1] = t;

          ???????? ???????? switch(op)

          posted on 2006-05-06 16:18 迷途書童 閱讀(970) 評論(1)  編輯  收藏 所屬分類: 編譯原理

          評論

          # re: 從lex&yacc說到編譯器(5.實用javacc)  回復  更多評論   

          如果不是寫的,請注明轉載
          如果轉載,請轉載完整,ok?
          2006-08-02 15:52 | fff
          主站蜘蛛池模板: 恩施市| 兴隆县| 庄浪县| 崇信县| 施甸县| 江津市| 德江县| 钟祥市| 藁城市| 县级市| 宁远县| 天长市| 定安县| 阳曲县| 吴江市| 徐州市| 白山市| 航空| 手游| 江城| 山阴县| 连南| 绥德县| 定安县| 莱芜市| 甘南县| 砀山县| 泸定县| 广昌县| 密山市| 阳山县| 上虞市| 延寿县| 潍坊市| 九龙城区| 石门县| 亚东县| 平潭县| 剑川县| 五大连池市| 临武县|