几个月前就一直有博友关心DSL的问题,于是我想一想,我在gac.codeplex.com里面也创建了一些DSL,于是今天就来说一说这个事情。
创建DSL恐怕是很多人第一次设计一门语言的经历,很少有人一开始上来就设计通用语言的。我自己第一次做这种事情是在高中写这个傻逼ARPG的时候了。当时做了一个超简单的脚本语言,长的就跟汇编差不多,虽然每一个指令都写成了调用函数的形态。虽然这个游戏需要脚本在剧情里面控制一些人物的走动什么的,但是所幸并不复杂,于是还是完成了任务。一眨眼10年过去了,现在在写GacUI,为了开发的方便,我自己做了一些DSL,或者实现了别人的DSL,渐渐地也明白了一些设计DSL的手法。不过在讲这些东西之前,我们先来看一个令我们又爱(对所有人)又恨(反正我不会)的DSL——正则表达式!
一、正则表达式
正则表达式可读性之差我们人人都知道,而且正则表达式之难写好都值得O’reilly出一本两厘米厚的书了。根据我的经验,只要先学好编译原理,然后按照.net的规格自己撸一个自己的正则表达式,基本上这本书就不用看了。因为正则表达式之所以要用奇怪的方法去写,只是因为你手上的引擎是那么实现的,所以你需要顺着他去写而已,没什么特别的原因。而且我自己的正则表达式拥有DFA和NFA两套解析器,我的正则表达式引擎会通过检查你的正则表达式来检查是否可以用DFA,从而可以优先使用DFA来运行,省去了很多其实不是那么重要的麻烦(譬如说a**会傻逼什么的)。这个东西我自己用的特别开心,代码也放在gac.codeplex.com上面。
正则表达式作为一门DSL是当之无愧的——因为它用了一种紧凑的语法来让我们可以定义一个字符串的集合,并且取出里面的特征。大体上语法我还是很喜欢的,我唯一不喜欢的是正则表达式的括号的功能。括号作为一种指定优先级的方法,几乎是无法避免使用的。但是很多流行的正则表达式的括号竟然还带有捕获的功能,实在是令我大跌眼镜——因为大部分时候我是不需要捕获的,这个时候只会浪费时间和空间去做一些多余的事情而已。所以在我自己的正则表达式引擎里面,括号是不捕获的。如果要捕获,就得用特殊的语法,譬如说(<name>pattern)把pattern捕获到一个叫做name的组里面去。
那我们可以从正则表达式的语法里面学到什么DSL的设计原则呢?我认为,DSL的原则其实很简单,只有以下三个:
短的语法要分配给常用的功能
语法要么可读性特别好(从而比直接用C#写直接),要么很紧凑(从而比直接用C#写短很多)
API要容易定义(从而用C#调用非常方便,还可以确保DSL的目标是明确又简单的)
很多DSL其实都满足这个定义。SQL就属于API简单而且可读性好的那一部分(想想ADO.NET),而正则表达式就属于API简单而且语法紧凑的那一部分。为什么正则表达式可以设计的那么紧凑呢?现在让我们来一一揭开它神秘的面纱。
正则表达式的基本元素是很少的,只有连接、分支和循环,还有一些简单的语法糖。连接不需要字符,分支需要一个字符“|”,循环也只需要一个字符“+”或者“*”,还有代表任意字符的“.”,还有代表多次循环的{5,},还有代表字符集合的[a-zA-Z0-9_]。对于单个字符的集合来讲,我们甚至不需要[],直接写就好了。除此之外因为我们用了一些特殊字符所以还得有转义(escaping)的过程。那让我们数数我们定义了多少字符:“|+*[]-\{},.()”。用的也不多,对吧。
尽管看起来很乱,但是正则表达式本身也有一个严谨的语法结构。关于我的正则表达式的语法树定义可以看这里:https://gac.codeplex.com/SourceControl/latest#Common/Source/Regex/RegexExpression.h。在这里我们可以整理出一个语法:
DIGIT ::= [0-9] LITERAL ::= [^|+*\[\]\-\\{}\^,.()] ANY_CHAR ::= LITERAL | "^" | "|" | "+" | "*" | "[" | "]" | "-" | "\" | "{" | "}" | "," | "." | "(" | ")" CHAR ::= LITERAL ::= "\" ANY_CHAR CHARSET_COMPONENT ::= CHAR ::= CHAR "-" CHAR CHARSET ::= CHAR ::= "[" ["^"] { CHARSET_COMPONENT } "]" REGEX_0 ::= CHARSET ::= REGEX_0 "+" ::= REGEX_0 "*" ::= REGEX_0 "{" { DIGIT } ["," [ { DIGIT } ]] "}" ::= "(" REGEX_2 ")" REGEX_1 ::= REGEX_0 ::= REGEX_1 REGEX_0 REGEX_2 ::= REGEX_1 ::= REGEX_2 "|" REGEX_1 REGULAR_EXPRESSION ::= REGEX_2
这只是随手写出来的语法,尽管可能不是那么严谨,但是代表了正则表达式的所有结构。为什么我们要熟练掌握EBNF的阅读和编写?因为当我们用EBNF来看待我们的语言的时候,我们就不会被愈发的表面所困扰,我们会投过语法的外衣,看到语言本身的结构。脱别人衣服总是很爽的。
以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索正则表达式
, regex
, 字符
, 语法
, dsl
, 一个
, 正则表达式语法
, regular_expression
, 求个简单正则
, 正则表达式语法
我的正则
dsl 领域特定语言、dsl 领域特定语言 pdf、dsl领域专用语言基础、正则表达式 特定字符、正则表达式特定字符串,以便于您获取更多的相关知识。