历史的角度
Wikipedia文章详细介绍了正则表达式的起源(Kleene,1956年)。原来的语法比较简单,只有*
,+
,?
,|
和分组(...)
。它是简洁的(而且可读性强,两者不一定是对立的),因为形式语言倾向于用简洁的数学符号表示。
后来,语法和功能随着编辑器的发展而发展,并随着Perl的发展而增长,Perl试图通过设计使其简洁(“通用结构应该简短”)。这使语法复杂化了很多,但是请注意,人们现在已经习惯了正则表达式,并且擅长编写(如果不阅读)它们。它们有时是只写的事实表明,当它们太长时,它们通常不是正确的工具。
正则表达式在被滥用时往往不可读。
超越基于字符串的正则表达式
在谈到替代语法,让我们来看看一个已经存在(CL-ppcre,在Common Lisp的)。您的长正则表达式可以ppcre:parse-string
按如下方式进行解析:
(let ((*print-case* :downcase)
(*print-right-margin* 50))
(pprint
(ppcre:parse-string "^(?:([A-Za-z]+):)?(\\/{0,3})(0-9.\\-A-Za-z]+)(?::(\\d+))?(?:\\/([^?#]*))?(?:\\?([^#]*))?(?:#(.*))?$")))
...,结果为以下形式:
(:sequence :start-anchor
(:greedy-repetition 0 1
(:group
(:sequence
(:register
(:greedy-repetition 1 nil
(:char-class (:range #\A #\Z)
(:range #\a #\z))))
#\:)))
(:register (:greedy-repetition 0 3 #\/))
(:register
(:sequence "0-9" :everything "-A-Za-z"
(:greedy-repetition 1 nil #\])))
(:greedy-repetition 0 1
(:group
(:sequence #\:
(:register
(:greedy-repetition 1 nil :digit-class)))))
(:greedy-repetition 0 1
(:group
(:sequence #\/
(:register
(:greedy-repetition 0 nil
(:inverted-char-class #\? #\#))))))
(:greedy-repetition 0 1
(:group
(:sequence #\?
(:register
(:greedy-repetition 0 nil
(:inverted-char-class #\#))))))
(:greedy-repetition 0 1
(:group
(:sequence #\#
(:register
(:greedy-repetition 0 nil :everything)))))
:end-anchor)
该语法较为冗长,如果您在下面查看注释,不一定更具可读性。因此,不要以为语法不那么紧凑,所以事情会自动变得更清晰。
但是,如果您开始对正则表达式感到麻烦,请将它们转换为这种格式可能会帮助您解密和调试代码。与基于字符串的格式相比,这是一个优点,在基于字符串的格式中很难发现单个字符错误。
这种语法的主要优点是使用结构化格式而不是基于字符串的编码来处理正则表达式。这样,您就可以像程序中的任何其他数据结构一样组成和构建这样的表达式。使用上述语法时,通常是因为我想从较小的部分构建表达式(另请参见CodeGolf答案)。对于您的示例,我们可以写1:
`(:sequence
:start-anchor
,(protocol)
,(slashes)
,(domain)
,(top-level-domain) ... )
基于字符串的正则表达式也可以使用辅助函数中的字符串连接和/或内插法来组成。不过,也有与字符串操作趋向于限制混乱的代码(想想嵌套问题,不象反引号与$(...)
在bash;同时,转义字符可以给你头痛)。
还请注意,上述形式允许使用(:regex "string")
形式,以便您可以将简洁的符号与树混合。所有这些使IMHO拥有良好的可读性和可组合性;它间接解决了delnan表示的三个问题(即,不是使用正则表达式本身的语言)。
总结一下
对于大多数目的,简洁的符号实际上是可读的。在处理涉及回溯等扩展符号时会遇到困难,但是很少使用它们。不正当使用正则表达式可能会导致表达式无法读取。
正则表达式无需编码为字符串。如果您有一个库或工具可以帮助您构建和组成正则表达式,则可以避免许多与字符串操作有关的潜在错误。
另外,形式语法更具可读性,并且在命名和抽象子表达式方面表现更好。终端通常表示为简单的正则表达式。
1.您可能更喜欢在读取时构建表达式,因为正则表达式在应用程序中往往是常量。请参阅create-scanner
和load-time-value
:
'(:sequence :start-anchor #.(protocol) #.(slashes) ... )