首页 > 学院 > 开发设计 > 正文

使用Antlr实现简单的DSL

2019-11-17 02:37:58
字体:
来源:转载
供稿:网友

使用Antlr实现简单的DSL

为什么要使用DSL

DSL是领域专用语言,常见的DSL有SQL,CSS,Shell等等,这些DSL语言有别于其他通用语言如:C++,java,C#,DSL常在特殊的场景或领域中使用。如下图:

image

领域专用语言通常是被领域专家使用,领域专家一般不熟悉通用编程语言,但是他们一般对业务非常了解,程序员一般对通用语言比较熟悉,但是在做行业软件的时候对业务部了解。这就需要协作的过程,一种方式是领域专家通过文档或者教授的方式把业务逻辑传递给程序员让程序员翻译成业务逻辑,而另一种方法,程序员为领域专家定制DSL,并编写解释DSL的环境嵌入在业务系统中。这样在某块功能的实现上,程序员可以不用去关系具体实现和业务,而领域专家也不用过多的理解程序背后的事情。

这种需求常常出现在OA系统或ERP系统的工作流中。比如说部门申请单的审批,如果是OA产品,那么这个审批流程将面对不同企业各式各样的审批的条件,一个企业中不同的部门审批的条件也不一样。如果全靠程序在后台死写,那么不可能穷尽用户的想法,那么遇见这类对性能要求不高,又需要很强的灵活性的需求,通常会用到DSL,让用户输入类似的业务逻辑:[部门]=”人事部” AND [金额] <= 1000 通过。

在举个例子,在车联网系统中,我们需要判断一辆车是否在经济区中运行,这个业务逻辑判断的因素比较多,常常不是程序员或者产品经理可以写出来的,需要交给车辆专家来编写。也许会写成这样: ( [天气]!=”下雨” AND 50< [车速] <= 80 ) OR ( [道路] ==”高速” AND 60< [车速] <= 110 )。这同样需要我们把他翻译成我们系统实现的代码。

如果上述的功能比较简单,DSL也不会很复杂,那么我们只需要简单的解释器模式就可以解决。但是如果遇见的业务比较复杂且变化比较多,那么使用工具来解析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版本的语言可以参看官方文档:,如果需要更加系统的学习的话,需要下载最新的antlr4的官方书籍antlr book 4,免费的电子书可以百度搜索”The Definitive ANTLR 4 Reference”。

Antlr实例

以在车联网系统中,判定车辆是否超速为例子。每个用户或者说是企业都需要管理自己所有的车辆,在业务系统中,也会对车辆是否超速给出一个定义。这个定义也许不会想[车速]>80这么简单,有时候还会出现如下的定义:”(([车速]*10+3)>(200)) && ([企业ID] == /"123/") && ([时间]>1200 && [时间]<1700)”。从这个例子中可以看出,判定超速的规则支持四则混合运算,还有一些特定的变量如车速,企业ID,时间。这中类型的定义是我们系统期望的让每个用户定义的方式。因为这种方式足够灵活。用户可以随意配置。为了实现这种方式,解释器模式是一个可行的方案,但是如果我们使用DSL,则更加灵活和可扩展。我们定义的这种DSL,不单单执行上述的四则混合运算,还必须支持变量。这些变量都是我们在真是的系统运行中需要去获取(数据库或者缓存)的,也就是说,我们的解析程序首先要获取这些变量的值,然后再进行运算,最后得出一个是否超速的结果。当然随着我们DSL的解析越来越完善,算法越来越先进,支持的变量也许会更多,也许还会有道路等级,天气因素等算法因子的出现。

要实现这个需求首先我们要定义文法,也就是g4文件的内容。

注意的是,在一些文法后面用”#”号定义了一个名称,就会在用于访问生成的抽象语法树AST的访问器中生成该方法,用于访问当这个规约被满足时候的那个树节点。

grammar ISL;

@header { using System; }

@members {

}

/* * Parser Rules */ /* * 表达式 */

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 : '[车速]' | '[天气]' | '[时间]' | '[企业ID]' | '[用户ID]'; // 数字变量 NUMBER : [1-9][0-9]*|[0]|([0-9]+[.][0-9]+) ; // 数字 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这本书)来实现语法树的访问。

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() == "[企业ID]") { r.Text = "企业ID"; r.Value = TestData.EntId; } else if (context.GetText() == "[用户ID]") { r.Text = "用户ID"; r.Value = TestData.AccountId; }

return r; }

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表