Đệ 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 /x
chế độ 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ả mà 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) | (?"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)+)
(?<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_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ể.
use strict;
use warnings;
use 5.010;
use Data::Dumper "Dumper";
my $rfc5322 = do {
use Regexp::Grammars;
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>+
<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>)
<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.