簡介
調用是將程式的執行交給其他的代碼段,通常是一個子例程,同時保存必要的信息,從而使被調用段執行完畢後返回到調用點繼續執行。嵌套調用是指某個程式調用另一個程式。嵌套宏調用即宏程式中包含著對其它宏的調用,宏嵌套定義即宏程式中包含著其它宏的定義。現代宏處理器一般都支持宏的嵌套調用和嵌套定義。宏處理器主要完成宏的擴展(macro expansion)和宏命令(macro directive)的執行,這些工作可在單獨的一遍中完成。宏處理器的輸出做彙編器的輸入,宏處理器也常稱預處理器。
宏
宏(Macro),是一種批量處理的稱謂。計算機科學裡的宏是一種抽象(Abstraction),它根據一系列預定義的規則替換一定的文本模式。解釋器或編譯器在遇到宏時會自動進行這一模式替換。對於編譯語言,宏展開在編譯時發生,進行宏展開的工具常被稱為宏展開器。宏這一術語也常常被用於許多類似的環境中,它們是源自宏展開的概念,這包括鍵盤宏和宏語言。絕大多數情況下,“宏”這個詞的使用暗示著將小命令或動作轉化為一系列指令。宏的用途在於自動化頻繁使用的序列或者是獲得一種更強大的抽象能力。
計算機語言如C語言或彙編語言有簡單的宏系統,由編譯器或彙編器的預處理器實現。C語言的宏預處理器的工作只是簡單的文本搜尋和替換,使用附加的文本處理語言如M4,C程式設計師可以獲得更精巧的宏。Lisp類語言如Common Lisp和Scheme有更精巧的宏系統:宏的行為如同是函式對自身程式文本的變形,並且可以套用全部語言來表達這種變形。一個C宏可以定義一段語法的替換,然而一個Lisp的宏卻可以控制一節代碼的計算。
獲得了控制代碼的執行順序(見惰性計算和非限制函式)的能力,使得新創建的語法結構與語言內建的語法結構不可區分。例如,一種Lisp方言有cond而沒有if,就可以使用宏由前者定義後者。Lisp語法的去部主要擴展,比如面向對象的CLOS系統,可以由宏來定義。
應用程式也可以使用一種和宏類似機理的系統來允許用戶將一系列(一般是最常使用到的操作)自定義為一個步驟。也就是用戶執行一系列操作,並且讓應用程式來“記住”這些操作以及順序。更高級的用戶可以通過內建的宏編程來直接使用那些應用程式的功能。
當使用一種不熟悉的宏語言來編程時,比較有效的方法就是記錄一連串用戶希望得到的操作,然後通過閱讀應用程式記錄下來的宏檔案來理解宏命令的結構組成。
宏的處理分為兩部分:宏定義的處理和宏調用的處理。宏的處理過程需要用到四個數據結構:P(Parameters)、A(Arguments)、MNT(MacroName Table)和MDT(Macro Definition Table)。表P在處理宏的定義時使用,它存放宏定義命令的形參名以及它們出現的順序號。表A在擴展宏時使用,它存放宏調用的實參。MDT保存宏定義的代碼,其中出現的形參已用相應的形參序號代替。MNT將宏的名字與它們在MDT中的定義關聯起來。
宏嵌套調用處理
為實現該種宏的處理,我們所建立的算法數據結構包含:宏擴展棧MES(Macro Expansion Stack)。棧中自棧底至棧頂依次保存外層至內層各當前所擴展的各層宏當前擴展位置(即MDT的代碼指針)。
擴充表A為棧結構,棧中存放各次宏調用的實參。棧MES和棧A的數據出入同步。當宏處理器遇到一次宏調用時,將MDT的代碼地址壓入棧,再將宏調用的實在參數壓入棧A。當宏的擴展結束時(碰到MEND命令),棧MES和棧A的棧頂元素同時彈出。為支持宏的嵌套增加一個工作模式:擴展定義模式(ED)。 ED模式下,宏處理器從MDT中讀取語句行,將它們輸出至MDT。
處理非嵌套宏時,形參可用其序號替代。替換處理嵌套宏時,形參要使用數對(定義層次,序號)來替換。定義層次表示形參所在的最內層宏的嵌套深度,序號含義則相同。例如:
宏P的定義中出現了宏Q的定義,且P的第二個形參與Q的第一個形參的名字相同。當處理第4行語句時,B所在的最內層宏是Q,傳統替換方法中它的嵌套深度是2,所以B用#(2,1)替換。它仍使用單一序號替換形參。由於處理一個宏時,僅需關心這個宏的定義(第一層或最外層的定義),而不需關注任何內層宏的定義,其作為單獨的宏存入MDT,因此G. Revesz提出了一種更好的方法。採用這種方法,上面的宏定義在MDT的代碼為:
採用前述數據結構來支持該種宏的處理。將參數表P擴充為棧,棧中自底向上存儲各層宏定義的形參名(註:對於外層宏,棧存放的是數對(形參名,序號))。宏處理器在替換宏定義中的單詞(token)時,由棧頂至棧底掃描P的形參名,若發現有某個非棧底的參數名與單詞匹配,則不予替換;若發現僅僅棧底的某個參數與單詞匹配,則用相應的序號替換;若不匹配,則不替換。