Грамматика слишком жадная в Perl6

У меня проблемы с этой мини-грамматикой, которая пытается сопоставить конструкции заголовка, подобные уценке.

role Like-a-word {
    regex like-a-word { \S+ }
}

role Span does Like-a-word {
    regex span { <like-a-word>[\s+ <like-a-word>]* } 
}
grammar Grammar::Headers does Span {
    token TOP {^ <header> \v+ $}

    token hashes { '#'**1..6 }

    regex header {^^ <hashes> \h+ <span> [\h* $0]? $$}
}

Я бы хотел, чтобы он соответствовал ## Easier ## в качестве заголовка, но вместо этого он принимает ## как часть span:

TOP
|  header
|  |  hashes
|  |  * MATCH "##"
|  |  span
|  |  |  like-a-word
|  |  |  * MATCH "Easier"
|  |  |  like-a-word
|  |  |  * MATCH "##"
|  |  |  like-a-word
|  |  |  * FAIL
|  |  * MATCH "Easier ##"
|  * MATCH "## Easier ##"
* MATCH "## Easier ##\n"
「## Easier ##
」
 header => 「## Easier ##」
  hashes => 「##」
  span => 「Easier ##」
   like-a-word => 「Easier」
   like-a-word => 「##」

Проблема в том, что [\h* $0]? просто не работает, а span поглощает все доступные слова. Есть идеи?


