使用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的更多相关文章
- [Node.js] DSL in action
原文地址:http://www.moye.me/2015/05/30/dsl-in-action/ 最近看了本有意思的书,受到了一些启发,在此记录一下: DSLs in action DSL是什么 ...
- Elasticsearch Query DSL查询入门
本篇为学习DSL时做的笔记,适合ES新手,大佬请略过~ Query DSL又叫查询表达式,是一种非常灵活又富有表现力的查询语言,采用JSON接口的方式实现丰富的查询,并使你的查询语句更灵活.更精确.更 ...
- GraphQL&DSL&API网关
车联网服务non-RESTful架构改造实践 导读 在构建面向企业项目.多端的内容聚合类在线服务API设计的过程中,由于其定制特点,采用常规的restful开发模式,通常会导致大量雷同API重复开 ...
- 一条Sql的Spark之旅
背景 SQL作为一门标准的.通用的.简单的DSL,在大数据分析中有着越来越重要的地位;Spark在批处理引擎领域当前也是处于绝对的地位,而Spark2.0中的SparkSQL也支持ANSI-SQL ...
- Cats(1)- 从Free开始,Free cats
cats是scala的一个新的函数式编程工具库,其设计原理基本继承了scalaz:大家都是haskell typeclass的scala版实现.当然,cats在scalaz的基础上从实现细节.库组织结 ...
- Scala 的确棒
我的确认为计算机学院应该开一门 Scala 的语言课程. 在这篇文章中,我会讲述为什么我会有这样的想法,在此之前,有几点我想要先声明一下: 本文无意对编程语言进行评比,我要讲述的主体是为什么你应该学习 ...
- Padrino 生成器指南
英文版出处:http://www.padrinorb.com/guides/generators Padrino提供了用于快速创建应用的生成器,其优势在于构建推荐的Padrino应用结构.自动生成罗列 ...
- Nancy总结(一)Nancy一个轻量的MVC框架
Nancy是一个基于.net 和Mono 构建的HTTP服务框架,是一个非常轻量级的web框架. 设计用于处理 DELETE, GET, HEAD, OPTIONS, POST, PUT 和 PATC ...
- 一些gem的简要翻译(欢迎提出问题共同讨论)
写这篇文章主要有两方面用途 1.希望给rails同行一定的帮助,翻译水平有限,贴出中英文,翻译有误的地方欢迎指正,非常感谢,转载请标明出处,谢谢. 2.加深作者对gem的理解,有需要更详细了解安装以及 ...
随机推荐
- 修改eclipse/MyEclipse中包的显示结构为树形
在右上边三角那里进去设置 选第一个是显示完整的包名,第二个显示的是树形结构,我们一般用第一种,如下图:
- 比RBAC更好的权限认证方式(Auth类认证)
Auth 类已经在ThinkPHP代码仓库中存在很久了,但是因为一直没有出过它的教程, 很少人知道它, 它其实比RBAC更方便 . RBAC是按节点进行认证的,如果要控制比节点更细的权限就有点困难了, ...
- 织梦dedecms调用子栏目的方法
织梦调用子栏目名称在栏目.文章页及首页的方法是有区别的.首页的调用方法和在栏目的调用基本是一样的,如下: {dede:channel typeid=''} <li><h3>&l ...
- MySQL查询重复出现次数最多的记录
MySQL查询的方法很多,下面为您介绍的MySQL查询语句用于实现查询重复出现次数最多的记录,对于学习MySQL查询有很好的帮助作用. 在有些应用里面,我们需要查询重复次数最多的一些记录,虽然这是一个 ...
- QueryHelp
//辅助查询 Author:高兵兵 public class QueryHelp { #region IList<T> ToList<T>(string cmdText,str ...
- SOA面向服务架构简述
在上篇中我们简单谈了下架构设计中服务层的简单理解,在这里我们将继续服务层的架构,在本节我们将重点在于分布式服务.在分布式系统中表现层和业务逻辑层 并不处于同一物理部署,所以我们必须存在分布式服务,以契 ...
- JAVA Socket 实现HTTP与HTTPS客户端发送POST与GET方式请求
JAVA Socket 实现HTTP与HTTPS客户端发送POST与GET方式请求 哇,一看标题怎么这么长啊,其实意思很简单,哥讨厌用HTTP Client做POST与GET提交 觉得那个毕竟是别人写 ...
- 工作者对象HttpWorkerRequest
在ASP.NET中,用于处理的请求,需要封装为HttpWorkerRequest类型的对象.该类为抽象类,定义在命名空间System.Web下. #region Assembly System.Web ...
- ubuntu系统自带的火狐(firefox)如何安装Adobe Flash
当你刚装完系统,发现打开某些网站时,提示你“需要安装flash”,然后你点击确定,过了一会,提示你安装失败. 我也是遇到这种情况.我第一个反应是,我先不用firefox,我安装chrome. 可是当你 ...
- 繁华模拟赛 Evensgn玩序列
#include<iostream> #include<cstdio> #include<string> #include<cstring> #incl ...