Этот ответ объясняет проблему, предлагает простое решение, а затем углубляется.
Проблема с вашей грамматикой
Во-первых, ваш SO демонстрирует то, что кажется либо необычной ошибкой, либо распространенное недоразумение. См. Ответ JJ по проблеме, которую он подал, чтобы исправить ее, и / или мою сноску. [4]
Оставляя ошибку / "ошибку" в стороне, ваша грамматика направляет Raku на соответствие не вашему вводу:
Атом [<subdomain> '.']*
с нетерпением потребляет строку 'baz.example.'
из вашего ввода;
Оставшийся вход ('com'
) не соответствует оставшимся атомам (<domain> '.' <tld>
);
:ratchet
, который находится в эффект для token
s означает, что грамматический движок не возвращается к атому [<subdomain> '.']*
.
Таким образом, полное совпадение не выполняется.
Простейшее решение
Самый простой способ заставить вашу грамматику работать - это добавить !
к шаблону [<subdomain> '.']*
в вашем token
.
Это даст следующий эффект:
-
Если какой-либо из остатка из token
терпит неудачу (после атома поддомена), грамматический движок вернется к атому поддомена, отбросит последнее совпадение. ns, а затем попробуйте двигаться вперед еще раз;
Если сопоставление снова не удается, движок снова вернется к атому поддомена, отбросит еще одно повторение и попытается снова;
Механизм грамматики будет повторять указанные выше действия до тех пор, пока не совпадет остальная часть token
или не останется совпадений с [<subdomain> '.']
атомом, по которому можно выполнить возврат.
Обратите внимание, что добавление !
к атому поддомена означает, что поведение обратного отслеживания ограничено только атомом поддомена; если атом домена совпадает, а атом tld не совпадает, токен завершится ошибкой вместо попытки возврата. Это потому, что весь смысл token
s заключается в том, что по умолчанию они не возвращаются к более ранним атомам после того, как им удалось.
Игра с Raku, разработка грамматик и отладка
Nil
прекрасен в качестве ответа от грамматики, которая, как известно (или считается), работает нормально, и вам не нужен более полезный ответ в случае сбоя синтаксического анализа.
Для любого в другом сценарии есть гораздо лучшие варианты, как описано в мой ответ на Как можно улучшить отчеты об ошибках в грамматике? .
В частности, для игры или разработка грамматики или ее отладка, лучший вариант - установить бесплатную запятую и использовать ее Grammar Live View .
Исправление грамматики; общие стратегии
Ваша грамматика предлагает два три варианта 1 :
Анализировать вперед с некоторым возвратом. (Самое простое решение.)
Анализировать в обратном направлении. Запишите шаблон в обратном порядке и поменяйте местами ввод и вывод.
Выполнить синтаксический анализ после синтаксического анализа.
Разобрать вперед с некоторым обратным отслеживанием
Отслеживание с возвратом - разумный подход для анализа некоторых шаблонов. Но его лучше всего свести к минимуму, чтобы максимизировать производительность, и даже тогда он все еще несет риски DoS, если написан неаккуратно. 2
Чтобы включить отслеживание с возвратом для всего токена, просто переключите декларатор на regex
. A regex
аналогичен токену, но, в частности, позволяет выполнять обратное отслеживание, как традиционное регулярное выражение.
Другой вариант - придерживаться token
и ограничить часть шаблона, которая может возвращаться. Один из способов сделать это - добавить !
после атома, чтобы дать ему возможность вернуться назад, явно переопределив общий «храповой механизм» token
, который в противном случае сработал бы, когда этот атом завершится успешно, и сопоставление перейдет к следующему атому:
token TOP { <name> '@' [<subdomain> '.']*! <domain> '.' <tld> }
?
Альтернативой !
является вставка :!ratchet
для отключения "храпового механизма" для части правила, а затем :ratchet
для повторного включения храпового механизма, например:
token TOP { <name> '@' :!ratchet [<subdomain> '.']* :ratchet <domain> '.' <tld> }
(Вы также можете использовать r
в качестве сокращения для ratchet
, т.е. :!r
и :r
.)
Обратный синтаксический анализ
Классический трюк с синтаксическим анализом c который работает для некоторых сценариев ios, заключается в обратном синтаксическом анализе, чтобы избежать обратного отслеживания.
grammar Email {
token TOP { <tld> '.' <domain> ['.' <subdomain> ]* '@' <name> }
token name { \w+ ['.' \w+]* }
token domain { \w+ }
token subdomain { \w+ }
token tld { \w+ }
}
say Email.parse(flip 'foo.bar@baz.example.com').hash>>.flip;
#{domain => example, name => foo.bar, subdomain => [baz], tld => com}
Вероятно, слишком сложно для нужд большинства людей, но я подумал, что включу его в свой ответ.
Опубликовать синтаксический анализ
Выше я представил решение, которое вводит некоторый возврат с возвратом, и другое решение, которое позволяет его избежать, но со значительными затратами с точки зрения уродства, когнитивной нагрузки и c. (анализ в обратном направлении?!?).
Есть еще один очень важный прием, который я упустил, пока не напомнил о нем ответ JJ. 1 Просто проанализируйте результаты синтаксического анализа.
Вот один способ. Я полностью реструктурировал грамматику, частично, чтобы лучше понять этот способ работы, а частично, чтобы продемонстрировать некоторые особенности грамматики Raku:
grammar Email {
token TOP {
<dotted-parts(1)> '@'
$<host> = <dotted-parts(2)>
}
token dotted-parts(\min) { <parts> ** {min..*} % '.' }
token parts { \w+ }
}
say Email.parse('foo.bar@baz.buz.example.com')<host><parts>
отображает:
[「baz」 「buz」 「example」 「com」]
Хотя эта грамматика соответствует тем же строкам, что и ваша, и выполняет пост-синтаксический анализ, как JJ, очевидно, очень отличается:
Грамматика сокращена до трех токенов.
Токен TOP
делает два вызова универсального c dotted-parts
токена с аргументом, указывающим минимальное количество частей.
$<host> = ...
захватывает следующее атом под именем <host>
.
(Обычно это избыточно, если атом сам является именованным шаблоном, как в данном случае - <dotted-parts>
. Но «части с точками» довольно общие; и чтобы сослаться на второе совпадение (первое идет перед @
), нам нужно будет написать <dotted-parts>[1]
. Итак, я прибрался, назвав это <host>
.)
Шаблон dotted-parts
может показаться немного сложным, но я На самом деле это довольно просто:
Он использует предложение квантификатора (** {min..max}
) для express любого количества частей при условии, что оно не менее минимального.
В нем используется модификатор (% <separator>
), в котором говорится, что между каждой частью должна быть точка.
<host><parts>
извлекает из дерева синтаксического анализа захваченные данные, связанные с токеном parts
второго использования в правиле TOP
для dotted-parts
. Это массив: [「baz」 「buz」 「example」 「com」]
.
Иногда хочется, чтобы повторный анализ частично или полностью выполнялся во время синтаксического анализа, чтобы результаты повторного анализа были готовы, когда вызов .parse
завершится.
JJ показал один способ кодирования того, что называется действия. Это включало:
Создание класса «действий», содержащего методы, имена которых соответствуют именованным правилам грамматики;
Указание методу синтаксического анализа используйте этот класс действий;
Если правило выполняется успешно, вызывается метод действия с соответствующим именем (пока правило остается в стеке вызовов);
Соответствующий правилу объект сопоставления передается методу действия;
Метод действия может делать все, что угодно, включая повторный анализ того, что только что было сопоставлено.
Проще, а иногда и лучше писать действия прямо в строке:
grammar Email {
token TOP {
<dotted-parts(1)> '@'
$<host> = <dotted-parts(2)>
# The new bit:
{
make (subs => .[ 0 .. *-3 ],
dom => .[ *-2 ],
tld => .[ *-1 ])
given $<host><parts>
}
}
token dotted-parts(\min) { <parts> ** {min..*} % '.' }
token parts { \w+ }
}
.say for Email.parse('foo.bar@baz.buz.example.com') .made;
отображает:
subs => (「baz」 「buz」)
dom => 「example」
tld => 「com」
Примечания:
Я непосредственно встроил код, выполняющий повторный анализ.
(Можно вставлять произвольные блоки кода ({...}
) везде, где в противном случае можно было бы вставить атом. В те дни, когда у нас были отладчики грамматики, classi c вариант использования был { say $/ }
который pri nts $/
, объект соответствия, как он есть в точке появления блока кода.)
Если блок кода помещен в конец rule, как и я, он почти эквивалентен методу действия.
(Он будет вызываться, когда правило будет выполнено в противном случае и $/
уже полностью заполнено. В некоторых сценариях ios встраивание анонимного блока действий является способом go. В других случаях лучше разбить его на именованный метод в классе действия, как это сделал JJ.)
make
- основной вариант использования кода действия.
(Все, что делает make
, - это сохраняет свой аргумент в атрибуте .made
элемента $/
, который в данном контексте является текущим узлом дерева синтаксического анализа. Результаты, сохраненные make
, автоматически отбрасываются, если при обратном отслеживании впоследствии отбрасываются закрывающие узел синтаксического анализа. Часто это именно то, что нужно.)
foo => bar
образует Pair
.
Оператор postcircumfix [...]
индексирует его invocant :
- В этом случае есть только префикс
.
без явного LHS, поэтому инициатор является «он». «Это» было установлено given
, т.е. это (извините за каламбур) $<host><parts>
.
*
в индексе *-n
- длина инвоканта; поэтому [ 0 .. *-3 ]
- это все, кроме двух последних элементов $<host><parts>
.
Строка .say for ...
заканчивается на .made
3 , чтобы подобрать make
значение d.
Значение make
'd представляет собой список из трех выходящих пар $<host><parts>
.
Сноски
1 Я действительно думал, что мои первые два варианта были двумя основными доступными. Прошло около 30 лет с тех пор, как я встретил Тима Тоуди в сети. Можно было подумать, что я уже выучил наизусть его одноименный афоризм - Есть более чем один способ сделать это!
2 Остерегайтесь «патологическое возвращение» . В производственном контексте, если у вас есть подходящий контроль над вводом данных или системой, на которой работает ваша программа, вам, возможно, не придется беспокоиться о преднамеренных или случайных DoS-атаках, потому что они либо не могут произойти, либо бесполезно отключат систему, которая перезагружается в случае недоступности. Но если вы делаете , вам нужно беспокоиться, т. Е. Анализ выполняется на компьютере, который должен быть защищен от DoS-атаки, тогда оценка угрозы будет разумной. (Прочтите подробности сбоя Cloudflare 2 июля 2019 , чтобы понять, что может go ошибаться.) Если вы запускаете код синтаксического анализа Raku в такой требовательной производственной среде, вам может потребоваться начать аудит кода с поиска шаблонов, которые используют regex
, /.../
(...
являются метасинтаксисом), :!r
(включая :!ratchet
) или *!
.
3 Для .made
есть псевдоним; это .ast
. Я думаю, это означает A S parse T ree или A nnotated S ubset T ree и есть вопрос cs.stackexchange.com , который со мной согласен.
4 Решите вашу проблему, это кажется неправильным:
say 'a' ~~ rule { .* a } # 「a」
В более общем плане, я думал , единственная разница между token
и rule
заключалась в том, что последний вводит <.ws>
в каждое значащее пространство . Но это означало бы, что это должно работать:
token TOP { <name> <.ws> '@' <.ws> [<subdomain> <.ws> '.']* <.ws>
<domain> <.ws> '.' <.ws> <tld> <.ws>
}
Но это не так!
Сначала это меня напугало. Написав эту сноску два месяца спустя, я чувствую себя несколько менее взволнованным.
Отчасти это мои предположения о причине, по которой я не смог найти никого, кто сообщил бы об этом за 15 лет, прошедших с момента первого Раку. прототип грамматики стал доступен через Pugs. Это предположение включает возможность того, что @Larry намеренно спроектировал их так, чтобы они работали так, как они, и это «ошибка» - это в первую очередь недопонимание среди нынешнего поколения простых смертных, подобных нам, пытающихся объяснить, почему Раку делает то, что он делает, основываясь на наш анализ наших источников - жаркое, исходная проектная документация, исходный код компилятора и т. д. c.
Вдобавок, учитывая, что текущее «глючное» поведение кажется идеальным и интуитивно понятным (за исключением противоречия c), я сосредотачиваюсь на интерпретации моего ощущения сильного дискомфорта - в течение этого промежуточного периода неизвестной продолжительности в что я не понимаю , почему это правильно - как положительный опыт. Я надеюсь, что другие тоже могут - или, намного лучше, выяснить, что на самом деле происходит, и сообщить нам!