Lex和Yacc入門教程(八).使用堆棧編譯文法

來源:互聯網
上載者:User

Lex和Yacc應用方法(八).使用堆棧編譯文法

草木瓜  20070604

一、序

    前面一些系列文章著重介紹了遞迴文法樹在編譯理論方面的應用。本文則會介紹另一種
實現方式----堆棧。  
    堆棧在底層系統有十分廣泛的應用,同樣也十分擅長處理文法結構,這裡通過實際樣本
探討如何構造堆棧完成文法分析。

    重要補充:下面是本系列文章全範例程式碼統一的調試測試環境,另對於lex,yacc檔案需
要儲存為Unix格式,這一點和Linux,Unix下shell很類似,DOS格式的Shell是不能夠被執行
的,同樣bison,lex編譯DOS格式檔案會出錯誤提示:
   
    Red Hat Linux release 9 (Shrike)
    Linux 2.4.20-8
    gcc version 3.2.2 20030222
    bison (GNU Bison) 1.35
    lex version 2.5.4
    flex version 2.5.4

    註:本站文章難免有錯誤疏漏之處。Lex,Yacc系列文章 http://blog.csdn.net/liwei_cmg/category/207528.aspx

二、具體樣本

    本樣本主要完成功能:
   
    1  支援整型,浮點型和字串型
    2  支援變數儲存,變數名可為多個字元
    3  支援整型,浮點型的+-*/()=運演算法則
    4  支援字串型賦值
    5  支援print列印整型,浮點型和字串型
    6  支援列印變數值
    7  支援while if else switch四種控制結構,並支援控制結構的嵌套
    8  支援> >= < <= != == 六種比較運算,同時也支援字串的比較
    9  支援 && || 複合比較運算
    10 支援對空格和TAB的忽略處理
    11 支援#的單行注釋
    12 支援{}多重組合
    13 支援編譯錯誤的具體顯示
    14 支援外部變數值傳入(整型,浮點型和字元型)
    15 支援外部變數擷取(整型,浮點型和字元型)
    16 完整的公司專屬應用程式模式
   
三、樣本全代碼(略)

 

  
A.stack.l
----------------------------------------------

B.stack.y
----------------------------------------------

 

C.stack.h
----------------------------------------------

 

D.stackparser.c
----------------------------------------------

E.public.h
----------------------------------------------

 

F.main.c
----------------------------------------------

 

G.mk 編譯shell檔案
----------------------------------------------

bison -d stack.y
lex stack.l
gcc -g -c lex.yy.c stack.tab.c stackparser.c
ar -rl stack.a *.o
gcc -g -o lw main.c stack.a

H.mkclean shell檔案
----------------------------------------------

rm stack.tab.c
rm stack.tab.h
rm lex.yy.c
rm *.o
rm *.a
rm lw

四、思路說明

    上面列出的代碼是目前最長的。
    可見寫一個堆棧編譯器並不是一簇而就的事情,即使對於目前的執行個體,也需要有很多完
善的地方。設計一個堆棧編譯器,我們往往需要從最簡單最容易的語句開始。

    A.簡單的堆棧分析思想
   
    我們先舉一個簡單的例子,a=1+2。要完成這個公式的計算,我們首先需要將1和2,壓
入堆棧,然後分析到+運算,此時又需要將1和2出棧,執行+法後將3壓入。繼續分析,需要
壓入a,在最後的=運算時,將3和a出棧進行賦值運算後,將a入棧。運作序列如下:

  id:  0 act:  pushvalue                  
  id:  1 act:  pushvalue                  
  id:  2 act:        add                  
  id:  3 act:    pushvar                  
  id:  4 act:     assign
  
  使用堆棧進行編譯的痛點在於,將無比複雜的文法結構抽象到簡單的入棧出棧操作。單
從這個角度講,是很難一步倒位的。一般地,我們需要先將指令字串根據設定的文法歸併
規則編譯成有序的指令序列。然後對指令序列制定堆棧動作執行函數,依次執行指令並調用
相應函數。

    值得欣慰的是,早在N年前,外國人就形成了一套十分強大的編譯理論體系(lex,yacc)
去完成歸併文法的工作。我們只需實現外部的規則動作。

    B.lex和yacc的歸併文法設計
   
    與前面例子相似的是,使用G_Var儲存編譯時間的變數資訊,G_sBuff儲存編譯語句,這裡
