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

rub*_*ion 3 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
Run Code Online (Sandbox Code Playgroud)

预期产量

Mark Twain
Jane Austen
Mark Twain
Jane Austen
Run Code Online (Sandbox Code Playgroud)

实际输出

Mark Twain
Klaus Teuber
Jane Austen
Mark Twain
Klaus Teuber
Jane Austen
Run Code Online (Sandbox Code Playgroud)

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

mob*_*mob 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>
Run Code Online (Sandbox Code Playgroud)

输出为:

FOO:<foo>
FOO:   <bar>
BAR:   <bar>
FOO:      123
BAR:      123
FOO:   </bar>
BAR:   </bar>
FOO:   <baz>
FOO:       456
FOO:   </baz>
FOO:</foo>
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都很好,对吗?当要跟踪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>
Run Code Online (Sandbox Code Playgroud)

现在的输出是

FOO:<foo>
BAR:<foo>
FOO:   <bar>
BAR:   <bar>
FOO:      123
BAR:      123
FOO:   </bar>
BAR:   </bar>
Run Code Online (Sandbox Code Playgroud)

发生了什么?BAR总是用和相同的行打印,即使标签中仍包含更多数据FOO,输出的最后一行也是该</bar><foo></foo>

所发生的是该代码包含一个在ff子例程中定义的单个触发器运算符,并且该运算符维护一个状态。当状态改变为“真” ff("foo")被称为与输入的第一行,并且它仍是“真”,直到它遇到的输入和其满足在触发器操作者的第二表达,其与第四线时发生操作数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__
...
Run Code Online (Sandbox Code Playgroud)

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

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

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

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__
...
Run Code Online (Sandbox Code Playgroud)

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

...
return ((/^$indent$block_rx/) ... (!/^$inner_indent/)) =~ s/.*E//r;
Run Code Online (Sandbox Code Playgroud)

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

(不确定s/.*E//r用途是什么)