Раку: эффект маркеров захвата теряется «выше»
Следующий сценарий Раку:
#!/usr/bin/env raku
use v6.d;
grammar MyGrammar
{
rule TOP { <keyword> '=' <value> }
token keyword { \w+ }
token value { <strvalue> | <numvalue> }
token strvalue { '"' <( <-["]>* )> '"' }
token numvalue { '-'? \d+ [ '.' \d* ]? }
}
say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');
имеет следующий вывод:
「foo = 42」
keyword => 「foo」
value => 「42」
numvalue => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「"Hello, World!"」
strvalue => 「Hello, World!」
Для второго пункта, заметим , что strvalue
содержит значение строки без кавычек, как и предполагалось с рынками захвата <(
... )>
. Однако, к моему удивлению, котировки будут включены в value
.
Это можно обойти?
Ответы
TL; DR Используйте "множественную отправку". [1,2] См. Ответ @ user0721090601 для подробного объяснения того, почему все так, как есть. См. @ P6steve для действительно умного изменения вашей грамматики, если вы хотите, чтобы синтаксис вашего числа соответствовал синтаксису Raku.
Решение для множественной отправки
Это можно обойти?
Один из способов - перейти на явную множественную отправку.
В настоящее время у вас есть value
токен, который вызывает варианты значений с конкретными именами:
token value { <strvalue> | <numvalue> }
Замените это на:
proto token value {*}
а затем переименуйте вызываемые токены в соответствии с правилами таргетинга на множественную отправку грамматики, чтобы грамматика стала такой:
grammar MyGrammar
{
rule TOP { <keyword> '=' <value> }
token keyword { \w+ }
proto token value {*}
token value:str { '"' <( <-["]>* )> '"' }
token value:num { '-'? \d+ [ '.' \d* ]? }
}
say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');
Это отображает:
「foo = 42」
keyword => 「foo」
value => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「Hello, World!」
По умолчанию это не фиксирует отдельные изменения. Мы можем придерживаться принципа «множественная отправка», но вновь ввести именование подзахватов:
grammar MyGrammar
{
rule TOP { <keyword> '=' <value> }
token keyword { \w+ }
proto token value { * }
token value:str { '"' <( $<strvalue>=(<-["]>*) )> '"' } token value:num { $<numvalue>=('-'? \d+ [ '.' \d* ]?) }
}
say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');
отображает:
「foo = 42」
keyword => 「foo」
value => 「42」
numvalue => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「Hello, World!」
strvalue => 「Hello, World!」
Сюрпризы
к моему удивлению, цитаты включены в
value
.
Я тоже изначально был удивлен. [3]
Но текущее поведение также имеет для меня смысл по крайней мере в следующих смыслах:
Существующее поведение имеет свои достоинства в некоторых обстоятельствах;
Было бы неудивительно, если бы я ожидал этого, что, я думаю, вполне мог бы сделать при каких-то других обстоятельствах;
Нелегко увидеть, как можно было бы получить текущее поведение, если бы оно было желательным, но вместо этого работало так, как вы (и я) изначально ожидали;
Есть решение, как описано выше.
Сноски
[1] Использование множественной диспетчеризации [2] являетсярешением, но кажется слишком сложными имоучетом исходной задачей. Возможно, есть более простое решение. Возможно, кто-то предоставит это в другом ответе на ваш вопрос. Если нет, я надеюсь, что однажды у нас будет хотя бы одно гораздо более простое решение. Однако я не удивлюсь, если мы его не получим в течение многих лет. У нас есть решение, описанное выше, и есть еще много дел.
[2] Хотя вы можете объявить, скажем,method value:foo { ... }
и написать метод (при условии, что каждый такой метод возвращает соответствующий объект), я не думаю, что Rakudo использует обычный механизм диспетчеризации нескольких методов для отправки к изменениям правил без методов, а вместо этого использует NFA .
[3] Некоторые могут возразить, что это «должно», «могло» или «было бы» «к лучшему», если бы Раку поступил так, как мы ожидали. Я считаю, что лучше всего буду избегать [sh | c | w] сообщений об ошибках / функциях, если только я не готов принять во внимание любые недостатки, которые другие поднимают, и готов помочь в работе, необходимой для того, чтобы все сделано. Поэтому я просто скажу, что в настоящее время я рассматриваю это как 10% ошибку, 90% функцию, но "могу" перейти на 100% ошибку или 100% функцию в зависимости от того, хочу ли я такое поведение или нет в данном сценарии и в зависимости от того, что думают другие.
Эти <(
и )>
маркеры захвата работают только в пределах заданного данный маркер. По сути, каждый токен возвращает Match
объект, который говорит: «Я сопоставил исходную строку от индекса X ( .from
) до индекса Y ( .to
)», что учитывается при преобразовании Match
объектов в строку . Вот что происходит с вашим токеном strvalue:
my $text = 'bar = "Hello, World!"'; my $m = MyGrammar.parse: $text; my $start = $m<value><strvalue>.from; # 7 my $end = $m<value><strvalue>.to; # 20 say $text.substr: $start, $end - $start; # Hello, World!
Вы заметите, что есть только два числа: начальное и конечное значение. Это означает, что когда вы смотрите на value
имеющийся у вас токен, он не может создать несмежное совпадение. Итак, он .from
установлен на 6, а его .to
на 21.
Есть два способа обойти это: используя (а) объект действий или (б) мультитокен. Оба имеют свои преимущества, и в зависимости от того, как вы хотите использовать это в более крупном проекте, вы можете выбрать один или другой.
Хотя технически вы можете определять действия непосредственно в грамматике, гораздо проще выполнять их через отдельный класс. Итак, у нас есть для вас:
class MyActions {
method TOP ($/) { make $<keyword>.made => $<value>.made }
method keyword ($/) { make ~$/ }
method value ($/) { make ($<numvalue> // $<strvalue>).made } method numvalue ($/) { make +$/ } method strvalue ($/) { make ~$/ }
}
Каждый уровень make
для передачи значений до любого токена, который его включает. И включающий токен имеет доступ к своим значениям через .made
метод. Это действительно приятно, когда вместо работы с чистыми строковыми значениями вы хотите сначала каким-то образом обработать их и создать объект или подобное.
Чтобы разобрать, вы просто делаете:
my $m = MyGrammar.parse: $text, :actions(MyActions); say $m.made; # bar => Hello, World!
Что на самом деле является Pair
объектом. Вы можете изменить точный результат, изменив TOP
метод.
Второй способ обойти ситуацию - использовать multi token
. При разработке грамматики довольно часто используется что-то вроде
token foo { <option-A> | <option-B> }
Но, как вы можете видеть из класса действий, он требует, чтобы мы проверили и увидели, какой из них действительно соответствует. Вместо этого, если чередование приемлемо |
, вы можете использовать мультивитокен:
proto token foo { * }
multi token:sym<A> { ... }
multi token:sym<B> { ... }
Когда вы используете его <foo>
в своей грамматике, он будет соответствовать любой из двух мультиверсий, как если бы он был в базовой линии <foo>
. Еще лучше, если вы используете класс действий, вы можете точно так же просто использовать $<foo>
и знать его без каких-либо условий или других проверок.
В вашем случае это будет выглядеть так:
grammar MyGrammar
{
rule TOP { <keyword> '=' <value> }
token keyword { \w+ }
proto token value { * }
multi token value:sym<str> { '"' <( <-["]>* )> '"' }
multi token value:sym<num> { '-'? \d+ [ '.' \d* ]? }
}
Теперь мы можем получить доступ к вещам, как вы изначально ожидали, без использования объекта действий:
my $text = 'bar = "Hello, World!"';
my $m = MyGrammar.parse: $text;
say $m; # 「bar = "Hello, World!"」 # keyword => 「bar」 # value => 「Hello, World!」 say $m<value>; # 「Hello, World!」
Для справки, вы можете комбинировать оба метода. Вот как бы я теперь написал объект действий с учетом мульти-токена:
class MyActions {
method TOP ($/) { make $<keyword>.made => $<value>.made } method keyword ($/) { make ~$/ } method value:sym<str> ($/) { make ~$/ } method value:sym<num> ($/) { make +$/ }
}
Что на первый взгляд кажется немного более привлекательным.
Вместо того, чтобы использовать собственное значение токена: str и значение токена: num, вы можете использовать логическую проверку Regex для соответствия Num (+) и Str (~) - как объяснено мне здесь и задокументировано здесь
token number { \S+ <?{ defined +"$/" }> } token string { \S+ <?{ defined ~"$/" }> }