• <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>
            隨筆-341  評(píng)論-2670  文章-0  trackbacks-0

            幾個(gè)月前就一直有博友關(guān)心DSL的問題,于是我想一想,我在gac.codeplex.com里面也創(chuàng)建了一些DSL,于是今天就來說一說這個(gè)事情。

            創(chuàng)建DSL恐怕是很多人第一次設(shè)計(jì)一門語言的經(jīng)歷,很少有人一開始上來就設(shè)計(jì)通用語言的。我自己第一次做這種事情是在高中寫這個(gè)傻逼ARPG的時(shí)候了。當(dāng)時(shí)做了一個(gè)超簡單的腳本語言,長的就跟匯編差不多,雖然每一個(gè)指令都寫成了調(diào)用函數(shù)的形態(tài)。雖然這個(gè)游戲需要腳本在劇情里面控制一些人物的走動(dòng)什么的,但是所幸并不復(fù)雜,于是還是完成了任務(wù)。一眨眼10年過去了,現(xiàn)在在寫GacUI,為了開發(fā)的方便,我自己做了一些DSL,或者實(shí)現(xiàn)了別人的DSL,漸漸地也明白了一些設(shè)計(jì)DSL的手法。不過在講這些東西之前,我們先來看一個(gè)令我們又愛(對(duì)所有人)又恨(反正我不會(huì))的DSL——正則表達(dá)式!

            一、正則表達(dá)式

            正則表達(dá)式可讀性之差我們?nèi)巳硕贾溃艺齽t表達(dá)式之難寫好都值得O’reilly出一本兩厘米厚的書了。根據(jù)我的經(jīng)驗(yàn),只要先學(xué)好編譯原理,然后按照.net的規(guī)格自己擼一個(gè)自己的正則表達(dá)式,基本上這本書就不用看了。因?yàn)檎齽t表達(dá)式之所以要用奇怪的方法去寫,只是因?yàn)槟闶稚系囊媸悄敲磳?shí)現(xiàn)的,所以你需要順著他去寫而已,沒什么特別的原因。而且我自己的正則表達(dá)式擁有DFA和NFA兩套解析器,我的正則表達(dá)式引擎會(huì)通過檢查你的正則表達(dá)式來檢查是否可以用DFA,從而可以優(yōu)先使用DFA來運(yùn)行,省去了很多其實(shí)不是那么重要的麻煩(譬如說a**會(huì)傻逼什么的)。這個(gè)東西我自己用的特別開心,代碼也放在gac.codeplex.com上面。

            正則表達(dá)式作為一門DSL是當(dāng)之無愧的——因?yàn)樗昧艘环N緊湊的語法來讓我們可以定義一個(gè)字符串的集合,并且取出里面的特征。大體上語法我還是很喜歡的,我唯一不喜歡的是正則表達(dá)式的括號(hào)的功能。括號(hào)作為一種指定優(yōu)先級(jí)的方法,幾乎是無法避免使用的。但是很多流行的正則表達(dá)式的括號(hào)竟然還帶有捕獲的功能,實(shí)在是令我大跌眼鏡——因?yàn)榇蟛糠謺r(shí)候我是不需要捕獲的,這個(gè)時(shí)候只會(huì)浪費(fèi)時(shí)間和空間去做一些多余的事情而已。所以在我自己的正則表達(dá)式引擎里面,括號(hào)是不捕獲的。如果要捕獲,就得用特殊的語法,譬如說(<name>pattern)把pattern捕獲到一個(gè)叫做name的組里面去。

            那我們可以從正則表達(dá)式的語法里面學(xué)到什么DSL的設(shè)計(jì)原則呢?我認(rèn)為,DSL的原則其實(shí)很簡單,只有以下三個(gè):

            1. 短的語法要分配給常用的功能
            2. 語法要么可讀性特別好(從而比直接用C#寫直接),要么很緊湊(從而比直接用C#寫短很多)
            3. API要容易定義(從而用C#調(diào)用非常方便,還可以確保DSL的目標(biāo)是明確又簡單的)

            很多DSL其實(shí)都滿足這個(gè)定義。SQL就屬于API簡單而且可讀性好的那一部分(想想ADO.NET),而正則表達(dá)式就屬于API簡單而且語法緊湊的那一部分。為什么正則表達(dá)式可以設(shè)計(jì)的那么緊湊呢?現(xiàn)在讓我們來一一揭開它神秘的面紗。

            正則表達(dá)式的基本元素是很少的,只有連接、分支和循環(huán),還有一些簡單的語法糖。連接不需要字符,分支需要一個(gè)字符“|”,循環(huán)也只需要一個(gè)字符“+”或者“*”,還有代表任意字符的“.”,還有代表多次循環(huán)的{5,},還有代表字符集合的[a-zA-Z0-9_]。對(duì)于單個(gè)字符的集合來講,我們甚至不需要[],直接寫就好了。除此之外因?yàn)槲覀冇昧艘恍┨厥庾址赃€得有轉(zhuǎn)義(escaping)的過程。那讓我們數(shù)數(shù)我們定義了多少字符:“|+*[]-\{},.()”。用的也不多,對(duì)吧。

            盡管看起來很亂,但是正則表達(dá)式本身也有一個(gè)嚴(yán)謹(jǐn)?shù)恼Z法結(jié)構(gòu)。關(guān)于我的正則表達(dá)式的語法樹定義可以看這里:https://gac.codeplex.com/SourceControl/latest#Common/Source/Regex/RegexExpression.h。在這里我們可以整理出一個(gè)語法:

            DIGIT ::= [0-9]
            LITERAL ::= [^|+*\[\]\-\\{}\^,.()]
            ANY_CHAR ::= LITERAL | "^" | "|" | "+" | "*" | "[" | "]" | "-" | "\" | "{" | "}" | "," | "." | "(" | ")"
            
            CHAR
                ::= LITERAL
                ::= "\" ANY_CHAR
            
            CHARSET_COMPONENT
                ::= CHAR
                ::= CHAR "-" CHAR
            
            CHARSET
                ::= CHAR
                ::= "[" ["^"] { CHARSET_COMPONENT } "]"
            
            REGEX_0
                ::= CHARSET
                ::= REGEX_0 "+"
                ::= REGEX_0 "*"
                ::= REGEX_0 "{" { DIGIT } ["," [ { DIGIT } ]] "}"
                ::= "(" REGEX_2 ")"
            
            REGEX_1
                ::= REGEX_0
                ::= REGEX_1 REGEX_0
            
            REGEX_2
                ::= REGEX_1
                ::= REGEX_2 "|" REGEX_1
            
            REGULAR_EXPRESSION
                ::= REGEX_2

            這只是隨手寫出來的語法,盡管可能不是那么嚴(yán)謹(jǐn),但是代表了正則表達(dá)式的所有結(jié)構(gòu)。為什么我們要熟練掌握EBNF的閱讀和編寫?因?yàn)楫?dāng)我們用EBNF來看待我們的語言的時(shí)候,我們就不會(huì)被愈發(fā)的表面所困擾,我們會(huì)投過語法的外衣,看到語言本身的結(jié)構(gòu)。脫別人衣服總是很爽的。

            于是我們也要透過EBNF來看到正則表達(dá)式本身的結(jié)構(gòu)。其實(shí)這是一件很簡單的事情,只要把EBNF里面那些“fuck”這樣的字符字面量去掉,然后規(guī)則就會(huì)分為兩種:

            1:規(guī)則僅由終結(jié)符構(gòu)成——這是基本概念,譬如說上面的CHAR什么的。
            2:規(guī)則的構(gòu)成包含非終結(jié)符——這就是一個(gè)結(jié)構(gòu)了。

            我們甚至可以利用這種方法迅速從EBNF確定出我們需要的語法樹長什么樣子。具體的方法我就不說了,大家自己聯(lián)系一下就會(huì)悟到這個(gè)簡單粗暴的方法了。但是,我們?cè)谠O(shè)計(jì)DSL的時(shí)候,是要反過來做的。首先確定語言的結(jié)構(gòu),翻譯成語法樹,再翻譯成不帶“fuck”的“骨架EBNF”,再設(shè)計(jì)具體的細(xì)節(jié)寫成完整的EBNF

            看到這里大家會(huì)覺得,其實(shí)正則表達(dá)式的結(jié)構(gòu)跟四則運(yùn)算式子是沒有區(qū)別的。正則表達(dá)式的*是后綴操作符,|是中綴操作符,連接也是中最操作符——而且操作符是隱藏的!我猜perl系正則表達(dá)式的作者當(dāng)初在做這個(gè)東西的時(shí)候,肯定糾結(jié)過“隱藏的中綴操作符”應(yīng)該給誰的問題。不過其實(shí)我們可以通過收集一些素材,用不同的方案寫出正則表達(dá)式,最后經(jīng)過統(tǒng)計(jì)發(fā)現(xiàn)——隱藏的中綴操作符給連接操作是最靠譜的。

            為什么呢?我們來舉個(gè)例子,如果我們把連接和分支的語法互換的話,那么原本“fuck|you”就要寫成“(f|u|c|k)(y|o|u)”了。寫多幾個(gè)你會(huì)發(fā)現(xiàn),的確連接是比分支更常用的,所以短的那個(gè)要給連接,所以連接就被分配了一個(gè)隱藏的中綴操作符了。

            上面說了這么多廢話,只是為了說明白一個(gè)道理——要先從結(jié)構(gòu)入手然后才設(shè)計(jì)語法,并且要把最短的語法分配給最常用的功能。因?yàn)楹芏嗳嗽O(shè)計(jì)DSL都反著來,然后做成了屎。

            二、Fpmacro

            第二個(gè)要講的是Fpmacro。簡單來說,F(xiàn)pmacro和C++的宏是類似的,但是C++的宏是從外向內(nèi)展開的,這意味著dynamic scoping和call by name。Fpmacro是從內(nèi)向外展開的,這意味著lexical scoping和call by value。這些概念我在第七篇文章已經(jīng)講了,大家也知道C++的宏是一件多么不靠譜的事情。但是為什么我要設(shè)計(jì)Fpmacro呢?因?yàn)橛幸惶煳医K于需要類似于Boost::Preprocessor那樣子的東西了,因?yàn)槲乙?a target="_blank">類似這樣的代碼。但是C++的宏實(shí)在是太他媽惡心了,惡心到連我都不能駕馭它。最終我就做出了Fpmacro,于是我可以用這樣的宏來生成上面提到的文件了。

            我來舉個(gè)例子,如果我要生成下面的代碼:

            int a1 = 1;
            int a2 = 2;
            int a3 = 3;
            int a4 = 4;
            cout<<a1<<a2<<a3<<a4<<endl;

            就要寫下面的Fpmacro代碼:

            $$define $COUNT 4 /*定義數(shù)量:4*/
            $$define $USE_VAR($index) a$index /*定義變量名字,這樣$USE_VAR(10)就會(huì)生成“a10”*/
            
            $$define $DEFINE_VAR($index) $$begin /*定義變量聲明,這樣$DEFINE_VAR(10)就會(huì)生成“int a10 = 10;”*/
            int $USE_VAR($index) = $index;
            $( ) /*用來換行——會(huì)多出一個(gè)多余的空格不過沒關(guān)系*/ 
            $$end
            
            $loop($COUNT,1,$DEFINE_VAR) /*首先,循環(huán)生成變量聲明*/
            cout<<$loopsep($COUNT,1,$USE_VAR,<<)<<endl; /*其次,循環(huán)使用這些變量*/

            順便,F(xiàn)pmacro的語法在這里,F(xiàn)pmacroParser.h/cpp是由這個(gè)語法生成的,剩下的幾個(gè)文件就是C++的源代碼了。不過因?yàn)榻裉熘v的是如何設(shè)計(jì)DSL,那我就來講一下,我當(dāng)初為什么要把Fpmacro設(shè)計(jì)成這個(gè)樣子。

            在設(shè)計(jì)之前,首先我們需要知道Fpmacro的目標(biāo)——設(shè)計(jì)一個(gè)沒有坑的宏,而且這個(gè)宏還要支持分支和循環(huán)。那如何避免坑呢?最簡單的方法就是把宏看成函數(shù),真正的函數(shù)。當(dāng)我們把一個(gè)宏的名字當(dāng)成參數(shù)傳遞給另一個(gè)宏的時(shí)候,這個(gè)名字就成為了函數(shù)指針。這一點(diǎn)C++的宏是不可能完全的做到的,這里的坑實(shí)在是太多了。而且Boost::Preprocessor用來實(shí)現(xiàn)循環(huán)的那個(gè)技巧實(shí)在是我操太他媽難受了。

            于是,我們就可以把需求整理成這樣:

            1. Fpmacro的代碼由函數(shù)組成,每一個(gè)函數(shù)的唯一目的都是生成C++代碼的片段。
            2. 函數(shù)和函數(shù)之間的空白可以用來寫代碼。把這些代碼收集起來就可以組成“main函數(shù)”了,從而構(gòu)成Fpmacro代碼的主體。
            3. 函數(shù)可以有內(nèi)部函數(shù),在代碼復(fù)雜的時(shí)候可以充當(dāng)一些namespace的功能,而且內(nèi)部函數(shù)都是私有的。
            4. Fpmacro代碼可以include另一份Fpmacro代碼,可以實(shí)現(xiàn)全局配置的功能。
            5. Fpmacro必須支持分支和循環(huán),而且他們的語法和函數(shù)調(diào)用應(yīng)該一致。
            6. 用來代表C++代碼的部分需要的轉(zhuǎn)義應(yīng)該降到最低。
            7. 即使是非功能代碼部分,括號(hào)也必須配對(duì)。這是為了定義出一個(gè)清晰的簡單的語法,而且因?yàn)镃++本身也是括號(hào)配對(duì)的,所以這個(gè)規(guī)則并沒有傷害。
            8. C++本身對(duì)空格是有很高的容忍度的,因此Fpmacro作為一個(gè)以換行作為分隔符的語言,并不需要具備特別精確的控制空格的功能。

            為什么要強(qiáng)調(diào)轉(zhuǎn)義呢?因?yàn)槿绻肍pmacro隨便寫點(diǎn)什么代碼都要到處轉(zhuǎn)義的話,那還怎么寫得下去呀!

            這個(gè)時(shí)候我們開始從結(jié)構(gòu)入手。Fpmacro的結(jié)構(gòu)是簡單的,只有下面幾種:

            1. 普通C++代碼
            2. 宏名字引用
            3. 宏調(diào)用
            4. 連接
            5. 括號(hào)
            6. 表達(dá)數(shù)組字面量(最后這被證明是沒有任何意義的功能)

            根據(jù)上面提到的DSL三大原則,我們要給最常用的功能配置最短的語法。那最短的功能是什么呢?跟正則表達(dá)式一樣,是連接。所以要給他一個(gè)隱藏的中綴運(yùn)算符。其次就要考慮到轉(zhuǎn)義了。如果Fpmacro大量運(yùn)用的字符與C++用到的字符一樣,那么我們?cè)贑++里面用這個(gè)字符的時(shí)候,就得轉(zhuǎn)義了。這個(gè)是絕對(duì)不能接受的。我們來看看鍵盤,C++沒用到的也就只有@和$了。這里我因?yàn)閭€(gè)人喜好,選擇了$,它的功能大概跟C++的宏里面的#差不多。

            那我們?nèi)绾沃牢覀兊拇a片段是訪問一個(gè)C++的名字,還是訪問一個(gè)Fpmacro的名字呢?為了避免轉(zhuǎn)義,而且也順便可以突出Fpmacro的結(jié)構(gòu)本身,我讓所有的Fpmacro名字都要用$開頭,無論是函數(shù)名還是參數(shù)都一樣。于是定義函數(shù)就用$$define開始,而且多行的函數(shù)還要用$$begin和$$end來提示(見上面的例子)。函數(shù)調(diào)用就可以這么做:$名字(一些參數(shù))。因?yàn)椴还苁菂?shù)名還是函數(shù)名都是$開頭的,所以函數(shù)調(diào)用肯定也是$開頭的。那寫出來的代碼真的需要轉(zhuǎn)義怎么辦呢?直接用$(字符)就行了。這個(gè)時(shí)候我們可以來檢查一下這樣做是不是會(huì)定義出歧義的語法,答案當(dāng)然是不會(huì)。

            我們定義了$作為Fpmacro的名字前綴之后,是不是一個(gè)普通的C++代碼(因此沒有$),直接貼上去就相當(dāng)于一個(gè)Fpmacro代碼呢?結(jié)論當(dāng)然是成立的。仔細(xì)選擇這些語法可以讓我們?cè)谥幌雽慍++的時(shí)候可以專心寫C++而不會(huì)被各種轉(zhuǎn)義干擾到(想想在C++里面寫正則表達(dá)式的那一堆斜杠臥槽)。

            到了這里,就到了最關(guān)鍵的一步了。那我們把一個(gè)Fpmacro的名字傳遞給參數(shù)的時(shí)候,究竟是什么意思呢?一個(gè)Fpmacro的名字,要么就是一個(gè)字符串,要么就是一個(gè)Fpmacro函數(shù),不會(huì)有別的東西了(其實(shí)還可能是數(shù)組,但是最后證明沒用)。這個(gè)純潔性要一直保持下去。就跟我們?cè)贑語言里面?zhèn)鬟f一個(gè)函數(shù)指針一樣,不管傳遞到了哪里,我們都可以隨時(shí)調(diào)用它。

            那Fpmacro的函數(shù)到底有沒有包括上下文呢?因?yàn)镕pmacro和pascal一樣有“內(nèi)部函數(shù)”,所以當(dāng)然是要有上下文的。但是Fpmacro的名字都是只讀的,所以只用shared_ptr來記錄就可以了,不需要出動(dòng)GC這樣的東西。關(guān)于為什么帶變量的閉包就必須用GC,這個(gè)大家可以去想一想。這是Fpmacro的函數(shù)像函數(shù)式語言而不是C語言的一個(gè)地方,這也是為什么我把名字寫成了Fpmacro的原因了。

            不過Fpmacro是不帶lambda表達(dá)式的,因?yàn)檫@樣只會(huì)把語法搞得更糟糕。再加上Fpmacro允許定義內(nèi)部函數(shù)和Fpmacro名字是只讀的這兩條規(guī)則,所有的lambda表達(dá)式都可以簡單的寫成一個(gè)內(nèi)部函數(shù)然后賦予它一個(gè)名字。因此這一點(diǎn)沒有傷害。那什么時(shí)候需要傳遞一個(gè)Fpmacro函數(shù)呢進(jìn)另一個(gè)函數(shù)呢?當(dāng)然就只有循環(huán)了。Fpmacro的內(nèi)置函數(shù)有分支循環(huán)還有簡單的數(shù)值計(jì)算和比較功能。

            我們來做一個(gè)小實(shí)驗(yàn),生成下面的代碼:

            void Print(int a1)
            {
                cout<<"1st"<<a1<<endl;
            }
            
            void Print(int a1, int a2)
            {
                cout<<"1st"<<a1<<", "<<"2nd"<<a2<<endl;
            }
            
            ....
            
            void Print(int a1, int a2, ... int a10)
            {
                cout<<...<<"10th"<<a10<<endl;
            }
            
            ....

            我們需要兩重循環(huán),第一重是生成Print,第二重是里面的cout。cout里面還要根據(jù)數(shù)字來產(chǎn)生st啊、nd啊、rd啊、這些前綴。于是我們可以開始寫了。Fpmacro的寫法是這樣的,因?yàn)闆]有l(wèi)ambda表達(dá)式,所以循環(huán)體都是一些獨(dú)立的函數(shù)。于是我們來定義一些函數(shù)來生成變量名、參數(shù)定義和cout的片段:

            $$define $VAR_NAME($index) a$index /*$VAR_NAME(3) -> a3*/
            $$define $VAR_DEF($index) int $VAR_NAME($index) /*$VAR_DEF(3) -> int a3*/
            $$define $ORDER($index) $$begin /*$ORDER(3) -> 3rd*/
                $$define $LAST_DIGIT $mod($index,10)
                $index$if($eq($LAST_DIGIT,1),st,$if($eq($LAST_DIGIT,2),nd,$if($eq($LAST_DIGIT,3),rd,th)))
            $$end
            $$define $OUTPUT($index) $(")$ORDER($index)$(")<<$VAR_NAME($index) /*$OUTPUT(3) -> "3rd"<<a3*/

            接下來就是實(shí)現(xiàn)Print函數(shù)的宏:

            $$define $PRINT_FUNCTION($count) $$begin
            void Print($loopsep($count,1,$VAR_DEF,$(,)))
            {
                cout<<$loopsep($count,1,$OUTPUT,<<)<<endl;
            }
            $( ) $$end

            最后就是生成整片代碼了:

            $define $COUNT 10 /*就算是20,那上面的代碼的11也會(huì)生成11st,特別方便*/
            $loop($COUNT,1,$PRINT_FUNCTION)

            注意:注釋其實(shí)是不能加的,因?yàn)槿绻慵恿俗⑨專@些注釋最后也會(huì)被生成成C++,所以上面那個(gè)$COUNT就會(huì)變成10+空格+注釋,他就不能放進(jìn)$loop函數(shù)里面了。Fpmacro并沒有添加“Fpmacro注釋”的代碼,因?yàn)槲矣X得沒必要

            為什么我們不需要C++的宏的#和##操作呢?因?yàn)樵谶@里,A(x)##B(x)被我們處理成了$A(x)$B(x),而L#A(x)被我們處理成了L$(“)$A(x)$(“)。雖然就這么看起來好像Fpmacro長了一點(diǎn)點(diǎn),但是實(shí)際上用起來是特別方便的。$這個(gè)前綴恰好幫我們解決了A(x)##B(x)的##的問題,寫的時(shí)候只需要直接寫下去就可以了,譬如說$ORDER里面的$index$if…。

            那么這樣做到底行不行呢?看在Fpmacro可以用這個(gè)宏來生成這么復(fù)雜的代碼的份上,我認(rèn)為“簡單緊湊”和“C++代碼幾乎不需要轉(zhuǎn)義”和“沒有坑”這三個(gè)目標(biāo)算是達(dá)到了。DSL之所以為DSL就是因?yàn)槲覀兪怯盟鼇硗瓿商厥獾哪康牡模皇莋eneral purpose的,因此不需要太復(fù)雜。因此設(shè)計(jì)DSL要有一個(gè)習(xí)慣,就是時(shí)刻審視一下,我們是不是設(shè)計(jì)了多余的東西。現(xiàn)在我回過頭來看,F(xiàn)pmacro支持?jǐn)?shù)組就是多余的,而且實(shí)踐證明,根本沒用上。

            大家可能會(huì)說,代碼遍地都是$看起來也很亂啊?沒關(guān)系,最近我剛剛搞定了一個(gè)基于語法文件驅(qū)動(dòng)的自動(dòng)著色和智能提示的算法,只需要簡單地寫一個(gè)Fpmacro的編輯器就可以了,啊哈哈哈哈。

            三、尾聲

            本來我是想舉很多個(gè)例子的,還有語法文件啊,GUI配置啊,甚至是SQL什么的。不過其實(shí)設(shè)計(jì)一個(gè)DSL首先要求你對(duì)領(lǐng)域本身有著足夠的理解,在長期的開發(fā)中已經(jīng)在這個(gè)領(lǐng)域里面感受到了極大的痛苦,這樣你才能真的設(shè)計(jì)出一個(gè)專門根除痛點(diǎn)的DSL來。

            像正則表達(dá)式,我們都知道手寫字符串處理程序經(jīng)常要人肉做錯(cuò)誤處理和回溯等工作,正則表達(dá)式幫我們自動(dòng)完成了這個(gè)功能。

            C++的宏生成復(fù)雜代碼的時(shí)候,動(dòng)不動(dòng)就會(huì)因?yàn)閐ynamic scoping和call by name掉坑里而且還沒有靠譜的工具來告訴我們究竟要怎么做,F(xiàn)pmacro就解決了這個(gè)問題。

            開發(fā)DSL需要語法分析器,而且?guī)isitor模式的語法樹可擴(kuò)展性好但是定義起來特別的麻煩,所以我定義了一個(gè)語法文件的格式,寫了一個(gè)ParserGen.exe(代碼在這里)來替我生成代碼。Fpmacro的語法分析器就是這么生成出來的。

            GUI的構(gòu)造代碼寫起來太他媽煩了,所以還得有一個(gè)配置的文件。

            查詢數(shù)據(jù)特別麻煩,而且就算是只有十幾個(gè)T的小型數(shù)據(jù)庫也很難自己設(shè)計(jì)一個(gè)靠譜的容器,所以我們需要SQLServer。這個(gè)DSL做起來不簡單,但是用起來簡單。這也是一個(gè)成功的DSL。

            類似的,Visual Studio為了生成代碼還提供了T4這種模板文件。這個(gè)東西其實(shí)超好用的——除了用來生成C++代碼,所以我還得自己擼一個(gè)Fpmacro……

            用MVC的方法來寫HTML,需要從數(shù)據(jù)結(jié)構(gòu)里面拼HTML。用過php的人都知道這種東西很容易就寫成了屎,所以Visual Studio里面又在ASP.NET MVC里面提供了razor模板。而且他的IDE支持特別號(hào),razor模板里面可以混著HTML+CSS+Javascript+C#的代碼,智能提示從不出錯(cuò)!

            還有各種數(shù)不清的配置文件。我們都知道,一個(gè)強(qiáng)大的配置文件最后都會(huì)進(jìn)化成為lisp,哦不,DSL的。

            這些都是DSL,用來解決我們的痛點(diǎn)的東西,而且他本身又不足以復(fù)雜到用來完成程序所有的功能(除了連http service都能寫的SQLServer我們就不說了=_=)。設(shè)計(jì)DSL的時(shí)候,首先要找到痛點(diǎn),其次要理清楚DSL的結(jié)構(gòu),然后再給他設(shè)計(jì)一個(gè)要么緊湊要么可讀性特別高的語法,然后再給一個(gè)簡單的API,用起來別提多爽了。

            posted on 2013-09-15 17:26 陳梓瀚(vczh) 閱讀(8602) 評(píng)論(11)  編輯 收藏 引用 所屬分類: 啟示

            評(píng)論:
            # re: 如何設(shè)計(jì)一門語言(十)&mdash;&mdash;正則表達(dá)式與領(lǐng)域特定語言(DSL) 2013-09-15 17:30 | ooseven
            沙發(fā)先占  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(十)&mdash;&mdash;正則表達(dá)式與領(lǐng)域特定語言(DSL) 2013-09-15 21:03 | DiryBoy
            很好很強(qiáng)大!  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(十)&mdash;&mdash;正則表達(dá)式與領(lǐng)域特定語言(DSL)[未登錄] 2013-09-15 21:13 | me
            膜拜!  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(十)&mdash;&mdash;正則表達(dá)式與領(lǐng)域特定語言(DSL) 2013-09-16 10:53 | jagd
            I'd like to see a DSL for COM server.  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(十)&mdash;&mdash;正則表達(dá)式與領(lǐng)域特定語言(DSL) 2013-09-16 18:11 | holyfire
            看了感覺這個(gè)dsl寫起來更麻煩啊,為什么不像protocol buffer那樣簡潔明了的語法呢  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(十)&mdash;&mdash;正則表達(dá)式與領(lǐng)域特定語言(DSL) 2013-09-16 22:19 | 陳梓瀚(vczh)
            @holyfire
            protocol buffer語法簡單是因?yàn)樗墓δ鼙緛砭秃唵巍H绻鹥rotocol buffer設(shè)計(jì)成SOAP那樣那肯定沒辦法保持這個(gè)簡單的。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(十)&mdash;&mdash;正則表達(dá)式與領(lǐng)域特定語言(DSL) 2013-09-16 22:21 | 陳梓瀚(vczh)
            @jagd
            用C#,就想寫一個(gè)普通的類一樣,加個(gè)attribute就成為了COM。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(十)&mdash;&mdash;正則表達(dá)式與領(lǐng)域特定語言(DSL) 2013-09-22 23:49 | tkgl
            要不要最后還來黑一下LISP啊.V神  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(十)&mdash;&mdash;正則表達(dá)式與領(lǐng)域特定語言(DSL) 2013-10-20 00:42 | unnamed
            “那上面的代碼的11也會(huì)生成11st,特別方便” eleventh。。11th。。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(十)&mdash;&mdash;正則表達(dá)式與領(lǐng)域特定語言(DSL) 2013-10-20 07:26 | 陳梓瀚(vczh)
            @unnamed
            噗  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(十)&mdash;&mdash;正則表達(dá)式與領(lǐng)域特定語言(DSL) 2014-12-05 08:07 | ha110w31t
            這一系列文章似乎就這篇與Metaprogramming有關(guān)係了,
            個(gè)人挺想看博主對(duì)Metaprogramming更詳細(xì)的個(gè)人見解。  回復(fù)  更多評(píng)論
              
            国产免费福利体检区久久| 777午夜精品久久av蜜臀 | 久久精品黄AA片一区二区三区| 国内精品人妻无码久久久影院 | 久久久免费观成人影院| 偷偷做久久久久网站| 久久ww精品w免费人成| 色天使久久综合网天天| 成人久久精品一区二区三区| 人人狠狠综合久久亚洲| 欧美黑人激情性久久| 欧美一级久久久久久久大片| 久久精品国产91久久综合麻豆自制 | 久久久久无码中| 2020久久精品国产免费| 伊人久久无码中文字幕| 日韩亚洲国产综合久久久| 久久久精品一区二区三区| 亚洲AV日韩精品久久久久久久| 77777亚洲午夜久久多喷| 久久久久av无码免费网| 久久精品成人免费观看97| 97久久超碰成人精品网站| 久久精品国产2020| 伊人久久无码精品中文字幕| 国产精品成人无码久久久久久| 996久久国产精品线观看| 色婷婷综合久久久久中文一区二区| 亚洲一级Av无码毛片久久精品| 国产精品成人久久久久久久| 久久美女网站免费| 久久被窝电影亚洲爽爽爽| 国产精品一久久香蕉国产线看观看 | 久久人人超碰精品CAOPOREN| 国产三级精品久久| 91超碰碰碰碰久久久久久综合| 久久国产精品99久久久久久老狼| 久久99精品国产自在现线小黄鸭| 久久国产亚洲高清观看| 久久99国产精品久久久| 99久久精品免费看国产免费|