国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

使用Antlr實現(xiàn)簡單的DSL

2019-11-17 02:37:58
字體:
供稿:網(wǎng)友

使用Antlr實現(xiàn)簡單的DSL

為什么要使用DSL

DSL是領(lǐng)域?qū)S谜Z言,常見的DSL有SQL,CSS,Shell等等,這些DSL語言有別于其他通用語言如:C++,java,C#,DSL常在特殊的場景或領(lǐng)域中使用。如下圖:

image

領(lǐng)域?qū)S谜Z言通常是被領(lǐng)域?qū)<沂褂茫I(lǐng)域?qū)<乙话悴皇煜ねㄓ镁幊陶Z言,但是他們一般對業(yè)務(wù)非常了解,程序員一般對通用語言比較熟悉,但是在做行業(yè)軟件的時候?qū)I(yè)務(wù)部了解。這就需要協(xié)作的過程,一種方式是領(lǐng)域?qū)<彝ㄟ^文檔或者教授的方式把業(yè)務(wù)邏輯傳遞給程序員讓程序員翻譯成業(yè)務(wù)邏輯,而另一種方法,程序員為領(lǐng)域?qū)<叶ㄖ艱SL,并編寫解釋DSL的環(huán)境嵌入在業(yè)務(wù)系統(tǒng)中。這樣在某塊功能的實現(xiàn)上,程序員可以不用去關(guān)系具體實現(xiàn)和業(yè)務(wù),而領(lǐng)域?qū)<乙膊挥眠^多的理解程序背后的事情。

這種需求常常出現(xiàn)在OA系統(tǒng)或ERP系統(tǒng)的工作流中。比如說部門申請單的審批,如果是OA產(chǎn)品,那么這個審批流程將面對不同企業(yè)各式各樣的審批的條件,一個企業(yè)中不同的部門審批的條件也不一樣。如果全靠程序在后臺死寫,那么不可能窮盡用戶的想法,那么遇見這類對性能要求不高,又需要很強的靈活性的需求,通常會用到DSL,讓用戶輸入類似的業(yè)務(wù)邏輯:[部門]=”人事部” AND [金額] <= 1000 通過。

在舉個例子,在車聯(lián)網(wǎng)系統(tǒng)中,我們需要判斷一輛車是否在經(jīng)濟(jì)區(qū)中運行,這個業(yè)務(wù)邏輯判斷的因素比較多,常常不是程序員或者產(chǎn)品經(jīng)理可以寫出來的,需要交給車輛專家來編寫。也許會寫成這樣: ( [天氣]!=”下雨” AND 50< [車速] <= 80 ) OR ( [道路] ==”高速” AND 60< [車速] <= 110 )。這同樣需要我們把他翻譯成我們系統(tǒng)實現(xiàn)的代碼。

如果上述的功能比較簡單,DSL也不會很復(fù)雜,那么我們只需要簡單的解釋器模式就可以解決。但是如果遇見的業(yè)務(wù)比較復(fù)雜且變化比較多,那么使用工具來解析DSL將是必然的選擇。

常見的語法分析器代碼生成工具有yacc,lexer,antlr,T4等等。yacc采用的是LALR(1),而antlr采用LL(k)的解析方法。對詞法分析,語言分析,AST或者編譯原理有了解的話,有助于這些工具的使用。

Antlr的安裝

Antlr可以生成C#,Java'和其他一些語言的解析工具代碼。我這里使用C#做例子,可以在NuGet(Java就是在Maven)中下載最新版本Antlr的DLL,Antlr,Antlr4.Runtime.并且下載Antlr在VisioStudio的項目模板(在VS中Tools->Extensions and Updates)。如果你使用的VS項目模板那么你可以在項目添加g4后綴的文件,antlr詞法和語言生成工具的文法文件都是使用g4為后綴。如下圖,對于小型項目我們一般使用Combined Grammar,詞法和語法都放在一起。

