青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆-341  評論-2670  文章-0  trackbacks-0

上一篇博客講到了構造語法樹的問題。有朋友在留言問我,為什么一定要讓語法分析器產生語法樹,而不是讓用戶自己決定要怎么辦呢?在這里我先解答這個問題。

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);
};

因為文法規則本身的東西也不多,所以這里的symbol只能是上面記載的9種對象。這些對象其實特別的相似,所以我們可以看出唯一的區別就是當GetType返回值不一樣的時候,GetDescriptorSymbol返回的對象的意思也不一樣。而這個區別記載在了enum SymbolType的注釋里面。實際上這種做法在面對超級復雜的符號表(考慮一下C++編譯器)的時候并不太好。那好的做法究竟是什么呢?看上面IDIASymbol的鏈接,那就是答案。

不可否認,微軟設計出來的API大部分還是很有道理的,除了Win32的原生GUI部分。

我們還可以看到,這個ParsingSymbol類的幾乎所有成員函數都是用來查詢這個Symbol的內容的。這里還有兩個特殊的函數,就是SearchClassSubSymbol和SearchCommonBaseClass——當且僅當symbol是ClassType的時候才起作用。為什么有了GetSubSymbolByName,還要這兩個api呢?因為我們在搜索一個類的成員的時候,是要搜索他的父類的。而一個類的父類的sub symbol并不是類自己的sub symbol,所以就有了這兩個api。所謂的sub symbol的意思現在也很明了了。enum類型里面的值就是enum的sub symbol,成員變量是類的sub symbol,總之只要是聲明在一個符號內部的符號都是這個符號的sub symbol。這就是所有符號都有的共性。

當然,有了ParsingSymbol,還要有他的manager才可以完成整個符號表的操作:

class ParsingSymbolManager : public Object
{
public:
    ParsingSymbolManager();
    ~ParsingSymbolManager();

    ParsingSymbol*                    GetGlobal();
    ParsingSymbol*                    GetTokenType();
    ParsingSymbol*                    GetArrayType(ParsingSymbol* elementType);

    ParsingSymbol*                    AddClass(const WString& name, ParsingSymbol* baseType, ParsingSymbol* parentType=0);
    ParsingSymbol*                    AddField(const WString& name, ParsingSymbol* classType, ParsingSymbol* fieldType);
    ParsingSymbol*                    AddEnum(const WString& name, ParsingSymbol* parentType=0);
    ParsingSymbol*                    AddEnumItem(const WString& name, ParsingSymbol* enumType);
    ParsingSymbol*                    AddTokenDefinition(const WString& name);
    ParsingSymbol*                    AddRuleDefinition(const WString& name, ParsingSymbol* ruleType);

    ParsingSymbol*                    CacheGetType(definitions::ParsingDefinitionType* type, ParsingSymbol* scope);
    bool                            CacheSetType(definitions::ParsingDefinitionType* type, ParsingSymbol* scope, ParsingSymbol* symbol);
    ParsingSymbol*                    CacheGetSymbol(definitions::ParsingDefinitionGrammar* grammar);
    bool                            CacheSetSymbol(definitions::ParsingDefinitionGrammar* grammar, ParsingSymbol* symbol);
    ParsingSymbol*                    CacheGetType(definitions::ParsingDefinitionGrammar* grammar);
    bool                            CacheSetType(definitions::ParsingDefinitionGrammar* grammar, ParsingSymbol* type);
};

這個ParsingSymbolManager有著符號表管理器的以下兩個典型作用

1、創建符號。
2、講符號與語法樹的對象綁定起來。譬如說我們在一個context下面推導了一個expression的類型,那下次對于同樣的context同樣的expression就不需要再推導一次了(語義分析有很多個pass,對同一個expression求類型的操作經常會重復很多次),把它cache下來就可以了。
3、搜索符號。具體到這個符號表,這個功能被做進了ParsingSymbol里面。
4、保存根節點。GetGlobal函數就是干這個作用的。所有的根符號都屬于global符號的sub symbol。