又增加了G_String統一儲存編譯語句中的所有字串。至於lex和yacc設計方法也是相近的,
只是冗餘了一些語句標誌,如ifx,elsex,switchx等。這些標誌是為了產生順序正確的指令
序列。

    lex和yacc會把編譯後的所有結果指令存於G_Command中。見AddCommand。
   
  /* 記憶體指令集結構 */
  typedef struct {
   
    int iTypeAction;
    int iTypeVal;
   float fVal;
   int iVar;
   int iString;
   int iControl;
  
  } TCommand;
  
  這個指令集的設計是關鍵所在,iTypeAction說明這個指令的類型,iTypeVal表示指令
的實值型別。fVal儲存整型浮點型數值,iVar儲存變數索引,iString如果不是-1,則表示字
符串的索引,iControl表示指令返回的控制資訊。

    C.堆棧編譯
   
    lex和yacc編譯後會把指令產生到G_Command中,隨後對G_Command進行遍曆處理,並調
用相關動作函數進行出入棧操作。(見Act系列函數) 這裡出入棧操作的是G_Command索引,處
理的結果皆存於G_Command中,這是外人比較難以理解的一點。
    TCommand結構體元素是相對獨立的,fVal,iString互斥,iVar標誌變數索引,iControl
只用於控制堆棧的值。

    在剛開始時需要對各種文法結構做統籌分析。比如拿分支和迴圈這類語句來說,if分支
就需要維護一個控制狀態,由於嵌套語句的存在,這個控制狀態需要具有堆棧的特點。每次壓
入新if語句要進行現有堆棧的判斷,如果前一if為false,這個if即使為true也還是false,另
外else也要做相似處理。之後對於endif做出棧操作,標誌這對if/else已處理。switch比if要
稍微複雜一些,還要記錄原始值,每次case要做比較。while不僅需要條件還要進行跳轉,這是
Act動作函數有傳回值的重要原因。
    以上這個分析要進行比較體系化的考慮,這些也便於以後的功能擴充,如goto等。
    這裡我引入了StackValue和StackControl兩個堆棧。Value用於普通的順序計算,Control
用於if else switch while等控制結構。關於控制堆棧,可以參見Act_If,Act_Else等控制
動作函數,在編譯指令序列時,會讀取控制資訊來判斷是否執行該指令。值的注意的是,Act
動作函數還返回了下一指令的索引,這主要用於對迴圈,跳轉等方面的處理,預設是順序執行。
    總得來說,TCommand的元素含義要獨立,並嚴格保證處理指示時G_Command的指令資料的
合法性。

    D.變數傳值和擷取
   
    還是回呼函數的思想,只是由於字串的存在製造了一些小麻煩,所以使用了二級指標,
並通過傳回值類型,來進行外部判斷處理。不過Linux Unix下的gcc不支援引用傳值,這裡使
用的都是指標傳遞。
   

五、一些注意事項

    A.stack.l,stack.y 檔案要求為Unix格式,這一點和Linux,Unix下shell很類似,DOS
格式的Shell是不能夠被執行的,同樣bison,lex編譯DOS格式檔案會出錯誤提示。

    B.SegmentFault多半產生於記憶體越界(前面已經著重說明過),除此以為還經常出現這類
情況,產生這類錯誤的位置的代碼並無錯誤,但是記憶體值已出現亂碼,這一般是指標使用不當。

    C.避免一切的warning項,比如在stack.y中,將函數的預說明去除,會提示warning,但
是在執行中,函數傳值就會發生根本性的錯誤。

    D.還在在編譯C/C++出現的堆疊溢位錯誤,當然可以用ulimit查看參數,但多半也由記憶體
越界有關,遇到莫名其妙的錯誤第一點就要想到記憶體問題。

    E.stack.l,stack.y 注意 規則應用順序,shift-reduce的順序
   
    F.耐心才是最重要的,盡量多列印一些調試資訊,對於複雜的文法結構調試起來並不是很
輕鬆的事。

六、總結

    lex和yacc應用目前是在Unix/Linux平台下,產生的是C代碼,固然C++使用這些介面沒有
問題,但不能滿足Windows平台的使用需求。從下文起會開始介紹Windows下這類工具的使用,
以及C/C++/Java代碼的產生。

   
   
     

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.