[解決済み] モダンな」正規表現が持つ認識力
質問
現代の正規表現はどのような種類の言語を実際に認識するのでしょうか?
後方参照を持つ、長さに制限のないキャプチャグループがある場合(例えば
(.*)_\1
など) がある場合、正規表現は非正規言語にマッチすることになります。しかし、これだけでは、次のようなものにマッチするのに十分ではありません。
S ::= '(' S ')' | ε
- のような、ペアのマッチングを行う文脈自由言語にはマッチしません。
再帰的正規表現(これは私にとって新しいものですが、PerlとPCREに存在することは確実です)は、少なくともほとんどのCFLを認識するようです。
誰かこの領域で研究を行ったか、読んだことがありますか?これらの "modern" 正規表現の限界は何でしょうか。LL または LR 文法の CFG よりも厳密に多くまたは少なく認識するのでしょうか?それとも、正規表現では認識できるがCFGでは認識できない言語が両方存在するのでしょうか? と の両方が存在するのでしょうか?
関連する論文へのリンクがあれば、とてもありがたいです。
どのように解決するのですか?
パターンの再帰
再帰的なパターンでは、再帰的な降下の形式をとります。 マッチング .
これは様々な問題に対して有効ですが、実際に再帰的降下を行おうとすると 構文解析 を行う場合は、キャプチャ グループをあちこちに挿入する必要があり、この方法で完全な解析構造を復元するのは厄介です。 Damian Conway の Regexp::Grammars モジュールは、単純なパターンを等価なものに変換し、再帰的なデータ構造への名前付き捕捉をすべて自動的に行うので、解析された構造をはるかに容易に取り出すことができます。 この投稿の最後に、これら 2 つのアプローチを比較したサンプルがあります。
再帰の制限
再帰的なパターンがマッチする文法とはどのようなものか、という質問でした。まあ、確かに彼らは 再帰的降下 型のマッチャーである。ただ、思い当たるのは 再帰的パターンが扱えない 左再帰 . これは、適用できる文法の種類に制約を与えます。時には、左再帰を排除するためにプロダクションを並べ替えることができます。
ところで、PCREとPerlは再帰をどのように表現することが許されるかについて若干の違いがあります。の「RECURSIVE PATTERNS」と「Perl との再帰性の違い」のセクションを参照してください。
pcrepattern
のセクションを参照のこと。Perl は
^(.|(.)(?1)\2)$
が必要なところ、PCRE は
^((.)(?1)\2|.)$
が必要です。
再帰のデモ
再帰的なパターンの必要性は驚くほど頻繁に発生します。 よく見られる例としては、入れ子にできるもの、たとえば、バランスのとれた括弧、引用符、あるいは 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の再帰の動作に制約があるためです。
適切な構文解析
私にとって、上記の例はほとんどトイマッチであり、すべての その は面白いとは言えません。これが面白くなるのは、実際に解析しようとしている文法があるときです。たとえば、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的です。 問題は、これは単なるマッチであり、キャプチャではないことです。そして、全体をキャプチャ用の括弧で囲むのは本当に困ります。なぜなら、それではどのプロダクションがどの部分にマッチしたのかがわからないからです。 以前紹介した 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
}
}
ご覧のように、パターン内で非常に微妙に異なる記法を使うことで、解析ツリー全体を
%/
変数に格納され、すべてがきちんとラベル付けされています。変換の結果はやはりパターンです。
=~
演算子で見ることができます。ただ、ちょっと不思議な感じです。
関連
-
[解決済み】Vimで正規表現に置換すると、`E488: Trailing characters`が発生します。
-
[解決済み] Regex:最初に出現する文字までのマッチング
-
[解決済み] RegEx: 引用符で囲まれた値を取得する
-
[解決済み] この文字にマッチしない」という意味の正規表現演算子はどれ?
-
[解決済み] 正規表現 - Gmailアドレスの検証
-
[解決済み] JavaScriptの正規表現でマッチしたグループにアクセスするにはどうすればよいですか?
-
[解決済み] 正規表現における「lazy」「greedy」の意味とは?
-
[解決済み] 最近のPerlはなぜデフォルトでUTF-8を避けるのですか?
-
[解決済み】Pythonの非欲求型正規表現
-
[解決済み] a^n b^n に一致させるには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】Vimで正規表現に置換すると、`E488: Trailing characters`が発生します。
-
[解決済み] 正規表現でのコロン記号の使用
-
[解決済み] 正規表現で複数の単語を任意の順序で並べる [重複]。
-
[解決済み] 最初のマッチで停止する正規表現
-
[解決済み] PostgreSQL での isnumeric()
-
[解決済み] R 文字列から最初の文字を削除する
-
[解決済み] (grep) 非 ASCII 文字にマッチする正規表現ですか?
-
[解決済み] Regex - ハイフンはエスケープされるべきか?重複
-
[解決済み] 正規表現[^ΘdΘs]と[ΘdΘs]の違いは何ですか?
-
[解決済み] Githubの「ブランチ名パターン」の否定