想用用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解析器需要具備那些知識?
※如何在編程中更好地踩坑與進步?