因此我們可以怎么使用他呢?首先看上面的Add函數。這些函數不僅會幫你在一個符號表里面添加一個sub symbol,還會替你做一些檢查,譬如說阻止你添加相同名字的sub symbol之類的。語法樹很復雜的時候,很多時候我們有很多不同的方法來給一個符號添加子符號,譬如說C#的成員變量和成員函數。成員變量不能同名,成員函數可以,但是成員函數和成員變量卻不能同名。這個時候我們就需要把這些添加操作封裝起來,這樣才可以在處理語法樹(聲明一個函數的方法可以有很多,所以添加函數符號的地方也可以有很多)的時候不需要重復寫驗證邏輯。

其次就是Cache函數。其實Cache函數這么寫,不是用來直接調用的。舉個例子,在分析一個文法的時候,我們需要把一個“類型”語法樹轉成一個“類型”符號(譬如說要決定一個文法要create什么類型的語法樹節點的時候)。因此就有了下面的函數:

ParsingSymbol* FindType(Ptr<definitions::ParsingDefinitionType> type, ParsingSymbolManager* manager, ParsingSymbol* scope, collections::List<Ptr<ParsingError>>& errors)
{
    ParsingSymbol* result=manager->CacheGetType(type.Obj(), scope);
    if(!result)
    {
        FindTypeVisitor visitor(manager, (scope?scope:manager->GetGlobal()), errors);
        type->Accept(&visitor);
        result=visitor.result;
        manager->CacheSetType(type.Obj(), scope, result);
    }
    return result;
}

