Perl 6中触发器运算符的使用

The use of flip-flop operator in Perl 6

我在doc.perl6.org中看到了flip-flop的用法,请看下面的代码:

my $excerpt = q:to/END/;
Here's some unimportant text.
=begin code
This code block is what we're after.
We'll use 'ff' to get it.
=end code
More unimportant text.
=begin code
I want this line.
and this line as well.
HaHa
=end code
More unimport text.
=begin code
Let's to go home.
=end code
END

my @codelines = gather for $excerpt.lines {
    take $_ if "=begin code" ff "=end code"
}

# this will print four lines, starting with "=begin code" and ending with
# "=end code"

.say for @codelines;
=begin code
This code block is what we're after.
We'll use 'ff' to get it.
=end code
=begin code
I want this line.
and this line as well.
HaHa
=end code
=begin code
Let's to go home.
=end code

我想将 =begin code=end code 之间的行保存到单独的数组中,例如:

['This code block is what we're after.', 'We'll use 'ff' to get it.']
['I want this line.', 'and this line as well.', 'HaHa']
['Let's to go home.']

我知道语法可以做到这一点,但我想知道是否有更好的方法?

您需要指定不包含匹配值。您可以通过将 ^ 添加到要排除的运算符的一侧来执行此操作。在这种情况下,它是运算符的两侧。

您还需要收集最多的值以将它们组合在一起。在这种情况下,最简单的方法是在比赛之间将其关闭。
(如果您想要包含端点,则需要更多思考才能正确完成)

my @codelines = gather {
  my @current;

  for $excerpt.lines {

    if "=begin code" ^ff^ "=end code" {

      # collect the values between matches
      push @current, $_;

    } else {
      # take the next value between matches

      # don't bother if there wasn't any values matched
      if @current {

        # you must do something so that you aren't
        # returning the same instance of the array
        take @current.List;
        @current = ();
      }
    }
  }
}

如果您需要结果是数组的数组(可变)。

if @current {
  take @current;
  @current := []; # bind it to a new array
}

另一种方法是对共享相同迭代器的序列使用 do for
这是有效的,因为 formap 更渴望。

my $iterator = $excerpt.lines.iterator;

my @codelines = do for Seq.new($iterator) {
    when "=begin code" {
        do for Seq.new($iterator) {
            last when "=end code";
            $_<> # make sure it is decontainerized
        }
    }
    # add this because `when` will return False if it doesn't match
    default { Empty }
}

map 获取一个序列并将其转换为另一个序列,但在您尝试从序列中获取下一个值之前不执行任何操作。
for 立即开始迭代,只有在您告诉它时才停止。

所以即使 运行 在单个线程上,map 也会导致竞争条件,但 for 不会。

您也可以使用古老的正则表达式:

say ( $excerpt ~~ m:s:g{\=begin code\s+(.+?)\s+\=end code} ).map( *.[0] ).join("\n\n")

s 用于重要的空格(不是真正需要的),g 用于提取所有匹配项(不是第一个),.map 遍历返回的 Match object 和提取第一个元素(它是一个包含整个匹配代码的数据结构)。这将创建一个最终打印的列表,每个元素由两个 CR 分隔。

另一个answer in reddit by bobthecimmerian,为了完整起见,我把它复制到这里:

my $excerpt = q:to/END/;
Here's some unimportant text.
=begin code
This code block is what we're after.
We'll use 'ff' to get it.
=end code
More unimportant text.
=begin code
I want this line.
and this line as well.
HaHa
=end code
More unimport text.
=begin code
Let's to go home.
=end code
END

sub doSomething(Iterator $iter) { 
    my @lines = [];
    my $item := $iter.pull-one;
    until ($item =:= IterationEnd || $item.Str ~~ / '=end code' /) {
       @lines.push($item);
       $item := $iter.pull-one;
    }
    say "Got @lines[]";
}
my Iterator $iter = $excerpt.lines.iterator;
my $item := $iter.pull-one;
until ($item =:= IterationEnd) {
    if ($item.Str ~~ / '=begin code' /) {
       doSomething($iter);
    }
    $item := $iter.pull-one;
}

输出是:

Got This code block is what we're after. We'll use 'ff' to get it.
Got I want this line. and this line as well. HaHa
Got Let's to go home.

使用语法:

#use Grammar::Tracer;
#use Grammar::Debugger;

my $excerpt = q:to/END/;
Here's some unimportant text.
=begin code
This code block is what we're after.
We'll use 'ff' to get it.
=end code
More unimportant text.
=begin code
I want this line.
and this line as well.
HaHa
=end code
More unimport text.
=begin code
Let's to go home.
=end code
END

grammar ExtractSection {
    rule TOP      { ^ <section>+ %% <.comment> $      }
    token section { <line>+ % <.ws>                   }
    token line    { <?!before <comment>> \N+ \n       }  
    token comment { ['=begin code' | '=end code' ] \n }

}

class ExtractSectionAction {
    method TOP($/)      { make $/.values».ast }
    method section($/)  { make ~$/.trim       }
    method line($/)     { make ~$/.trim       }
    method comment($/)  { make Empty          }
}

my $em = ExtractSection.parse($excerpt, :actions(ExtractSectionAction)).ast;

for @$em -> $line {
    say $line;
    say '-' x 35;
}

输出:

Here's some unimportant text.
-----------------------------------
This code block is what we're after.
We'll use 'ff' to get it.
-----------------------------------
More unimportant text.
-----------------------------------
I want this line.
and this line as well.
HaHa
-----------------------------------
More unimport text.
-----------------------------------
Let's to go home.
-----------------------------------

但它包含不相关的行,基于@Brad Gilbert 的解决方案,我将上面的答案更新如下(再次感谢):

#use Grammar::Tracer;
#use Grammar::Debugger;

my $excerpt = q:to/END/;
Here's some unimportant text.
=begin code
This code block is what we're after.
We'll use 'ff' to get it.
=end code
More unimportant text.
=begin code
I want this line.
and this line as well.
HaHa
=end code
More unimport text.
=begin code
Let's to go home.
=end code
END

grammar ExtractSection {
  token start   { ^^ '=begin code' \n          }
  token finish  { ^^ '=end code' \n            }
  token line    { ^^ \N+)> \n                  }
  token section { <start> ~ <finish> <line>+?  }
  token comment { ^^\N+ \n                     }
  token TOP     { [<section> || <comment>]+    } 
}

