使用 Antlr 實(shí)現(xiàn)簡(jiǎn)單的 DSL
為什么要使用DSL
DSL是領(lǐng)域?qū)S谜Z言,常見的DSL有SQL,CSS,Shell等等,這些DSL語言有別于其他通用語言如:C++,Java,C#,DSL常在特殊的場(chǎng)景或領(lǐng)域中使用。如下圖:

領(lǐng)域?qū)S谜Z言通常是被領(lǐng)域?qū)<沂褂茫I(lǐng)域?qū)<乙话悴皇煜ねㄓ镁幊陶Z言,但是他們一般對(duì)業(yè)務(wù)非常了解,程序員一般對(duì)通用語言比較熟悉,但是在做行業(yè)軟件的時(shí)候?qū)I(yè)務(wù)部了解。這就需要協(xié)作的過程,一種方式是領(lǐng)域?qū)<彝ㄟ^文檔或者教授的方式把業(yè)務(wù)邏輯傳遞給程序員讓程序員翻譯成業(yè)務(wù)邏輯,而另一種方法,程序員為領(lǐng)域?qū)<叶ㄖ艱SL,并編寫解釋DSL的環(huán)境嵌入在業(yè)務(wù)系統(tǒng)中。這樣在某塊功能的實(shí)現(xiàn)上,程序員可以不用去關(guān)系具體實(shí)現(xiàn)和業(yè)務(wù),而領(lǐng)域?qū)<乙膊挥眠^多的理解程序背后的事情。
這種需求常常出現(xiàn)在OA系統(tǒng)或ERP系統(tǒng)的工作流中。比如說部門申請(qǐng)單的審批,如果是OA產(chǎn)品,那么這個(gè)審批流程將面對(duì)不同企業(yè)各式各樣的審批的條件,一個(gè)企業(yè)中不同的部門審批的條件也不一樣。如果全靠程序在后臺(tái)死寫,那么不可能窮盡用戶的想法,那么遇見這類對(duì)性能要求不高,又需要很強(qiáng)的靈活性的需求,通常會(huì)用到DSL,讓用戶輸入類似的業(yè)務(wù)邏輯:[部門]=”人事部” AND [金額] <= 1000 通過。
在舉個(gè)例子,在車聯(lián)網(wǎng)系統(tǒng)中,我們需要判斷一輛車是否在經(jīng)濟(jì)區(qū)中運(yùn)行,這個(gè)業(yè)務(wù)邏輯判斷的因素比較多,常常不是程序員或者產(chǎn)品經(jīng)理可以寫出來的,需要交給車輛專家來編寫。也許會(huì)寫成這樣: ( [天氣]!=”下雨” AND 50< [車速] <= 80 ) OR ( [道路] ==”高速” AND 60< [車速] <= 110 )。這同樣需要我們把他翻譯成我們系統(tǒng)實(shí)現(xiàn)的代碼。
如果上述的功能比較簡(jiǎn)單,DSL也不會(huì)很復(fù)雜,那么我們只需要簡(jiǎn)單的解釋器模式就可以解決。但是如果遇見的業(yè)務(wù)比較復(fù)雜且變化比較多,那么使用工具來解析DSL將是必然的選擇。
常見的語法分析器代碼生成工具有yacc,lexer,antlr,T4等等。yacc采用的是LALR(1),而antlr采用LL(k)的解析方法。對(duì)詞法分析,語言分析,AST或者編譯原理有了解的話,有助于這些工具的使用。
Antlr的安裝
Antlr可以生成C#,Java'和其他一些語言的解析工具代碼。我這里使用C#做例子,可以在NuGet(Java就是在Maven)中下載最新版本Antlr的DLL,Antlr,Antlr4.Runtime.并且下載Antlr在VisioStudio的項(xiàng)目模板(在VS中Tools->Extensions and Updates)。如果你使用的VS項(xiàng)目模板那么你可以在項(xiàng)目添加g4后綴的文件,antlr詞法和語言生成工具的文法文件都是使用g4為后綴。如下圖,對(duì)于小型項(xiàng)目我們一般使用Combined Grammar,詞法和語法都放在一起。
可以參考如下地址:https://github.com/tunnelvisionlabs/antlr4cs

