真正的现代正则表达式真正识别哪种语言?
每当有一个带有反向引用的无限长度捕获组(例如(.*)_\1
)时,正则表达式现在匹配非常规语言。但是,这本身并不足以匹配S ::= '(' S ')' | ε
之类的东西 - 匹配对的parens的无上下文语言。
递归正则表达式(对我而言是新的,但我确信存在于Perl和PCRE中)似乎至少能识别出大多数CFL。
有没有人在这方面做过或读过任何研究?这些“现代”正则表达的限制是什么?对于LL或LR语法,他们是否严格认可或严格低于CFG?或者是否存在可以被正则表达式识别的两种语言而不是CFG 和相反的语言?
非常感谢与相关论文的链接。
答案 0 :(得分:102)
使用递归模式,您有一种递归下降匹配的形式。
这适用于各种问题,但是一旦你想要实际进行递归下降解析,你需要在这里和那里插入捕获组,并且恢复完整的解析结构很尴尬通过这种方式。用于Perl的Damian Conway的Regexp::Grammars模块将简单模式转换为等效模式,自动执行所有命名捕获到递归数据结构的模式,从而更容易检索解析后的结构。我有一个样本在这篇帖子的末尾比较这两种方法。
问题是递归模式可以匹配哪种语法。嗯,他们肯定是recursive descent类型的匹配器。我唯一想到的是递归模式无法处理left recursion。这会对可以应用它们的各种语法产生约束。有时您可以重新排序您的作品以消除左递归。
BTW,PCRE和Perl在如何允许短语递归方面略有不同。请参阅 pcrepattern 联机帮助页中有关“RECURSIVE PATTERNS”和“Perl的递归差异”部分。例如:Perl可以处理^(.|(.)(?1)\2)$
,其中PCRE需要^((.)(?1)\2|.)$
。
频繁出现对递归模式的需求。一个很好的例子是当你需要匹配可以嵌套的东西时,例如平衡的括号,引号,甚至是HTML / XML标签。这是balenced parens的匹配:
\((?:[^()]*+|(?0))*\)
由于其紧凑的特性,我发现阅读起来比较棘手。这可以通过/x
模式轻松实现,以使空白不再显着:
\( (?: [^()] *+ | (?0) )* \)
然后,再次,因为我们使用parens进行递归,一个更清晰的例子是匹配嵌套的单引号:
‘ (?: [^‘’] *+ | (?0) )* ’
您可能希望匹配的另一个递归定义的事物是回文。这个简单的模式适用于Perl:
^((.)(?1)\2|.?)$
您可以使用以下内容在大多数系统上进行测试:
$ perl -nle 'print if /^((.)(?1)\2|.?)$/i' /usr/share/dict/words
请注意,PCRE的递归实现需要更精细的
^(?:((.)(?1)\2|)|((.)(?3)\4|.))
这是因为对PCRE递归的工作方式有限制。
对我来说,上面的例子主要是玩具匹配,而不是所有 有趣的,真的。当它变得有趣时,你有一个真正的语法,你正试图解析。例如,RFC 5322相当精细地定义了邮件地址。这是一个匹配它的“语法”模式:
$rfc5322 = qr{
(?(DEFINE)
(?<address> (?&mailbox) | (?&group))
(?<mailbox> (?&name_addr) | (?&addr_spec))
(?<name_addr> (?&display_name)? (?&angle_addr))
(?<angle_addr> (?&CFWS)? < (?&addr_spec) > (?&CFWS)?)
(?<group> (?&display_name) : (?:(?&mailbox_list) | (?&CFWS))? ; (?&CFWS)?)
(?<display_name> (?&phrase))
(?<mailbox_list> (?&mailbox) (?: , (?&mailbox))*)
(?<addr_spec> (?&local_part) \@ (?&domain))
(?<local_part> (?&dot_atom) | (?"ed_string))
(?<domain> (?&dot_atom) | (?&domain_literal))
(?<domain_literal> (?&CFWS)? \[ (?: (?&FWS)? (?&dcontent))* (?&FWS)?
\] (?&CFWS)?)
(?<dcontent> (?&dtext) | (?"ed_pair))
(?<dtext> (?&NO_WS_CTL) | [\x21-\x5a\x5e-\x7e])
(?<atext> (?&ALPHA) | (?&DIGIT) | [!#\$%&'*+-/=?^_`{|}~])
(?<atom> (?&CFWS)? (?&atext)+ (?&CFWS)?)
(?<dot_atom> (?&CFWS)? (?&dot_atom_text) (?&CFWS)?)
(?<dot_atom_text> (?&atext)+ (?: \. (?&atext)+)*)
(?<text> [\x01-\x09\x0b\x0c\x0e-\x7f])
(?<quoted_pair> \\ (?&text))
(?<qtext> (?&NO_WS_CTL) | [\x21\x23-\x5b\x5d-\x7e])
(?<qcontent> (?&qtext) | (?"ed_pair))
(?<quoted_string> (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))*
(?&FWS)? (?&DQUOTE) (?&CFWS)?)
(?<word> (?&atom) | (?"ed_string))
(?<phrase> (?&word)+)
# Folding white space
(?<FWS> (?: (?&WSP)* (?&CRLF))? (?&WSP)+)
(?<ctext> (?&NO_WS_CTL) | [\x21-\x27\x2a-\x5b\x5d-\x7e])
(?<ccontent> (?&ctext) | (?"ed_pair) | (?&comment))
(?<comment> \( (?: (?&FWS)? (?&ccontent))* (?&FWS)? \) )
(?<CFWS> (?: (?&FWS)? (?&comment))*
(?: (?:(?&FWS)? (?&comment)) | (?&FWS)))
# No whitespace control
(?<NO_WS_CTL> [\x01-\x08\x0b\x0c\x0e-\x1f\x7f])
(?<ALPHA> [A-Za-z])
(?<DIGIT> [0-9])
(?<CRLF> \x0d \x0a)
(?<DQUOTE> ")
(?<WSP> [\x20\x09])
)
(?&address)
}x;
如你所见,那就像BNF一样。问题是它只是匹配而不是捕获。并且你真的不想仅通过捕获parens来包围整个事物,因为这并不能告诉你哪个产品与哪个部分相匹配。使用前面提到的Regexp :: Grammars模块,我们可以。
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
use Data::Dumper "Dumper";
my $rfc5322 = do {
use Regexp::Grammars; # ...the magic is lexically scoped
qr{
# Keep the big stick handy, just in case...
# <debug:on>
# Match this...
<address>
# As defined by these...
<token: address> <mailbox> | <group>
<token: mailbox> <name_addr> | <addr_spec>
<token: name_addr> <display_name>? <angle_addr>
<token: angle_addr> <CFWS>? \< <addr_spec> \> <CFWS>?
<token: group> <display_name> : (?:<mailbox_list> | <CFWS>)? ; <CFWS>?
<token: display_name> <phrase>
<token: mailbox_list> <[mailbox]> ** (,)
<token: addr_spec> <local_part> \@ <domain>
<token: local_part> <dot_atom> | <quoted_string>
<token: domain> <dot_atom> | <domain_literal>
<token: domain_literal> <CFWS>? \[ (?: <FWS>? <[dcontent]>)* <FWS>?
<token: dcontent> <dtext> | <quoted_pair>
<token: dtext> <.NO_WS_CTL> | [\x21-\x5a\x5e-\x7e]
<token: atext> <.ALPHA> | <.DIGIT> | [!#\$%&'*+-/=?^_`{|}~]
<token: atom> <.CFWS>? <.atext>+ <.CFWS>?
<token: dot_atom> <.CFWS>? <.dot_atom_text> <.CFWS>?
<token: dot_atom_text> <.atext>+ (?: \. <.atext>+)*
<token: text> [\x01-\x09\x0b\x0c\x0e-\x7f]
<token: quoted_pair> \\ <.text>
<token: qtext> <.NO_WS_CTL> | [\x21\x23-\x5b\x5d-\x7e]
<token: qcontent> <.qtext> | <.quoted_pair>
<token: quoted_string> <.CFWS>? <.DQUOTE> (?:<.FWS>? <.qcontent>)*
<.FWS>? <.DQUOTE> <.CFWS>?
<token: word> <.atom> | <.quoted_string>
<token: phrase> <.word>+
# Folding white space
<token: FWS> (?: <.WSP>* <.CRLF>)? <.WSP>+
<token: ctext> <.NO_WS_CTL> | [\x21-\x27\x2a-\x5b\x5d-\x7e]
<token: ccontent> <.ctext> | <.quoted_pair> | <.comment>
<token: comment> \( (?: <.FWS>? <.ccontent>)* <.FWS>? \)
<token: CFWS> (?: <.FWS>? <.comment>)*
(?: (?:<.FWS>? <.comment>) | <.FWS>)
# No whitespace control
<token: NO_WS_CTL> [\x01-\x08\x0b\x0c\x0e-\x1f\x7f]
<token: ALPHA> [A-Za-z]
<token: DIGIT> [0-9]
<token: CRLF> \x0d \x0a
<token: DQUOTE> "
<token: WSP> [\x20\x09]
}x;
};
while (my $input = <>) {
if ($input =~ $rfc5322) {
say Dumper \%/; # ...the parse tree of any successful match
# appears in this punctuation variable
}
}
正如您所看到的,通过在模式中使用略有不同的表示法,您现在可以在%/
变量中获得存储整个解析树的内容,并且所有内容都被整齐地标记。正如=~
运算符所示,转换的结果仍然是一种模式。这有点神奇。