可以參考如下地址:https://github.com/tunnelvisionlabs/antlr4cs

]H~)K6@95W)YNYQ~U(PR%BJ

在新建的g4中編輯語法,保存并編譯,就會在項目路徑下的obj/Debug目錄下生成語法解析和詞法解析的基類代碼。

Antlr的語法簡介

最新的g4版本的語言可以參看官方文檔:,如果需要更加系統(tǒng)的學(xué)習(xí)的話,需要下載最新的antlr4的官方書籍a(chǎn)ntlr book 4,免費的電子書可以百度搜索”The Definitive ANTLR 4 Reference”。

Antlr實例

以在車聯(lián)網(wǎng)系統(tǒng)中,判定車輛是否超速為例子。每個用戶或者說是企業(yè)都需要管理自己所有的車輛,在業(yè)務(wù)系統(tǒng)中,也會對車輛是否超速給出一個定義。這個定義也許不會想[車速]>80這么簡單,有時候還會出現(xiàn)如下的定義:”(([車速]*10+3)>(200)) && ([企業(yè)ID] == /"123/") && ([時間]>1200 && [時間]<1700)”。從這個例子中可以看出,判定超速的規(guī)則支持四則混合運算,還有一些特定的變量如車速,企業(yè)ID,時間。這中類型的定義是我們系統(tǒng)期望的讓每個用戶定義的方式。因為這種方式足夠靈活。用戶可以隨意配置。為了實現(xiàn)這種方式,解釋器模式是一個可行的方案,但是如果我們使用DSL,則更加靈活和可擴展。我們定義的這種DSL,不單單執(zhí)行上述的四則混合運算,還必須支持變量。這些變量都是我們在真是的系統(tǒng)運行中需要去獲取(數(shù)據(jù)庫或者緩存)的,也就是說,我們的解析程序首先要獲取這些變量的值,然后再進(jìn)行運算,最后得出一個是否超速的結(jié)果。當(dāng)然隨著我們DSL的解析越來越完善,算法越來越先進(jìn),支持的變量也許會更多,也許還會有道路等級,天氣因素等算法因子的出現(xiàn)。

要實現(xiàn)這個需求首先我們要定義文法,也就是g4文件的內(nèi)容。

注意的是,在一些文法后面用”#”號定義了一個名稱,就會在用于訪問生成的抽象語法樹AST的訪問器中生成該方法,用于訪問當(dāng)這個規(guī)約被滿足時候的那個樹節(jié)點。

grammar ISL;

@header { using System; }

@members {

}

/* * Parser Rules */ /* * 表達(dá)式 */

exPRession : NUMBER #Number | STRING #String | VARIABLE #Variable | SUB expression #SubExpr | expression op=(MUL|DIV) expression #MulDiv | expression op=(ADD|SUB) expression #AddSub | LEFT_PAREN expression RIGHT_PAREN #Paren ;

equality_expression : TRUE #LogicalTrue | FALSE #LogicalFalse | expression op=(GREATE_THAN | GREATE_EQUAL_THAN | LESS_THAN | LESS_EQUAL_THAN | EQUAL | NOT_EQUAL) expression #LogicalOp | equality_expression op=(LOGICAL_NOT | LOGICAL_AND | LOGICAL_OR | EQUAL | NOT_EQUAL) equality_expression #LogicalAndOrNot | LEFT_PAREN equality_expression RIGHT_PAREN #Paren2 ;

/* * 返回語句 */ return_statement : RETURN equality_expression SEMICOLON #Return ;

elseif_list : elseif+ //| elseif_list elseif ;

elseif : ELSEIF LEFT_PAREN expression RIGHT_PAREN block ;

if_statement : IF LEFT_PAREN expression RIGHT_PAREN block | IF LEFT_PAREN expression RIGHT_PAREN block ELSE block | IF LEFT_PAREN expression RIGHT_PAREN block elseif_list | IF LEFT_PAREN expression RIGHT_PAREN block elseif_list ELSE block ;

statement : expression SEMICOLON | if_statement ;

block : LEFT_CURLY statement_list RIGHT_CURLY | LEFT_CURLY RIGHT_CURLY ;

statement_list : statement+ ;

/* * Lexer Rules */

VARIABLE : '[車速]' | '[天氣]' | '[時間]' | '[企業(yè)ID]' | '[用戶ID]'; // 數(shù)字變量 NUMBER : [1-9][0-9]*|[0]|([0-9]+[.][0-9]+) ; // 數(shù)字 STRING : '"' ('//"'|.)*? '"' ; // 字符串

WS : [ /t/r/n]+ -> skip ; // skip spaces, tabs, newlines

ADD : '+' ; SUB : '-' ; MUL : '*' ; DIV : '/' ; MOD : '%' ; GREATE_THAN : '>' ; GREATE_EQUAL_THAN : '>=' ; LESS_THAN : '<' ; LESS_EQUAL_THAN : '<=' ; EQUAL : '==' ; TRUE : 'true' ; FALSE : 'false' ; NOT_EQUAL : '!=' ; LOGICAL_AND : '&&' ; LOGICAL_OR : '||' ; LOGICAL_NOT : '!' ; LEFT_PAREN : '(' ; RIGHT_PAREN : ')' ; LEFT_CURLY : '{' ; RIGHT_CURLY : '}' ; CR : '/n' ; IF : 'if' ; ELSE : 'else' ; ELSEIF : 'else if' ; SEMICOLON : ';' ; DOUBLE_QUOTATION : '"' ; RETURN : 'return' ;

LINE_COMMENT : '//' .*? '/n' -> skip ; COMMENT : '/*' .*? '*/' -> skip ;

生成好代碼之后,我們使用Visitor訪問器(參看The Definitive ANTLR 4 Reference這本書)來實現(xiàn)語法樹的訪問。

public class ISLVisitor2 : ISLBaseVisitor<Result> { public override Result VisitNumber(ISLParser.NumberContext context) { Result r = new Result(); r.Value = double.Parse(context.NUMBER().GetText()); r.Text = context.NUMBER().GetText(); return r; }

public override Result VisitParen(ISLParser.ParenContext context) { Result o = Visit(context.expression()); o.Text = "(" + o.Text + ")"; return o; }

public override Result VisitParen2(ISLParser.Paren2Context context) { Result o = Visit(context.equality_expression()); o.Text = "(" + o.Text + ")"; return o; }

public override Result VisitMulDiv(ISLParser.MulDivContext context) { Result r = new Result();

double left = Convert.ToDouble(Visit(context.expression(0)).Value); double right = Convert.ToDouble(Visit(context.expression(1)).Value);

if (context.op.Type == ISLParser.MUL) { r.Value = left * right; r.Text = Visit(context.expression(0)).Text + " 乘以 " + Visit(context.expression(1)).Text; }

if (context.op.Type == ISLParser.DIV) { r.Value = left / right; r.Text = Visit(context.expression(0)).Text + " 除以 " + Visit(context.expression(1)).Text; }

return r; }

public override Result VisitAddSub(ISLParser.AddSubContext context) { Result r = new Result();

double left = (double)Visit(context.expression(0)).Value; double right = (double)Visit(context.expression(1)).Value;

if (context.op.Type == ISLParser.ADD) { r.Value = left + right; r.Text = Visit(context.expression(0)).Text + " 加上 " + Visit(context.expression(1)).Text; } else { r.Value = left - right; r.Text = Visit(context.expression(0)).Text + " 減去 " + Visit(context.expression(1)).Text; }

return r; }

public override Result VisitVariable(ISLParser.VariableContext context) { Result r = new Result(); if (context.GetText() == "[車速]") { r.Text = "車速"; r.Value = TestData.VehicleSpeed; } else if (context.GetText() == "[天氣]") { r.Text = "天氣"; r.Value = TestData.Weather; } else if (context.GetText() == "[時間]") { r.Text = "時間"; r.Value = TestData.Now; } else if (context.GetText() == "[企業(yè)ID]") { r.Text = "企業(yè)ID"; r.Value = TestData.EntId; } else if (context.GetText() == "[用戶ID]") { r.Text = "用戶ID"; r.Value = TestData.AccountId; }

return r; }

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 康定县| 成安县| 凤台县| 通州市| 霍邱县| 织金县| 即墨市| 沙河市| 娄底市| 东平县| 莒南县| 新昌县| 准格尔旗| 宁陵县| 宽甸| 婺源县| 柏乡县| 滁州市| 老河口市| 民乐县| 青阳县| 桃园县| 平潭县| 鄂托克前旗| 青铜峡市| 北流市| 佛教| 岳西县| 邢台县| 阳西县| 寿光市| 定州市| 子长县| 蕉岭县| 金华市| 胶南市| 滦南县| 大同县| 宜城市| 南康市| 重庆市|