「現代の」正規表現の認識力


83

実際の現代の正規表現は実際にどのクラスの言語を認識しますか?

後方参照(例(.*)_\1)を持つ無制限の長さのキャプチャグループがある場合は常に、正規表現が非正規言語と一致するようになりました。しかし、これだけでは、次のようなものに一致するのに十分ではありませんS ::= '(' S ')' | ε—親のペアを一致させる文脈自由言語。

再帰的な正規表現(これは私にとっては新しいものですが、PerlとPCREに存在すると確信しています)は、少なくともほとんどのCFLを認識しているように見えます。

誰かがこの分野で何か研究をしたり読んだりしましたか?これらの「現代の」正規表現の制限は何ですか?彼らは、LLまたはLR文法のCFGよりも厳密に多いか厳密に少ないかを認識していますか?または、正規表現では認識できるがCFGでは認識できない言語その逆の言語の両方が存在しますか?

関連する論文へのリンクをいただければ幸いです。


1
再帰的パターンによって解決可能な問題の計算可能性クラスへの正式な作業を私は知りません。上記の再帰的生成は、PCREまたはPerlの再帰的パターンとして非常に簡単にコード化できることを私は知っています。
tchrist 2011年

5
これはcstheory.stackexchange.comにより適してますか?
アルカイン2011年

3
@arcain、私はこれを「研究レベルの質問」とは考えていません。死ぬまで行われた可能性が高いからです...何も聞こえない場合は、そこに投稿してみるかもしれません...
tobyodavies

2
@ toby-確かに、しかしそれ理論的な質問であり、cstheoryのコミュニティははるかに専門的な聴衆です。音量も小さいので、答えやすい質問の洪水で質問が失われる可能性が低くなります。私はあなたの質問が答えを得るのを見たいだけです。
アルカイン2011年

2
古い投稿ですが、このリンクを何度か参照しました:nikic.github.io/2012/06/15/…–
Anders

回答:


106

パターン再帰

再帰パターンを使用すると、再帰下降マッチングの形式が得られます。

これはさまざまな問題に問題ありませんが、実際に再帰下降構文解析を実行する場合は、キャプチャグループをあちこちに挿入する必要があり、この方法で完全な解析構造を復元するのは厄介です。Perl用のDamianConwayRegexp :: Grammarsモジュールは、単純なパターンを同等のパターンに変換し、名前付きのキャプチャをすべて自動的に再帰的なデータ構造に変換して、解析された構造をはるかに簡単に取得できるようにします。この投稿の最後に、これら2つのアプローチを比較するサンプルがあります。

再帰の制限

問題は、再帰的パターンがどのような種類の文法に一致できるかということでした。まあ、彼らは確かに再帰下降型マッチャーです。頭に浮かぶ唯一のことは、再帰パターンは左再帰を処理できないということです。これにより、適用できる文法の種類に制約が課せられます。場合によっては、左再帰を排除するためにプロダクションを並べ替えることができます。

ところで、PCREとPerlは、再帰の表現方法が少し異なります。pcrepatternのマンページの「再帰パターン」と「Perlとの再帰の違い」のセクションを参照してください。例:Perl^(.|(.)(?1)\2)$は、^((.)(?1)\2|.)$代わりにPCREが必要とする場所を処理できます。

再帰デモ

再帰的パターンの必要性は驚くほど頻繁に発生します。よく見られる例の1つは、バランスの取れた括弧、引用符、さらにはHTML / XMLタグなど、ネストできるものと一致させる必要がある場合です。バランスの取れた親の試合は次のとおりです。

\((?:[^()]*+|(?0))*\)

コンパクトな性質のため、読むのが難しいと思います。これは/x、空白を重要でなくなるモードで簡単に修正できます。

\( (?: [^()] *+ | (?0) )* \)

繰り返しになりますが、再帰に親を使用しているため、より明確な例は、ネストされた一重引用符に一致することです。

‘ (?: [^‘’] *+ | (?0) )* ’

一致させたいと思うかもしれない別の再帰的に定義されたものは回文です。この単純なパターンはPerlで機能します。

^((.)(?1)\2|.?)$

これは、次のようなものを使用してほとんどのシステムでテストできます。

$ perl -nle 'print if /^((.)(?1)\2|.?)$/i' /usr/share/dict/words

PCREの再帰の実装には、より複雑なものが必要であることに注意してください

^(?:((.)(?1)\2|)|((.)(?3)\4|.))

これは、PCRE再帰の動作に制限があるためです。

適切な構文解析

私には、上記の例では、主におもちゃの試合、すべてではないですそれは本当に、面白いです。面白くなるのは、解析しようとしている実際の文法があるときです。たとえば、RFC5322はメールアドレスをかなり複雑に定義しています。これに一致する「文法」パターンは次のとおりです。

$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) | (?&quoted_string))
     (?<domain>          (?&dot_atom) | (?&domain_literal))
     (?<domain_literal>  (?&CFWS)? \[ (?: (?&FWS)? (?&dcontent))* (?&FWS)?
                                   \] (?&CFWS)?)
     (?<dcontent>        (?&dtext) | (?&quoted_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) | (?&quoted_pair))
     (?<quoted_string>   (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))*
                          (?&FWS)? (?&DQUOTE) (?&CFWS)?)

     (?<word>            (?&atom) | (?&quoted_string))
     (?<phrase>          (?&word)+)

     # Folding white space
     (?<FWS>             (?: (?&WSP)* (?&CRLF))? (?&WSP)+)
     (?<ctext>           (?&NO_WS_CTL) | [\x21-\x27\x2a-\x5b\x5d-\x7e])
     (?<ccontent>        (?&ctext) | (?&quoted_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に似ています。問題は、それが単なる一致であり、キャプチャではないことです。そして、どのプロダクションがどの部分に一致したかがわからないため、全体をキャプチャーパレンで囲みたくはありません。前述の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
    }
}

ご覧のとおり、パターンで非常にわずかに異なる表記を使用することで、%/すべてがきちんとラベル付けされた、解析ツリー全体を変数に格納するものが得られます。=~演算子からわかるように、変換の結果はまだパターンです。それはちょっと魔法です。


2
左再帰の制限は間違いなく知っておく価値がありますが、正しく覚えていれば、厳密には「認識力」には影響しません。左再帰の文法には、同じものに一致する右再帰の文法があるからです。言語-それはもっと面倒かもしれません。
hobbs 2011年

7
@tobyodavies:PCREの制限についてさらに説明できたはずです。それらはグループのアトミック性と関係があります。PCREではまだ完了していないグループに対して再帰を呼び出すことはできませんが、Perlでは呼び出すことができます。文法的なRFC5322パターンは、PCREでも同様に機能するはずです。((DEFINE)…)アイデア全体は非常に強力で便利であり、すべてのトップダウンプログラミングと同様に、宣言(およびその順序)を実行から分離できます。他のどの言語がグループ再帰を持っているか思い出せません。それはC♯やその同類のようなエキゾチックなものかもしれません。
tchrist 2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.