class ExtractSectionAction {
    method TOP($/)     { make @<section>».ast.List }
    method section($/) { make ~«@<line>.List       }
    method line($/)    { make ~$/.trim             }
    method comment($/) { make Empty                }
}

my $em = ExtractSection.parse($excerpt, :actions(ExtractSectionAction)).ast;

for @$em -> $line {
    say $line.perl;
    say '-' x 35;
}

输出为:

$("This code block is what we're after.", "We'll use 'ff' to get it.")
-----------------------------------
$("I want this line.", "and this line as well.", "HaHa")
-----------------------------------
$("Let's to go home.",)
-----------------------------------

所以它按预期工作。

可能是另一种解决方案,使用rotor

my $excerpt = q:to/END/;
Here's some unimportant text.
=begin code
This code block is what we're after.
We'll use 'ff' to get it.
=end code
More unimportant text.
=begin code
I want this line.
and this line as well.
HaHa
=end code
More unimport text.
=begin code
Let's to go home.
=end code
END

my @sections =
gather for $excerpt.lines -> $line {
    if $line ~~ /'=begin code'/ ff $line ~~ /'end code'/  {  
      take $line.trim;
    }
}

my @idx = # gather take the indices of every `=begin code` and `=end code`
gather for @sections.kv -> $k, $v {
    if $v ~~ /'=begin code'/ or $v ~~ /'end code'/ {
        take $k;
    }
}

my @r = # gather take the lines except every line of `=begin code` and `=end code`
gather for @sections.kv -> $k, $v {
    if $v !~~ /'=begin code' | '=end code'/  {
        take $v;
    }
}

my @counts = @idx.rotor(2)».minmax».elems »-» 2;
say @r.rotor(|@counts).perl;

输出:

(("This code block is what we're after.", "We'll use 'ff' to get it."), ("I want this line.", "and this line as well.", "HaHa"), ("Let's to go home.",)).Seq

另一个答案:

my $excerpt = q:to/END/;
Here's some unimportant text.
=begin code
This code block is what we're after.
We'll use 'ff' to get it.
=end code
More unimportant text.
=begin code
I want this line.
and this line as well.
HaHa
=end code
More unimport text.
=begin code
Let's to go home.
=end code
END

for $excerpt.comb(/'=begin code' \s* <( .+? )> \s+ '=end code' /) -> $c {
    say $c;
    say '-' x 15;
}

使用 comb 运算符:

my $str = q:to/EOS/;
Here's some unimportant text.
=begin code
This code block is what we're after.
We'll use 'ff' to get it.
=end code
More unimportant text.
=begin code
I want this line.
and this line as well.
HaHa.
=end code
More unimport text.
=begin code
Let's go home.
=end code
EOS

my token separator { '=begin code' \n | '=end code' \n }
my token lines { [<!separator> .]+ }

say $str.comb(
/
    <lines>       # match lines that not start with
                  # =begin code or =end code
    <separator>   # match lines that start with
                  # =begin code or =end code
    <(            # start capture
        <lines>+  # match lines between
                  # =begin code and =end code
    )>            # end capture
    <separator>   # match lines that start with
                  # =begin code or =end code
/).raku;

输出:

("This code block is what we're after.\nWe'll use 'ff' to get it.\n", "I want this line.\nand this line as well.\nHaHa.\n", "Let's go home.\n").Seq