Sức mạnh công nhận của regexes "hiện đại"


83

Các regex hiện đại thực sự nhận ra lớp ngôn ngữ nào?

Bất cứ khi nào có một nhóm thu thập độ dài không giới hạn với tham chiếu ngược (ví dụ (.*)_\1), regex hiện đang khớp với một ngôn ngữ không thông thường. Nhưng điều này tự nó không đủ để so khớp với một thứ gì đó như S ::= '(' S ')' | ε- ngôn ngữ không có ngữ cảnh của các cặp parens phù hợp.

Các regex đệ quy (mới đối với tôi, nhưng tôi đảm bảo tồn tại trong Perl và PCRE) dường như nhận ra ít nhất hầu hết các CFL.

Có ai đã thực hiện hoặc đọc bất kỳ nghiên cứu nào trong lĩnh vực này? Những hạn chế của những regex "hiện đại" này là gì? Họ nhận ra đúng hơn hay ít hơn CFG, của ngữ pháp LL hoặc LR? Hoặc có tồn tại cả hai ngôn ngữ có thể được nhận dạng bởi regex nhưng không phải là CFG ngược lại?

Liên kết đến các giấy tờ liên quan sẽ được đánh giá cao.


1
Tôi không biết bất kỳ công việc chính thức nào về lớp tính toán của các bài toán có thể giải được bằng các mẫu đệ quy. Tôi biết rằng sản xuất đệ quy của bạn ở trên khá dễ dàng, đủ được mã hóa như một mẫu đệ quy trong PCRE hoặc Perl.
tchrist vào

5
Điều này có phù hợp hơn với cstheory.stackexchange.com không?
arcain

3
@arcain, tôi không thực sự coi đây là "câu hỏi cấp độ nghiên cứu", vì nó có khả năng đã được thực hiện đến chết ... Tôi có thể thử đăng nó lên đó nếu tôi không nghe thấy gì ...
tobyodavies

2
@toby - chắc chắn rồi, nhưng đó một câu hỏi lý thuyết và cộng đồng tại cstheory là đối tượng chuyên biệt hơn nhiều. Âm lượng cũng thấp hơn, do đó, ít có khả năng câu hỏi của bạn bị lạc vào lũ những câu dễ trả lời hơn. Tôi chỉ muốn xem câu hỏi của bạn có được câu trả lời.
arcain

2
Bài cũ, nhưng tôi đã tham khảo liên kết này nhiều lần: nikic.github.io/2012/06/15/…
Anders,

Câu trả lời:


106

Đệ quy mẫu

Với các mẫu đệ quy, bạn có một dạng kết hợp đệ quy xuống dưới .

Điều này là tốt cho nhiều vấn đề khác nhau, nhưng một khi bạn muốn thực sự thực hiện phân tích cú pháp gốc đệ quy , bạn cần phải chèn các nhóm nắm bắt ở đây và ở đó và thật khó để khôi phục cấu trúc phân tích cú pháp đầy đủ theo cách này. Mô-đun Regexp :: Grammars của Damian Conway cho Perl biến đổi mẫu đơn giản thành một mẫu tương đương tự động thực hiện tất cả những gì được đặt tên là chụp thành cấu trúc dữ liệu đệ quy, giúp truy xuất cấu trúc đã phân tích cú pháp dễ dàng hơn nhiều. Tôi có một mẫu so sánh hai cách tiếp cận này ở cuối bài đăng này.

Hạn chế đối với đệ quy

Câu hỏi đặt ra là loại ngữ pháp nào mà các mẫu đệ quy có thể phù hợp. Chà, chúng chắc chắn là những trình so khớp kiểu gốc đệ quy . Điều duy nhất xuất hiện trong tâm trí là các mẫu đệ quy không thể xử lý đệ quy trái . Điều này đặt ra một hạn chế đối với các loại ngữ pháp mà bạn có thể áp dụng chúng. Đôi khi bạn có thể sắp xếp lại thứ tự sản xuất của mình để loại bỏ đệ quy trái.

BTW, PCRE và Perl hơi khác nhau về cách bạn được phép tạo cụm từ đệ quy. Xem các phần về “ MẪU THU NHẬN ” và “Chênh lệch đệ quy so với Perl” trong trang pcrepattern . ví dụ: Perl có thể xử lý ^(.|(.)(?1)\2)$nơi PCRE yêu cầu ^((.)(?1)\2|.)$thay thế.

