Невозможно написать грамматику в perl6 для разбора строк со специальными символами - PullRequest
0 голосов
/ 11 января 2019

У меня есть код в: https://gist.github.com/ravbell/d94b37f1a346a1f73b5a827d9eaf7c92

use v6;
#use Grammar::Tracer;


grammar invoice {

    token ws { \h*};
    token super-word {\S+};
    token super-phrase { <super-word> [\h  <super-word>]*}
    token line {^^ \h* [ <super-word> \h+]* <super-word>* \n};

    token invoice-prelude-start {^^'Invoice Summary'\n}
    token invoice-prelude-end {<line> <?before 'Start Invoice Details'\n>};

    rule invoice-prelude {
        <invoice-prelude-start>
        <line>*?
        <invoice-prelude-end>
        <line>
    }
}

multi sub MAIN(){ 

    my $t = q :to/EOQ/; 
    Invoice Summary
    asd fasdf
    asdfasdf
    asd 123-fasdf $1234.00
    qwe {rq} [we-r_q] we
    Start Invoice Details 
    EOQ


    say $t;
    say invoice.parse($t,:rule<invoice-prelude>);
}

multi sub MAIN('test'){
    use Test;
    ok invoice.parse('Invoice Summary' ~ "\n", rule => <invoice-prelude-start>);

    ok invoice.parse('asdfa {sf} asd-[fasdf] #werwerw'~"\n", rule => <line>);
    ok invoice.parse('asdfawerwerw'~"\n", rule => <line>);

    ok invoice.subparse('fasdff;kjaf asdf asderwret'~"\n"~'Start Invoice Details'~"\n",rule => <invoice-prelude-end>);
    ok invoice.parse('fasdff;kjaf asdf asderwret'~"\n"~'Start Invoice Details'~"\n",rule => <invoice-prelude-end>);
    done-testing;
}

Мне не удалось выяснить, почему анализ rule <invoice-prelude> не удается с Nil. Обратите внимание, что даже .subparse также не работает.

Тесты для отдельных токенов проходят, как вы можете видеть, запустив MAIN с аргументом 'test' (за исключением того, что .parse на <invoice-prelude> не пройден, поскольку он не является полной строкой).

Что следует изменить в rule <invoice-prelude>, чтобы вся строка $t в MAIN() могла быть правильно проанализирована?

Ответы [ 3 ]

0 голосов
/ 11 января 2019

Во-первых, экономный квантификатор *? без обратного отслеживания, вероятно, каждый раз соответствует пустой строке. Вы можете использовать regex вместо rule.

Во-вторых, в конце строки есть пробел, который начинается с Start Invoice Details.

rule invoice-prelude-end {<line> <?before 'Start Invoice Details' \n>};

regex invoice-prelude {
    <invoice-prelude-start>
    <line>*?
    <invoice-prelude-end>
    <line>
}

Если вы хотите избежать возврата, вы можете использовать отрицательный прогноз.

token invoice-prelude-end { <line> };

rule invoice-prelude {
    <invoice-prelude-start>
    [<line> <!before 'Start Invoice Details' \n>]*
    <invoice-prelude-end>
    <line>
}

Весь пример с некоторыми изменениями в качестве вдохновения:

use v6;
#use Grammar::Tracer;


grammar invoice {
    token ws { <!ww>\h* }
    token super-word { \S+ }
    token line { <super-word>* % <.ws> }

    token invoice-prelude-start   { 'Invoice Summary' }
    rule  invoice-prelude-midline { <line> <!before \n <invoice-details-start> \n> }
    token invoice-prelude-end     { <line> }
    token invoice-details-start   { 'Start Invoice Details' }

    rule invoice-prelude {
        <invoice-prelude-start> \n
        <invoice-prelude-midline> * %% \n
        <invoice-prelude-end> \n
        <invoice-details-start> \n
    }
}

multi sub MAIN(){

    my $t = q :to/EOQ/;
    Invoice Summary
    asd fasdf
    asdfasdf
    asd 123-fasdf $1234.00
    qwe {rq} [we-r_q] we
    Start Invoice Details 
    EOQ


    say $t;
    say invoice.parse($t,:rule<invoice-prelude>);
}
0 голосов
/ 11 января 2019

