【问题标题】:Unable to write a grammar in perl6 for parsing lines with special characters无法在 perl6 中编写语法来解析带有特殊字符的行
【发布时间】:2019-01-11 11:38:07
【问题描述】:

我的代码在: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 &lt;invoice-prelude&gt; 上的解析失败并显示Nil。请注意,即使.subparse 也会失败。

通过运行带有'test' 参数的MAIN 可以看到单个令牌的测试通过(当然&lt;invoice-prelude&gt; 上的.parse 失败,因为它不是完整的字符串)。

应该在rule &lt;invoice-prelude&gt; 中修改什么,以便可以正确解析MAIN() 中的整个字符串$t

【问题讨论】:

  • Start Invoice Details 的行尾似乎有一个空格。这使得前瞻正则表达式 &lt;?before 'Start Invoice Details'\n&gt; 失败,因为它期望在行尾有一个换行符

标签: regex parsing grammar raku


【解决方案1】:

注意$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

这会使&lt;invoice-prelude-end&gt; 令牌失败,因为它包含前瞻正则表达式&lt;?before 'Start Invoice Details'\n&gt;。这种前瞻在行尾不包括可能的空格(由于前瞻末尾的显式换行符\n)。因此,&lt;invoice-prelude&gt; 规则也不能匹配。

快速解决方法是删除Start Invoice Details 行末尾的空格。

【讨论】:

  • Håkon Hægland:对上好的渔获赞叹不已!现在可以了!只是好奇你是如何检测到这一点的。我知道这是与正则表达式相关的编程过程中常犯的错误之一。我反复查看代码并错过了它。这有助于其他人查看代码而您也这样做了。
  • @RavBell 谢谢!我首先使用Grammar::Tracer 来查看它是否能给我一些关于解析失败原因的指示。这导致我使用前瞻正则表达式。似乎这就是问题发生的地方,但Grammar::Tracer 并没有确切地揭示出问题所在。所以我开始更改前瞻正则表达式,首先我删除了前瞻末尾的换行符,然后我看到了现在解析成功了。之后很容易找到隐藏的空间:)
【解决方案2】:

首先,没有回溯的节俭量词*? 可能每次都匹配空字符串。您可以使用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>);
}

【讨论】:

  • 中的需要什么?
【解决方案3】:

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)

以下是我认为会有所帮助的更多内容

我会在linesuper-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
}

这些 [几乎] 完全等同于您所写的。 (你写的内容必须在&lt;line&gt; 中匹配&lt;super-word&gt; 两次失败,但这只需要失败一次。)


我会在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 实际上并没有获得任何好处,因为所有水平空白都已由其余代码处理。


我不认为发票前奏的最后一行有什么特别之处,所以从invoice-prelude-end 中删除&lt;line&gt;。 (invoice-prelude 中的&lt;line&gt;*? 将捕获它。)

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

唯一可以从rule 中受益的正则表达式是invoice-prelude-startinvoice-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+ | &lt;.wb&gt; 之类的值。 (单词边界)


#! /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);
}

【讨论】:

  • 布拉德·吉尔伯特:彻底而优雅的回答!我会选择这个作为答案,但事实上我已经接受了第一个给我解决方案的答案。我很可能会实施您的解决方案。从这个答案中学到了很多东西。谢谢!
  • @RavBell 我不再真正回答声誉点数了。我这样做主要是为了提高遇到它的人的知识水平。
  • 你为什么用额外的<.wb>定义?在我需要 作为 \S+ 的情况下,这会有所帮助吗?如果我理解 是围绕 的边界。
  • @RavBell 假设在'abc  {'abc{ 应该分别匹配。现在假设我们希望abc{ 完全一样地工作。这是一个单词边界,所以&lt;wb&gt; 将匹配。内置的&lt;ws&gt; 令牌有点像&lt;.wb&gt; | \s+。添加&lt;.wb&gt; 只是对未来的一点验证。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-21
  • 2020-10-01
  • 2013-09-09
  • 2013-04-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多