隨筆 - 312, 文章 - 14, 評論 - 1393, 引用 - 0
          數(shù)據(jù)加載中……

          從lex&yacc說到編譯器--Javacc

          前言

              本系列的文章的宗旨是讓大家能夠?qū)懗鲎约旱木幾g器,解釋器或者腳本引擎,所以每到理論介紹到一個(gè)程度后,我都會(huì)來討論實(shí)踐問題.理論方面,編譯原理的教材已經(jīng)是夠多了,而實(shí)踐的問題卻很少討論.

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

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

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

           

          Javacc的獲取

              同lex 和yacc一樣,javacc也是一個(gè)免費(fèi)可以獲取的通用工具,它可以在很多JAVA相關(guān)的工具下載網(wǎng)站下載,當(dāng)然,javacc所占的磁盤空間比起 lex和yacc更大一些,里面有標(biāo)準(zhǔn)的文檔和examples.相對lex和yacc來說,javacc做得更人性化,更容易一些.如果你實(shí)在找不到 javacc,還是可以聯(lián)系我,我這里有.現(xiàn)在最新的就是javacc 3.2版本.

           

          Javacc的原理

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

           

          Javacc的輸入文件

          Javacc的輸入文件格式做得比較簡單.每個(gè)非終結(jié)符產(chǎn)生式對應(yīng)一個(gè)Class中的函數(shù),函數(shù)中可以嵌入相應(yīng)的識(shí)別出該終結(jié)符文法時(shí)候的處理代碼(也叫動(dòng)作).這個(gè)與YACC中是一致的.

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

           

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

          5.1

          這個(gè)例子可以在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() ] "}"

          }

           

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

          javacc Simple1.jj

          然后javacc就會(huì)為你生成下面幾個(gè)java源代碼文件

          Simple1.java

          Simple1TokenManager.java

          Simple1Constants.java

          SimpleCharStream.java

          Token.java

          TokenMgrError.java

           

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

          class Simple1就定義在標(biāo)記PARSER_BEGIN(Simple1)

          PARSER_END(Simple1)之間.

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

           

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

          Simple1的文法基本就是:

           

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

          MatchedBraces -> { MatchedBraces }

           

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

          比如Input的過程

          void Input() :

          {}

          {

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

          }

           

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

          第一個(gè){}中的代碼是定義數(shù)據(jù),初試化數(shù)據(jù)的代碼.第二個(gè){}中的部分就是真正定義Input的產(chǎn)生式了.

          每個(gè)產(chǎn)生式之間用”|”符號連接.

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

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

          (“\n”|”\r”)* 就是個(gè)正則表達(dá)式,表示的是\n或者\r0個(gè)到無限個(gè)的重復(fù)的記號.

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

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

           

          每個(gè)非終結(jié)符號(InputMatchedBraces)都會(huì)在javacc生成的Simple1.java中形成Class Simple1的成員函數(shù).當(dāng)你在外部調(diào)用Simple1Input,那么語法分析器就會(huì)開始進(jìn)行語法分析了.

           

          5.2

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

           

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

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

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

          為了讓詞法分析做得更簡單,我們通常都不會(huì)在文法分析的時(shí)候,使用”(”,”)“等字符號串來表示終結(jié)符號,而需要轉(zhuǎn)而使用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)

                             {

                                      case PlusOP:

                                      newNode.name = "Operator: +";

                                      break;

                                      case MinusOP:

                                      newNode.name = "Operator: -";

                                      break;

                             }

                             node = newNode;

                   }

            )*

            { return node; }

          }

          int addop() : {}

          {

                   <PLUS> { return PlusOP; }

          |   <MINUS> { return MinusOP; }

          }

          ParseTreeNode Term() :

          {

                   ParseTreeNode node;

                   ParseTreeNode t;

                   int op;

          }

          {

            node=Factor(){}

            (

            op=mulop() t=Factor()

          {

                            ParseTreeNode newNode = new ParseTreeNode();

                            newNode.nodetype = op;

                            newNode.child[0] = node;

                            newNode.child[1] = t;

                            switch(op)

                             {

                                      case TimersOP:

                                      newNode.name = "Operator: *";

                                      break;

                                      case OverOP:

                                      newNode.name = "Operator: /";

                                      break;

                             }

                             node = newNode;

                   }

            )*

            {

                 return node;

            }

          }

          int mulop() :{}

          {

                   <TIMERS> { return TimersOP; }

                   | <OVER> { return OverOP;   }

          }

          ParseTreeNode Factor() :

          {

                   ParseTreeNode node;

                   Token t;

          }

          {

            t=<ID>

          {

                     node=new ParseTreeNode();

                     node.nodetype= IDstmt;

                     node.name = t.image;

                     return node;

                   }

            |

            t=<NUM>

            {

                node=new ParseTreeNode();

                     node.nodetype= NUMstmt;

                     node.name = t.image;

                     node.value= Integer.parseInt(t.image);

                     return node;

              }

            |

            <LPAREN> node=Simple_Expression() <RPAREN>

            {

                   return node;

            }

          }

           

          其中SKIP 中的定義就是在進(jìn)行詞法分析的同時(shí),忽略掉的記號.TOKEN中的,就是需要在做詞法分析的時(shí)候,識(shí)別的詞法記號.當(dāng)然,這一切都是以正則表達(dá)式來表示的.

          這個(gè)例子就有多個(gè)非終結(jié)符號,可以看出,我們需要為每個(gè)非終結(jié)符號寫出一個(gè)過程.不同的非終結(jié)符號的識(shí)別過程中可以互相調(diào)用.

              以Simple_Expression()過程為例,它的產(chǎn)生式是Expression -> Term { addop Term },而在javacc的輸入文件格式是,它的識(shí)別是這樣寫的node=Term(){} ( op=addop() t=Term(){ … })* 前面說過,這里的”*”符號和正則表達(dá)式是一樣的,就是0次到無限次的重復(fù).那么Simple_Expression等于文法Term Addop Term Addop Term Addop Term … Addop也就相當(dāng)于PLUSMINUS兩個(gè)運(yùn)算符號.這里我們在寫Expression的文法的時(shí)候,同時(shí)還使用了賦值表達(dá)式,因?yàn)檫@個(gè)和Yacc不同的時(shí)候,Javacc把文法識(shí)別完全地做到了函數(shù)過程中,那么如果我們要識(shí)別Simple_Expression的文法,就相當(dāng)于按順序識(shí)別TermAddop兩個(gè)文法,而識(shí)別那個(gè)文法,就相當(dāng)于調(diào)用那兩個(gè)非終結(jié)符的識(shí)別函數(shù).正是這一點(diǎn),我覺得Javacc的文法識(shí)別處理上就很接近程序的操作過程,我們不需要像YACC那樣使用嚴(yán)格的文法表示格式,復(fù)雜的系統(tǒng)參數(shù)了.

          關(guān)于Yacc的使用,其實(shí)比Javacc要復(fù)雜,還需要考慮到和詞法分析器接口的問題,這個(gè)我會(huì)在以后細(xì)細(xì)講到.

              至于其它的文法操作解釋我就不再多說了,如果要說,就是再寫上十篇這樣的文章也寫不完.本文只能給讀者們一個(gè)方向,至于深入的研究,還是請大家看javacc提供的官方文檔資料.

          最后

              由于國外使用JAVA做項(xiàng)目的程序員比國內(nèi)多,那么討論JAVA技術(shù)的人員也比較多.可能來這里讀我的文章的人都是C/C++程序員,但是關(guān)注其它領(lǐng)域同方向的技術(shù)也是可以讓我們的知識(shí)領(lǐng)域更加寬廣.關(guān)于JavaCC的討論主要是在國際新聞組comp.compilers.tools.javacc如果大家在使用JavaCC做實(shí)際問題的時(shí)候遇到什么問題,不妨上去找找專家.

          原文:http://dev.csdn.net/article/22/22189.shtm





          Android開發(fā)完全講義(第2版)(本書版權(quán)已輸出到臺(tái)灣)

          http://product.dangdang.com/product.aspx?product_id=22741502



          Android高薪之路:Android程序員面試寶典 http://book.360buy.com/10970314.html


          新浪微博:http://t.sina.com.cn/androidguy   昵稱:李寧_Lining

          posted on 2008-10-12 23:12 銀河使者 閱讀(669) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 嵊泗县| 清丰县| 建阳市| 镇康县| 辰溪县| 京山县| 霍州市| 文昌市| 泰州市| 镇康县| 中阳县| 广昌县| 马关县| 平安县| 海伦市| 蕲春县| 江门市| 怀集县| 荆门市| 依安县| 上虞市| 南丰县| 广德县| 英超| 永昌县| 湖南省| 荣成市| 南皮县| 塔河县| 天等县| 南京市| 万宁市| 太保市| 获嘉县| 敦煌市| 喀喇| 宁夏| 荥阳市| 股票| 巴林左旗| 清远市|