TLDR: Проблема в том, что тестовая строка ввода с Start Invoice Details заканчивается горизонтальным пробелом, с которым вы не имеете дела.

Два способа справиться с этим (кроме изменения ввода)

# Explicitly:                                                       vvv
token invoice-prelude-end { <line> <?before 'Start Invoice Details' \h* \n>}

# Implicitly:
rule  invoice-prelude-end { <line><?before 'Start Invoice Details' \n>}
# ^ must be a rule                      and there must be a space ^
# (uses the fact that you wrote your own <ws> token)

Ниже приведены некоторые вещи, которые, я думаю, будут полезны

Я бы использовал «разделенный» функцией % в line и super-phrase

token super-phrase { <super-word>+ % \h } # single % doesn't capture trailing separator

token line {
  ^^ \h*
  <super-word>* %% \h+ # double %% can capture optional trailing separator
  \n
}

Это [почти] точно соответствует тому, что вы написали. (То, что вы написали, должно не совпадать с <super-word> дважды в <line>, но это должно произойти только один раз.)


Я бы использовал функцию объемного звучания ~ в invoice-prelude

token invoice-prelude {
    # zero or more <line>s surrounded by <invoice-prelude-start> and <invoice-prelude-end>
    <invoice-prelude-start> ~ <invoice-prelude-end> <line>*?

    <line> # I assume this is here for debugging
}

Обратите внимание, что на самом деле он ничего не получил, будучи rule, потому что весь горизонтальный пробел уже обработан остальной частью кода.


Я не думаю, что последняя строка прелюдии счета-фактуры является особенной, поэтому удалите <line> из invoice-prelude-end. (<line>*? в invoice-prelude захватит его вместо этого.)

token invoice-prelude-end {<?before 'Start Invoice Details' \h* \n>}

Единственное регулярное выражение, которое может быть полезным для rule - это invoice-prelude-start и invoice-prelude-end.

rule  invoice-prelude-start {^^ Invoice Summary \n}
# `^^` is needed  so the space ^ will match <.ws>

rule  invoice-prelude-end {<?before ^^ Start Invoice Details $$>}

Это сработало бы, только если у вас все в порядке с чем-то вроде Invoice Summary ␤.

Обратите внимание, что invoice-prelude-start необходимо использовать \n для захвата, но invoice-prelude-end может использовать $$ вместо этого, потому что он все равно не захватывает \n.


Если вы измените super-word на что-то отличное от \S+, тогда вы также можете изменить ws на что-то вроде \h+ | <.wb>. (граница слова)


#! /usr/bin/env perl6
use v6.d;

grammar invoice {
    token TOP { # testing
         <invoice-prelude>
         <line>
    }

    token ws { \h* | <.wb> };
    token super-word { \S+ };
    token super-phrase { <super-word>+ % \h }
    token line {
        ^^ \h*
        <super-word>* %% \h+
        \n
    };

    rule invoice-prelude-start {^^ Invoice Summary \n}
    rule invoice-prelude-end {<?before ^^ Start Invoice Details $$>};

    token invoice-prelude {
        <invoice-prelude-start> ~ <invoice-prelude-end>
            <line>*?
    }
}

multi sub MAIN(){ 
    my $t = q :to/EOQ/; 
    Invoice Summary
    asd fasdf
    asdfasdf
    asd 123-fasdf $1234.00
    qwe {rq} [we-r_q] we
    Start Invoice Details 
    EOQ


    say $t;
    say invoice.parse($t);
}
0 голосов
/ 11 января 2019

Обратите внимание, что в конце последней строки в строке $t есть скрытое пространство:

my $t = q :to/EOQ/; 
    Invoice Summary
    asd fasdf
    asdfasdf
    asd 123-fasdf $1234.00
    qwe {rq} [we-r_q] we
    Start Invoice Details␣   <-- Space at the end of the line
    EOQ

Это приводит к сбою токена <invoice-prelude-end>, так как он содержит регулярное выражение упреждения <?before 'Start Invoice Details'\n>. Этот запрос не содержит возможного пробела в конце строки (из-за явного символа новой строки \n в конце просмотра). Следовательно, правило <invoice-prelude> также не может совпадать.

Быстрое решение - удалить пробел в конце строки Start Invoice Details.

...