Есть несколько дополнительных сложностей, которые у вас есть. Например, вы определяете tie
как (...)
или просто ...
. Но это внутреннее содержание идентично линии.
Вот переписанная грамматика, которая значительно упрощает то, что вы хотите. При написании грамматик полезно начинать с малого и go вверх.
grammar Tied {
rule TOP { <notes>+ %% \v+ }
token notes {
[
| <tie>
| <simple-note>
] +
%%
<.ws>?
}
token open-tie { '(' }
token close-tie { ')' }
token tie { <.open-tie> ~ <.close-tie> <notes> }
token simple-note { <[A..Ga..g,'>0..9|\]]> }
}
Несколько стилистических c заметок здесь. Грамматика - это классы, и их принято использовать с большой буквы. Токены являются методами и, как правило, строчными буквами с оболочкой для кебаба (вы, конечно, можете использовать любой тип, какой захотите) В токене tie
вы заметите, что я использовал <.open-tie>
. .
означает, что нам не нужно захватывать его (то есть, мы просто используем его для сопоставления и ничего больше). В токене notes
мне удалось многое упростить, используя %%
и сделав TOP
правилом, которое автоматически добавляет пробел.
Теперь порядок, в котором я буду создавать токены, это:
<simple-note>
, потому что это элемент самого базового уровня. Их группа будет <notes>
, поэтому я сделаю это следующим. Делая это, я понимаю, что ряд заметок может также включать… <tie>
, так что это следующий. Внутри в ie, хотя я просто собираюсь провести еще одну серию заметок, так что я могу использовать <notes>
внутри нее. <TOP>
наконец, потому что, если в строке просто есть ряд Заметки, мы можем опустить строку и использовать %% \v+
Действия (часто с тем же именем, что и ваша грамматика, плюс -Actions
, поэтому здесь я использую class Tied-Actions { … }
) обычно используются для создания абстрактное синтаксическое дерево. Но на самом деле, лучший способ думать об этом - спрашивать каждый уровень грамматики, чего мы от него хотим. Я считаю, что, хотя написание грамматик проще всего построить из самого маленького элемента вверх, для действий проще всего go сверху вниз. Это также поможет вам построить более сложные действия в будущем:
- Что мы хотим от
TOP
?
В нашем случае мы просто хотим, чтобы все связи, которые мы нашли в каждом * Токен 1042 * Это можно сделать с помощью простого l oop (поскольку мы сделали квантификатор для <notes>
, это будет Positional
:
method TOP ($/) {
my @ties;
@ties.append: .made for $<notes>;
make @ties;
}
Приведенный выше код создает нашу временную переменную, циклически перебирая все <note>
и добавляет ко всему, что <note>
сделал для нас - что на данный момент ничего, но это нормально. Тогда, потому что мы хотим связи из TOP, поэтому мы make
их, что позволяет нам чтобы получить к нему доступ после разбора. - Что вы хотите от
<notes>
?
Опять же, мы просто хотим связи (но, может быть, в другой раз, вам нужны связи и списки или какая-то другая информация). Таким образом, мы можем захватить связи, делая в основном одну и ту же вещь:
method notes ($/) {
my @ties;
@ties.append: .made for $<tie>.grep(*.defined);
make @ties;
}
Единственное отличие состоит не в том, чтобы просто делать for $<tie>
, мы должны захватывать только определенные - это является следствием выполнения [<foo>|<bar>]+
: $<foo>
будет иметь слот для каждого количественного совпадения, независимо от того, было ли замечание <foo>
выполнено сопоставление (это когда вам часто захочется выдать, скажем, proto token note
с at ie и простой вариант заметки, но это би Преимущество для этого). Опять же, мы берем все, что $<tie>
сделано для нас - мы определим это позже, и мы «сделаем» это. Что бы мы ни make
, это то, что другие действия найдут made
на <notes>
(как в TOP
). - Что вы хотите от
<tie>
? Здесь я собираюсь просто go для содержимого t ie - достаточно просто взять скобки, если хотите. Вы могли бы подумать, что мы просто будем использовать make ~$<notes>
, но это исключает что-то важное: $<notes>
также имеет некоторые связи. Но их достаточно легко захватить:
method tie ($/) {
my @ties = ~$<notes>;
@ties.append: $<notes>.made;
make @ties;
}
Это гарантирует, что мы передаем не только текущий внешний t ie, но также каждый отдельный внутренний t ie (который, в свою очередь, может иметь другой внутренний один и т. д.).
Когда вы анализируете, все, что вам нужно сделать, это захватить .made
из Match
:
say Tied.parse("a(b(c))d");
# 「a(b(c))d」
# notes => 「a(b(c))d」
# simple-note => 「a」
# tie => 「(b(c))」 <-- there's a tie!
# notes => 「b(c)」
# simple-note => 「b」
# tie => 「(c)」 <-- there's another!
# notes => 「c」
# simple-note => 「c」
# simple-note => 「d」
say Tied.parse("a(b(c))d", actions => TiedActions).made;
# [b(c) c]
Теперь, если вы на самом деле только когда-либо понадобятся связи - и ничто иное - (что я не думаю, что так), вы можете делать вещи намного проще. Используя ту же грамматику, используйте вместо этого следующие действия:
class Tied-Actions {
has @!ties;
method TOP ($/) { make @!ties }
method tie ($/) { @!ties.push: ~$<notes> }
}
Это имеет несколько недостатков по сравнению с предыдущим: хотя оно работает, оно не очень масштабируемо. Хотя вы получите каждый т ie, вы ничего не узнаете о его контексте. Кроме того, вы должны создать экземпляр Tied-Actions (то есть actions => TiedActions.new
), тогда как если вы можете избежать использования каких-либо атрибутов, вы можете передать объект типа.