Как использовать соответствующие разделители в Raku - PullRequest
5 голосов
/ 17 января 2020

Я пытаюсь написать токен, который разрешает вложенный контент с соответствующими разделителями. Где (AB) должно привести к совпадению по крайней мере с "AB", если не "(AB)". И (A (c) B) вернет два совпадения "(A (c) B)" и т. Д.

Код, сброшенный из его источника:

#!/home/hsmyers/rakudo741/bin/perl6
use v6d;

my @tie;

class add-in {
    method tie($/) { @tie.push: $/; }
}

grammar tied {
    rule TOP { <line>* }
    token line {
        <.ws>?
        [
            | <tie>
            | <simpleNotes>
        ]+
        <.ws>?
    }
    token tie {
        [
            || <.ws>? <simpleNotes>+ <tie>* <simpleNotes>* <.ws>?
            || <openParen> ~ <closeParen> <tie>
        ]+
    }
    token openParen { '(' }
    token closeParen { ')' }
    token simpleNotes {
        [
            | <[A..Ga..g,'>0..9]>
            | <[|\]]>
            | <blank>
        ]
    }
}

my $text = "(c2D) | (aA) (A2 | B)>G A>F G>E (A,2 |\nD)>F A>c d>f |]";

tied.parse($text, actions => add-in.new).say;
$text.say;
for (@tie) {
    s:g/\v/\\n/;
    say "«$_»";
}

Это дает частично правильный результат:

«c2D»
«aA»
«(aA)»
«A2 | B»
«\nD»
«A,2 |\nD»
«(A,2 |\nD)>F A>c d>f |]»
«(c2D) | (aA) (A2 | B)>G A>F G>E (A,2 |\nD)>F A>c d>f |]»

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

Очевидно, у меня есть проблема в моем коде. Мои знания Perl6 лучше всего описать как «новичка», поэтому я прошу вашей помощи. Я ищу общее решение или хотя бы пример, который можно обобщить, и, как всегда, предложения и исправления приветствуются.

1 Ответ

7 голосов
/ 17 января 2020

Есть несколько дополнительных сложностей, которые у вас есть. Например, вы определяете 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 правилом, которое автоматически добавляет пробел.

Теперь порядок, в котором я буду создавать токены, это:

  1. <simple-note>, потому что это элемент самого базового уровня. Их группа будет
  2. <notes>, поэтому я сделаю это следующим. Делая это, я понимаю, что ряд заметок может также включать…
  3. <tie>, так что это следующий. Внутри в ie, хотя я просто собираюсь провести еще одну серию заметок, так что я могу использовать <notes> внутри нее.
  4. <TOP> наконец, потому что, если в строке просто есть ряд Заметки, мы можем опустить строку и использовать %% \v+

Действия (часто с тем же именем, что и ваша грамматика, плюс -Actions, поэтому здесь я использую class Tied-Actions { … }) обычно используются для создания абстрактное синтаксическое дерево. Но на самом деле, лучший способ думать об этом - спрашивать каждый уровень грамматики, чего мы от него хотим. Я считаю, что, хотя написание грамматик проще всего построить из самого маленького элемента вверх, для действий проще всего go сверху вниз. Это также поможет вам построить более сложные действия в будущем:

  1. Что мы хотим от TOP?
    В нашем случае мы просто хотим, чтобы все связи, которые мы нашли в каждом * Токен 1042 * Это можно сделать с помощью простого l oop (поскольку мы сделали квантификатор для <notes>, это будет Positional:
    method TOP ($/) { my @ties; @ties.append: .made for $<notes>; make @ties; }
    Приведенный выше код создает нашу временную переменную, циклически перебирая все <note> и добавляет ко всему, что <note> сделал для нас - что на данный момент ничего, но это нормально. Тогда, потому что мы хотим связи из TOP, поэтому мы make их, что позволяет нам чтобы получить к нему доступ после разбора.
  2. Что вы хотите от <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).
  3. Что вы хотите от <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), тогда как если вы можете избежать использования каких-либо атрибутов, вы можете передать объект типа.

...