很明顯,這個函數做的事情就是,查詢一個ParsingDefinitionType節點有沒有被查詢過,如果有直接用cache,沒有的話再從頭計算他然后cache起來。因此這些Cache函數就是給類似FindType的函數用的,而語義分析的代碼則直接使用FindType,而不是Cache函數,來獲取一個類型的符號。聰明的朋友們可以看出來,這種寫法蘊含著一個條件,就是語法樹創建完就不會改了(廢話,當然不會改?。?。

這一篇的內容就講到這里了?,F在的進度是正在寫文法生成狀態機的算法。下一篇文章應該講的就是狀態機究竟是怎么運作的了。文法所需要的狀態機叫做下推狀態機(push down automaton),跟regex用的NFA和DFA不太一樣,理解起來略有難度。所以我想需要用單獨的一篇文章來通俗的講一講。

posted on 2012-11-28 08:50 陳梓瀚(vczh) 閱讀(6866) 評論(6)  編輯 收藏 引用 所屬分類: C++

評論:
# re: 可配置語法分析器開發紀事(二)&mdash;&mdash;構造符號表 2012-11-28 17:21 | ooseven
我說的語法樹的構造讓用戶自己在bnf里決定,并不是說語法樹不重要或者大部分都不需要,而恰恰相反,就是因為語法樹太重要了,所以,應該讓用戶自己決定語法樹的結構,嘗試構造一顆在符合任何場景,任何用戶需求的語法樹我覺得非必要之舉。而且在bnf里定義語法樹其實也就是在bnf腳本里多寫一行#include語句而已,很簡單。
如果,設計一個語法生成器的目的只是讓作者自己使用,那當然無可非議,但是,如果想構造一個通用的語法生成器,則有過度設計之嫌。個人意見,不一定正確哈,不要生氣。
  回復  更多評論
  
# re: 可配置語法分析器開發紀事(二)&mdash;&mdash;構造符號表 2012-11-28 19:50 | 陳梓瀚(vczh)
@ooseven
我沒有生氣哈。話說回來,難道我的語法樹不是通過用戶自己聲明的嗎,看上一篇文章。通用的語法樹只是parse出來的結果而已,然后我在根據用戶自己聲明的語法樹生成的C++代碼里面,會包含一個轉換過程,這樣用戶無論如何拿到的都是自己的語法樹(當然他不做轉換也可以)。  回復  更多評論
  
# re: 可配置語法分析器開發紀事(二)&mdash;&mdash;構造符號表 2012-11-28 22:16 | phoenixbing
老大,你的博客很好,代碼很好,但是我們有時候消化不了這么快,有時候想找人交流交流,卻找不到,我建議你建立一個群,把群號放在博客首頁,這樣研究你源碼的人會聚集在一起,大家也可以討論,你也不需要參與討論,甚至你不在群里都可以,畢竟你時間有限,你還要去微博灌水,二次元啥的。

這樣你也沒啥損失。但對祖國的編譯器苦手們就大有幫助。

你的開源代碼已經夠多了,也需要一個粉絲群了吧。

盼考慮。祝健康。  回復  更多評論
  
# re: 可配置語法分析器開發紀事(二)&mdash;&mdash;構造符號表 2012-11-29 02:31 | 陳梓瀚(vczh)
@phoenixbing
這個嘛,我考慮考慮……  回復  更多評論
  
# re: 可配置語法分析器開發紀事(二)&mdash;&mdash;構造符號表 2012-11-29 05:20 | P
各種理論和事實證明:群不是一個技術討論的好載體啊。。  回復  更多評論
  
# re: 可配置語法分析器開發紀事(二)&mdash;&mdash;構造符號表 2012-11-29 21:55 | Zblc(邱震鈺)
@P
個人覺得還是可以的  回復  更多評論
  
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            久久亚洲欧美国产精品乐播| 国产精品国产一区二区| 久久综合九色综合网站| 午夜一区在线| 亚洲免费一区二区| 制服丝袜激情欧洲亚洲| 亚洲一区二区三区四区在线观看 | 国产欧美一级| 国产午夜精品全部视频播放| 欧美激情精品久久久久久大尺度| 久久精品一本久久99精品| 久久青草欧美一区二区三区| 欧美大片免费看| 国产精品裸体一区二区三区| 国产亚洲一区在线| 亚洲激情视频在线播放| 在线午夜精品| 乱人伦精品视频在线观看| 欧美激情1区2区3区| 一区二区三区视频在线| 久久超碰97中文字幕| 欧美激情亚洲一区| 久久久国际精品| 国产亚洲一区二区精品| 影音先锋日韩有码| 欧美视频一区二区三区在线观看| 亚洲一区二区三区久久| 亚洲人成网站在线播| 亚洲国产精品福利| 99香蕉国产精品偷在线观看| 午夜久久影院| 欧美国产欧美亚洲国产日韩mv天天看完整 | 在线日韩中文字幕| 国产精品成人观看视频国产奇米| 欧美亚洲成人精品| 国产亚洲欧美一区二区| 亚洲电影免费观看高清| 亚洲视频观看| 久久九九免费视频| 欧美激情视频在线播放| 日韩亚洲精品在线| 久久国产精品电影| 欧美日韩精品免费观看视频完整| 国产精品久久久久免费a∨| 国产伊人精品| 一区二区三区成人| 久久一区国产| 亚洲伦理网站| 久久精品视频免费观看| 欧美视频在线观看视频极品| 精品1区2区3区4区| 亚洲欧美中文在线视频| 亚洲电影激情视频网站| 一区二区三区日韩在线观看| 老妇喷水一区二区三区| 国产精品免费一区豆花| 亚洲乱码一区二区| 久久久亚洲欧洲日产国码αv| 亚洲精品国产欧美| 一区二区三区高清在线观看| 久久精品九九| 国产精品久久久久久影视| 禁断一区二区三区在线| 亚洲欧美日韩精品一区二区| 欧美激情一区二区三区蜜桃视频| 欧美在线视频不卡| 久久女同精品一区二区| 亚洲精品日韩在线观看| 亚洲影视中文字幕| 亚洲第一福利视频| 亚洲视频在线观看免费| 亚洲自拍偷拍色片视频| 亚洲经典自拍| 久久综合婷婷| 欧美精选一区| 在线精品视频一区二区| 欧美一区二区视频在线观看2020 | 欧美日本一区二区高清播放视频| 原创国产精品91| 久久激情五月激情| 亚洲欧美日本在线| 国产精品久久久久国产a级| 一区二区三区高清不卡| 亚洲精品黄色| 欧美国产视频在线观看| 亚洲高清一二三区| 老司机成人网| 久久久噜噜噜久久| 在线观看视频免费一区二区三区| 久久国产手机看片| 久久激情五月丁香伊人| 国产亚洲精久久久久久| 久久久久久婷| 久久蜜桃资源一区二区老牛 | 国产精品任我爽爆在线播放| 午夜精品福利一区二区三区av | 欧美日韩色综合| 在线精品国产欧美| 亚洲成色精品| 国产精品久久久久久久久借妻| 亚洲高清电影| 亚洲大胆视频| 欧美另类女人| 亚洲欧美日韩国产精品| 久久精品一区二区国产| 亚洲天堂免费观看| 欧美刺激性大交免费视频| 亚洲人成网站色ww在线| 99riav国产精品| 国产精品影音先锋| 久久综合伊人77777蜜臀| 久久久久久久综合狠狠综合| 亚洲精品久久久久久久久久久 | 99精品欧美| 亚洲男人的天堂在线观看| 狠狠入ady亚洲精品| 亚洲国产精彩中文乱码av在线播放| 亚洲国产日韩欧美| 亚洲一区二区在线| 亚洲精品无人区| 亚洲免费影院| 欧美精品精品一区| 一区二区三区成人| 久久激五月天综合精品| 亚洲网站在线观看| 久久精品盗摄| 亚洲欧美在线免费观看| 亚洲你懂的在线视频| 欧美午夜精品伦理| 欧美激情第10页| 国产午夜精品理论片a级探花| 亚洲国产免费| 一区一区视频| 亚洲高清在线观看| 91久久精品美女高潮| 亚洲欧美国产精品专区久久| 亚洲伦理精品| 久久躁日日躁aaaaxxxx| 性欧美xxxx大乳国产app| 欧美一级电影久久| 亚洲图片欧洲图片日韩av| 免费成人av在线看| 久久裸体艺术| 国产欧美日韩一区二区三区在线| 亚洲人成在线观看| 亚洲国产精品一区| 麻豆精品在线视频| 最新亚洲激情| 国产自产在线视频一区| 一区二区三区国产在线观看| 亚洲日本中文字幕| 久久夜色撩人精品| 美女日韩在线中文字幕| 国内激情久久| 小处雏高清一区二区三区| 亚洲嫩草精品久久| 欧美日韩综合在线免费观看| 亚洲国产精品va在看黑人| 亚洲欧洲午夜| 欧美jizz19性欧美| 亚洲高清免费| 日韩亚洲欧美一区二区三区| 久久爱www久久做| 久久se精品一区二区| 国产日韩亚洲欧美| 欧美在线视频播放| 久久亚洲精品中文字幕冲田杏梨| 国产一区二区丝袜高跟鞋图片| 亚洲综合另类| 久久精品91久久久久久再现| 国产亚洲欧洲一区高清在线观看| 亚洲精品免费网站| 亚洲乱码国产乱码精品精| 欧美激情一区二区三区在线视频 | 国产精品久久久久久久久借妻| 亚洲视频欧美视频| 欧美一区二区在线| 黑人中文字幕一区二区三区| 美女主播视频一区| 亚洲精品国产精品国自产观看| 日韩视频永久免费观看| 国产精品ⅴa在线观看h| 亚洲欧美国产一区二区三区| 国产综合色精品一区二区三区| 亚洲午夜电影网| 裸体女人亚洲精品一区| 亚洲高清不卡一区| 美女诱惑一区| 99视频精品| 亚洲理伦电影| 国产色综合天天综合网| 久久理论片午夜琪琪电影网| 日韩亚洲欧美一区二区三区| 欧美伊人影院| 一区二区久久| 国产欧美日韩亚洲一区二区三区| 欧美大片免费久久精品三p | 国产伪娘ts一区| 欧美日韩亚洲一区三区 | 午夜精品久久久久久久99樱桃|