Bản trình diễn đệ quy

Nhu cầu về các mẫu đệ quy phát sinh thường xuyên một cách đáng ngạc nhiên. Một ví dụ được nhiều người truy cập là khi bạn cần đối sánh thứ gì đó có thể lồng vào nhau, chẳng hạn như dấu ngoặc đơn cân đối, dấu ngoặc kép hoặc thậm chí là thẻ HTML / XML. Đây là trận đấu cho những người có rào chắn:

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

Tôi thấy nó khó đọc hơn vì tính chất nhỏ gọn của nó. Điều này có thể dễ dàng sửa chữa với /xchế độ làm cho khoảng trắng không còn đáng kể:

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

Sau đó, một lần nữa, vì chúng tôi đang sử dụng parens cho đệ quy của mình, một ví dụ rõ ràng hơn sẽ khớp với các dấu nháy đơn lồng nhau:

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

Một thứ khác được định nghĩa đệ quy mà bạn có thể muốn so khớp sẽ là một palindrome. Mô hình đơn giản này hoạt động trong Perl:

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

mà bạn có thể kiểm tra trên hầu hết các hệ thống bằng cách sử dụng một cái gì đó như sau:

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

Lưu ý rằng việc thực hiện đệ quy của PCRE đòi hỏi sự phức tạp hơn

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

Điều này là do các hạn chế về cách hoạt động của đệ quy PCRE.

Phân tích cú pháp thích hợp

Đối với tôi, các ví dụ trên chủ yếu là trận đồ chơi, không phải tất cả thú vị, thực sự. Khi nó trở nên thú vị là khi bạn có một ngữ pháp thực sự mà bạn đang cố gắng phân tích cú pháp. Ví dụ, RFC 5322 định nghĩa một địa chỉ thư khá phức tạp. Đây là một mẫu "ngữ pháp" để khớp với nó:

$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;

Như bạn thấy, đó rất giống BNF. Vấn đề là nó chỉ là một trận đấu, không phải là một bắt giữ. Và bạn thực sự không muốn chỉ xoay quanh toàn bộ vấn đề bằng việc chụp các parens vì điều đó không cho bạn biết sản phẩm nào phù hợp với phần nào. Sử dụng mô-đun Regexp :: Grammars đã đề cập trước đó, chúng ta có thể.

#!/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
    }
}

Như bạn thấy, bằng cách sử dụng một ký hiệu hơi khác trong mẫu, bây giờ bạn nhận được thứ gì đó lưu trữ toàn bộ cây phân tích cú pháp cho bạn trong %/biến, với mọi thứ được gắn nhãn gọn gàng. Kết quả của phép biến đổi vẫn là một mẫu, như bạn có thể thấy bởi =~nhà điều hành. Nó chỉ là một chút kỳ diệu.


2
Giới hạn về đệ quy trái chắc chắn đáng biết, nhưng nếu tôi nhớ đúng, nó không ảnh hưởng đến việc "công nhận sức mạnh" một cách nghiêm ngặt, vì đối với bất kỳ ngữ pháp đệ quy trái nào, có một ngữ pháp đệ quy phải phù hợp với cùng một ngôn ngữ - nó chỉ có thể cồng kềnh hơn nhiều.
hobbs vào

7
@tobyodavies: Tôi có thể đã giải thích thêm về các hạn chế của PCRE; chúng liên quan đến tính nguyên tử của các nhóm: bạn không thể gọi đệ quy trên một nhóm chưa được hoàn thành trong PCRE nhưng bạn có thể trong Perl. Mẫu RFC 5322 ngữ pháp sẽ hoạt động tốt như nhau trong PCRE; toàn bộ ((DEFINE)…)ý tưởng cực kỳ mạnh mẽ và hữu ích, cho phép tách phần khai báo (và thứ tự của nó) khỏi việc thực thi, giống như tất cả các lập trình từ trên xuống. Tôi không thể nhớ ngôn ngữ nào khác có đệ quy nhóm; nó có thể là một cái gì đó kỳ lạ như C♯ hoặc ilk của nó.
tchrist,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.