上一篇部落格講到了構造文法樹的問題。有朋友在留言問我,為什麼一定要讓文法分析器產生文法樹,而不是讓使用者自己決定要怎麼辦呢?在這裡我先解答這個問題。
1、大部分情況下都是真的需要有文法樹
2、如果要直接返回計算結果之類的事情的話,只需要寫一個visitor運行一下文法樹就好了,除去自動產生的程式碼以外(反正這不用人寫,不計入代價),代碼量基本上沒什麼區別
3、加入文法樹可以讓文法本身描述起來更簡單,如果要讓程式員把文法單獨放在一邊,然後自己寫完整的語義函數來讓他產生文法樹的話,會讓大部分情況(需要文法樹)變得特別複雜,而少數情況(不需要文法樹)又沒有獲得什麼好處。
儘管類似yacc這樣的東西的確是不包含文法樹的內容而要你自己寫的,但是用起來難道不是很難受嗎?
現在轉入正題。這一篇文章講的主要是構造符號表的問題。想要把符號表構造的好是一件很麻煩的問題。我曾經嘗試過很多種方法,包括強型別的符號表,弱類型的符號表,基於map的符號表等等,最後還是挑選了跟Visual Studio內建的用來讀pdb檔案的DIA類其中的IDIASymbol(http://msdn.microsoft.com/en-us/library/w0edf0x4.aspx)基本上一樣的結構:所有的符號都只有這麼一個symbol類,然後包羅永珍,什麼都有。為什麼最後選擇這麼做呢?因為在做語義分析的時候,其實做的最多的事情不是構造符號表,而是查詢符號表。如果符號表是強型別的畫,譬如說類型要一個類,變數要一個類,函數要一個類之類的,總是需要到處cast來cast去,也找不到什麼好方法來在完成相同事情的情況下,保留強型別而不在代碼裡面出現cast。為什麼文法樹就要用visitor來解決這個問題,而符號表就不行呢?因為通常我們在處理文法樹的時候都是遞迴的形式,而符號表並不是。在一個上下文裡面,實際上我們是知道那個symbol對象究竟是什麼東西的(譬如說我們查詢了一個變數的type,那這傳回值肯定只能是type了)。這個時候我們要cast才能用,本身也只是浪費表情而已。這個時候,visitor模式就不是和面對這種情況了。如果硬要用visitor模式來寫,會導致語義分析的代碼分散得過於離譜導致可讀性幾乎就喪失了。這是一個辯證的問題,大家可以好好體會體會。
說了這麼一大段,實際上就是怎麼樣呢?讓我們來看“文法規則”本身的符號表吧。既然這個新的可配置文法分析器也是通過parse一個文本形式的文法規則來產生parser,那實際上就跟編譯器一樣要經曆那麼多階段,其中肯定有符號表:
class ParsingSymbol : public Object{public: enum SymbolType { Global, EnumType, ClassType, // descriptor == base type ArrayType, // descriptor == element type TokenType, EnumItem, // descriptor == parent ClassField, // descriptor == field type TokenDef, // descriptor == token type RuleDef, // descriptor == rule type };public: ~ParsingSymbol(); ParsingSymbolManager* GetManager(); SymbolType GetType(); const WString& GetName(); vint GetSubSymbolCount(); ParsingSymbol* GetSubSymbol(vint index); ParsingSymbol* GetSubSymbolByName(const WString& name); ParsingSymbol* GetDescriptorSymbol(); ParsingSymbol* GetParentSymbol(); bool IsType(); ParsingSymbol* SearchClassSubSymbol(const WString& name); ParsingSymbol* SearchCommonBaseClass(ParsingSymbol* classType);};
本欄目更多精彩內容:http://www.bianceng.cn/Programming/cplus/