person jjmerelo    schedule 05.01.2018    source источник
comment
Попробуйте ? после *.   -  person Rahul    schedule 05.01.2018
comment
Если вы имеете в виду определение Span, то да. Тот же результат.   -  person jjmerelo    schedule 05.01.2018
comment
Наверное я чего-то не понимаю, но что вы ожидаете от совпадения $0, когда нет позиционных захватов?   -  person Eugene Barsky    schedule 05.01.2018
comment
Это абсолютно верно. Остался от старых попыток.   -  person jjmerelo    schedule 05.01.2018
comment
К вашему сведению: вы должны всегда использовать token, если вы знаете, что вам нужен один из других вариантов (regex, rule и method).   -  person raiph    schedule 06.01.2018
comment
Используйте regex только в том случае, если вы уверены, что вам нужен возврат, потому что возврат часто приводит к тому, что синтаксический анализ выполняется буквально в миллионы раз медленнее, чем необходимо, или еще хуже. Если вы переключите все свои объявления regex на token, вы увидите, что ваш код будет продолжать анализироваться правильно (по крайней мере, для вашего пробного ввода ## Easier ##\n), но вполне вероятно, что он будет работать значительно быстрее на больших или сложных входных данных.   -  person raiph    schedule 06.01.2018
comment
Я думаю, что здесь нужно вернуться назад. Например, ## Easy Peasy ## завершится ошибкой. Однако я могу изменить самый низкий уровень, like-a-word, на токен.   -  person jjmerelo    schedule 08.01.2018


Ответы (3)


Во-первых, как указывали другие, <hashes> не захватывается в $0, а вместо этого захватывается в $<hashes>, поэтому вам нужно написать:

regex header {^^ <hashes> \h+ <span> [\h* $<hashes>]? $$}

Но это все еще не соответствует тому, как вы хотите, потому что часть [\h* $<hashes>]? счастливо соответствует нулю вхождений.

Правильное решение состоит в том, чтобы не позволять span соответствовать ## как слову:

role Like-a-word {
    regex like-a-word { <!before '#'> \S+ }
}

role Span does Like-a-word {
    regex span { <like-a-word>[\s+ <like-a-word>]* } 
}
grammar Grammar::Headers does Span {
    token TOP {^ <header> \v+ $}

    token hashes { '#'**1..6 }

    regex header {^^ <hashes> \h+ <span> [\h* $<hashes>]? $$}
}

say Grammar::Headers.subparse("## Easier ##\n", :rule<header>);

Если вы не хотите изменять like-a-word, вы также можете принудительно исключить из него последний # следующим образом:

role Like-a-word {
    regex like-a-word { \S+ }
}

role Span does Like-a-word {
    regex span { <like-a-word>[\s+ <like-a-word>]* } 
}
grammar Grammar::Headers does Span {
    token TOP {^ <header> \v+ $}

    token hashes { '#'**1..6 }

    regex header {^^ <hashes> \h+ <span> <!after '#'> [\h* $<hashes>]? $$}
}

say Grammar::Headers.subparse("## Easier ##\n", :rule<header>);
person moritz    schedule 06.01.2018
comment
Все в порядке, за исключением того, что я, возможно, захочу захватить # как слово. Уценка не может потерпеть неудачу, поэтому, если у меня есть что-то вроде ### not a header ##, я бы хотел, чтобы ## интерпретировалось like-a-word. Итак, я думаю, что с первым все в порядке, но оставить like-a-word таким, каким оно было. Большое спасибо! - person jjmerelo; 06.01.2018
comment
@jjmerelo Пища для размышлений/тестов: что должно произойти с ## two hashes on left, three on right ###; и ## two hashes on left, two plus two on right ## ##; и ## two hashes on left, two plus three on right ## ###; и ## two hashes on left, three plus two on right ### ##; и ## ## two plus two hashes on left, two plus two on right ## ##; и ## two hashes on left, two plus two on right ## ##; и ## two hashes on left, two in middle ## and some more text ##;? - person raiph; 06.01.2018
comment
@jjmerelo, вы всегда можете сначала попытаться выполнить более строгий анализ, а затем использовать ||, чтобы вернуться к чему-то, что всегда соответствует. - person moritz; 06.01.2018

Просто измените

  regex header {^^ <hashes> \h+ <span> [\h* $0]? $$}

to

  regex header {^^ (<hashes>) \h+ <span> [\h* $0]? $$}

Так что захват работает. Спасибо Евгению Барскому за звонок.

person jjmerelo    schedule 05.01.2018

Я немного поиграл с этим, потому что подумал, что есть две интересные вещи, которые вы могли бы сделать.

Во-первых, вы можете заставить hashes принять аргумент о том, сколько из них будет соответствовать. Таким образом, вы можете делать специальные вещи в зависимости от уровня, если хотите. Вы можете повторно использовать hashes в разных частях грамматики, где вам требуется разное, но точное количество решеток.

Затем сшиватель ~ позволяет вам указать, что что-то будет отображаться в середине двух вещей, чтобы вы могли поместить эти вещи-обертки рядом друг с другом. Например, чтобы соответствовать (Foo), вы можете написать '(' ~ ')' Foo. При этом похоже, что я придумал то же самое, что вы опубликовали:

use Grammar::Tracer;

role Like-a-word {
    regex like-a-word { \S+ }
}

role Span does Like-a-word {
    regex span { <like-a-word>[\s+ <like-a-word>]* }
}

grammar Grammar::Headers does Span {
    token TOP {^ <header> \v+ $}

    token hashes ( $n = 1 ) { '#' ** {$n} }

    regex header { [(<hashes(2)>) \h*] ~ [\h* $0] <span>  }
}

my $result = Grammar::Headers.parse( "## Easier ##\n" );

say $result;
person brian d foy    schedule 06.01.2018
comment
Спасибо за ответ. Интересно, как хеши будут отображаться в объекте Match. Кроме того, нужно ли будет таким же образом объявлять заголовок, используя $n в качестве параметра? - person jjmerelo; 06.01.2018
comment
Я думаю, вы могли бы объявить заголовок, чтобы он принимал параметр, а затем передал его чему-то под ним. Однако я, вероятно, склоняюсь к созданию header1, header2 и так далее. Это может упростить AST, когда вы захотите поиграть с ним. Но я не думал об этом так долго. :) - person brian d foy; 08.01.2018