使用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();
        }
    }

最后,点击这里下载示例。

时间: 2024-11-05 21:37:13

使用Antlr实现简单的DSL的相关文章

[Node.js] DSL in action

原文地址:http://www.moye.me/2015/05/30/dsl-in-action/ 最近看了本有意思的书,受到了一些启发,在此记录一下: DSLs in action DSL是什么 即 domain-specific language ,是指和业务域模型相关的语言,粗糙的说法:行(业黑)话.关于什么是DSL,见仁见智,比如我认为SQL是一种DSL,有人却认为不是. 用途 对于“然并卵”一族来说,世界上大多数事情对他们来说都没什么用,DSL也不例外:于我而言,用DSL的一套理论能实

markdown中的锚点处理

markdown markdown是一个相对简单的DSL,定义了简单的标签来描述html文档格式. 比如: #一级标题 来生成html <h1>一级标题<h1> ##二级标题 来生成html <h2>二级标题<h2> [链接文字][link id] 与 [link id]:www.baidu.com 的组合来描述一个链接: [链接文字][url name] [url name]: http://www.baidu.com markdown的局限 使用了一段时

grape: A Ruby framework for rapid API development with great conventions.

1.Grape是运行在rack或与rails/sinatra配合使用的一种restful风格的ruby微框架,通过提供简单的DSL(领域特定语言)简化APIs开发.它内置支持mutiple formats(),subdomain/prefix restriction, versioning等通用约束(ruby约束高于配置).详见http://intridea.github.io/grape/. 2.安装Grape gem install grape 或者编辑Gemfile. gem "grape

Scala 的确棒

我的确认为计算机学院应该开一门 Scala 的语言课程. 在这篇文章中,我会讲述为什么我会有这样的想法,在此之前,有几点我想要先声明一下: 本文无意对编程语言进行评比,我要讲述的主体是为什么你应该学习 Scala.51CTO之前曾发布过一篇 Java 程序员为何要学习Scala的文章,可能也会对你有所帮助. 目前 Scala 有两个实现方式,一种是在 JVM(Java 虚拟机)上运行,另一种是在 CLR(Common Language Runtime 的缩写,即公共语言运行库)上运行.不过,JV

Cats(1)- 从Free开始,Free cats

cats是scala的一个新的函数式编程工具库,其设计原理基本继承了scalaz:大家都是haskell typeclass的scala版实现.当然,cats在scalaz的基础上从实现细节.库组织结构和调用方式上进行了一些优化,所以对用户来说:cats的基础数据类型.数据结构在功能上与scalaz是大致相同的,可能有一些语法上的变化.与scalaz著名抽象.复杂的语法表现形式相比,cats的语法可能更形象.简单直白.在scalaz的学习过程中,我们了解到所谓函数式编程就是monadic Pro

Elasticsearch java api 基本搜索部分详解

版权声明:本文非原创文章,转载出处:http://blog.csdn.net/molong1208/article/details/50512149 一.所使用版本的介绍 使用的是elasticsearch2.1.0版本,在此只是简单介绍搜索部分的api使用 二.简单的搜索 使用api的时候,基本上可以将DSL搜索的所有情况均写出来,在此给出一个最简单搜索的全部的过程以及代码,之后将对不同的搜索只是针对函数进行介绍 (1)DSL搜索 对于最简单的DSL搜索,对一个词进行搜索使用url直接进行通信

net 和Mono 构建的HTTP服务框架

Nancy是一个基于.net 和Mono 构建的HTTP服务框架,是一个非常轻量级的web框架. 设计用于处理 DELETE, GET, HEAD, OPTIONS, POST, PUT 和 PATCH 等请求方法,并提供简单优雅的 DSL 以返回响应.它提供了一个super-duper-happy-path的交互方式,无需定义各种复杂的路由规则,Nancy会根据你的请求路径自动匹配.Nancy和ASP.NET MVC不同,它更多的是借鉴了Ruby的一些特性. Nancy 的特点: 1. 建立了

[转]DSL-让你的 Ruby 代码更优秀

https://ruby-china.org/topics/38428 以下摘录 DSL和Gpl DSL : domain-specific language.比如HTML是用于组织网页的‘语言’, CSS专门调整页面样式的‘语言’. SQL是数据库操作的‘语句’. GPL: general-purpose language.通用目的语言.即不是为了特定领域设计的语言.Ruby,Python,C都是. 简单的DSL 我们遇到不少的Ruby开源库都会有其对应DSL,其中就包括Rspec,Rabl,

大数据生态之数据处理框架探索

数据处理框架 数据处理是一个非常宽泛的概念,数据处理框架在数据架构中,主要是用于数据移动和分析这两大功能当中.对于数据移动,有离线数据移动和实时数据移动,也可以叫做是批量数据移动和流式数据移动.而对于分析这一块,有离线数据分析和实时数据分析,也可以称作是批量数据分析和流式数据分析.离线和实时,批量和流式,针对这两种不同的形式,就出现了多种不同的数据处理框架.有批量的数据处理框架,有流式的数据处理框架,也有批流融合的框架. 批量数据处理框架 批量数据处理框架最经典的就是 mapreduce 了,这