在新建的g4中編輯語法,保存并編譯,就會(huì)在項(xiàng)目路徑下的obj\Debug目錄下生成語法解析和詞法解析的基類代碼。
?
Antlr的語法簡(jiǎn)介
最新的g4版本的語言可以參看官方文檔:,如果需要更加系統(tǒng)的學(xué)習(xí)的話,需要下載最新的antlr4的官方書籍antlr book 4,免費(fèi)的電子書可以百度搜索”The Definitive ANTLR 4 Reference”。
Antlr實(shí)例
以在車聯(lián)網(wǎng)系統(tǒng)中,判定車輛是否超速為例子。每個(gè)用戶或者說是企業(yè)都需要管理自己所有的車輛,在業(yè)務(wù)系統(tǒng)中,也會(huì)對(duì)車輛是否超速給出一個(gè)定義。這個(gè)定義也許不會(huì)想[車速]>80這么簡(jiǎn)單,有時(shí)候還會(huì)出現(xiàn)如下的定義:”(([車速]*10+3)>(200)) && ([企業(yè)ID] == \"123\") && ([時(shí)間]>1200 && [時(shí)間]<1700)”。從這個(gè)例子中可以看出,判定超速的規(guī)則支持四則混合運(yùn)算,還有一些特定的變量如車速,企業(yè)ID,時(shí)間。這中類型的定義是我們系統(tǒng)期望的讓每個(gè)用戶定義的方式。因?yàn)檫@種方式足夠靈活。用戶可以隨意配置。為了實(shí)現(xiàn)這種方式,解釋器模式是一個(gè)可行的方案,但是如果我們使用DSL,則更加靈活和可擴(kuò)展。我們定義的這種DSL,不單單執(zhí)行上述的四則混合運(yùn)算,還必須支持變量。這些變量都是我們?cè)谡媸堑南到y(tǒng)運(yùn)行中需要去獲取(數(shù)據(jù)庫(kù)或者緩存)的,也就是說,我們的解析程序首先要獲取這些變量的值,然后再進(jìn)行運(yùn)算,最后得出一個(gè)是否超速的結(jié)果。當(dāng)然隨著我們DSL的解析越來越完善,算法越來越先進(jìn),支持的變量也許會(huì)更多,也許還會(huì)有道路等級(jí),天氣因素等算法因子的出現(xiàn)。
要實(shí)現(xiàn)這個(gè)需求首先我們要定義文法,也就是g4文件的內(nèi)容。
注意的是,在一些文法后面用”#”號(hào)定義了一個(gè)名稱,就會(huì)在用于訪問生成的抽象語法樹AST的訪問器中生成該方法,用于訪問當(dāng)這個(gè)規(guī)約被滿足時(shí)候的那個(gè)樹節(jié)點(diǎn)。
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 : '[車速]' | '[天氣]' | '[時(shí)間]' | '[企業(yè)ID]' | '[用戶ID]';???????????? // 數(shù)字變量?
NUMBER : [1-9][0-9]*|[0]|([0-9]+[.][0-9]+) ;???? // 數(shù)字?
STRING : '"' ('\\"'|.)*? '"' ; // 字符串WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlinesADD : '+' ;?
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這本書)來實(shí)現(xiàn)語法樹的訪問。
?
public class ISLVisitor2 : ISLBaseVisitor{ 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() == "[時(shí)間]") { r.Text = "時(shí)間"; 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; } public override Result VisitLogicalFalse(ISLParser.LogicalFalseContext context) { Result r = new Result(); r.Value = false; return r; } public override Result VisitLogicalTrue(ISLParser.LogicalTrueContext context) { Result r = new Result(); r.Value = true; return r; } public override Result VisitLogicalAndOrNot(ISLParser.LogicalAndOrNotContext context) { Result r = new Result(); if (context.op.Type == ISLParser.LOGICAL_AND) { bool o1 = Convert.ToBoolean(Visit(context.equality_expression(0)).Value); bool o2 = Convert.ToBoolean(Visit(context.equality_expression(1)).Value); r.Value = o1 && o2; r.Text = Visit(context.equality_expression(0)).Text + " 并且 " + Visit(context.equality_expression(1)).Text; } return r; } public override Result VisitString(ISLParser.StringContext context) { Result r = new Result(); r.Value = context.GetText().Replace("\"", ""); r.Text = context.GetText().Replace("\"", ""); return r; } public override Result VisitLogicalOp(ISLParser.LogicalOpContext context) { Result r = new Result(); object result = null; if (context.op.Type == ISLParser.GREATE_THAN) { double left = Convert.ToDouble(Visit(context.expression(0)).Value); double right = Convert.ToDouble(Visit(context.expression(1)).Value); if (left > right) { result = 1; } else { result = 0; } r.Text = Visit(context.expression(0)).Text + " 大于 " + Visit(context.expression(1)).Text; } if (context.op.Type == ISLParser.LESS_THAN) { double left = Convert.ToDouble(Visit(context.expression(0)).Value); double right = Convert.ToDouble(Visit(context.expression(1)).Value); if (left < right) { result = 1; } else { result = 0; } r.Text = Visit(context.expression(0)).Text + " 小于 " + Visit(context.expression(1)).Text; } if (context.op.Type == ISLParser.EQUAL) { object left = Visit(context.expression(0)).Value; object right = Visit(context.expression(1)).Value; if (left is string) { result = left.ToString() == right.ToString(); } else { result = Visit(context.expression(0)).Value == Visit(context.expression(1)).Value; } r.Text = Visit(context.expression(0)).Text + " 等于 " + Visit(context.expression(1)).Text; } r.Value = result; return r; } public override Result VisitReturn(ISLParser.ReturnContext context) { Result o = Visit(context.equality_expression()); return o; } } public class Result { public string Text { get; set; } public object Value { get; set; } }
?
class Program { static void Main(string[] args) { TestISL(); Console.ReadLine(); } private static void TestISL() { string text = string.Empty; ParseISL(""); } private static void ParseISL(string input) { input = "return (([車速]*10+3)>(200)) && ([企業(yè)ID] == \"123\") && ([時(shí)間]>1200 && [時(shí)間]<1700);"; AntlrInputStream inputStream = new AntlrInputStream(input); ISLLexer lexer = new ISLLexer(inputStream); CommonTokenStream tokens = new CommonTokenStream(lexer); ISLParser parser = new ISLParser(tokens); IParseTree tree = parser.return_statement(); //ISLVisitor visitor = new ISLVisitor(); //object ret = visitor.Visit(tree); ISLVisitor2 visitor = new ISLVisitor2(); Result ret = visitor.Visit(tree); //Console.WriteLine(ret); Console.WriteLine(ret.Value); Console.WriteLine(ret.Text); Console.ReadLine(); } }
?

?
最后,點(diǎn)擊這里下載示例。
作者:nick hao
原文鏈接:https://www.cnblogs.com/haoxinyue/p/4225006.html
本文轉(zhuǎn)自博客園網(wǎng),版權(quán)歸原作者所有。
