使用Antlr实现简单的DSL
为什么要使用DSL
DSL是领域专用语言,常见的DSL有SQL,CSS,Shell等等,这些DSL语言有别于其他通用语言如:C++,Java,C#,DSL常在特殊的场景或领域中使用。如下图:
领域专用语言通常是被领域专家使用,领域专家一般不熟悉通用编程语言,但是他们一般对业务非常了解,程序员一般对通用语言比较熟悉,但是在做行业软件的时候对业务部了解。这就需要协作的过程,一种方式是领域专家通过文档或者教授的方式把业务逻辑传递给程序员让程序员翻译成业务逻辑,而另一种方法,程序员为领域专家定制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
在新建的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;
}
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)) && ([企业ID] == \"123\") && ([时间]>1200 && [时间]<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();}}
最后,点击这里下载示例。
使用Antlr实现简单的DSL相关推荐
- 使用Antlr实现简单的DSL - nick hao - 博客园
使用Antlr实现简单的DSL - nick hao - 博客园
- 使用ANTLR和Go实现DSL入门
一. 引子 设计与实现一门像Go这样的通用编程语言的确很难!那是世界上少数程序员从事的事业,但是实现一门领域特定语言(Domain Specific Language, DSL)[1]似乎是可行的. ...
- es中的dsl练习题-----简单的dsl语句
slirp4netns >= 0.4 fuse-overlayfs >= 0.7 这是在本地安装elasticsearch数据然后通过kibana来操作数据的dsl来实现的 其中的查询ap ...
- antlr java_使用ANTLR和Java创建外部DSL
antlr java 在我以前的文章中,有一段时间我写了关于使用Java的内部DSL的文章. 在Martin Fowler撰写的< 领域特定语言 >一书中,他讨论了另一种称为外部DSL的D ...
- 使用ANTLR和Java创建外部DSL
在以前的一段时间里,我曾写过有关使用Java的内部DSL的文章. 在Martin Fowler撰写的< 领域特定语言 >一书中,他讨论了另一种称为外部DSL的DSL,其中DSL是用另一种语 ...
- 为什么awt_为AWT的机器人创建DSL
为什么awt Java SDK附带了java.awt.Robot类,该类允许键盘和鼠标输入的自动化以及屏幕捕获的创建. 当您要编写一个模拟用户输入的小型测试应用程序时,或者只想自动化一些重复文本的输入 ...
- 为AWT的机器人创建DSL
Java SDK附带了java.awt.Robot类,该类允许键盘和鼠标输入的自动化以及屏幕捕获的创建. 如果您想编写一个模拟用户输入的小型测试应用程序,或者只想自动化一些重复文本的输入,则此功能非常 ...
- 【转】开发者需要了解的领域特定语言(DSL)
转自:开发者需要了解的领域特定语言(DSL) - 知乎 领域特定语言是在特定领域下用于特定上下文的语言.作为开发者,很有必要了解领域特定语言的含义,以及为什么要使用特定领域语言. 领域特定语言doma ...
- 开发者需要了解的领域特定语言(DSL)
领域特定语言是在特定领域下用于特定上下文的语言.作为开发者,很有必要了解领域特定语言的含义,以及为什么要使用特定领域语言. 领域特定语言 domain-specific language (DSL)是 ...
- 模仿Retrofit封装一个使用更简单的网络请求框架
本文已授权微信公众号:郭霖 在微信公众号平台原创首发.会用Retrofit了?你也能自己动手写一个! 前言 想封装一套网络请求,不想直接上来就用别人写好的,或者说对项目可以更好的掌控,所以自己模仿着 ...
最新文章
- 使用bert或者xlnet做预测类的事情
- Azure手把手系列 2:微软中国云服务介绍
- 【弱化版】【P3371 【模板】单源最短路径(弱化版)】-C++
- python ipaddr库_用Python脚本查询纯真IP库QQWry.dat(Demon修改版)
- java冒泡排序经典代码6_经典排序算法之冒泡排序
- 啦啦外卖40.7 APP小程序三端 独立开源版本
- 三十正青春!苏宁818要用“好服务”抢占年轻用户心智
- laravel5.5通过yansongda/pay组件实现支付宝电脑网站支付
- 软件项目管理实验一补充
- 笔记本(无线网卡)配置虚拟机上网
- 利用seven 7zip的API来进行压缩和解压文件夹
- 华为交换机端口配置删除_华为交换机配置_华为交换机怎么清除端口下所有配置?...
- Java数组知识点概述
- 二进制,十进制,十六进制相互转换(小白友好)
- Ps 初学者教程「63」如何在 Ps 中制作 GIF 动画?
- 发字的楷书写法图片_永字八法”楷书笔画用笔技巧的方法(附书法图)
- Python黑科技:20行Python代码打造一个微信群聊助手
- for update
- Pandas学习(3)——Pandas基础
- CSS实现强制不换行、自动换行、强制换行的css代码