Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The use of flip-flop operator in Perl 6

Tags:

raku

I see the use of flip-flop in doc.perl6.org, see the code below :

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

I want to save the lines between =begin code and =end code into separate arrays, such as this:

['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.']

I know grammar can do this, but I want to know if there is a beter way?

like image 975
ohmycloudy Avatar asked Mar 14 '18 14:03

ohmycloudy


2 Answers

You need to specify that you want the matched values to not be included. You do this by adding ^ to the side of the operator you want excluded. In this case it is both sides of the operator.

You also need to collect the values up to have the grouped together. The simplest way in this case is to put that off between matches.
(If you wanted the endpoints included it would require more thought to get it right)

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 you need the result to be an array of arrays (mutable).

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

An alternative would be to use do for with sequences that share the same iterator.
This works because for is more eager than map would be.

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 takes one sequence and turns it into another, but doesn't do anything until you try to get the next value from the sequence.
for starts iterating immediately, only stopping when you tell it to.

So map would cause race conditions even when running on a single thread, but for won't.

like image 143
Brad Gilbert Avatar answered Sep 28 '22 13:09

Brad Gilbert


You can also use good old regular expressions:

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

s for significant whitespace (not really needed), g for extracting all matches (not the first one), .map goes through the returned Match object and extracts the first element (it's a data structure that contains the whole matched code). This creates a List that is ultimatelly printed with every element separated by two CRs.

like image 44
jjmerelo Avatar answered Sep 28 '22 13:09

jjmerelo