[英]Keep track of indentation-based state with flip-flop operator
我试图熟悉触发器操作符,所以我可以在进行有状态循环时将它作为我头脑中的额外抽象,即使教科书式状态机运行良好(并且冗长和变量丰富)这种情况。 我想跟踪缩进,似乎我仍然需要在每个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
,那也很好。
具有动态操作数的触发器操作符使用起来很棘手,可能无法达到预期效果。 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”,并且在遇到输入和满足触发器操作符中第二个表达式的操作数时它保持“真”调用ff("bar")
时的第4行。 正如第一个例子所做的那样, 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__
...
我仍然不相信触发器操作符会为这个问题增加任何值,但要发现你必须使用memoized触发器操作符生成函数。 更换
...
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
是什么)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.