使用触发器运算符跟踪基于缩进的状态

时间:2019-04-11 16:06:52

标签: perl

我正在尝试熟悉触发器运算符,因此,即使进行教科书式的状态机运行非常完美(以及冗长和可变的内容,在这种情况下)。我想跟踪缩进,并且似乎仍然需要在每个if块的开始手动调整缩进,在这种情况下,我将其称为缩进触发器,对吗? 这是我想出的:

程序

use v5.20;
use strict;
use warnings;

my $shiftwidth = 3;

# block_rx: start of indented block marker, without leading spaces
# Keeps state of indentation, which is increased on encountering block marker
# and decreased on matching outdent.
# Function should always get indentation level from context it was called in.
# Returns: true if in indented block, ^ff^, else false

sub indenting_flipflop {
    my $block_rx = $_[0];
    $_ = $_[1];
    my $level = $_[2];
    my $indent = indent($level);
    my $inner_indent = indent($level + 1);
    return ((/^$indent$block_rx/) ... (!/^$inner_indent/)) =~ s/.*E//r;
}

sub indent {
    return ' ' x ($shiftwidth * $_[0]);
}

while (<DATA>) {
    my $level = 0;
    if (indenting_flipflop('books', $_, $level)) {
        $level++;
        if (indenting_flipflop('book', $_, $level)) {
            $level++;
            if (/author: (.*)/) {
              say $1;
            }
        }
    }
}

__DATA__
books:
   book:
      author: Mark Twain
      price: 10.99
   game:
      author: Klaus Teuber
      price: 15.99
   book:
      author: Jane Austen
      price: 12.00

books:
   book:
      author: Mark Twain
      price: 10.99
   game:
      author: Klaus Teuber
      price: 15.99
   book:
      author: Jane Austen
      price: 12.00

预期输出

Mark Twain
Jane Austen
Mark Twain
Jane Austen

实际输出

Mark Twain
Klaus Teuber
Jane Austen
Mark Twain
Klaus Teuber
Jane Austen

如果我不必在循环中手动调整$level,那也很好。

1 个答案:

答案 0 :(得分:5)

具有动态操作数的触发器运算符使用起来很棘手,可能无法达到您的期望。 Perl为代码中出现的每个触发器运算符维护一个“状态”,而不是为作为操作数提供给触发器运算符的每个表达式单独的状态。

考虑以下代码:

sub foo { m[<foo>] .. m[</foo>] }
sub bar { m[<bar>] .. m[</bar>] }

while (<DATA>) {    
    print "FOO:$_" if foo();
    print "BAR:$_" if bar();
}    
__DATA__
<foo>
   <bar>
      123
   </bar>
   <baz>
       456
   </baz>
</foo>

输出为:

FOO:<foo>
FOO:   <bar>
BAR:   <bar>
FOO:      123
BAR:      123
FOO:   </bar>
BAR:   </bar>
FOO:   <baz>
FOO:       456
FOO:   </baz>
FOO:</foo>

到目前为止,很好,对吗?当要跟踪100个不同的标签而不是两个时,这种方法无法很好地扩展,因此让我们尝试以下代码:

sub ff { my $tag = shift; m[<$tag>] .. m[</$tag>] }
while (<DATA>) {
    print "FOO:$_" if ff("foo");
    print "BAR:$_" if ff("bar");
}
__DATA__
<foo>
   <bar>
      123
   </bar>
   <baz>
       456
   </baz>
</foo>

现在的输出是

FOO:<foo>
BAR:<foo>
FOO:   <bar>
BAR:   <bar>
FOO:      123
BAR:      123
FOO:   </bar>
BAR:   </bar>

发生了什么事? BAR始终以与FOO相同的行打印,并且输出的最后一行是</bar>行,即使仍有更多数据包含在<foo></foo>标记中。

发生的事情是该代码包含一个在ff子例程中定义的触发器操作符,并且该操作符维护一个状态。当使用输入的第一行调用ff("foo")时,状态变为“ true”,并保持“ true”,直到遇到输入和触发器操作符中满足第二个表达式的操作数为止,这种情况发生在调用ff("bar")时的第四行。它没有像第一个示例那样为foo标签和bar标签保持单独的状态。

将不同的输入传递给indenting_flipflop函数,并期望该函数中的触发器运算符仅对这种输入进行操作就不会起作用。


更新:因此,为每个标签定义一个新功能的方法有效:

sub fff { my $tag = shift; sub { m[<$tag>] .. m[</$tag>] } }
my $foo = fff("foo");
my $bar = fff("bar");
while (<DATA>) {
    print "FOO:$_" if $foo->();
    print "BAR:$_" if $bar->();
}
__DATA__
...

但是,这一项(在每一行输入中定义新功能)没有:

sub fff { my $tag = shift; sub { m[<$tag>] .. m[</$tag>] } }
while (<DATA>) {
    print "FOO:$_" if fff("foo")->();
    print "BAR:$_" if fff("bar")->();
}
__DATA__
...

另一方面,它的记忆版本将起作用:

my %FF;
sub fff { my $tag = shift; $FF{$tag} //= sub { m[<$tag>] .. m[</$tag>] } }
while (<DATA>) {
    print "FOO:$_" if fff("foo")->();
    print "BAR:$_" if fff("bar")->();
}
__DATA__
...

我仍然不相信触发器运算符会为这个问题增加任何价值,但是要找出答案,您必须使用记忆化的触发器运算符生成函数。替换

...
return ((/^$indent$block_rx/) ... (!/^$inner_indent/)) =~ s/.*E//r;

使用

my %FF;
sub flipflopfunc {
    my ($expr1,$expr2) = @_;
    return $FF{$expr1}{$expr2} //= 
        sub { /^$expr1/ ... !/^$expr2/ };
}
...
return flipflopfunc("$indent$block_rx",$inner_indent)->() =~ s/.*E//r;

(不确定s/.*E//r的用途)