<commit_before>class X::Cannot::Lazy { ... }

# Now that Iterable is defined, we add extra methods into Any for the list
# operations. (They can't go into Any right away since we need Attribute to
# define the various roles, and Attribute inherits from Any. We will do a
# re-compose of Attribute to make sure it gets the list methods at the end
# of this file. Note the general pattern for these list-y methods is that
# they check if they have an Iterable already, and if not obtain one to
# work on by doing a .list coercion.
use MONKEY-TYPING;
augment class Any {

    proto method map(|) is nodal { * }
    multi method map(Hash \h) {
        die "Cannot map a {self.^name} to a {h.^name}.
Did you mean to add a stub (\{...\}) or did you mean to .classify?"
    }

    multi method map(\SELF: &block;; :$label, :$item) {
        sequential-map(($item ?? (SELF,) !! SELF).iterator, &block, :$label);
    }

    multi method map(HyperIterable:D: &block;; :$label) {
        # For now we only know how to parallelize when we've only one input
        # value needed per block. For the rest, fall back to sequential.
        if &block.count != 1 {
            sequential-map(self.iterator, &block, :$label)
        }
        else {
            HyperSeq.new(class :: does HyperIterator {
                has $!source;
                has &!block;

                method new(\source, &block) {
                    my \iter = nqp::create(self);
                    nqp::bindattr(iter, self, '$!source', source);
                    nqp::bindattr(iter, self, '&!block', &block);
                    iter
                }

                method fill-buffer(HyperWorkBuffer:D $work, int $items) {
                    $!source.fill-buffer($work, $items);
                }

                method process-buffer(HyperWorkBuffer:D $work) {
                    unless $!source.process-buffer($work) =:= Nil {
                        $work.swap();
                    }
                    my \buffer-mapper = sequential-map($work.input-iterator, &!block, :$label);
                    buffer-mapper.iterator.push-all($work.output);
                    $work
                }

                method configuration() {
                    $!source.configuration
                }
            }.new(self.hyper-iterator, &block))
        }
    }

    sub sequential-map(\source, &block, :$label) {
        # We want map to be fast, so we go to some effort to build special
        # case iterators that can ignore various interesting cases.
        my $count = &block.count;

        # "loop" taking 0 or 1 parameter
        if $count == 1 || $count == 0 || $count === Inf {
            if nqp::istype(&block,Block) && &block.has-phasers {
                Seq.new(class :: does SlippyIterator {
                    has &!block;
                    has $!source;
                    has $!label;
                    has Int $!NEXT;         # SHOULD BE int
                    has Int $!did-init;     # SHOULD BE int
                    has Int $!did-iterate;  # SHOULD BE int

                    method new(&block, $source, $label) {
                        my $iter := nqp::create(self);
                        nqp::bindattr($iter, self, '&!block', &block);
                        nqp::bindattr($iter, self, '$!source', $source);
                        nqp::bindattr($iter, self, '$!label',
                          nqp::decont($label));
                        nqp::bindattr($iter, self, '$!NEXT',
                          &block.has-phaser('NEXT'));
                        $iter
                    }

                    method is-lazy() { $!source.is-lazy }

                    method pull-one() is raw {
                        my int $redo = 1;
                        my $value;
                        my $result;

                        nqp::unless(
                          $!did-init,
                          nqp::stmts(
                            ($!did-init = 1),
                            nqp::if(
                              &!block.has-phaser('FIRST'),
                              nqp::p6setfirstflag(&!block)
                            )
                          )
                        );

                        if $!slipping && nqp::not_i(nqp::eqaddr(($result := self.slip-one),IterationEnd)) {
                            # $result will be returned at the end
                        }
                        elsif nqp::eqaddr(($value := $!source.pull-one),IterationEnd) {
                            $result := $value;
                            nqp::if($!did-iterate,&!block.fire_phasers('LAST'));
                        }
                        else {
                            nqp::while(
                              $redo,
                              nqp::stmts(
                                $redo = 0,
                                nqp::handle(
                                  nqp::stmts(
                                    ($result := &!block($value)),
                                    ($!did-iterate = 1),
                                    nqp::if(
                                      nqp::istype($result, Slip),
                                      nqp::if(
                                        nqp::eqaddr(($result := self.start-slip($result)), IterationEnd),
                                        nqp::if(
                                          nqp::not_i(nqp::eqaddr(($value := $!source.pull-one),IterationEnd)),
                                          ($redo = 1)
                                        ),
                                      )
                                    ),
                                    nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                                  ),
                                  'LABELED', $!label,
                                  'NEXT', nqp::stmts(
                                     ($!did-iterate = 1),
                                     nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                                     nqp::eqaddr(($value := $!source.pull-one), IterationEnd)
                                       ?? ($result := IterationEnd)
                                       !! ($redo = 1)
                                  ),
                                  'REDO', $redo = 1,
                                  'LAST', nqp::stmts(
                                    ($!did-iterate = 1),
                                    ($result := IterationEnd)
                                  )
                                )
                              ),
                              :nohandler);
                        }
                        nqp::if(
                          $!did-iterate && nqp::eqaddr($result,IterationEnd),
                          &!block.fire_phasers('LAST')
                        );
                        $result
                    }

                    method sink-all() {
                        nqp::unless(
                          $!did-init,
                          nqp::stmts(
                            ($!did-init = 1),
                            nqp::if(
                              &!block.has-phaser('FIRST'),
                              nqp::p6setfirstflag(&!block)
                            )
                          )
                        );

                        my int $redo;
                        my int $running = 1;
                        my $value;
                        while $running {
                            nqp::eqaddr(($value := $!source.pull-one()), IterationEnd)
                              ?? nqp::stmts(
                                   ($running = 0),
                                   nqp::if(
                                     $!did-iterate,
                                     &!block.fire_phasers('LAST')
                                   );
                                 )
                              !! nqp::stmts(
                                   ($redo = 1),
                                   nqp::while(
                                     $redo,
                                     nqp::stmts(
                                       $redo = 0,
                                       nqp::handle(
                                         nqp::stmts(  # doesn't sink
                                           (&!block($value)),
                                           ($!did-iterate = 1),
                                           nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                                         ),
                                         'LABELED', $!label,
                                         'NEXT', nqp::stmts(
                                           ($!did-iterate = 1),
                                           nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                                           nqp::eqaddr(($value := $!source.pull-one), IterationEnd)
                                             ?? ($running = 0)
                                             !! ($redo = 1)),
                                         'REDO', $redo = 1,
                                         'LAST', nqp::stmts(
                                           ($!did-iterate = 1),
                                           ($running = 0))
                                       )  
                                     ),
                                     :nohandler
                                   ),
                                   nqp::if(
                                     nqp::not_i($running) && $!did-iterate,
                                     &!block.fire_phasers('LAST')
                                   )
                                 );
                        }
                        IterationEnd
                    }
                }.new(&block, source, $label))
            }

            # normal, without phasers
            else {
                Seq.new(class :: does SlippyIterator {
                    has &!block;
                    has $!source;
                    has $!label;

                    method new(&block,$source,$label) {
                        my $iter := nqp::create(self);
                        nqp::bindattr($iter, self, '&!block', &block);
                        nqp::bindattr($iter, self, '$!source', $source);
                        nqp::bindattr($iter, self, '$!label', nqp::decont($label));
                        $iter
                    }

                    method is-lazy() { $!source.is-lazy }

                    method pull-one() is raw {
                        my int $redo = 1;
                        my $value;
                        my $result;

                        if $!slipping && nqp::not_i(nqp::eqaddr(
                          ($result := self.slip-one),
                          IterationEnd
                        )) {
                            # $result will be returned at the end
                        }
                        elsif nqp::eqaddr(
                          ($value := $!source.pull-one),
                          IterationEnd
                        ) {
                            $result := $value
                        }
                        else {
                          nqp::while(
                            $redo,
                            nqp::stmts(
                              $redo = 0,
                              nqp::handle(
                                nqp::if(
                                  nqp::istype(($result := &!block($value)),Slip),
                                  nqp::if(
                                    nqp::eqaddr(
                                      ($result := self.start-slip($result)), IterationEnd),
                                    nqp::if(
                                      nqp::not_i(nqp::eqaddr(
                                        ($value := $!source.pull-one),
                                        IterationEnd
                                      )),
                                      $redo = 1
                                    )
                                  )
                                ),
                                'LABELED',
                                $!label,
                                'NEXT',
                                nqp::if(
                                  nqp::eqaddr(
                                    ($value := $!source.pull-one),IterationEnd
                                  ),
                                  ($result := IterationEnd),
                                  ($redo = 1)
                                ),
                                'REDO',
                                ($redo = 1),
                                'LAST',
                                ($result := IterationEnd)
                              ),
                            ),
                          :nohandler);
                        }
                        $result
                    }

                    method push-all($target) {
                        my int $redo;
                        my $value;
                        my $result;

                        nqp::until(
                          nqp::eqaddr(
                            ($value := $!source.pull-one),IterationEnd
                          ),
                          nqp::stmts(
                            ($redo = 1),
                            nqp::while(
                              $redo,
                              nqp::stmts(
                                ($redo = 0),
                                nqp::handle(
                                  nqp::if(
                                    nqp::istype(
                                      ($result := &!block($value)),Slip
                                    ) && nqp::defined($result),
                                    $result.iterator.push-all($target),
                                    $target.push($result)
                                  ),
                                  'LABELED',
                                  $!label,
                                  'REDO',
                                  ($redo = 1),
                                  'LAST',
                                  (return IterationEnd)
                                )
                              ),
                              :nohandler
                            )
                          )
                        );
                        IterationEnd
                    }

                    method sink-all() {
                        my int $redo;
                        my int $running = 1;
                        my $value;

# for some reason, this scope is needed.  Otherwise, settings compilation
# will end in the mast stage with something like:
#   Cannot reference undeclared local '__lowered_lex_3225'
{
                        nqp::while(
                          $running,
                          nqp::if(
                            nqp::eqaddr(
                              ($value := $!source.pull-one()),IterationEnd
                            ),
                            ($running = 0),
                            nqp::stmts(
                              ($redo = 1),
                              nqp::while(
                                $redo,
                                nqp::stmts(
                                  $redo = 0,
                                  nqp::handle(  # doesn't sink
                                    (&!block($value)),
                                    'LABELED',
                                    $!label,
                                    'NEXT',
                                    nqp::if(
                                      nqp::eqaddr(
                                        ($value := $!source.pull-one),
                                        IterationEnd
                                      ),
                                      ($running = 0),
                                      ($redo = 1)
                                    ),
                                    'REDO',
                                    ($redo = 1),
                                    'LAST',
                                    ($running = 0)
                                  )
                                ),
                              :nohandler
                              )
                            )
                          )
                        );
} # needed for some reason
                        IterationEnd
                    }
                }.new(&block,source,$label))
            }
        }

        # code without phasers taking 2 params, typically .kv -> $k, $v
        elsif $count == 2 && !(nqp::istype(&block,Block) && &block.has-phasers) {
            Seq.new(class :: does SlippyIterator {
                has &!block;
                has $!source;
                has $!label;

                method new(&block,$source,$label) {
                    my $iter := nqp::create(self);
                    nqp::bindattr($iter, self, '&!block', &block);
                    nqp::bindattr($iter, self, '$!source', $source);
                    nqp::bindattr($iter, self, '$!label', nqp::decont($label));
                    $iter
                }

                method is-lazy() { $!source.is-lazy }

                method pull-one() is raw {
                    my int $redo = 1;
                    my $value;
                    my $value2;
                    my $result;

                    if $!slipping && nqp::not_i(nqp::eqaddr(
                      ($result := self.slip-one),
                      IterationEnd
                    )) {
                        # $result will be returned at the end
                    }
                    elsif nqp::eqaddr(
                      ($value := $!source.pull-one),
                      IterationEnd
                    ) {
                        $result := IterationEnd;
                    }
                    else {
                      nqp::while(
                        $redo,
                        nqp::stmts(
                          $redo = 0,
                          nqp::handle(
                            nqp::stmts(
                              nqp::if(
                                nqp::eqaddr(($value2 := $!source.pull-one),IterationEnd),
                                nqp::if(                                 # don't have 2 params
                                  nqp::istype(($result := &!block($value)),Slip),
                                  ($result := self.start-slip($result))  # don't care if empty
                                ),
                                nqp::if(
                                  nqp::istype(($result := &!block($value,$value2)),Slip),
                                  nqp::if(
                                    nqp::eqaddr(($result := self.start-slip($result)),IterationEnd),
                                    nqp::unless(
                                      nqp::eqaddr(($value := $!source.pull-one),IterationEnd),
                                      ($redo = 1)
                                    )
                                  )
                                )
                              )
                            ),
                            'LABELED',
                            $!label,
                            'NEXT',
                            nqp::if(
                              nqp::eqaddr(
                                ($value := $!source.pull-one),IterationEnd
                              ),
                              ($result := IterationEnd),
                              ($redo = 1)
                            ),
                            'REDO',
                            ($redo = 1),
                            'LAST',
                            ($result := IterationEnd)
                          ),
                        ),
                      :nohandler);
                    }
                    $result
                }

                method push-all($target) {
                    my int $redo;
                    my $value;
                    my $value2;
                    my $result;

                    nqp::until(
                      nqp::eqaddr(
                        ($value := $!source.pull-one),IterationEnd
                      ),
                      nqp::stmts(
                        ($redo = 1),
                        nqp::while(
                          $redo,
                          nqp::stmts(
                            ($redo = 0),
                            nqp::handle(
                              nqp::if(
                                nqp::eqaddr(($value2 := $!source.pull-one),IterationEnd),
                                nqp::stmts(
                                  ($result := &!block($value)),
                                  nqp::if(
                                    (nqp::istype($result,Slip) && nqp::defined($result)),
                                    $result.iterator.push-all($target),
                                    $target.push($result)
                                  ),
                                  (return IterationEnd)
                                ),
                                nqp::stmts(
                                  ($result := &!block($value,$value2)),
                                  nqp::if(
                                    (nqp::istype($result,Slip) && nqp::defined($result)),
                                    $result.iterator.push-all($target),
                                    $target.push($result)
                                  )
                                )
                              ),
                              'LABELED',
                              $!label,
                              'REDO',
                              ($redo = 1),
                              'LAST',
                              (return IterationEnd)
                            )
                          ),
                          :nohandler
                        )
                      )
                    );
                    IterationEnd
                }

                method sink-all() {
                    my int $redo;
                    my int $running = 1;
                    my $value;
                    my $value2;

# for some reason, this scope is needed.  Otherwise, settings compilation
# will end in the mast stage with something like:
#   Cannot reference undeclared local '__lowered_lex_3225'
{
                    nqp::while(
                      $running,
                      nqp::if(
                        nqp::eqaddr(
                          ($value := $!source.pull-one()),IterationEnd
                        ),
                        ($running = 0),
                        nqp::stmts(
                          ($redo = 1),
                          nqp::while(
                            $redo,
                            nqp::stmts(
                              $redo = 0,
                              nqp::handle(  # doesn't sink
                                nqp::if(
                                  nqp::eqaddr(($value2 := $!source.pull-one),IterationEnd),
                                  nqp::stmts(
                                    (&!block($value)),
                                    (return IterationEnd)
                                  ),
                                  (&!block($value,$value2))
                                ),
                                'LABELED',
                                $!label,
                                'NEXT',
                                nqp::if(
                                  nqp::eqaddr(
                                    ($value := $!source.pull-one),
                                    IterationEnd
                                  ),
                                  ($running = 0),
                                  ($redo = 1)
                                ),
                                'REDO',
                                ($redo = 1),
                                'LAST',
                                ($running = 0)
                              )
                            ),
                          :nohandler
                          )
                        )
                      )
                    );
} # needed for some reason
                    IterationEnd
                }
            }.new(&block,source,$label))
        }

        # loop/map taking more than 1 param
        else {
            Seq.new(class :: does SlippyIterator {
                has &!block;
                has $!source;
                has $!count;
                has $!label;
                has $!value-buffer;
                has $!did-init;
                has $!did-iterate;
                has $!NEXT;
                has $!CAN_FIRE_PHASERS;

                method new(&block, $source, $count, $label) {
                    my $iter := nqp::create(self);
                    nqp::bindattr($iter, self, '&!block', &block);
                    nqp::bindattr($iter, self, '$!source', $source);
                    nqp::bindattr($iter, self, '$!count', $count);
                    nqp::bindattr($iter, self, '$!label', nqp::decont($label));
                    $iter
                }

                method is-lazy() { $!source.is-lazy }

                method pull-one() is raw {
                    $!value-buffer.DEFINITE
                        ?? nqp::setelems($!value-buffer, 0)
                        !! ($!value-buffer := IterationBuffer.new);
                    my int $redo = 1;
                    my $result;

                    if !$!did-init && nqp::can(&!block, 'fire_phasers') {
                        $!did-init         = 1;
                        $!CAN_FIRE_PHASERS = 1;
                        $!NEXT             = &!block.has-phaser('NEXT');
                        nqp::p6setfirstflag(&!block)
                          if &!block.has-phaser('FIRST');
                    }

                    if $!slipping && !(($result := self.slip-one()) =:= IterationEnd) {
                        # $result will be returned at the end
                    }
                    elsif $!source.push-exactly($!value-buffer, $!count) =:= IterationEnd
                            && nqp::elems($!value-buffer) == 0 {
                        $result := IterationEnd
                    }
                    else {
                        nqp::while(
                          $redo,
                          nqp::stmts(
                            $redo = 0,
                            nqp::handle(
                              nqp::stmts(
                                ($result := nqp::p6invokeflat(&!block, $!value-buffer)),
                                ($!did-iterate = 1),
                                nqp::if(
                                  nqp::istype($result, Slip),
                                  nqp::stmts(
                                    ($result := self.start-slip($result)),
                                    nqp::if(
                                      nqp::eqaddr($result, IterationEnd),
                                      nqp::stmts(
                                        (nqp::setelems($!value-buffer, 0)),
                                        ($redo = 1
                                          unless nqp::eqaddr(
                                            $!source.push-exactly($!value-buffer, $!count),
                                            IterationEnd)
                                          && nqp::elems($!value-buffer) == 0)
                                      )
                                    )
                                  )
                                ),
                                nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                              ),
                              'LABELED', $!label,
                              'NEXT', nqp::stmts(
                                ($!did-iterate = 1),
                                nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                                  (nqp::setelems($!value-buffer, 0)),
                                  nqp::eqaddr($!source.push-exactly($!value-buffer, $!count), IterationEnd)
                                  && nqp::elems($!value-buffer) == 0
                                    ?? ($result := IterationEnd)
                                    !! ($redo = 1)),
                              'REDO', $redo = 1,
                              'LAST', nqp::stmts(
                                ($!did-iterate = 1),
                                ($result := IterationEnd)
                              )
                            )
                          ),
                        :nohandler);
                    }
                    &!block.fire_phasers('LAST')
                      if $!CAN_FIRE_PHASERS
                      && $!did-iterate
                      && nqp::eqaddr($result, IterationEnd);
                    $result
                }
            }.new(&block, source, $count, $label))
        }
    }

    proto method flatmap (|) is nodal { * }
    multi method flatmap(&block, :$label) {
        self.map(&block, :$label).flat
    }

    method !grep-k(Callable:D $test) {
        Seq.new(class :: does Iterator {
            has  Mu $!iter;
            has  Mu $!test;
            has int $!index;
            method !SET-SELF(\list,Mu \test) {
                $!iter  = list.iterator;
                $!test := test;
                $!index = -1;
                self
            }
            method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) }
            method pull-one() is raw {
                $!index = $!index + 1
                  until ($_ := $!iter.pull-one) =:= IterationEnd || $!test($_);
                $_ =:= IterationEnd
                  ?? IterationEnd
                  !! nqp::p6box_i($!index = $!index + 1)
            }
            method push-all($target) {
                until ($_ := $!iter.pull-one) =:= IterationEnd {
                    $!index = $!index + 1;
                    $target.push(nqp::p6box_i($!index)) if $!test($_);
                }
                IterationEnd
            }
        }.new(self, $test))
    }
    method !grep-kv(Callable:D $test) {
        Seq.new(class :: does Iterator {
            has  Mu $!iter;
            has  Mu $!test;
            has int $!index;
            has Mu $!value;
            method !SET-SELF(\list,Mu \test) {
                $!iter  = list.iterator;
                $!test := test;
                $!index = -1;
                self
            }
            method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) }
            method pull-one() is raw {
                if $!value.DEFINITE {
                    my \tmp  = $!value;
                    $!value := Mu;
                    tmp
                }
                else {
                    $!index = $!index + 1
                      until ($_ := $!iter.pull-one) =:= IterationEnd
                        || $!test($_);
                    if $_ =:= IterationEnd {
                        IterationEnd;
                    }
                    else {
                        $!value := $_;
                        nqp::p6box_i($!index = $!index + 1)
                    }
                }
            }
            method push-all($target) {
                nqp::until(
                  nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
                  nqp::stmts(
                    $!index = nqp::add_i($!index,1);
                    nqp::if(
                      $!test($_),
                      nqp::stmts(  # doesn't sink
                        $target.push(nqp::p6box_i($!index));
                        $target.push($_);
                      )
                    )
                  )
                );
                IterationEnd
            }
        }.new(self, $test))
    }
    method !grep-p(Callable:D $test) {
        Seq.new(class :: does Iterator {
            has  Mu $!iter;
            has  Mu $!test;
            has int $!index;
            method !SET-SELF(\list,Mu \test) {
                $!iter  = list.iterator;
                $!test := test;
                $!index = -1;
                self
            }
            method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) }
            method pull-one() is raw {
                $!index = $!index + 1
                  until ($_ := $!iter.pull-one) =:= IterationEnd || $!test($_);
                $_ =:= IterationEnd
                  ?? IterationEnd
                  !! Pair.new($!index = $!index + 1,$_)
            }
            method push-all($target) {
                until ($_ := $!iter.pull-one) =:= IterationEnd {
                    $!index = $!index + 1;
                    $target.push(Pair.new($!index,$_)) if $!test($_);
                }
                IterationEnd
            }
        }.new(self, $test))
    }

    role Grepper does Iterator {
        has Mu $!iter;
        has Mu $!test;
        method !SET-SELF(\list,Mu \test) {
            $!iter  = list.iterator;
            $!test := test;
            self
        }
        method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) }
    }
    method !grep-regex(Regex:D $test) {
        Seq.new(class :: does Grepper {
            method pull-one() is raw {
                nqp::until(
                  nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd)
                    || $_.match($!test),
                  Nil
                );
                $_
            }
            method push-all($target) {
                nqp::until(
                  nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
                  nqp::if(  # doesn't sink
                    $_.match($!test),
                    $target.push($_)
                  )
                );
                IterationEnd
            }
        }.new(self, $test))
    }
    method !grep-callable(Callable:D $test) {
        if ($test.count == 1) {
            $test.?has-phasers
              ?? self.map({ next unless $test($_); $_ })  # cannot go fast
              !! Seq.new(class :: does Grepper {
                     method pull-one() is raw {
                         nqp::until(
                           nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd)
                             || $!test($_),
                           Nil
                         );
                         $_
                     }
                     method push-all($target) {
                         nqp::until(
                           nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
                           nqp::if(  # doesn't sink
                             $!test($_),
                             $target.push($_)
                           )
                         );
                         IterationEnd
                     }
                     method sink-all() {
                         nqp::until(
                           nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
                           $!test($_)
                         );
                         IterationEnd
                     }
                 }.new(self, $test))
        } else {
            my role CheatArity {
                has $!arity;
                has $!count;

                method set-cheat($new-arity, $new-count) {
                    $!arity = $new-arity;
                    $!count = $new-count;
                }

                method arity(Code:D:) { $!arity }
                method count(Code:D:) { $!count }
            }

            my &tester = -> |c {
                #note "*cough* {c.perl} -> {$test(|c).perl}";
                next unless $test(|c);
                c.list
            } but CheatArity;

            &tester.set-cheat($test.arity, $test.count);

            self.map(&tester);
        }
    }
    method !grep-accepts(Mu $test) {
        Seq.new(class :: does Grepper {
            method pull-one() is raw {
                nqp::until(
                  nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd)
                    || $!test.ACCEPTS($_),
                  Nil
                );
                $_
            }
            method push-all($target) {
                nqp::until(
                  nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
                  nqp::if(  # doesn't sink
                    $!test.ACCEPTS($_),
                    $target.push($_)
                  )
                );
                IterationEnd
            }
        }.new(self, $test))
    }

    method !first-result(\index,\value,$what,%a) is raw {
        nqp::stmts(
          (my $storage := nqp::getattr(%a,Map,'$!storage')),
          nqp::if(
            nqp::elems($storage),
            nqp::if(
              nqp::iseq_i(nqp::elems($storage),1),
              nqp::if(
                nqp::atkey($storage,"k"),
                nqp::p6box_i(index),
                nqp::if(
                  nqp::atkey($storage,"p"),
                  Pair.new(index,value),
                  nqp::if(
                    nqp::atkey($storage,"v"),
                    value,
                    nqp::if(
                      nqp::atkey($storage,"kv"),
                      (index,value),
                      nqp::stmts(
                        (my str $key =
                          nqp::iterkey_s(nqp::shift(nqp::iterator($storage)))),
                        nqp::if(
                          (nqp::iseq_s($key,"k")
                            || nqp::iseq_s($key,"p")
                            || nqp::iseq_s($key,"kv")),
                          value,
                          nqp::if(
                            nqp::iseq_s($key,"v"),
                            Failure.new("Specified a negated :v adverb"),
                            Failure.new(X::Adverb.new(
                              :$what,
                              :source(try { self.VAR.name } // self.WHAT.perl),
                              :unexpected(%a.keys)))
                          )
                        )
                      )
                    )
                  )
                )
              ),
              Failure.new(X::Adverb.new(
                :$what,
                :source(try { self.VAR.name } // self.WHAT.perl),
                :nogo(%a.keys.grep: /k|v|p/)
                :unexpected(%a.keys.grep: { !.match(/k|v|p/) } )))
            ),
            value
          )
        )
    }

    proto method grep(|) is nodal { * }
    multi method grep(Bool:D $t) {
        Failure.new(X::Match::Bool.new( type => '.grep' ))
    }
    multi method grep(Mu $t) {
        my $storage := nqp::getattr(%_,Map,'$!storage');
        if nqp::iseq_i(nqp::elems($storage),0) {
            nqp::istype($t,Regex:D)
              ?? self!grep-regex: $t
              !! nqp::istype($t,Callable:D)
                   ?? self!grep-callable: $t
                   !! self!grep-accepts: $t
        }
        elsif nqp::iseq_i(nqp::elems($storage),1) {
            if nqp::atkey($storage,"k") {
                nqp::istype($t,Regex:D)
                  ?? self!grep-k: { $_.match($t) }
                  !! nqp::istype($t,Callable:D)<commit_msg>Add some inline docs for !first-result<commit_after>class X::Cannot::Lazy { ... }

# Now that Iterable is defined, we add extra methods into Any for the list
# operations. (They can't go into Any right away since we need Attribute to
# define the various roles, and Attribute inherits from Any. We will do a
# re-compose of Attribute to make sure it gets the list methods at the end
# of this file. Note the general pattern for these list-y methods is that
# they check if they have an Iterable already, and if not obtain one to
# work on by doing a .list coercion.
use MONKEY-TYPING;
augment class Any {

    proto method map(|) is nodal { * }
    multi method map(Hash \h) {
        die "Cannot map a {self.^name} to a {h.^name}.
Did you mean to add a stub (\{...\}) or did you mean to .classify?"
    }

    multi method map(\SELF: &block;; :$label, :$item) {
        sequential-map(($item ?? (SELF,) !! SELF).iterator, &block, :$label);
    }

    multi method map(HyperIterable:D: &block;; :$label) {
        # For now we only know how to parallelize when we've only one input
        # value needed per block. For the rest, fall back to sequential.
        if &block.count != 1 {
            sequential-map(self.iterator, &block, :$label)
        }
        else {
            HyperSeq.new(class :: does HyperIterator {
                has $!source;
                has &!block;

                method new(\source, &block) {
                    my \iter = nqp::create(self);
                    nqp::bindattr(iter, self, '$!source', source);
                    nqp::bindattr(iter, self, '&!block', &block);
                    iter
                }

                method fill-buffer(HyperWorkBuffer:D $work, int $items) {
                    $!source.fill-buffer($work, $items);
                }

                method process-buffer(HyperWorkBuffer:D $work) {
                    unless $!source.process-buffer($work) =:= Nil {
                        $work.swap();
                    }
                    my \buffer-mapper = sequential-map($work.input-iterator, &!block, :$label);
                    buffer-mapper.iterator.push-all($work.output);
                    $work
                }

                method configuration() {
                    $!source.configuration
                }
            }.new(self.hyper-iterator, &block))
        }
    }

    sub sequential-map(\source, &block, :$label) {
        # We want map to be fast, so we go to some effort to build special
        # case iterators that can ignore various interesting cases.
        my $count = &block.count;

        # "loop" taking 0 or 1 parameter
        if $count == 1 || $count == 0 || $count === Inf {
            if nqp::istype(&block,Block) && &block.has-phasers {
                Seq.new(class :: does SlippyIterator {
                    has &!block;
                    has $!source;
                    has $!label;
                    has Int $!NEXT;         # SHOULD BE int
                    has Int $!did-init;     # SHOULD BE int
                    has Int $!did-iterate;  # SHOULD BE int

                    method new(&block, $source, $label) {
                        my $iter := nqp::create(self);
                        nqp::bindattr($iter, self, '&!block', &block);
                        nqp::bindattr($iter, self, '$!source', $source);
                        nqp::bindattr($iter, self, '$!label',
                          nqp::decont($label));
                        nqp::bindattr($iter, self, '$!NEXT',
                          &block.has-phaser('NEXT'));
                        $iter
                    }

                    method is-lazy() { $!source.is-lazy }

                    method pull-one() is raw {
                        my int $redo = 1;
                        my $value;
                        my $result;

                        nqp::unless(
                          $!did-init,
                          nqp::stmts(
                            ($!did-init = 1),
                            nqp::if(
                              &!block.has-phaser('FIRST'),
                              nqp::p6setfirstflag(&!block)
                            )
                          )
                        );

                        if $!slipping && nqp::not_i(nqp::eqaddr(($result := self.slip-one),IterationEnd)) {
                            # $result will be returned at the end
                        }
                        elsif nqp::eqaddr(($value := $!source.pull-one),IterationEnd) {
                            $result := $value;
                            nqp::if($!did-iterate,&!block.fire_phasers('LAST'));
                        }
                        else {
                            nqp::while(
                              $redo,
                              nqp::stmts(
                                $redo = 0,
                                nqp::handle(
                                  nqp::stmts(
                                    ($result := &!block($value)),
                                    ($!did-iterate = 1),
                                    nqp::if(
                                      nqp::istype($result, Slip),
                                      nqp::if(
                                        nqp::eqaddr(($result := self.start-slip($result)), IterationEnd),
                                        nqp::if(
                                          nqp::not_i(nqp::eqaddr(($value := $!source.pull-one),IterationEnd)),
                                          ($redo = 1)
                                        ),
                                      )
                                    ),
                                    nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                                  ),
                                  'LABELED', $!label,
                                  'NEXT', nqp::stmts(
                                     ($!did-iterate = 1),
                                     nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                                     nqp::eqaddr(($value := $!source.pull-one), IterationEnd)
                                       ?? ($result := IterationEnd)
                                       !! ($redo = 1)
                                  ),
                                  'REDO', $redo = 1,
                                  'LAST', nqp::stmts(
                                    ($!did-iterate = 1),
                                    ($result := IterationEnd)
                                  )
                                )
                              ),
                              :nohandler);
                        }
                        nqp::if(
                          $!did-iterate && nqp::eqaddr($result,IterationEnd),
                          &!block.fire_phasers('LAST')
                        );
                        $result
                    }

                    method sink-all() {
                        nqp::unless(
                          $!did-init,
                          nqp::stmts(
                            ($!did-init = 1),
                            nqp::if(
                              &!block.has-phaser('FIRST'),
                              nqp::p6setfirstflag(&!block)
                            )
                          )
                        );

                        my int $redo;
                        my int $running = 1;
                        my $value;
                        while $running {
                            nqp::eqaddr(($value := $!source.pull-one()), IterationEnd)
                              ?? nqp::stmts(
                                   ($running = 0),
                                   nqp::if(
                                     $!did-iterate,
                                     &!block.fire_phasers('LAST')
                                   );
                                 )
                              !! nqp::stmts(
                                   ($redo = 1),
                                   nqp::while(
                                     $redo,
                                     nqp::stmts(
                                       $redo = 0,
                                       nqp::handle(
                                         nqp::stmts(  # doesn't sink
                                           (&!block($value)),
                                           ($!did-iterate = 1),
                                           nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                                         ),
                                         'LABELED', $!label,
                                         'NEXT', nqp::stmts(
                                           ($!did-iterate = 1),
                                           nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                                           nqp::eqaddr(($value := $!source.pull-one), IterationEnd)
                                             ?? ($running = 0)
                                             !! ($redo = 1)),
                                         'REDO', $redo = 1,
                                         'LAST', nqp::stmts(
                                           ($!did-iterate = 1),
                                           ($running = 0))
                                       )  
                                     ),
                                     :nohandler
                                   ),
                                   nqp::if(
                                     nqp::not_i($running) && $!did-iterate,
                                     &!block.fire_phasers('LAST')
                                   )
                                 );
                        }
                        IterationEnd
                    }
                }.new(&block, source, $label))
            }

            # normal, without phasers
            else {
                Seq.new(class :: does SlippyIterator {
                    has &!block;
                    has $!source;
                    has $!label;

                    method new(&block,$source,$label) {
                        my $iter := nqp::create(self);
                        nqp::bindattr($iter, self, '&!block', &block);
                        nqp::bindattr($iter, self, '$!source', $source);
                        nqp::bindattr($iter, self, '$!label', nqp::decont($label));
                        $iter
                    }

                    method is-lazy() { $!source.is-lazy }

                    method pull-one() is raw {
                        my int $redo = 1;
                        my $value;
                        my $result;

                        if $!slipping && nqp::not_i(nqp::eqaddr(
                          ($result := self.slip-one),
                          IterationEnd
                        )) {
                            # $result will be returned at the end
                        }
                        elsif nqp::eqaddr(
                          ($value := $!source.pull-one),
                          IterationEnd
                        ) {
                            $result := $value
                        }
                        else {
                          nqp::while(
                            $redo,
                            nqp::stmts(
                              $redo = 0,
                              nqp::handle(
                                nqp::if(
                                  nqp::istype(($result := &!block($value)),Slip),
                                  nqp::if(
                                    nqp::eqaddr(
                                      ($result := self.start-slip($result)), IterationEnd),
                                    nqp::if(
                                      nqp::not_i(nqp::eqaddr(
                                        ($value := $!source.pull-one),
                                        IterationEnd
                                      )),
                                      $redo = 1
                                    )
                                  )
                                ),
                                'LABELED',
                                $!label,
                                'NEXT',
                                nqp::if(
                                  nqp::eqaddr(
                                    ($value := $!source.pull-one),IterationEnd
                                  ),
                                  ($result := IterationEnd),
                                  ($redo = 1)
                                ),
                                'REDO',
                                ($redo = 1),
                                'LAST',
                                ($result := IterationEnd)
                              ),
                            ),
                          :nohandler);
                        }
                        $result
                    }

                    method push-all($target) {
                        my int $redo;
                        my $value;
                        my $result;

                        nqp::until(
                          nqp::eqaddr(
                            ($value := $!source.pull-one),IterationEnd
                          ),
                          nqp::stmts(
                            ($redo = 1),
                            nqp::while(
                              $redo,
                              nqp::stmts(
                                ($redo = 0),
                                nqp::handle(
                                  nqp::if(
                                    nqp::istype(
                                      ($result := &!block($value)),Slip
                                    ) && nqp::defined($result),
                                    $result.iterator.push-all($target),
                                    $target.push($result)
                                  ),
                                  'LABELED',
                                  $!label,
                                  'REDO',
                                  ($redo = 1),
                                  'LAST',
                                  (return IterationEnd)
                                )
                              ),
                              :nohandler
                            )
                          )
                        );
                        IterationEnd
                    }

                    method sink-all() {
                        my int $redo;
                        my int $running = 1;
                        my $value;

# for some reason, this scope is needed.  Otherwise, settings compilation
# will end in the mast stage with something like:
#   Cannot reference undeclared local '__lowered_lex_3225'
{
                        nqp::while(
                          $running,
                          nqp::if(
                            nqp::eqaddr(
                              ($value := $!source.pull-one()),IterationEnd
                            ),
                            ($running = 0),
                            nqp::stmts(
                              ($redo = 1),
                              nqp::while(
                                $redo,
                                nqp::stmts(
                                  $redo = 0,
                                  nqp::handle(  # doesn't sink
                                    (&!block($value)),
                                    'LABELED',
                                    $!label,
                                    'NEXT',
                                    nqp::if(
                                      nqp::eqaddr(
                                        ($value := $!source.pull-one),
                                        IterationEnd
                                      ),
                                      ($running = 0),
                                      ($redo = 1)
                                    ),
                                    'REDO',
                                    ($redo = 1),
                                    'LAST',
                                    ($running = 0)
                                  )
                                ),
                              :nohandler
                              )
                            )
                          )
                        );
} # needed for some reason
                        IterationEnd
                    }
                }.new(&block,source,$label))
            }
        }

        # code without phasers taking 2 params, typically .kv -> $k, $v
        elsif $count == 2 && !(nqp::istype(&block,Block) && &block.has-phasers) {
            Seq.new(class :: does SlippyIterator {
                has &!block;
                has $!source;
                has $!label;

                method new(&block,$source,$label) {
                    my $iter := nqp::create(self);
                    nqp::bindattr($iter, self, '&!block', &block);
                    nqp::bindattr($iter, self, '$!source', $source);
                    nqp::bindattr($iter, self, '$!label', nqp::decont($label));
                    $iter
                }

                method is-lazy() { $!source.is-lazy }

                method pull-one() is raw {
                    my int $redo = 1;
                    my $value;
                    my $value2;
                    my $result;

                    if $!slipping && nqp::not_i(nqp::eqaddr(
                      ($result := self.slip-one),
                      IterationEnd
                    )) {
                        # $result will be returned at the end
                    }
                    elsif nqp::eqaddr(
                      ($value := $!source.pull-one),
                      IterationEnd
                    ) {
                        $result := IterationEnd;
                    }
                    else {
                      nqp::while(
                        $redo,
                        nqp::stmts(
                          $redo = 0,
                          nqp::handle(
                            nqp::stmts(
                              nqp::if(
                                nqp::eqaddr(($value2 := $!source.pull-one),IterationEnd),
                                nqp::if(                                 # don't have 2 params
                                  nqp::istype(($result := &!block($value)),Slip),
                                  ($result := self.start-slip($result))  # don't care if empty
                                ),
                                nqp::if(
                                  nqp::istype(($result := &!block($value,$value2)),Slip),
                                  nqp::if(
                                    nqp::eqaddr(($result := self.start-slip($result)),IterationEnd),
                                    nqp::unless(
                                      nqp::eqaddr(($value := $!source.pull-one),IterationEnd),
                                      ($redo = 1)
                                    )
                                  )
                                )
                              )
                            ),
                            'LABELED',
                            $!label,
                            'NEXT',
                            nqp::if(
                              nqp::eqaddr(
                                ($value := $!source.pull-one),IterationEnd
                              ),
                              ($result := IterationEnd),
                              ($redo = 1)
                            ),
                            'REDO',
                            ($redo = 1),
                            'LAST',
                            ($result := IterationEnd)
                          ),
                        ),
                      :nohandler);
                    }
                    $result
                }

                method push-all($target) {
                    my int $redo;
                    my $value;
                    my $value2;
                    my $result;

                    nqp::until(
                      nqp::eqaddr(
                        ($value := $!source.pull-one),IterationEnd
                      ),
                      nqp::stmts(
                        ($redo = 1),
                        nqp::while(
                          $redo,
                          nqp::stmts(
                            ($redo = 0),
                            nqp::handle(
                              nqp::if(
                                nqp::eqaddr(($value2 := $!source.pull-one),IterationEnd),
                                nqp::stmts(
                                  ($result := &!block($value)),
                                  nqp::if(
                                    (nqp::istype($result,Slip) && nqp::defined($result)),
                                    $result.iterator.push-all($target),
                                    $target.push($result)
                                  ),
                                  (return IterationEnd)
                                ),
                                nqp::stmts(
                                  ($result := &!block($value,$value2)),
                                  nqp::if(
                                    (nqp::istype($result,Slip) && nqp::defined($result)),
                                    $result.iterator.push-all($target),
                                    $target.push($result)
                                  )
                                )
                              ),
                              'LABELED',
                              $!label,
                              'REDO',
                              ($redo = 1),
                              'LAST',
                              (return IterationEnd)
                            )
                          ),
                          :nohandler
                        )
                      )
                    );
                    IterationEnd
                }

                method sink-all() {
                    my int $redo;
                    my int $running = 1;
                    my $value;
                    my $value2;

# for some reason, this scope is needed.  Otherwise, settings compilation
# will end in the mast stage with something like:
#   Cannot reference undeclared local '__lowered_lex_3225'
{
                    nqp::while(
                      $running,
                      nqp::if(
                        nqp::eqaddr(
                          ($value := $!source.pull-one()),IterationEnd
                        ),
                        ($running = 0),
                        nqp::stmts(
                          ($redo = 1),
                          nqp::while(
                            $redo,
                            nqp::stmts(
                              $redo = 0,
                              nqp::handle(  # doesn't sink
                                nqp::if(
                                  nqp::eqaddr(($value2 := $!source.pull-one),IterationEnd),
                                  nqp::stmts(
                                    (&!block($value)),
                                    (return IterationEnd)
                                  ),
                                  (&!block($value,$value2))
                                ),
                                'LABELED',
                                $!label,
                                'NEXT',
                                nqp::if(
                                  nqp::eqaddr(
                                    ($value := $!source.pull-one),
                                    IterationEnd
                                  ),
                                  ($running = 0),
                                  ($redo = 1)
                                ),
                                'REDO',
                                ($redo = 1),
                                'LAST',
                                ($running = 0)
                              )
                            ),
                          :nohandler
                          )
                        )
                      )
                    );
} # needed for some reason
                    IterationEnd
                }
            }.new(&block,source,$label))
        }

        # loop/map taking more than 1 param
        else {
            Seq.new(class :: does SlippyIterator {
                has &!block;
                has $!source;
                has $!count;
                has $!label;
                has $!value-buffer;
                has $!did-init;
                has $!did-iterate;
                has $!NEXT;
                has $!CAN_FIRE_PHASERS;

                method new(&block, $source, $count, $label) {
                    my $iter := nqp::create(self);
                    nqp::bindattr($iter, self, '&!block', &block);
                    nqp::bindattr($iter, self, '$!source', $source);
                    nqp::bindattr($iter, self, '$!count', $count);
                    nqp::bindattr($iter, self, '$!label', nqp::decont($label));
                    $iter
                }

                method is-lazy() { $!source.is-lazy }

                method pull-one() is raw {
                    $!value-buffer.DEFINITE
                        ?? nqp::setelems($!value-buffer, 0)
                        !! ($!value-buffer := IterationBuffer.new);
                    my int $redo = 1;
                    my $result;

                    if !$!did-init && nqp::can(&!block, 'fire_phasers') {
                        $!did-init         = 1;
                        $!CAN_FIRE_PHASERS = 1;
                        $!NEXT             = &!block.has-phaser('NEXT');
                        nqp::p6setfirstflag(&!block)
                          if &!block.has-phaser('FIRST');
                    }

                    if $!slipping && !(($result := self.slip-one()) =:= IterationEnd) {
                        # $result will be returned at the end
                    }
                    elsif $!source.push-exactly($!value-buffer, $!count) =:= IterationEnd
                            && nqp::elems($!value-buffer) == 0 {
                        $result := IterationEnd
                    }
                    else {
                        nqp::while(
                          $redo,
                          nqp::stmts(
                            $redo = 0,
                            nqp::handle(
                              nqp::stmts(
                                ($result := nqp::p6invokeflat(&!block, $!value-buffer)),
                                ($!did-iterate = 1),
                                nqp::if(
                                  nqp::istype($result, Slip),
                                  nqp::stmts(
                                    ($result := self.start-slip($result)),
                                    nqp::if(
                                      nqp::eqaddr($result, IterationEnd),
                                      nqp::stmts(
                                        (nqp::setelems($!value-buffer, 0)),
                                        ($redo = 1
                                          unless nqp::eqaddr(
                                            $!source.push-exactly($!value-buffer, $!count),
                                            IterationEnd)
                                          && nqp::elems($!value-buffer) == 0)
                                      )
                                    )
                                  )
                                ),
                                nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                              ),
                              'LABELED', $!label,
                              'NEXT', nqp::stmts(
                                ($!did-iterate = 1),
                                nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                                  (nqp::setelems($!value-buffer, 0)),
                                  nqp::eqaddr($!source.push-exactly($!value-buffer, $!count), IterationEnd)
                                  && nqp::elems($!value-buffer) == 0
                                    ?? ($result := IterationEnd)
                                    !! ($redo = 1)),
                              'REDO', $redo = 1,
                              'LAST', nqp::stmts(
                                ($!did-iterate = 1),
                                ($result := IterationEnd)
                              )
                            )
                          ),
                        :nohandler);
                    }
                    &!block.fire_phasers('LAST')
                      if $!CAN_FIRE_PHASERS
                      && $!did-iterate
                      && nqp::eqaddr($result, IterationEnd);
                    $result
                }
            }.new(&block, source, $count, $label))
        }
    }

    proto method flatmap (|) is nodal { * }
    multi method flatmap(&block, :$label) {
        self.map(&block, :$label).flat
    }

    method !grep-k(Callable:D $test) {
        Seq.new(class :: does Iterator {
            has  Mu $!iter;
            has  Mu $!test;
            has int $!index;
            method !SET-SELF(\list,Mu \test) {
                $!iter  = list.iterator;
                $!test := test;
                $!index = -1;
                self
            }
            method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) }
            method pull-one() is raw {
                $!index = $!index + 1
                  until ($_ := $!iter.pull-one) =:= IterationEnd || $!test($_);
                $_ =:= IterationEnd
                  ?? IterationEnd
                  !! nqp::p6box_i($!index = $!index + 1)
            }
            method push-all($target) {
                until ($_ := $!iter.pull-one) =:= IterationEnd {
                    $!index = $!index + 1;
                    $target.push(nqp::p6box_i($!index)) if $!test($_);
                }
                IterationEnd
            }
        }.new(self, $test))
    }
    method !grep-kv(Callable:D $test) {
        Seq.new(class :: does Iterator {
            has  Mu $!iter;
            has  Mu $!test;
            has int $!index;
            has Mu $!value;
            method !SET-SELF(\list,Mu \test) {
                $!iter  = list.iterator;
                $!test := test;
                $!index = -1;
                self
            }
            method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) }
            method pull-one() is raw {
                if $!value.DEFINITE {
                    my \tmp  = $!value;
                    $!value := Mu;
                    tmp
                }
                else {
                    $!index = $!index + 1
                      until ($_ := $!iter.pull-one) =:= IterationEnd
                        || $!test($_);
                    if $_ =:= IterationEnd {
                        IterationEnd;
                    }
                    else {
                        $!value := $_;
                        nqp::p6box_i($!index = $!index + 1)
                    }
                }
            }
            method push-all($target) {
                nqp::until(
                  nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
                  nqp::stmts(
                    $!index = nqp::add_i($!index,1);
                    nqp::if(
                      $!test($_),
                      nqp::stmts(  # doesn't sink
                        $target.push(nqp::p6box_i($!index));
                        $target.push($_);
                      )
                    )
                  )
                );
                IterationEnd
            }
        }.new(self, $test))
    }
    method !grep-p(Callable:D $test) {
        Seq.new(class :: does Iterator {
            has  Mu $!iter;
            has  Mu $!test;
            has int $!index;
            method !SET-SELF(\list,Mu \test) {
                $!iter  = list.iterator;
                $!test := test;
                $!index = -1;
                self
            }
            method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) }
            method pull-one() is raw {
                $!index = $!index + 1
                  until ($_ := $!iter.pull-one) =:= IterationEnd || $!test($_);
                $_ =:= IterationEnd
                  ?? IterationEnd
                  !! Pair.new($!index = $!index + 1,$_)
            }
            method push-all($target) {
                until ($_ := $!iter.pull-one) =:= IterationEnd {
                    $!index = $!index + 1;
                    $target.push(Pair.new($!index,$_)) if $!test($_);
                }
                IterationEnd
            }
        }.new(self, $test))
    }

    role Grepper does Iterator {
        has Mu $!iter;
        has Mu $!test;
        method !SET-SELF(\list,Mu \test) {
            $!iter  = list.iterator;
            $!test := test;
            self
        }
        method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) }
    }
    method !grep-regex(Regex:D $test) {
        Seq.new(class :: does Grepper {
            method pull-one() is raw {
                nqp::until(
                  nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd)
                    || $_.match($!test),
                  Nil
                );
                $_
            }
            method push-all($target) {
                nqp::until(
                  nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
                  nqp::if(  # doesn't sink
                    $_.match($!test),
                    $target.push($_)
                  )
                );
                IterationEnd
            }
        }.new(self, $test))
    }
    method !grep-callable(Callable:D $test) {
        if ($test.count == 1) {
            $test.?has-phasers
              ?? self.map({ next unless $test($_); $_ })  # cannot go fast
              !! Seq.new(class :: does Grepper {
                     method pull-one() is raw {
                         nqp::until(
                           nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd)
                             || $!test($_),
                           Nil
                         );
                         $_
                     }
                     method push-all($target) {
                         nqp::until(
                           nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
                           nqp::if(  # doesn't sink
                             $!test($_),
                             $target.push($_)
                           )
                         );
                         IterationEnd
                     }
                     method sink-all() {
                         nqp::until(
                           nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
                           $!test($_)
                         );
                         IterationEnd
                     }
                 }.new(self, $test))
        } else {
            my role CheatArity {
                has $!arity;
                has $!count;

                method set-cheat($new-arity, $new-count) {
                    $!arity = $new-arity;
                    $!count = $new-count;
                }

                method arity(Code:D:) { $!arity }
                method count(Code:D:) { $!count }
            }

            my &tester = -> |c {
                #note "*cough* {c.perl} -> {$test(|c).perl}";
                next unless $test(|c);
                c.list
            } but CheatArity;

            &tester.set-cheat($test.arity, $test.count);

            self.map(&tester);
        }
    }
    method !grep-accepts(Mu $test) {
        Seq.new(class :: does Grepper {
            method pull-one() is raw {
                nqp::until(
                  nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd)
                    || $!test.ACCEPTS($_),
                  Nil
                );
                $_
            }
            method push-all($target) {
                nqp::until(
                  nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
                  nqp::if(  # doesn't sink
                    $!test.ACCEPTS($_),
                    $target.push($_)
                  )
                );
                IterationEnd
            }
        }.new(self, $test))
    }

    method !first-result(\index,\value,$what,%a) is raw {
        nqp::stmts(
          (my $storage := nqp::getattr(%a,Map,'$!storage')),
          nqp::if(
            nqp::elems($storage),                       # some adverb
            nqp::if(
              nqp::iseq_i(nqp::elems($storage),1),      # one adverb
              nqp::if(
                nqp::atkey($storage,"k"),               # :k
                nqp::p6box_i(index),
                nqp::if(
                  nqp::atkey($storage,"p"),             # :p
                  Pair.new(index,value),
                  nqp::if(
                    nqp::atkey($storage,"v"),           # :v
                    value,
                    nqp::if(
                      nqp::atkey($storage,"kv"),        # :kv
                      (index,value),
                      nqp::stmts(                       # no truthy or different
                        (my str $key =
                          nqp::iterkey_s(nqp::shift(nqp::iterator($storage)))),
                        nqp::if(
                          (nqp::iseq_s($key,"k")        # :!k || :!p || :!kv
                            || nqp::iseq_s($key,"p")
                            || nqp::iseq_s($key,"kv")),
                          value,
                          nqp::if(
                            nqp::iseq_s($key,"v"),      # :!v
                            Failure.new("Specified a negated :v adverb"),
                            Failure.new(X::Adverb.new(  # :foo ??
                              :$what,
                              :source(try { self.VAR.name } // self.WHAT.perl),
                              :unexpected(%a.keys)))
                          )
                        )
                      )
                    )
                  )
                )
              ),
              Failure.new(X::Adverb.new(                # multiple adverbs ??
                :$what,
                :source(try { self.VAR.name } // self.WHAT.perl),
                :nogo(%a.keys.grep: /k|v|p/)
                :unexpected(%a.keys.grep: { !.match(/k|v|p/) } )))
            ),
            value                                       # no adverb
          )
        )
    }

    proto method grep(|) is nodal { * }
    multi method grep(Bool:D $t) {
        Failure.new(X::Match::Bool.new( type => '.grep' ))
    }
    multi method grep(Mu $t) {
        my $storage := nqp::getattr(%_,Map,'$!storage');
        if nqp::iseq_i(nqp::elems($storage),0) {
            nqp::istype($t,Regex:D)
              ?? self!grep-regex: $t
              !! nqp::istype($t,Callable:D)
                   ?? self!grep-callable: $t
                   !! self!grep-accepts: $t
        }
        elsif nqp::iseq_i(nqp::elems($storage),1) {
            if nqp::atkey($storage,"k") {
                nqp::istype($t,Regex:D)
                  ?? self!grep-k: { $_.match($t) }
                  !! nqp::istype($t,Callable:D)
{'fraction_non_alphanumeric': 0.11259592318609413, 'fraction_numerical': 0.003014280152221148, 'mean_word_length': 0.6613529190835872, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 378, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5809916', 'n_tokens_mistral': 19725, 'n_tokens_neox': 18081, 'n_words': 3309}