Сила визнання “сучасних” регулярних виразів


83

Який клас мов насправді визнають справжні сучасні регулярні вирази?

Всякий раз, коли існує група захоплення необмеженої довжини із зворотним посиланням (наприклад (.*)_\1), регулярний вираз тепер відповідає нерегулярній мові. Але цього, самого по собі, недостатньо, щоб зіставити щось на зразок S ::= '(' S ')' | ε- контекстно-вільної мови відповідних пар парен.

Рекурсивні регулярні вирази (які для мене є новими, але я впевнений, що існують у Perl та PCRE), схоже, розпізнають принаймні більшість CFL.

Хтось робив чи читав якісь дослідження в цій галузі? Які обмеження мають ці "сучасні" регулярні вирази? Чи визнають вони строго більше або строгіше, ніж CFG, граматики LL або LR? Або існують обидві мови, які можна розпізнати за допомогою регулярного виразу, але не CFG, і навпаки?

Будемо дуже вдячні за посилання на відповідні статті.


1
Я не знаю жодної формальної роботи щодо класу обчислюваності задач, розв'язуваних рекурсивними шаблонами. Я знаю, що ваше рекурсивне виробництво вище досить легко закодувати як рекурсивний шаблон у PCRE або Perl.
christ

5
Чи це більше підходить для cstheory.stackexchange.com ?
arcain

3
@arcain, я насправді не вважаю це "питанням дослідницького рівня", оскільки це, мабуть, було зроблено до смерті ... Я можу спробувати розмістити його там, якщо нічого не почую ...
tobyodavies

2
@toby - впевнене, але це теоретичне питання, і громада в cstheory є набагато більш спеціалізованої аудиторією. Там обсяг також менший, тому менше шансів, що ваше питання загубиться у потоці більш легких відповідей. Я просто хочу побачити відповідь на ваше запитання.
arcain

2
Стара публікація, але я посилався на це посилання кілька разів: nikic.github.io/2012/06/15/…
Андерс,

Відповіді:


106

Рекурсія візерунка

З рекурсивними візерунками ви маєте форму збігу рекурсивного спуску .

Це чудово для різних проблем, але як тільки ви хочете здійснити рекурсивний синтаксичний аналіз , вам потрібно вставити групи захоплення тут і там, і незручно відновлювати повну структуру синтаксичного аналізу таким чином. Модуль Regexp :: Grammars Даміана Конвея для Perl перетворює простий зразок в еквівалентний, який автоматично виконує все те, що іменовано, в рекурсивну структуру даних, що полегшує отримання аналізованої структури. У мене є зразок, що порівнює ці два підходи в кінці цієї публікації.

Обмеження на рекурсію

Питання полягало в тому, які типи граматик, яким можуть відповідати рекурсивні шаблони. Ну, це, звичайно, матчі типу рекурсивного спуску . Єдине, що спадає на думку, це те, що рекурсивні візерунки не можуть обробляти ліву рекурсію . Це обмежує види граматик, до яких ви можете їх застосувати. Іноді ви можете впорядкувати свої постановки, щоб усунути рекурсію ліворуч.

До речі, PCRE і Perl дещо відрізняються від того, як вам дозволено формулювати рекурсію. Див. Розділи «РЕКУРСИВНІ ШАБЛОНИ» та «Різниця рекурсії від Perl» на сторінці керівництва . наприклад: Perl може замість цього обробляти те, що ^(.|(.)(?1)\2)$вимагає PCRE ^((.)(?1)\2|.)$.

Демонстрація рекурсії

Потреба в рекурсивних моделях виникає напрочуд часто. Одним з добре відвідуваних прикладів є випадки, коли вам потрібно зіставити щось, що може вкластися, наприклад, збалансовані дужки, лапки або навіть теги HTML / XML. Ось відповідність для збалансованих панелей:

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

Я вважаю, що це складніше читати через його компактність. Це легко вилікувати за допомогою /xрежиму, щоб зробити пробіли більше не значущими:

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

Знову ж таки, оскільки ми використовуємо parens для нашої рекурсії, то більш наочним прикладом буде відповідність вкладених одинарних лапок:

‘ (?: [^‘’] *+ | (?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;

Як бачите, це дуже подобається БНФ. Проблема в тому, що це просто сірник, а не захоплення. І ви дійсно не хочете просто оточувати все це захопленням парен, тому що це не говорить вам, яке виробництво відповідало якій частині. Використовуючи згаданий раніше модуль 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

7
@tobyodavies: Я міг би пояснити подальші обмеження PCRE; вони пов’язані з атомністю груп: ви не можете викликати рекурсію для групи, яка ще не завершена в PCRE, але ви можете в Perl. Граматичний шаблон RFC 5322 повинен однаково добре працювати в PCRE; вся ((DEFINE)…)ідея надзвичайно потужна та корисна, дозволяючи відокремити декларацію (та її порядок) від виконання, як і всі програми зверху вниз. Я не можу згадати, в яких інших мовах є групова рекурсія; це може бути щось екзотичне, як C♯ або подібні.
christ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.