在blog.joycode.com里面,Ninputer就问我这个是干什么的。实际上很多人都问过我这个问题,甚至dudu也问过,甚至还有人因此对这个东西有误会。这个怪我,没有说清楚。今天我就来给大家说明白这个事情。
这个NfaGen2是用来产生复杂正则表达式的,比如如果我在NfaGen2里面这么写:
ws:\s
#_SemiColon:;
#_IdHead:a-zA-Z_
#_IdTail:0-9<IdHead>
_Id:<IdHead><IdTail:any>
Type:<Id>
Name:<Id>
SimpleDefine:<Type><ws:many><Name><SemiColon>
那么SimpleDefine就会生成:
(?<SimpleDefine>(?<Type>[a-zA-Z_][0-9a-zA-Z]*)\s+(?<Name>[a-zA-Z_][0-9a-zA-Z]*))
你觉得是上面那种写法(NfaGen2)比较清晰呢,还是下面这种写法(Regex)清晰?我觉得如果我看到
SimpleDefine:<Type><ws:many><Name><SemiColon>
我就知道SimpleDefine匹配的是一个类似
类型 名称;
这样的字符串,至于类型(Type)是什么东西,我再找Type的定义好了。现在给出的这个例子已经足够简单了,如果我们需要产生比较复杂的正则表达式的时候,区别就更大了。
如果让你直接看或者写标准的正则表达式,估计光是括号对齐就已经让你头痛不已,甚至有时候你需要搞清楚某一个“捕获组”到底捕获了一些什么,这个时候会非常头痛。如果这个表达式本身需要匹配各种括号,那就更加麻烦了,比如我要匹配类似(a+(a+a)+(a))这样的表达式,你觉得用Regex要怎么写呢?
(?>(?<=\(|[+]|\A)(?<left>\()(?=[a-zA-Z_]|\()|(?<=[0-9a-zA-Z_]|\))(?<right-left>\))(?=[+]|\)|\z)|(?<=\(|[+]|\A)(?<var>(?<id>[a-zA-Z_](?:[0-9a-zA-Z_])*))(?=\)|[+]|\z)|(?<=\)|[0-9a-zA-Z_])(?<operator>[+])(?=\(|[a-zA-Z_]))+
如果我不告诉你这个正则表达式就是用来匹配这样的字符串的,你自己能够看得懂这个表达式的作用吗?如果你声称能够看得懂,我再来问你,你说说看捕获组left捕获的内容是什么?var组呢?还有它能够接受那些运算符呢?上述这个式子你如果自己来写的话,要花多少时间才能够写正确呢?如果我现在给你这么一个正则表达式,我要求你修改成能够接受加减乘除这四个运算符,你要花多少时间来修改这样一个表达式呢?
大家再来看看,下面这些就是我用于产生上面那个正则表达式的NfaGen1的源代码(NfaGen2的格式跟这个差不多):
_root:(?><_leftPart>|<_rightPart>|<_varPart>|<_operatorPart>)+
_leftPart:<_left_start><left><_left_end>
_rightPart:<_right_start><right-left><_right_end>
_varPart:<_var_start><var><_var_end>
_operatorPart:<_operator_start><operator><_operator_end>
_left_start:(?<=<_left>|<_operator>|\A)
left:<_left>
_left_end:(?=<_id_head>|<_left>)
_right_start:(?<=<_id_tail>|<_right>)
right-left:<_right>
_right_end:(?=<_operator>|<_right>|\z)
_var_start:(?<=<_left>|<_operator>|\A)
var:<id>
_var_end:(?=<_right>|<_operator>|\z)
_operator_start:(?<=<_right>|<_id_tail>)
operator:<_operator>
_operator_end:(?=<_left>|<_id_head>)
id:<_id_head><_id_tail>*
_id_head:[a-zA-Z_]
_id_tail:[0-9a-zA-Z_]
_left:\(
_right:\)
_operator:[+]
虽然长了一点,不过看完第一行已经知道大概是什么样的了,而要接受加减乘除四个运算符,只需要修改最下面那一个_operator,变成[-+*/]就可以了。如果你直接修改正则表达式,你可能就会漏掉了检测左括号前面的合法字符的那部分,以及其它的你没有注意到的地方,结果没有办法完全正确的匹配,甚至你不知道问题出在什么地方。
用上述方法定义的时候还有一个好处,就是你可以进行局部的匹配测试,看看局部的正则表达式是否书写正确。例如你可以测试_leftPart,测试的内容包括:
(
(a
a(a
a(+
+(a
+(+
+()
((a
)(a
看看对于左括号的匹配是否正确。如果你直接从标准的正则表达式上面进行类似的提取,是非常困难的一件事情,你必须数一下有多少个"("以及")",中间还得提防"\("和"\)"这样的被转义了的字符。
上面这个表达式还是很简单的,如果你看看NfaGen2用于进行语法加亮的表达式,你会剧烈的头痛得,像那种几K级别的正则表达式,要对某一个局部提取,进行上面那种局部测试,简直就是异想天开!
好了,说到这里不知道大家是否明白了NfaGen2的作用了呢?如果还是不明白,那表明我表达得不够清晰,或者漏了什么内容,我会再找时间来给大家解释的。谢谢大家的支持。