想用用flex和bison寫個C的編譯器,應該如何處理C的宏?

對編譯原理不太熟悉,只看過一點《自製編程語言》,裡面的例子大部分也都能看明白,但是想寫個簡單的C的編譯器卻卡在了處理宏這裡,C的宏和正常代碼混雜在一起,我搞不清楚該如何寫詞法分析將他們分開。另外我怎麼加不上編譯原理這個話題啊...


處理宏的時候,你不要用正常的詞法分析器。按照規則,你得先替換,然後對替換後的字元串整個重新做一遍詞法分析,然後繼續替換。所以你很難用flex和bison來做。這部分你要獨立做出來,等你與處理結束了之後,才能進入正常的工作中。

宏可牛逼了,是圖靈完備的,不僅可以在替換的時候產生新的token,還能借用奇技淫巧做加減乘除。我覺得你現在想要搞肯定不可能做對,你先去看boost的preprocessor庫,看懂了之後再來做。


如果參考一個很簡單但完整的C編譯器,lcc:drh/lcc · GitHub

它是單獨寫了一個預處理器(C Pre-Processor,簡稱cpp):lcc/cpp at master · drh/lcc · GitHub

源碼交給cpp做好預處理之後,再交給後面的編譯器主體去完成編譯。

對比cpp里的lexer與編譯器主體的lexer,可以看到兩者的詞法規則不完全一樣,所以才要分開寫。有興趣的話可以試試參考lcc的cpp里的lexer詞法規則來寫個flex版的——如果懶得讀規範的話。

(不過讀規範顯然更加正解 &>_&<)

這麼蛋疼的事顯然已經有人做過了:code golf - Create a C preprocessor

其它就跟前面幾個答案所說一樣。


C語言的token有PPToken 和Token之分。PPToken就是Preprocessor Token。它與C語言剩下部分的Token是不一樣的(不過仍然很相近)。所以正確的步驟是先以PPToken的詞法規則進行詞法分析,然後進行預處理。

預處理器的關鍵部分是宏展開和替換(而不是詞法分析)。其次也包括 #(stringize), ##(glue)這樣的麻煩的東西。學習和掌握C預處理器的最有效辦法不是看一個現成的實現,也不是去看C11的規範, 而是看這裡的偽代碼描述(wgtcc/cpp.algo.pdf at master · wgtdkp/wgtcc · GitHub)。原始出處還不太好找,這裡是我自己的副本。當初ISO標準委員會對預處理器的規範也是攪不清楚。最後是先寫出了這份函數式偽代碼,再根據它去制定宏展開和替換的規則。C11 標準文件裡面關於預處理器的部分很含糊,很多沒有講明白。看懂這份偽代碼,寫出一個work的預處理器是沒問題的了(偽代碼的翻譯相當直接)。

如果看完還不會寫,可以看看我的實現(https://github.com/wgtdkp/wgtcc/blob/master/src/cpp.cc),才800行的預處理器。


宏是個特殊的詞法分析/替換的Pass。在這一遍不是考慮C的語法結構,而是有個特殊的詞法/語法分析過程,將directive和一般的token分開處理。

如果你不想去做這一部分,可以用C編譯器的Preprocessing功能或者是Boost.Wave幫助你處理宏。


寫個m4?


C的編譯過程中,首先有一步是預編譯,然後再是編譯,你要做的是編譯期的詞法與語法分析等,你直接交給預編譯器處理好預編譯過程,然後再來進行詞法分析,語法分析等編譯期的過程。


曾經玩過,說說我的實現思路:

1.把所有宏解析成函數,寫入指令集.

2.分析所有宏調用,按函數邏輯取值.

3.用計算好的值替換宏,消除已替換宏指令.

4.重複,直到所有宏被替換.


先單獨做一個預處理器 或 用現成的預處理器。


還是別想著寫完整的了,把一些核心feature先寫出來再說吧

想要練手只需要實現核心語法一般就夠了


推薦閱讀:

peg, ometa 解決什麼問題,ometa-js怎麼入門/正確理解和認知?
(發散性問題,請隨意加上假設)一門編程語言同時擁有解釋器和編譯器的必要性有多大?
如何快速而準確地判斷一門語言的語法(或部分語法)屬於哪個Chomsky Hierarchy?
實現一個markdown解析器需要具備那些知識?
如何在編程中更好地踩坑與進步?

TAG:C編程語言 | 編譯原理 | 編譯器 |