• <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>

            longshanks

              C++博客 :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
              14 Posts :: 0 Stories :: 214 Comments :: 0 Trackbacks

            常用鏈接

            留言簿(10)

            我參與的團(tuán)隊(duì)

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            當(dāng)GPL遇上MP

            莫華楓

                GPL,也就是General Purpose Language,是我們使用的最多的一類語言。傳統(tǒng)上,GPL的語法,或者特性,是固態(tài)的。然而,程序員都是聰明人(即便算不上“最聰明”,也算得上 “很聰明”吧:)),往往不愿受到語法的束縛,試圖按自己的心意“改造”語言。實(shí)際上,即便是早期的語言,也提供了一些工具,供聰明人們玩弄語法。我看的第一本C語言的書里,就有這么一個(gè)例子,展示出這種“邪惡”的手段:
                  #define procedure void
                  #define begin {
                  #define end }
                然后:
                  procedure fun(int x)
                  begin
                      ...
                  end
                實(shí)際上,它的意思是:
                  void fun(int x)
                  {
                      ...
                  }
                這可以看作是對初學(xué)C語言的Pascal程序員的安慰。這種蹩腳的戲法可以算作元編程的一種,在一種語言里創(chuàng)造了另一個(gè)語法。不過,實(shí)在有些無聊。然而,在實(shí)際開發(fā)中,我們或多或少地會(huì)需要一些超出語法范圍的機(jī)制。有時(shí)為了完善語言,彌補(bǔ)一些缺憾;有時(shí)為了增強(qiáng)一些功能;有時(shí)為了獲得一些方便。更新潮的,是試圖在一種GPL里構(gòu)建Domain Specific Language,或者說“子語言”,以獲得某個(gè)特性領(lǐng)域上更直觀、更簡潔的編程方式。這些對語言的擴(kuò)展需求的實(shí)現(xiàn),依賴于被稱為Meta- Programming(MP)的技術(shù)。
                另一方面,隨著語言功能和特性的不斷增加,越來越多的人開始抱怨語言太復(fù)雜。一方面:“難道我們會(huì)需要那些一輩子也用不到幾回的語言機(jī)制,來增加語言的復(fù)雜性和學(xué)習(xí)使用者的負(fù)擔(dān)嗎?”。另一方面:“有備無患,一個(gè)語言機(jī)制要到迫在眉睫的時(shí)候才去考慮嗎?”。但MP技術(shù)則將這對矛盾消弭于無形。一種語言,可以簡潔到只需最基本的一些特性。而其他特定的語言功能需求,可以通過MP加以擴(kuò)展。如果不需要某種特性,那么只要不加載相應(yīng)的MP代碼即可,而無需為那些機(jī)制而煩惱。
                MP最誘人的地方,莫過于我們可以通過編寫一個(gè)代碼庫便使得語言具備以往沒有的特性。
                然而,全面的MP能力往往帶來巨大的副作用,以至于我們無法知道到底是好處更多,還是副作用更多。語言的隨意擴(kuò)展往往帶來某些危險(xiǎn),比如語法的沖突和不兼容,對基礎(chǔ)語言的干擾,關(guān)鍵字的泛濫等等。換句話說,MP是孫悟空,本領(lǐng)高強(qiáng)。但沒有緊箍咒,是管不住他的。
                那么,緊箍咒是什么呢?這就是這里打算探討的主題。本文打算通過觀察兩種已存在的MP技術(shù),分析它們的特點(diǎn)與缺陷,從中找出解決問題的(可能)途徑。

            AST宏

                首先,先來看一下宏,這種遠(yuǎn)古時(shí)代遺留下來的技術(shù)。以及它的后裔,ast宏。
                關(guān)于傳統(tǒng)的宏的MP功能,上面的代碼已經(jīng)簡單地展示了。但是,這種功能是極其有限的。宏是通過文本替換的形式,把語言中的一些符號、操作符、關(guān)鍵字等等替換成另一種形式。而對于復(fù)雜的語法構(gòu)造的創(chuàng)建無能為力。問題的另一面,宏帶來了很多副作用。由于宏的基礎(chǔ)是文本替換,所以幾乎不受語法和語義的約束。而且,宏的調(diào)試?yán)щy,通常也不受命名空間的約束。它帶來的麻煩,往往多于帶來的好處。
                ast宏作為傳統(tǒng)宏的后繼者,做了改進(jìn),使得宏可以在ast(Abstract Syntax Tree)結(jié)構(gòu)上執(zhí)行語法的匹配。(這里需要感謝TopLanguage上的Olerev兄,他用簡潔而又清晰的文字,對我進(jìn)行了ast宏的初級培訓(xùn):))。這樣,便可以創(chuàng)造新的語法:
                  syntax(x, "<->", y, ";")
                  {
                      std::swap(x, y);
                  }
                當(dāng)遇到代碼:
                  x <-> y;
                的時(shí)候,編譯器用std::swap(x,y);加以替換。實(shí)際上,這是將一種語法結(jié)構(gòu)映射到另一個(gè)語法結(jié)構(gòu)上。而ast宏則是這種映射的執(zhí)行者。
                但是,ast宏并未消除宏本身的那些缺陷。如果x或者y本身不符合swap的要求(類型相同,并且能復(fù)制構(gòu)造和賦值,或者擁有swap成員函數(shù)),那么 ast宏調(diào)用的時(shí)候無法對此作出檢驗(yàn)。宏通常以預(yù)編譯器處理,ast宏則將其推遲到語法分析之時(shí)。但是此時(shí)依然無法得到x或y的語義特征,無法直接在調(diào)用點(diǎn)給出錯(cuò)誤信息。
                同時(shí),ast宏還是無法處理二義性的語法構(gòu)造。如果一個(gè)ast宏所定義的語法構(gòu)造與主語言,或者其他ast宏的相同,則會(huì)引發(fā)混亂。但是,如果簡單粗暴地將這種“重定義”作為非法處理,那么會(huì)大大縮小ast宏(以及MP)的應(yīng)用范圍。實(shí)際上,這種語法構(gòu)造的重定義有其現(xiàn)實(shí)意義,可以看作一種語法構(gòu)造的“重載”,或者函數(shù)(操作符)重載的一種擴(kuò)展。
                解決的方法并不復(fù)雜,就是為ast宏加上約束。實(shí)際上,類似的情形在C++98的模板上也存在,而C++則試圖通過為模板添加concept約束加以解決。這種約束有兩個(gè)作用:其一,在第一時(shí)間對ast宏的使用進(jìn)行正確性檢驗(yàn),而無需等到代碼展開之后;其二,用以區(qū)分同一個(gè)語法構(gòu)造的不同版本。
                于是,對于上述例子可以這樣施加約束(這些代碼只能表達(dá)一個(gè)意思,還無法看作真正意義上的MP語法):
                  syntax(x, "<->", y, ";")
                       where x,y is object of concept (has_swap_mem or (CopyConstructable and Assignable))
                            && typeof(x)==typeof(y)
                  {
                      std::swap(x,y);
                  }
                如此,除非x,y都是對象,并且符合所指定的concept,否則編譯器會(huì)當(dāng)即加以拒絕,而且直截了當(dāng)。
                不過,如此變化之后,ast宏將不會(huì)再是宏了。因?yàn)檫@種約束是語義上的,必須等到語義分析階段,方能檢驗(yàn)。這就超出了宏的領(lǐng)地了。不過既然ast宏可以從預(yù)處理階段推遲到語法分析階段,那么再推遲一下也沒關(guān)系。再說,我們關(guān)注的是這種功能,帶約束的ast宏到底是不是宏,也無關(guān)緊要。

            TMP

                下面,我們回過頭,再來看看另一種MP技術(shù)——TMP(參考David Abrahams, Aleksey Gurtovoy所著的《C++ Template Metaprogramming》)。對于TMP存在頗多爭議,支持者認(rèn)為它提供了更多的功能和靈活性;反對者認(rèn)為TMP過于tricky,難于運(yùn)用和調(diào)試。不管怎么樣,TMP的出現(xiàn)向我們展示了一種可能性,即在GPL中安全地進(jìn)行MP編程的可能性。由于TMP所運(yùn)用的都是C++本身的語言機(jī)制,而這些機(jī)制都是相容的。所以,TMP所構(gòu)建的 MP體系不會(huì)同GPL和其他子語言的語法機(jī)制相沖突。
                實(shí)際上,TMP依賴于C++的模板及其特化機(jī)制所構(gòu)建的編譯期計(jì)算體系,以及操作符的重載和模板化。下面的代碼摘自boost::spirit的文檔:
                  group = '(' >> expr >> ')';

                  expr1 = integer | group;

                  expr2 = expr1 >> *(('*' >> expr1) | ('/' >> expr1));

               expr = expr2 >> *(('+' >> expr2) | ('-' >> expr2));

                這里表達(dá)了一組EBNF(語法著實(shí)古怪,這咱待會(huì)兒再說):>>代表了標(biāo)準(zhǔn)EBNF的“followed by”,*代表了標(biāo)準(zhǔn)EBNF的*(從右邊移到左邊),括號還是括號,|依舊表示Union。通過對這些操作符的重載,賦予了它們新的語義(即EBNF的相關(guān)語義)。然后配合模板的類型推導(dǎo)、特化等等機(jī)制,變戲法般地構(gòu)造出一個(gè)語法解析器,而且是編譯時(shí)完成的。

                盡管在spirit中,>>、*、|等操作符被挪作他用,但是我們依然可以在這些代碼的前后左右插入諸如:cin>> *ptrX;的代碼,而不會(huì)引發(fā)任何問題。這是因?yàn)?gt;>等操作符是按照不同的類型重載的,對于不同類型的對象的調(diào)用,會(huì)調(diào)用不同版本的操作符重載,互不干擾,老少無欺。

                但是,TMP存在兩個(gè)問題。其一,錯(cuò)誤處理不足。如果我不小心把第二行代碼錯(cuò)寫成:expr1 = i | group;,而i是一個(gè)int類型的變量,那么編譯器往往會(huì)給出一些稀奇古怪的錯(cuò)誤。無非就是說類型不匹配之類的,但是很晦澀。這方面也是TMP受人詬病的一個(gè)主要原因。好在C++0x中的concept可以對模板作出約束,并且在調(diào)用點(diǎn)直接給出錯(cuò)誤提示。隨著這些技術(shù)的引入,這方面問題將會(huì)得到緩解。

                其二,受到C++語法體系的約束,MP無法自由地按我們習(xí)慣的形式定義語法構(gòu)造。前面說過了,spirit的EBNF語法與標(biāo)準(zhǔn)EBNF有不小的差異,這對于spirit的使用造成了不便。同樣,如果試圖運(yùn)用TMP在C++中構(gòu)造更高級的DSL應(yīng)用,諸如一種用于記賬的帳務(wù)處理語言,將會(huì)遇到更大的限制。實(shí)際上TMP下的DSL也很少有令人滿意的。

                所以說,TMP在使用上的安全性來源于操作符復(fù)用(或重載)的特性。但是,操作符本身的語法特性是固定的,這使得依賴于操作符(泛化或非泛化)重載的TMP不可能成為真正意義上的MP手段。

                那么,對于TMP而言,我們感興趣的是它的安全性和相容性。而對其不滿的,則是語法構(gòu)造的靈活性。本著“去其糟粕,取其精華”的宗旨,我們可以對TMP做一番改進(jìn),以獲得更完整的MP技術(shù)。TMP的核心自然是模板(類模板和函數(shù)/操作符模板),在concept的幫助下,模板可以獲得最好的安全性和相容性。以此為基礎(chǔ),如果我們將模板擴(kuò)展到語法構(gòu)造上,那么便可以在保留TMP的安全性和相容性的情況下,獲得更大的語法靈活性。也就是說,我們需要增加一種模板——語法模板

                  template<typename T>
                  syntax synSwap=x "<->" y ";"
                     require SameType<decltype(x), T> && SameType<decltype(y), T>
                            && (has_swap_mem<T> || (CopyConstructable<T> and Assignable<T>)
                  {
                      std::swap(x, y);
                  }

                這里我杜撰了關(guān)鍵字syntax,并且允許為語法構(gòu)造命名,便于使用。而語法構(gòu)造描述,則是等號后面的部分。require沿用自C++0x的concept提案。稍作了些改造,允許concept之間的||。

            用戶定義的語法

                如果比較帶約束的ast宏和語法模板,會(huì)發(fā)現(xiàn)極其相似。實(shí)際上,兩者殊途同歸,展示了幾乎完全相同的東西。ast宏和TMP分別位于同一個(gè)問題的兩端,當(dāng)它們的缺陷得到彌補(bǔ)時(shí),便會(huì)相互靠攏,最終歸結(jié)到一種形式上。 

                現(xiàn)在,通過結(jié)合兩種方案的特色,我們便可以得到一個(gè)最終的MP構(gòu)造,既安全,又靈活:

                  syntax synSwap=x "<->" y ";";

                  where

                      (x,y is object)

                      && SameType<x, y>

                      && (has_swap_mem<x> || (CopyConstructable<x> and Assignable<x>))

                  {

                      std::swap(x, y);

                  }

                我去掉了template關(guān)鍵字,在約束(where)的作用下,template和類型參數(shù)列表都已經(jīng)沒有必要了。同時(shí),也允許直接將對象放入 concept:Assignable<x>,這相當(dāng)于:Assignable<decltype<x>>。

                “is ...”是特別的約束,用來描述那些超越concept范疇的特性。“...”可以是關(guān)鍵字“object”,表明相應(yīng)的標(biāo)識符是對象;也可以是關(guān)鍵字 “type”,表明相應(yīng)的標(biāo)識符是類型;或者是關(guān)鍵字“concept”,指定相應(yīng)的標(biāo)識符是concept等等。操作符的重載所涉及的參數(shù)只會(huì)是對象,只需對其類型做出約束,因此concept便足夠使用。但語法構(gòu)造的定義則廣大的多,它不僅僅會(huì)涉及對象,更可能涉及其它類型的語法要素。實(shí)際上,語法構(gòu)造所面對的參數(shù)是“標(biāo)識符”。那么一個(gè)標(biāo)識符上可能具備的各種特性,都應(yīng)當(dāng)作為約束的內(nèi)容。這里大致歸納出以下幾點(diǎn)需要約束的特性:

            1. 分類。對象、類型、concept、函數(shù)等等。is ...;
            2. 來源。來自哪個(gè)namespace。from ...;
            3. 尺寸。類型大小。sizeof(x)>20;
            4. 從屬。指定一個(gè)語法構(gòu)造是否從屬于其他語法構(gòu)造(其他語法構(gòu)造的一部分)。如果是從屬語法構(gòu)造,那么將不能單獨(dú)出現(xiàn)在獨(dú)立的語句中。更進(jìn)一步,可以強(qiáng)行指定一個(gè)語法構(gòu)造從屬于某個(gè)語法構(gòu)造,不允許在其他語法構(gòu)造中使用。belong_to ...;
            5. 類型及對象間的關(guān)系。諸如繼承、子對象、子類型、true typedef、別名、成員等等。
            6. ...

                也可以認(rèn)為,語法構(gòu)造的約束是concept的自然延伸。concept對類型做出約束,而語法構(gòu)造的約束的對象,則是標(biāo)識符。

                為了強(qiáng)化語法構(gòu)造的創(chuàng)建能力,應(yīng)當(dāng)允許在語法構(gòu)造的定義中使用BNF,或其他類似的語法描述語言。

                在語法構(gòu)造約束的支援下,便可以化解一些子語言同宿主語言之間的語法沖突。比如,我們創(chuàng)建了一種類似spirit的EBNF子語言,可以按照標(biāo)準(zhǔn)的EBNF形式編寫語法,構(gòu)造一個(gè)語法解析器:

                  syntax repeat1=p '+'

                  where

                      p is object of type(Parser)

                  {...}

                這樣便定義了一個(gè)后綴形式的+操作符,但僅僅作用于類型Parser的對象上。對于如下的代碼:

                  letter + number

                使用主語言的+(加),還是使用EBNF的+(重復(fù),至少一次),取決于letter和number的特征(這里是類型):

                  int letter, number;

                  letter + number; //使用主語言的+,表示letter加number

                  Parser letter, number;

                  letter + number; //使用EBNF的+,表示一串letter后面跟一個(gè)number

                如此,便使得用戶定義的語法不會(huì)同主語言的相同語法發(fā)生沖突。

                但是,語法構(gòu)造的約束并不能完全消除所有的語法沖突。其中一種情況便是常量的使用。比如:

                  'x' + 'y'

                它在宿主語言中,會(huì)被識別為兩個(gè)字符的相加。但在BNF子語言中,則會(huì)是若干個(gè)字符'x'之后緊跟一個(gè)‘y'。由于沒有類型、對象等代碼實(shí)體的參與,編譯器無法分辨使用哪種語言的語法規(guī)則來處理。解決的方法是使得常量類型化。這種做法在C++等語言中已經(jīng)運(yùn)用多年。通常采用為常量加前后綴:

                  'x'b_ + 'y'b_

                以此代表BNF子語言的常量。常量的語法定義可以是:

                  syntax bnf_const="'" letter "'" "b_";

                  where

                      is const of ... //...可以是某種類型

                  {...}

                通過is const of約束,將所定義的常量與某個(gè)類型綁定。而通過類型,編譯器便可以推斷出應(yīng)當(dāng)使用那種語法對其處理。

                然而,盡管帶約束的語法構(gòu)造定義可以在很大程度上消除語法沖突和歧義,但不可能消除所有的語法矛盾。另一方面,結(jié)構(gòu)相似,但語義完全不同的語法構(gòu)造混合在一起,即便不引發(fā)矛盾,也會(huì)對使用者造成難以預(yù)計(jì)的混亂。因此,在實(shí)際環(huán)境下,需要通過某種“語法圍欄”嚴(yán)格限定某種用戶定義語法的作用范圍。最直接的形式,就是通過namespace實(shí)現(xiàn)。namespace在隔離和協(xié)調(diào)命名沖突中起到很好的作用,可以進(jìn)一步將其運(yùn)用到MP中。由于namespace一經(jīng)打開,其作用范圍不會(huì)超出最內(nèi)層的代碼塊作用域:

                  {

                      using namespace XXX;

                      ...

                  } //XXX的作用范圍不會(huì)超出這個(gè)范圍

                運(yùn)用這個(gè)特性,可以很自然地將蘊(yùn)藏在一個(gè)namespace中的用戶定義語法構(gòu)造限制在一個(gè)確定的范圍內(nèi)。

                但是,僅此不夠。畢竟語法不同于命名,不僅會(huì)存在沖突,還會(huì)存在(合法的)混淆,而且往往是非常危險(xiǎn)的。為此,需要允許在using namespace的時(shí)候進(jìn)一步指定語法的排他性。比如:

                  {

                      using namespace XXX exclusive;

                      ...

                  }

                如果namespace中的用戶定義語法與外部語法(宿主語言,或外圍引用的namespace)重疊(沖突或混淆),外部語法將被屏蔽。更進(jìn)一步,可以允許不同級別的排他:排斥重疊和完全屏蔽。前者只屏蔽重疊的語法,這種級別通常用于擴(kuò)展性的用戶語法構(gòu)造(擴(kuò)展主語言,需要同主語言協(xié)同工作。而且語法重疊較少);而后者則將外部語法統(tǒng)統(tǒng)屏蔽,只保留所打開的namespace中的語法(子語言在代碼塊中是唯一語言),這種級別用于相對獨(dú)立的功能性子語言(可以獨(dú)立工作的子語言,通常會(huì)與主語言和其他子語言間存在較大的語法重疊,比如上述BNF子語言等等)。

                為提供更多的靈活性,可以在完全屏蔽的情況下,通過特定語句引入某種不會(huì)引發(fā)沖突的外部語法。比如:

                  {

                      using namespace XXX exclusive full;

                      using host_lang::syntax(...); //引入主語言的某個(gè)語法,...代表語法名稱

                      ...

                  }

                通常情況下,子語言不會(huì)完全獨(dú)立運(yùn)作,需要同宿主語言或其他子語言交互,也就是數(shù)據(jù)交換。主語言代碼可能需要將數(shù)據(jù)對象傳遞給子語言,處理完成后再取回。由于編譯器依賴于標(biāo)識符的特性(主要是類型)來選擇語法構(gòu)造。所以,一種子語言必須使用其內(nèi)部定義的類型和對象。為了能夠交換數(shù)據(jù),必須能夠把主語言的對象包裝成子語言的內(nèi)部類型。通過在子語言的namespace中進(jìn)行true typedef,可以將主語言類型定義成一個(gè)新的(真)類型,在進(jìn)入子語言作用域的時(shí)候,做顯式的類型轉(zhuǎn)換。另外,為了方便數(shù)據(jù)轉(zhuǎn)換,還可以采用兩種方法:將主語言類型map到一個(gè)子語言類型(相當(dāng)于給予“雙重國籍”),進(jìn)入子語言的語法范圍時(shí),相應(yīng)的對象在類型上具有兩重性,可以被自動(dòng)識別成所需的類型,但必須在確保不發(fā)生語法混淆的情況下使用;另一種穩(wěn)妥些的方法,可以允許執(zhí)行一種typedef,介于alias和true typedef之間,它定義了一個(gè)新類型,但可以隱式地同原來的類型相互轉(zhuǎn)換。數(shù)據(jù)交換的問題較復(fù)雜,很多問題尚未理清,有待進(jìn)一步考察。

            總結(jié)

                作為MP手段,ast宏擁有靈活性,而TMP則具備安全性,將兩者各自的優(yōu)點(diǎn)相結(jié)合,使我們可以獲得更加靈活和安全的語法構(gòu)造定義手段。通過在用戶定義的語法構(gòu)造上施加全面的約束,可以很好地規(guī)避語法沖突和歧義。

                但是,需要說明的是,這里所考察的僅僅局限在用戶定義語法構(gòu)造的沖突和歧義的消除上。GPL/MP要真正達(dá)到實(shí)用階段,還需要面對更多問題。比如,由于存在用戶定義的語法構(gòu)造,語法分析階段所面對的語法不是固態(tài)的,需要隨時(shí)隨地接受新語法,甚至重疊的語法(存在多個(gè)候選語法,不同的候選語法又會(huì)產(chǎn)生不同的下級語法),這就使語法分析大大復(fù)雜;語法模式匹配被推遲到語義分析階段,此前將無法對某些語法錯(cuò)誤作出檢驗(yàn);一個(gè)語法構(gòu)造的語義需要通過宿主語言定義,如何銜接定義代碼和周邊的環(huán)境和狀態(tài);如何為用戶定義的語法構(gòu)造設(shè)置出錯(cuò)信息;由于某些語法構(gòu)造的二義性,如何判別語法錯(cuò)誤屬于哪個(gè)語法構(gòu)造;...。此外還有一些更本質(zhì)性的問題,諸如語法構(gòu)造的重載和二義性是否會(huì)更容易誘使使用者產(chǎn)生更多的錯(cuò)誤等等,牽涉到錯(cuò)綜復(fù)雜的問題,需要更多的分析和試驗(yàn)。

                另外,作為MP的重要組成部分,編譯期計(jì)算能力也至關(guān)重要。TMP運(yùn)用了C++模板特化,D語言通過更易于理解的static_if等機(jī)制,都試圖獲得編譯期的計(jì)算能力,這些機(jī)制在完整的MP中需要進(jìn)一步擴(kuò)展,而并非僅僅局限在與類型相關(guān)的計(jì)算上。其他一些與此相關(guān)的特性,包括反射(編譯期和運(yùn)行期)、類型traits等,也應(yīng)作為MP必不可少的特性。

            posted on 2008-01-25 15:09 longshanks 閱讀(1334) 評論(4)  編輯 收藏 引用

            Feedback

            # re: 當(dāng)GPL遇上MP 2008-01-27 15:20 Magic
            原來GPL還有這個(gè)意思,學(xué)習(xí)了!  回復(fù)  更多評論
              

            # re: 當(dāng)GPL遇上MP 2008-01-27 17:01 WindyWinter
            標(biāo)題黨  回復(fù)  更多評論
              

            # re: 當(dāng)GPL遇上MP 2008-01-30 09:55 K120
            MP 太過分了吧。  回復(fù)  更多評論
              

            # re: 當(dāng)GPL遇上MP 2008-03-09 16:49 DK_jims
            現(xiàn)在喜歡kiss原則,,,,喜歡笨的  回復(fù)  更多評論
              


            只有注冊用戶登錄后才能發(fā)表評論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            国产精品激情综合久久| 国产AV影片久久久久久| AV无码久久久久不卡蜜桃| 精品久久久久久综合日本| 久久久久一本毛久久久| 狼狼综合久久久久综合网| 亚洲综合久久综合激情久久 | 亚洲国产精品久久| 亚洲国产成人精品无码久久久久久综合 | 久久久久亚洲AV成人网人人软件| 久久人人爽人人人人片av| 国产精品视频久久| 精品久久久中文字幕人妻| 久久国产精品久久精品国产| 伊人久久大香线蕉无码麻豆 | 久久午夜夜伦鲁鲁片免费无码影视 | 久久久久亚洲AV成人网人人网站 | 漂亮人妻被中出中文字幕久久| 久久最新精品国产| 99久久久精品免费观看国产| 国产精品久久新婚兰兰| 久久香蕉国产线看观看猫咪?v| 99久久这里只有精品| 久久亚洲中文字幕精品有坂深雪 | 国产农村妇女毛片精品久久| 久久国产免费观看精品3| 无码人妻精品一区二区三区久久| 中文成人无码精品久久久不卡| 久久久久亚洲精品男人的天堂| 国产农村妇女毛片精品久久| 久久婷婷久久一区二区三区| 国内精品九九久久久精品| 久久无码AV中文出轨人妻| 国产精品久久久久免费a∨| 久久久久无码精品国产app| 丁香五月综合久久激情| 91精品久久久久久无码| 国产99久久久国产精品~~牛| 精品久久人人做人人爽综合| 国产福利电影一区二区三区,免费久久久久久久精 | 久久国产欧美日韩精品|