[regex] “현대”정규식의 인식 능력

실제 현대 정규식이 실제로 인식하는 언어 클래스는 무엇입니까?

역 참조 (예 :)가있는 무제한 길이 캡처 그룹이있을 때마다 (.*)_\1정규식은 이제 비정규 언어와 일치합니다. 그러나 이것은 그 자체 S ::= '(' S ')' | ε로는 일치하는 괄호 쌍의 문맥없는 언어 와 같은 것과 일치하기에 충분하지 않습니다 .

재귀 정규식 (나에게는 처음이지만 Perl과 PCRE에 존재한다고 확신합니다)은 최소한 대부분의 CFL을 인식하는 것으로 보입니다.

이 분야에 대한 연구를했거나 읽은 사람이 있습니까? 이러한 “현대”정규식의 한계는 무엇입니까? 그들은 LL 또는 LR 문법의 CFG보다 엄격하게 더 많거나 더 적게 인식합니까? 또는 모두 정규식에 의해 인식 할 수있는 언어 아니지만 CFG가 존재 하고 반대?

관련 논문에 대한 링크를 많이 주시면 감사하겠습니다.



답변

패턴 재귀

재귀 패턴을 사용하면 재귀 하강 일치 형식이 있습니다.

다양한 문제에 대해서는 괜찮지 만 실제로 재귀 하강 구문 분석 을 수행하려면 여기 저기 캡처 그룹을 삽입해야하며 이러한 방식으로 전체 구문 분석 구조를 복구하는 것은 어색합니다. Damian Conway의 Perl 용 Regexp :: Grammars 모듈은 간단한 패턴을 동일한 패턴으로 변환하여 명명 된 모든 캡처를 재귀 데이터 구조로 자동으로 수행하므로 구문 분석 된 구조를 훨씬 쉽게 검색 할 수 있습니다. 이 게시물 끝에이 두 가지 접근 방식을 비교하는 샘플이 있습니다.

재귀에 대한 제한

질문은 재귀 패턴이 일치 할 수있는 문법의 종류였습니다. 글쎄, 그들은 확실히 재귀 하강 유형 일치 자입니다. 유일하게 떠오르는 것은 재귀 패턴이 왼쪽 재귀를 처리 할 수 ​​없다는 것 입니다. 이것은 당신이 적용 할 수있는 문법의 종류에 제약을가합니다. 때로는 제작물을 재정렬하여 왼쪽 재귀를 제거 할 수 있습니다.

BTW, PCRE 및 Perl은 재귀를 표현하는 방법이 약간 다릅니다. pcrepattern 맨 페이지 에서 “RECURSIVE PATTERNS”및 “Perl과의 재귀 차이”섹션을 참조하십시오 . 예 : 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) | (?&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
    }
}

보시다시피 패턴에서 매우 약간 다른 표기법을 사용하면 이제 %/모든 것이 깔끔하게 레이블이 지정된 전체 구문 분석 트리를 변수에 저장하는 무언가를 얻을 수 있습니다 . =~연산자 가 볼 수 있듯이 변환의 결과는 여전히 패턴 입니다. 그것은 약간 마술 적입니다.


답변