view src/core.c/Any-iterable-methods.pm6 @ 0:c341f82e7ad7 default tip

Rakudo branch in cr.ie.u-ryukyu.ac.jp
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Thu, 26 Dec 2019 16:50:27 +0900
parents
children
line wrap: on
line source

class X::Cannot::Map { ... }

# 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:D \hash) {
        X::Cannot::Map.new(
          what       => self.^name,
          using      => "a {hash.^name}",
          suggestion =>
"Did you mean to add a stub (\{ ... \}) or did you mean to .classify?"
        ).throw;
    }
    multi method map(Iterable:D \iterable) {
        X::Cannot::Map.new(
          what       => self.^name,
          using      => "a {iterable.^name}",
          suggestion => 
"Did a * (Whatever) get absorbed by a comma, range, series, or list repetition?
Consider using a block if any of these are necessary for your mapping code."
        ).throw;
    }
    multi method map(|c) {
        X::Cannot::Map.new(
          what       => self.^name,
          using      => "'{c.perl.substr(2).chop}'",
          suggestion => "Did a * (Whatever) get absorbed by a list?"
        ).throw;
    }

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

    my class IterateOneWithPhasers does SlippyIterator {
        has &!block;
        has $!source;
        has $!label;
        has Int $!NEXT;         # SHOULD BE int, but has Int performs better
        has Int $!did-init;     # SHOULD BE int, but has Int performs better
        has Int $!did-iterate;  # SHOULD BE int, but has Int performs better

        method !SET-SELF(\block,\source,\label) {
            nqp::stmts(
              (&!block  := block),
              ($!source := source),
              ($!label  := label),
              ($!NEXT = block.has-phaser('NEXT')),
              self
            )
        }
        method new(\bl,\sou,\la) { nqp::create(self)!SET-SELF(bl,sou,la) }

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

        method pull-one() is raw {
            my int $stopped;
            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 := IterationEnd
            }
            else {
                nqp::until(
                  $stopped,
                  nqp::handle(
                    nqp::stmts(
                      ($stopped = 1),
                      ($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)),
                            ($stopped = 0)
                          ),
                        )
                      ),
                      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)
                         !! ($stopped = 0)
                    ),
                    'REDO', ($stopped = 0),
                    'LAST', nqp::stmts(
                      ($!did-iterate = 1),
                      ($result := IterationEnd)
                    )
                  ),
                  :nohandler
                )
            }
            nqp::if(
              $!did-iterate && nqp::eqaddr($result,IterationEnd),
              &!block.fire_if_phasers('LAST')
            );
            $result
        }

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

            my int $stopped;
            my int $done;
            my $pulled;
            my $value;

            nqp::if(
              $!slipping,
              nqp::until(
                nqp::eqaddr(($value := self.slip-one),IterationEnd),
                target.push($value)
              )
            );

            until $done
                || nqp::eqaddr(($value := $!source.pull-one),IterationEnd) {
                nqp::stmts(
                  ($stopped = 0),
                  nqp::until(
                    $stopped,
                    nqp::stmts(
                      ($stopped = 1),
                      nqp::handle(
                        nqp::stmts(  # doesn't sink
                          ($pulled := &!block($value)),
                          ($!did-iterate = 1),
                          nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                          nqp::if(
                            nqp::istype($pulled,Slip),
                            self.slip-all($pulled,target),
                            target.push($pulled)
                          )
                        ),
                        'LABELED', $!label,
                        'NEXT', nqp::stmts(
                          ($!did-iterate = 1),
                          nqp::if($!NEXT, &!block.fire_phasers('NEXT')),
                          nqp::eqaddr(
                            ($value := $!source.pull-one),
                            IterationEnd
                          )
                            ?? ($done = 1)
                            !! ($stopped = 0)),
                        'REDO', ($stopped = 0),
                        'LAST', ($done = $!did-iterate = 1)
                      )
                    ),
                    :nohandler
                  )
                )
            }
            nqp::if($!did-iterate,&!block.fire_if_phasers('LAST'))
        }

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

            nqp::if(
              $!slipping,
              nqp::until(
                nqp::eqaddr(self.slip-one,IterationEnd),
                nqp::null
              )
            );

            my int $stopped;
            my int $done;
            my $value;
            until $done
                || nqp::eqaddr(($value := $!source.pull-one()),IterationEnd) {
                nqp::stmts(
                  ($stopped = 0),
                  nqp::until(
                    $stopped,
                    nqp::stmts(
                      ($stopped = 1),
                      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
                          )
                            ?? ($done = 1)
                            !! ($stopped = 0)),
                        'REDO', ($stopped = 0),
                        'LAST', ($done = $!did-iterate = 1)
                      )
                    ),
                    :nohandler
                  )
                )
            }
            nqp::if($!did-iterate,&!block.fire_if_phasers('LAST'))
        }
    }

    my class IterateOneNotSlippingWithoutPhasers does Iterator {
        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 {
            if nqp::eqaddr((my $pulled := $!source.pull-one),IterationEnd) {
                IterationEnd
            }
            else {
                my $result;
                my int $stopped;
                nqp::stmts(
                  nqp::until(
                    $stopped,
                    nqp::stmts(
                      ($stopped = 1),
                      nqp::handle(
                        ($result := &!block($pulled)),
                        'LABELED', $!label,
                        'NEXT', nqp::if(
                          nqp::eqaddr(
                            ($pulled := $!source.pull-one),
                            IterationEnd
                          ),
                          ($result := IterationEnd),
                          ($stopped = 0)
                        ),
                        'REDO', ($stopped = 0),
                        'LAST', ($result := IterationEnd)
                      ),
                    ),
                    :nohandler
                  ),
                  $result
                )
            }
        }

        method push-all(\target --> IterationEnd) {
            my $pulled;
            my int $stopped;
            nqp::until(
              nqp::eqaddr(($pulled := $!source.pull-one),IterationEnd),
               nqp::stmts(
                ($stopped = 0),
                nqp::until(
                  $stopped,
                  nqp::stmts(
                    ($stopped = 1),
                    nqp::handle(
                      target.push(&!block($pulled)),
                      'LABELED', $!label,
                      'REDO', ($stopped = 0),
                      'NEXT', nqp::null, # need NEXT for next LABEL support
                      'LAST', return
                    )
                  ),
                  :nohandler
                )
              )
            )
        }

        method sink-all(--> IterationEnd) {
            my $pulled;
            my int $stopped;
            nqp::until(
              nqp::eqaddr(($pulled := $!source.pull-one),IterationEnd),
              nqp::stmts(
                ($stopped = 0),
                nqp::until(
                  $stopped,
                  nqp::stmts(
                    ($stopped = 1),
                    nqp::handle(
                      &!block($pulled),
                      'LABELED', $!label,
                      'REDO', ($stopped = 0),
                      'NEXT', nqp::null, # need NEXT for next LABEL support
                      'LAST', return
                    )
                  ),
                  :nohandler
                )
              )
            )
        }
    }

    my class IterateOneWithoutPhasers 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 --> IterationEnd) {
            nqp::stmts(
              (my $value),
              nqp::if(
                $!slipping,
                nqp::until(
                  nqp::eqaddr(($value := self.slip-one),IterationEnd),
                  target.push($value)
                )
              ),
              nqp::until(
                nqp::eqaddr(($value := $!source.pull-one),IterationEnd),
                nqp::stmts(
                  (my int $redo = 1),
                  nqp::while(
                    $redo,
                    nqp::stmts(
                      ($redo = 0),
                      nqp::handle(
                        nqp::if(
                          nqp::istype((my $result := &!block($value)),Slip),
                          self.slip-all($result,target),
                          target.push($result)
                        ),
                        'LABELED', $!label,
                        'REDO', ($redo = 1),
                        'LAST', return,
                        'NEXT', nqp::null, # need NEXT for next LABEL support
                      )
                    ),
                    :nohandler
                  )
                )
              )
            )
        }

        method sink-all(--> IterationEnd) {
            nqp::stmts(
              nqp::if(
                $!slipping,
                nqp::until(
                  nqp::eqaddr(self.slip-one,IterationEnd),
                  nqp::null
                )
              ),
              nqp::until(
                nqp::eqaddr((my $value := $!source.pull-one()),IterationEnd),
                nqp::stmts(
                  (my int $redo = 1),
                  nqp::while(
                    $redo,
                    nqp::stmts(
                      ($redo = 0),
                      nqp::handle(  # doesn't sink
                        &!block($value),
                        'LABELED', $!label,
                        'NEXT', nqp::null,  # need NEXT for next LABEL support
                        'REDO', ($redo = 1),
                        'LAST', return
                      ),
                    :nohandler
                    )
                  )
                )
              )
            )
        }
    }

    my class IterateTwoWithoutPhasers 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 --> IterationEnd) {
            nqp::stmts(
              (my $value),
              nqp::if(
                $!slipping,
                nqp::until(
                  nqp::eqaddr(($value := self.slip-one),IterationEnd),
                  target.push($value)
                )
              ),
              nqp::until(
                nqp::eqaddr(($value := $!source.pull-one),IterationEnd),
                nqp::stmts(
                  (my int $redo = 1),
                  nqp::while(
                    $redo,
                    nqp::stmts(
                      ($redo = 0),
                      nqp::handle(
                        nqp::if(
                          nqp::eqaddr(
                            (my $value2 := $!source.pull-one),
                            IterationEnd
                          ),
                          nqp::stmts(
                            (my $result := &!block($value)),
                            nqp::if(
                              nqp::istype($result,Slip),
                              self.slip-all($result,target),
                              target.push($result)
                            ),
                            return
                          ),
                          nqp::if(
                            nqp::istype(
                              ($result := &!block($value,$value2)),
                              Slip
                            ),
                            self.slip-all($result,target),
                            target.push($result)
                          )
                        ),
                        'LABELED', $!label,
                        'REDO', ($redo = 1),
                        'LAST', return,
                        'NEXT', nqp::null, # need NEXT for next LABEL support
                      )
                    ),
                    :nohandler
                  )
                )
              )
            )
        }

        method sink-all(--> IterationEnd) {
            nqp::stmts(
              nqp::if(
                $!slipping,
                nqp::until(
                  nqp::eqaddr(self.slip-one,IterationEnd),
                  nqp::null,
                )
              ),
              nqp::until(
                nqp::eqaddr((my $value := $!source.pull-one()),IterationEnd),
                nqp::stmts(
                  (my int $redo = 1),
                  nqp::while(
                    $redo,
                    nqp::stmts(
                      ($redo = 0),
                      nqp::handle(  # doesn't sink
                        nqp::if(
                          nqp::eqaddr(
                            (my $value2 := $!source.pull-one),
                            IterationEnd
                          ),
                          nqp::stmts(
                            (&!block($value)),
                            return
                          ),
                          (&!block($value,$value2))
                        ),
                        'LABELED', $!label,
                        'NEXT', nqp::null,  # need NEXT for next LABEL support
                        'REDO', ($redo = 1),
                        'LAST', return
                      )
                    ),
                  :nohandler
                  )
                )
              )
            )
        }
    }

    my class IterateMoreWithPhasers 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 {
            nqp::if(
              nqp::isconcrete($!value-buffer),
              nqp::setelems($!value-buffer,0),
              ($!value-buffer := nqp::create(IterationBuffer))
            );
            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 && nqp::not_i(
              nqp::eqaddr(($result := self.slip-one),IterationEnd)) {
                # $result will be returned at the end
            }
            elsif nqp::eqaddr(
              $!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_if_phasers('LAST')
              if $!CAN_FIRE_PHASERS
              && $!did-iterate
              && nqp::eqaddr($result, IterationEnd);
            $result
        }
    }

    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;

        Seq.new(
          nqp::istype(&block,Block) && &block.has-phasers
            ?? $count < 2 || $count === Inf
              ?? IterateOneWithPhasers.new(&block,source,$label)
              !! IterateMoreWithPhasers.new(&block,source,$count,$label)
            !! $count < 2 || $count === Inf
              ?? nqp::istype(Slip,&block.returns)
                ?? IterateOneWithoutPhasers.new(&block,source,$label)
                !! IterateOneNotSlippingWithoutPhasers.new(&block,source,$label)
              !! $count == 2
                ?? IterateTwoWithoutPhasers.new(&block,source,$label)
                !! IterateMoreWithPhasers.new(&block,source,$count,$label)
        )
    }

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

    my class Grep-K 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 {
            nqp::stmts(
              nqp::until(
                nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd)
                  || $!test($_),
                ($!index = nqp::add_i($!index,1))
              ),
              nqp::if(
                nqp::eqaddr($_,IterationEnd),
                IterationEnd,
                nqp::p6box_i($!index = nqp::add_i($!index,1))
              )
            )
        }
        method push-all(\target --> IterationEnd) {
            nqp::stmts(
              (my $iter := $!iter),  # lexicals faster than attrs
              (my $test := $!test),
              (my int $i = $!index),
              nqp::until(
                nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
                nqp::stmts(
                  ($i = nqp::add_i($i,1)),
                  nqp::if(
                    $!test($_),
                    target.push(nqp::p6box_i($i))
                  )
                )
              ),
              ($!index = $i)
            )
        }
    }
    method !grep-k(Callable:D $test) { Seq.new(Grep-K.new(self,$test)) }

    my class Grep-KV 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 {
            nqp::if(
              nqp::isconcrete($!value),
              nqp::stmts(
                ($_ := $!value),
                ($!value := nqp::null),
                $_
              ),
              nqp::stmts(
                nqp::until(
                  nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd)
                    || $!test($_),
                  ($!index = nqp::add_i($!index,1))
                ),
                nqp::if(
                  nqp::eqaddr($_,IterationEnd),
                  IterationEnd,
                  nqp::stmts(
                    ($!value := $_),
                    nqp::p6box_i($!index = nqp::add_i($!index,1))
                  )
                )
              )
            )
        }
        method push-all(\target --> IterationEnd) {
            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($_);
                  )
                )
              )
            );
        }
    }
    method !grep-kv(Callable:D $test) { Seq.new(Grep-KV.new(self,$test)) }

    my class Grep-P 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 {
            nqp::stmts(
              nqp::until(
                nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd)
                  || $!test($_),
                ($!index = nqp::add_i($!index,1))
              ),
              nqp::if(
                nqp::eqaddr($_,IterationEnd),
                IterationEnd,
                Pair.new(($!index = nqp::add_i($!index,1)),$_)
              )
            )
        }
        method push-all(\target --> IterationEnd) {
            nqp::stmts(
              (my $iter := $!iter),   # lexicals are faster than attrs
              (my $test := $!test),
              (my int $i = $!index),
              nqp::until(
                nqp::eqaddr(($_ := $iter.pull-one),IterationEnd),
                nqp::stmts(
                  ($i = nqp::add_i($i,1)),
                  nqp::if(
                    $test($_),
                    target.push(Pair.new($i,$_))
                  )
                )
              ),
              ($!index = $i)
            )
        }
    }
    method !grep-p(Callable:D $test) { Seq.new(Grep-P.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 is-lazy() { $!iter.is-lazy }
    }
    method !grep-callable(Callable:D $test) {
        nqp::if(
          $test.count == 1,
          sequential-map(
            self.iterator,
            {
                (nqp::istype((my \result := $test($_)),Regex)
                  ?? result.ACCEPTS($_)
                  !! nqp::istype(result,Junction)
                    ?? result.Bool
                    !! result
                ) ?? $_
                  !! Empty
            },
            Any)
          ,
          nqp::stmts(
            (my role CheatArity {
                has $!arity;
                has $!count;

                method set-cheat($new-arity, $new-count --> Nil) {
                    $!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)
          )
        )
    }

    my class Grep-Accepts does Grepper {
        method pull-one() is raw {
            nqp::until(
              nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd)
                || $!test.ACCEPTS($_),
              nqp::null
            );
            $_
        }
        method push-all(\target --> IterationEnd) {
            nqp::until(
              nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd),
              nqp::if(  # doesn't sink
                $!test.ACCEPTS($_),
                target.push($_)
              )
            );
        }
    }
    method !grep-accepts(Mu $test) { Seq.new(Grep-Accepts.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) {
        X::Match::Bool.new( type => '.grep').throw
    }
    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-accepts: $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: { $t.ACCEPTS($_) }
                  !! nqp::istype($t,Callable:D)
                       ?? self!grep-k: self!wrap-callable-for-grep($t)
                       !! self!grep-k: { $t.ACCEPTS($_) }
            }
            elsif nqp::atkey($storage,"kv") {
                nqp::istype($t,Regex:D)
                  ?? self!grep-kv: { $t.ACCEPTS($_) }
                  !! nqp::istype($t,Callable:D)
                       ?? self!grep-kv: self!wrap-callable-for-grep($t)
                       !! self!grep-kv: { $t.ACCEPTS($_) }
            }
            elsif nqp::atkey($storage,"p") {
                nqp::istype($t,Regex:D)
                  ?? self!grep-p: { $t.ACCEPTS($_) }
                  !! nqp::istype($t,Callable:D)
                       ?? self!grep-p: self!wrap-callable-for-grep($t)
                       !! self!grep-p: { $t.ACCEPTS($_) }
            }
            elsif nqp::atkey($storage,"v") {
                nqp::istype($t,Regex:D)
                  ?? self!grep-accepts: $t
                  !! nqp::istype($t,Callable:D)
                       ?? self!grep-callable: self!wrap-callable-for-grep($t)
                       !! self!grep-accepts: $t
            }
            else {
                my str $key =
                  nqp::iterkey_s(nqp::shift(nqp::iterator($storage)));
                if nqp::iseq_s($key,"k") || nqp::iseq_s($key,"kv") || nqp::iseq_s($key,"p") {
                    nqp::istype($t,Regex:D)
                      ?? self!grep-accepts: $t
                      !! nqp::istype($t,Callable:D)
                           ?? self!grep-callable: self!wrap-callable-for-grep($t)
                           !! self!grep-accepts: $t
                }
                else {
                    nqp::iseq_s($key,"k")
                      ?? die "Specified a negated :v adverb"
                      !! X::Adverb.new(
                           :what<grep>,
                           :source(try { self.VAR.name } // self.WHAT.perl),
                           :unexpected($key)
                         ).throw
                }
            }
        }
        else {
            X::Adverb.new(
              :what<grep>,
              :source(try { self.VAR.name } // self.WHAT.perl),
              :nogo(%_.keys.grep: /k|v|kv|p/),
              :unexpected(%_.keys.grep: { !.match(/k|v|kv|p/) } )
            ).throw
        }
    }

    method !wrap-callable-for-grep($test) {
        ({
            nqp::istype((my \result := $test($_)),Regex)
              ?? result.ACCEPTS($_)
              !! nqp::istype(result,Junction)
                ?? result.Bool
                !! result
        })
    }

    proto method first(|) is nodal {*}
    multi method first(Bool:D $t) {
        Failure.new(X::Match::Bool.new( type => '.first' ))
    }
    # need to handle Regex differently, since it is also Callable
    multi method first(Regex:D $test, :$end, *%a) is raw {
        $end
          ?? self!first-accepts-end($test,%a)
          !! self!first-accepts($test,%a)
    }
    multi method first(Callable:D $test, :$end, *%a is copy) is raw {
        if $end {
            nqp::stmts(
              (my $elems = self.elems),
              nqp::if(
                ($elems && nqp::not_i($elems == Inf)),
                nqp::stmts(
                  (my int $index = $elems),
                  nqp::while(
                    nqp::isge_i(($index = nqp::sub_i($index,1)),0),
                    nqp::if(
                      $test(self.AT-POS($index)),
                      return self!first-result(
                        $index,self.AT-POS($index),'first :end',%a)
                    )
                  ),
                  Nil
                ),
                Nil
              )
            )
        }
        else {
            nqp::stmts(
              (my $iter := self.iterator),
              (my int $index),
              nqp::until(
                (nqp::eqaddr(($_ := $iter.pull-one),IterationEnd)
                  || $test($_)),
                ($index = nqp::add_i($index,1))
              ),
              nqp::if(
                nqp::eqaddr($_,IterationEnd),
                Nil,
                self!first-result($index,$_,'first',%a)
              )
            )
        }
    }
    multi method first(Mu $test, :$end, *%a) is raw {
        $end
          ?? self!first-accepts-end($test,%a)
          !! self!first-accepts($test,%a)
    }
    multi method first(:$end, *%a) is raw {
        nqp::elems(nqp::getattr(%a,Map,'$!storage'))
          ?? $end
            ?? self!first-accepts-end(True,%a)
            !! self!first-accepts(True,%a)
          !! $end
            ?? self.tail
            !! self.head
    }
    method !first-accepts(Mu $test,%a) is raw {
        nqp::stmts(
          (my $iter := self.iterator),
          (my int $index),
          nqp::until(
            (nqp::eqaddr(($_ := $iter.pull-one),IterationEnd)
              || $test.ACCEPTS($_)),
            ($index = nqp::add_i($index,1))
          ),
          nqp::if(
            nqp::eqaddr($_,IterationEnd),
            Nil,
            self!first-result($index,$_,'first',%a)
          )
        )
    }
    method !first-accepts-end(Mu $test,%a) is raw {
        nqp::stmts(
          (my $elems = self.elems),
          nqp::if(
            ($elems && nqp::not_i($elems == Inf)),
            nqp::stmts(
              (my int $index = $elems),
              nqp::while(
                nqp::isge_i(($index = nqp::sub_i($index,1)),0),
                nqp::if(
                  $test.ACCEPTS(self.AT-POS($index)),
                  return self!first-result(
                    $index,self.AT-POS($index),'first :end',%a)
                )
              ),
              Nil
            ),
            Nil
          )
        )
    }
    method !iterator-and-first($action,\first) is raw {
        nqp::if(
          self.is-lazy,
          X::Cannot::Lazy.new(:$action).throw,
          nqp::stmts(
            (my $iterator := self.iterator),
            nqp::until(
              nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd),
              nqp::if(
                nqp::isconcrete($pulled),
                nqp::stmts(
                  (first = $pulled),
                  (return $iterator)
                )
              )
            ),
            Mu
          )
        )
    }

    proto method min (|) is nodal {*}
    multi method min() {
        nqp::stmts(
          nqp::if(
            (my $iter := self!iterator-and-first(".min",my $min)),
            nqp::until(
              nqp::eqaddr((my $pulled := $iter.pull-one),IterationEnd),
              nqp::if(
                (nqp::isconcrete($pulled) && $pulled cmp $min < 0),
                $min = $pulled
              )
            )
          ),
          nqp::if(nqp::defined($min),$min,Inf)
        )
    }
    multi method min(&by) {
        nqp::stmts(
          (my $cmp := nqp::if(
            nqp::iseq_i(&by.arity,2),&by,{ &by($^a) cmp &by($^b) })),
          nqp::if(
            (my $iter := self!iterator-and-first(".min",my $min)),
            nqp::until(
              nqp::eqaddr((my $pulled := $iter.pull-one),IterationEnd),
              nqp::if(
                (nqp::isconcrete($pulled) && $cmp($pulled,$min) < 0),
                $min = $pulled
              )
            )
          ),
          nqp::if(nqp::defined($min),$min,Inf)
        )
    }

    proto method max (|) is nodal {*}
    multi method max() {
        nqp::stmts(
          nqp::if(
            (my $iter := self!iterator-and-first(".max",my $max)),
            nqp::until(
              nqp::eqaddr((my $pulled := $iter.pull-one),IterationEnd),
              nqp::if(
                (nqp::isconcrete($pulled) && $pulled cmp $max > 0),
                $max = $pulled
              )
            )
          ),
          nqp::if(nqp::defined($max),$max,-Inf)
        )
    }
    multi method max(&by) {
        nqp::stmts(
          (my $cmp := nqp::if(
            nqp::iseq_i(&by.arity,2),&by,{ &by($^a) cmp &by($^b) })),
          nqp::if(
            (my $iter := self!iterator-and-first(".max",my $max)),
            nqp::until(
              nqp::eqaddr((my $pulled := $iter.pull-one),IterationEnd),
              nqp::if(
                (nqp::isconcrete($pulled) && $cmp($pulled,$max) > 0),
                $max = $pulled
              )
            )
          ),
          nqp::if(nqp::defined($max),$max,-Inf)
        )
    }

    method !minmax-range-init(\value,\mi,\exmi,\ma,\exma --> Nil) {
        mi   = value.min;
        exmi = value.excludes-min;
        ma   = value.max;
        exma = value.excludes-max;
    }
    method !minmax-range-check(\value,\mi,\exmi,\ma,\exma --> Nil) {
        nqp::stmts(
          nqp::if(
            ((value.min cmp mi) < 0),
            nqp::stmts(
              (mi   = value.min),
              (exmi = value.excludes-min)
            )
          ),
          nqp::if(
            ((value.max cmp ma) > 0),
            nqp::stmts(
              (ma   = value.max),
              (exma = value.excludes-max)
            )
          )
        )
    }
    method !cmp-minmax-range-check(\value,$cmp,\mi,\exmi,\ma,\exma --> Nil) {
        nqp::stmts(                     # $cmp sigillless confuses the optimizer
          nqp::if(
            ($cmp(value.min,mi) < 0),
            nqp::stmts(
              (mi   = value.min),
              (exmi = value.excludes-min)
            )
          ),
          nqp::if(
            ($cmp(value.max,ma) > 0),
            nqp::stmts(
              (ma   = value.max),
              (exma = value.excludes-max)
            )
          )
        )
    }

    proto method minmax (|) is nodal {*}
    multi method minmax() {
        nqp::stmts(
          nqp::if(
            (my $iter := self!iterator-and-first(".minmax",my $pulled)),
            nqp::stmts(
              nqp::if(
                nqp::istype($pulled,Range),
                self!minmax-range-init($pulled,
                  my $min,my int $excludes-min,my $max,my int $excludes-max),
                nqp::if(
                  nqp::istype($pulled,Positional),
                  self!minmax-range-init($pulled.minmax, # recurse for min/max
                    $min,$excludes-min,$max,$excludes-max),
                  ($min = $max = $pulled)
                )
              ),
              nqp::until(
                nqp::eqaddr(($pulled := $iter.pull-one),IterationEnd),
                nqp::if(
                  nqp::isconcrete($pulled),
                  nqp::if(
                    nqp::istype($pulled,Range),
                    self!minmax-range-check($pulled,
                       $min,$excludes-min,$max,$excludes-max),
                    nqp::if(
                      nqp::istype($pulled,Positional),
                      self!minmax-range-check($pulled.minmax,
                         $min,$excludes-min,$max,$excludes-max),
                      nqp::if(
                        (($pulled cmp $min) < 0),
                        ($min = $pulled),
                        nqp::if(
                          (($pulled cmp $max) > 0),
                          ($max = $pulled)
                        )
                      )
                    )
                  )
                )
              )
            )
          ),
          nqp::if(
            nqp::defined($min),
            Range.new($min,$max,:$excludes-min,:$excludes-max),
            Range.new(Inf,-Inf)
          )
        )
    }
    multi method minmax(&by) {
        nqp::stmts(
          nqp::if(
            (my $iter := self!iterator-and-first(".minmax",my $pulled)),
            nqp::stmts(
              (my $cmp = nqp::if(
                nqp::iseq_i(&by.arity,2),&by,{ &by($^a) cmp &by($^b) })
              ),
              nqp::if(
                nqp::istype($pulled,Range),
                self!minmax-range-init($pulled,
                  my $min,my int $excludes-min,my $max,my int $excludes-max),
                nqp::if(
                  nqp::istype($pulled,Positional),
                  self!minmax-range-init($pulled.minmax(&by), # recurse min/max
                    $min,$excludes-min,$max,$excludes-max),
                  ($min = $max = $pulled)
                )
              ),
              nqp::until(
                nqp::eqaddr(($pulled := $iter.pull-one),IterationEnd),
                nqp::if(
                  nqp::isconcrete($pulled),
                  nqp::if(
                    nqp::istype($pulled,Range),
                    self!cmp-minmax-range-check($pulled,
                       $cmp,$min,$excludes-min,$max,$excludes-max),
                    nqp::if(
                      nqp::istype($pulled,Positional),
                      self!cmp-minmax-range-check($pulled.minmax(&by),
                         $cmp,$min,$excludes-min,$max,$excludes-max),
                      nqp::if(
                        ($cmp($pulled,$min) < 0),
                        ($min = $pulled),
                        nqp::if(
                          ($cmp($pulled,$max) > 0),
                          ($max = $pulled)
                        )
                      )
                    )
                  )
                )
              )
            )
          ),
          nqp::if(
            nqp::defined($min),
            Range.new($min,$max,:$excludes-min,:$excludes-max),
            Range.new(Inf,-Inf)
          )
        )
    }

    proto method sort(|) is nodal {*}
    multi method sort() {
        nqp::if(
          nqp::eqaddr(
            self.iterator.push-until-lazy(
              my \buf := nqp::create(IterationBuffer)),
            IterationEnd
          ),
          Seq.new(
            Rakudo::Iterator.ReifiedList(
              Rakudo::Sorting.MERGESORT-REIFIED-LIST(buf.List)
            )
          ),
          X::Cannot::Lazy.new(:action<sort>).throw
        )
    }
    multi method sort(&by) {
        nqp::stmts(
          nqp::unless(
            nqp::eqaddr(
              self.iterator.push-until-lazy(
                my \buf := nqp::create(IterationBuffer)),
              IterationEnd
            ),
            X::Cannot::Lazy.new(:action<sort>).throw
          ),
          Seq.new(
            Rakudo::Iterator.ReifiedList(
              nqp::if(
                nqp::eqaddr(&by,&infix:<cmp>),
                Rakudo::Sorting.MERGESORT-REIFIED-LIST(buf.List),
                nqp::if(
                  &by.count < 2,
                  Rakudo::Sorting.MERGESORT-REIFIED-LIST-AS(  buf.List,&by),
                  Rakudo::Sorting.MERGESORT-REIFIED-LIST-WITH(buf.List,&by)
                )
              )
            )
          )
        )
    }

    method collate {
        self.sort(&[coll]);
    }
    sub find-reducer-for-op(&op) {
        nqp::if(
          nqp::iseq_s(&op.prec("prec"),"f="),
          &METAOP_REDUCE_LISTINFIX,
          nqp::if(
            nqp::iseq_i(nqp::chars(my str $assoc = &op.prec("assoc")),0),
            &METAOP_REDUCE_LEFT,
            ::(nqp::concat('&METAOP_REDUCE_',nqp::uc($assoc)))
          )
        )
    }

    proto method reduce(|) is nodal {*}
    multi method reduce(Any:U: & --> Nil) { }
    multi method reduce(Any:D: &with) {
        (find-reducer-for-op(&with))(&with)(self)
    }

    proto method produce(|) is nodal {*}
    multi method produce(Any:U: & --> Nil) { }
    multi method produce(Any:D: &with) {
        (find-reducer-for-op(&with))(&with,1)(self)
    }

    proto method unique(|) is nodal {*}

    my class Unique does Iterator {
        has $!iter;
        has $!seen;
        method !SET-SELF(\list) {
            nqp::stmts(
              ($!iter := list.iterator),
              ($!seen := nqp::hash),
              self
            )
        }
        method new(\list) { nqp::create(self)!SET-SELF(list) }
        method pull-one() is raw {
            nqp::stmts(
              nqp::until(
                nqp::eqaddr((my \pulled := $!iter.pull-one),IterationEnd)
                  || (nqp::not_i(nqp::existskey(
                    $!seen,
                    (my \needle := pulled.WHICH)
                  )) && nqp::bindkey($!seen,needle,1)),
                nqp::null
              ),
              pulled
            )
        }
        method push-all(\target --> IterationEnd) {
            nqp::until(
              nqp::eqaddr((my \pulled := $!iter.pull-one),IterationEnd),
              nqp::unless(
                nqp::existskey($!seen,(my \needle := pulled.WHICH)),
                nqp::stmts(
                  nqp::bindkey($!seen,needle,1),
                  target.push(pulled)
                )
              )
            )
        }
        method is-lazy() { $!iter.is-lazy }
        method sink-all(--> IterationEnd) { $!iter.sink-all }
    }
    multi method unique() { Seq.new(Unique.new(self)) }

    multi method unique( :&as!, :&with! ) {
        nqp::if(
          nqp::eqaddr(&with,&[===]), # use optimized version
          self.unique(:&as),
          Seq.new(
            Rakudo::Iterator.UniqueRepeatedAsWith(self.iterator,&as,&with,1)
          )
        )
    }

    my class Unique-As does Iterator {
        has Mu $!iter;
        has &!as;
        has $!seen;
        method !SET-SELF(\list, &!as) {
            $!iter  = list.iterator;
            $!seen := nqp::hash();
            self
        }
        method new(\list, &as) { nqp::create(self)!SET-SELF(list, &as) }
        method pull-one() is raw {
            nqp::stmts(
              nqp::until(
                nqp::eqaddr((my \value := $!iter.pull-one),IterationEnd),
                nqp::unless(
                  nqp::existskey($!seen,my \needle := &!as(value).WHICH),
                  nqp::stmts(
                    nqp::bindkey($!seen,needle,1),
                    return-rw value
                  )
                )
              ),
              IterationEnd
            )
        }
        method push-all(\target --> IterationEnd) {
            nqp::until(
              nqp::eqaddr((my \value := $!iter.pull-one),IterationEnd),
              nqp::unless(
                nqp::existskey($!seen,my \needle := &!as(value).WHICH),
                nqp::stmts(  # doesn't sink
                  nqp::bindkey($!seen,needle,1),
                  target.push(value)
                )
              )
            )
        }
    }
    multi method unique( :&as! ) { Seq.new(Unique-As.new(self,&as)) }

    multi method unique( :&with! ) {
        nqp::if(
          nqp::eqaddr(&with,&[===]), # use optimized version
          self.unique,
          Seq.new(Rakudo::Iterator.UniqueRepeatedWith(self.iterator,&with,1))
        )
    }

    proto method repeated(|) is nodal {*}

    my class Repeated does Iterator {
        has Mu $!iter;
        has $!seen;
        method !SET-SELF(\list) {
            $!iter = list.iterator;
            $!seen := nqp::hash();
            self
        }
        method new(\list) { nqp::create(self)!SET-SELF(list) }
        method pull-one() is raw {
            my Mu $value;
            my str $needle;
            nqp::until(
              nqp::eqaddr(($value := $!iter.pull-one),IterationEnd),
              nqp::existskey($!seen,$needle = nqp::unbox_s($value.WHICH))
                ?? return-rw $value
                !! nqp::bindkey($!seen, $needle, 1)
            );
            IterationEnd
        }
        method push-all(\target --> IterationEnd) {
            my Mu $value;
            my str $needle;
            nqp::until( # doesn't sink
              nqp::eqaddr(($value := $!iter.pull-one),IterationEnd),
              nqp::existskey($!seen,$needle = nqp::unbox_s($value.WHICH))
                ?? target.push($value)
                !! nqp::bindkey($!seen, $needle, 1)
            );
        }
        method is-lazy() { $!iter.is-lazy }
    }
    multi method repeated() { Seq.new(Repeated.new(self)) }

    multi method repeated( :&as!, :&with! ) {
        nqp::if(
          nqp::eqaddr(&with,&[===]), # use optimized version
          self.repeated(:&as),
          Seq.new(
            Rakudo::Iterator.UniqueRepeatedAsWith(self.iterator,&as,&with,0)
          )
        )
    }

    class Repeated-As does Iterator {
        has Mu $!iter;
        has &!as;
        has $!seen;
        method !SET-SELF(\list, &!as) {
            $!iter  = list.iterator;
            $!seen := nqp::hash();
            self
        }
        method new(\list, &as) { nqp::create(self)!SET-SELF(list, &as) }
        method pull-one() is raw {
            my Mu $value;
            my str $needle;
            nqp::until(
              nqp::eqaddr(($value := $!iter.pull-one),IterationEnd),
              nqp::existskey($!seen,$needle = nqp::unbox_s(&!as($value).WHICH))
                ?? return-rw $value
                !! nqp::bindkey($!seen, $needle, 1)
            );
            IterationEnd
        }
        method push-all(\target --> IterationEnd) {
            my Mu $value;
            my str $needle;
            nqp::until(  # doesn't sink
              nqp::eqaddr(($value := $!iter.pull-one),IterationEnd),
              nqp::existskey($!seen,$needle = nqp::unbox_s(&!as($value).WHICH))
                ?? target.push($value)
                !! nqp::bindkey($!seen, $needle, 1)
            );
        }
        method is-lazy() { $!iter.is-lazy }
    }
    multi method repeated( :&as! ) { Seq.new(Repeated-As.new(self,&as)) }

    multi method repeated( :&with! ) {
        nqp::if(
          nqp::eqaddr(&with,&[===]), # use optimized version
          self.repeated,
          Seq.new(Rakudo::Iterator.UniqueRepeatedWith(self.iterator,&with,0))
        )
    }

    proto method squish(|) is nodal {*}

    my class Squish-As does Iterator {
        has Mu $!iter;
        has &!as;
        has &!with;
        has $!last_as;
        has int $!first;
        method !SET-SELF($!iter, &!as, &!with) {
            $!first = 1;
            self
        }
        method new(\iter, \as, \with) {
            nqp::create(self)!SET-SELF(iter, as, with)
        }
        method pull-one() is raw {
            nqp::if(
              nqp::eqaddr((my $pulled := $!iter.pull-one),IterationEnd),
              IterationEnd,
              nqp::stmts(
                (my $which := &!as($pulled)),
                nqp::if(
                  $!first,
                  ($!first = 0),
                  nqp::until(
                    nqp::isfalse(&!with($!last_as,$which))
                      || nqp::eqaddr(
                           ($pulled := $!iter.pull-one),
                           IterationEnd
                         ),
                    nqp::stmts(
                      ($!last_as := $which),
                      ($which := &!as($pulled))
                    )
                  )
                ),
                ($!last_as := $which),
                $pulled
              )
            )
        }
        method push-all(\target --> IterationEnd) {
            my Mu $value := $!iter.pull-one;
            unless nqp::eqaddr($value,IterationEnd) {
                my $which;
                my $last_as := $!last_as;
                nqp::if(
                  $!first,
                  nqp::stmts(  # doesn't sink
                    (target.push($value)),
                    ($which := &!as($value)),
                    ($last_as := $which),
                    ($value := $!iter.pull-one)
                  )
                );
                nqp::until(
                  nqp::eqaddr($value,IterationEnd),
                  nqp::stmts(
                    nqp::unless(  # doesn't sink
                      &!with($last_as,$which := &!as($value)),
                      target.push($value)
                    ),
                    ($last_as := $which),
                    ($value := $!iter.pull-one)
                  )
                );
            }
        }
        method is-lazy() { $!iter.is-lazy }
    }
    multi method squish( :&as!, :&with = &[===] ) {
        Seq.new(Squish-As.new(self.iterator, &as, &with))
    }

    my class Squish-With does Iterator {
        has Mu $!iter;
        has &!with;
        has Mu $!last;
        has int $!first;
        method !SET-SELF($!iter, &!with) {
            $!first = 1;
            self
        }
        method new(\iter, \with) { nqp::create(self)!SET-SELF(iter, with) }
        method pull-one() is raw {
            nqp::if(
              nqp::eqaddr((my $pulled := $!iter.pull-one),IterationEnd),
              IterationEnd,
              nqp::stmts(
                nqp::if(
                  $!first,
                  ($!first = 0),
                  nqp::stmts(
                    (my $old := $pulled),
                    nqp::until(
                      nqp::isfalse(&!with($!last,$pulled))
                        || nqp::eqaddr(
                             ($pulled := $!iter.pull-one),
                             IterationEnd
                           ),
                      nqp::stmts(
                        ($!last := $old),
                        ($old := $pulled)
                      )
                    )
                  )
                ),
                ($!last := $pulled)
              )
            )
        }
        method push-all(\target --> IterationEnd) {
            my Mu $value := $!iter.pull-one;
            unless nqp::eqaddr($value,IterationEnd) {
                my $last_val = $!last;
                nqp::if(
                  $!first,
                  nqp::stmts(  # doesn't sink
                    (target.push($value)),
                    ($last_val := $value),
                    ($value := $!iter.pull-one)
                  )
                );
                nqp::until(
                  nqp::eqaddr($value,IterationEnd),
                  nqp::stmts(
                    nqp::unless(  # doesn't sink
                      &!with($last_val, $value),
                      target.push($value)
                    ),
                    ($last_val := $value),
                    ($value := $!iter.pull-one)
                  )
                );
            }
        }
        method is-lazy() { $!iter.is-lazy }
    }
    multi method squish( :&with = &[===] ) {
        Seq.new(Squish-With.new(self.iterator,&with))
    }

    proto method pairup(|) is nodal {*}
    multi method pairup(Any:U:) { ().Seq }
    multi method pairup(Any:D:) {
        my \iter := self.iterator;
        gather {
            nqp::until(
              nqp::eqaddr((my $pulled := iter.pull-one),IterationEnd),
              nqp::if(
                nqp::istype($pulled,Pair),
                (take nqp::p6bindattrinvres(
                  nqp::clone($pulled),
                  Pair,
                  '$!value',
                  nqp::clone(nqp::decont(nqp::getattr($pulled,Pair,'$!value')))
                )),
                nqp::if(
                  nqp::istype($pulled,Map) && nqp::not_i(nqp::iscont($pulled)),
                  (take Slip.from-iterator($pulled.iterator)),
                  nqp::if(
                    nqp::eqaddr((my $value := iter.pull-one),IterationEnd),
                    X::Pairup::OddNumber.new.throw,
                    take Pair.new($pulled,$value)
                  )
                )
              )
            )
        }
    }

    proto method toggle(|) {*}
    multi method toggle(Any:D: Callable:D \condition, :$off!) {
        Seq.new( $off
          ?? Rakudo::Iterator.Until(self.iterator, condition)
          !! Rakudo::Iterator.While(self.iterator, condition)
        )
    }
    multi method toggle(Any:D: Callable:D \condition) {
        Seq.new(Rakudo::Iterator.While(self.iterator, condition))
    }
    multi method toggle(Any:D: *@conditions, :$off) {
        Seq.new(
          Rakudo::Iterator.Toggle(self.iterator, @conditions.iterator, !$off)
        )
    }

    proto method head(|) {*}
    multi method head(Any:D:) is raw {
        nqp::if(
          nqp::eqaddr((my $pulled := self.iterator.pull-one),IterationEnd),
          Nil,
          $pulled
        )
    }
    multi method head(Any:D: Callable:D $w) {
        Seq.new(
           Rakudo::Iterator.AllButLastNValues(self.iterator,-($w(0).Int))
        )
    }
    multi method head(Any:D: $n) {
        Seq.new(Rakudo::Iterator.NextNValues(self.iterator,$n))
    }

    proto method tail(|) {*}
    multi method tail() is raw {
        nqp::if(
          nqp::eqaddr((my $pulled :=
            Rakudo::Iterator.LastValue(self.iterator,'tail')),
            IterationEnd
          ),
          Nil,
          $pulled
        )
    }
    multi method tail($n) {
        Seq.new(
          nqp::if(
            nqp::istype($n,Callable),
            nqp::stmts(
              (my $iterator := self.iterator),
              nqp::if(
                nqp::isgt_i((my $skip := -($n(0).Int)),0),
                nqp::if(
                  $iterator.skip-at-least($skip),
                  $iterator,
                  Rakudo::Iterator.Empty),
                $iterator)),
            Rakudo::Iterator.LastNValues(self.iterator,$n,'tail')
          )
        )
    }

    proto method skip(|) {*}
    multi method skip() {
        my $iter := self.iterator;
        Seq.new( $iter.skip-one ?? $iter !! Rakudo::Iterator.Empty )
    }
    multi method skip(Whatever) { Seq.new(Rakudo::Iterator.Empty) }
    multi method skip(Callable:D $w) {
       nqp::if(
         nqp::isgt_i((my $tail := -($w(0).Int)),0),
         self.tail($tail),
         Seq.new(Rakudo::Iterator.Empty)
       )
    }
    multi method skip(Int() $n) {
        my $iter := self.iterator;
        Seq.new( $iter.skip-at-least($n) ?? $iter !! Rakudo::Iterator.Empty )
    }

    # Note that the implementation of minpairs/maxpairs only differs in the
    # value against which the result of cmp is compared (-1 for minpairs,
    # +1 for maxpairs).  Abstracting the logic in a helper sub, did not change
    # the binary size significantly, but would introduce a runtime penalty.
    # Hence the almost identical pieces of code here.
    proto method minpairs(|) {*}
    multi method minpairs(Any:D:) {
        my \iter   := self.pairs.iterator;
        my \result := nqp::create(IterationBuffer);
        nqp::until(
          nqp::eqaddr((my \pair := iter.pull-one),IterationEnd)
            || nqp::isconcrete(my \target := pair.value),
          nqp::null
        );
        nqp::unless(
          nqp::eqaddr(pair,IterationEnd),
          nqp::stmts(                               # found at least one value
            nqp::push(result,pair),
            nqp::until(
              nqp::eqaddr(nqp::bind(pair,iter.pull-one),IterationEnd),
              nqp::if(
                nqp::isconcrete(my \value := pair.value),
                nqp::if(
                  nqp::iseq_i((my \cmp-result := value cmp target),-1),
                  nqp::stmts(                       # new best
                    nqp::push(nqp::setelems(result,0),pair),
                    nqp::bind(target,value)
                  ),
                  nqp::if(                          # additional best
                    nqp::iseq_i(cmp-result,0),
                    nqp::push(result,pair)
                  )
                )
              )
            )
          )
        );
        Seq.new(Rakudo::Iterator.ReifiedList(result))
    }

    proto method maxpairs(|) {*}
    multi method maxpairs(Any:D:) {
        my \iter   := self.pairs.iterator;
        my \result := nqp::create(IterationBuffer);
        nqp::until(
          nqp::eqaddr((my \pair := iter.pull-one),IterationEnd)
            || nqp::isconcrete(my \target := pair.value),
          nqp::null
        );
        nqp::unless(
          nqp::eqaddr(pair,IterationEnd),
          nqp::stmts(                               # found at least one value
            nqp::push(result,pair),
            nqp::until(
              nqp::eqaddr(nqp::bind(pair,iter.pull-one),IterationEnd),
              nqp::if(
                nqp::isconcrete(my \value := pair.value),
                nqp::if(
                  nqp::iseq_i((my \cmp-result := value cmp target),+1),
                  nqp::stmts(                       # new best
                    nqp::push(nqp::setelems(result,0),pair),
                    nqp::bind(target,value)
                  ),
                  nqp::if(                          # additional best
                    nqp::iseq_i(cmp-result,0),
                    nqp::push(result,pair)
                  )
                )
              )
            )
          )
        );
        Seq.new(Rakudo::Iterator.ReifiedList(result))
    }

    proto method batch(|) is nodal {*}
    multi method batch(Any:D: Int:D :$elems!) {
        Seq.new(Rakudo::Iterator.Batch(self.iterator,$elems,1))
    }
    multi method batch(Any:D: Int:D $batch) {
        Seq.new(Rakudo::Iterator.Batch(self.iterator,$batch,1))
    }

    proto method rotor(|) is nodal {*}
    multi method rotor(Any:D: Int:D $batch, :$partial) {
        Seq.new(Rakudo::Iterator.Batch(self.iterator,$batch,$partial))
    }
    multi method rotor(Any:D: *@cycle, :$partial) {
        Seq.new(Rakudo::Iterator.Rotor(self.iterator,@cycle,$partial))
    }
}

BEGIN Attribute.^compose;

proto sub infix:<min>(|) is pure {*}
multi sub infix:<min>(Mu:D \a, Mu:U) { a }
multi sub infix:<min>(Mu:U, Mu:D \b) { b }
multi sub infix:<min>(Mu:D \a, Mu:D \b) { (a cmp b) < 0 ?? a !! b }
multi sub infix:<min>(Int:D \a, Int:D \b) { nqp::if(nqp::islt_i(nqp::cmp_I(nqp::decont(a), nqp::decont(b)), 0), a, b) }
multi sub infix:<min>(int   \a, int   \b) { nqp::if(nqp::islt_i(nqp::cmp_i(a, b), 0), a, b) }
multi sub infix:<min>(Num:D \a, Num:D \b) { nqp::if(nqp::islt_i(nqp::cmp_n(a, b), 0), a, b) }
multi sub infix:<min>(num   \a, num   \b) { nqp::if(nqp::islt_i(nqp::cmp_n(a, b), 0), a, b) }
multi sub infix:<min>(+args is raw) { args.min }

proto sub min(|) is pure {*}
multi sub min(+args, :&by!) { args.min(&by) }
multi sub min(+args)        { args.min      }

proto sub infix:<max>(|) is pure {*}
multi sub infix:<max>(Mu:D \a, Mu:U) { a }
multi sub infix:<max>(Mu:U, Mu:D \b) { b }
multi sub infix:<max>(Mu:D \a, Mu:D \b) { (a cmp b) > 0 ?? a !! b }
multi sub infix:<max>(Int:D \a, Int:D \b) { nqp::if(nqp::isgt_i(nqp::cmp_I(nqp::decont(a), nqp::decont(b)), 0), a, b) }
multi sub infix:<max>(int   \a, int   \b) { nqp::if(nqp::isgt_i(nqp::cmp_i(a, b), 0), a, b) }
multi sub infix:<max>(Num:D \a, Num:D \b) { nqp::if(nqp::isgt_i(nqp::cmp_n(a, b), 0), a, b) }
multi sub infix:<max>(num   \a, num   \b) { nqp::if(nqp::isgt_i(nqp::cmp_n(a, b), 0), a, b) }
multi sub infix:<max>(+args) { args.max }

proto sub max(|) is pure {*}
multi sub max(+args, :&by!) { args.max(&by) }
multi sub max(+args)        { args.max }

proto sub infix:<minmax>(|) is pure {*}
multi sub infix:<minmax>(+args) { args.minmax }

proto sub minmax(|) is pure {*}
multi sub minmax(+args, :&by!) { args.minmax(&by) }
multi sub minmax(+args)        { args.minmax      }

proto sub map($, |) {*}
multi sub map(&code, +values) { my $laze = values.is-lazy; values.map(&code).lazy-if($laze) }

proto sub grep(Mu, |) {*}
multi sub grep(Mu $test, +values, *%a) {
    my $laze = values.is-lazy;
    values.grep($test,|%a).lazy-if($laze)
}
multi sub grep(Bool:D $t, |) { X::Match::Bool.new(:type<grep>).throw }

proto sub first(Mu, |) {*}
multi sub first(Bool:D $t, |) { Failure.new(X::Match::Bool.new(:type<first>)) }
multi sub first(Mu $test, +values, *%a) { values.first($test,|%a) }

proto sub join($?, |) {*}
multi sub join($sep = '', *@values) { @values.join($sep) }

proto sub reduce ($, |) {*}
multi sub reduce (&with, +list)  { list.reduce(&with) }

proto sub produce ($, |) {*}
multi sub produce (&with, +list)  { list.produce(&with) }

proto sub unique(|) {*}
multi sub unique(+values, |c) { my $laze = values.is-lazy; values.unique(|c).lazy-if($laze) }

proto sub squish(|) {*}
multi sub squish(+values, |c) { my $laze = values.is-lazy; values.squish(|c).lazy-if($laze) }

proto sub repeated(|) {*}
multi sub repeated(+values, |c) { my $laze = values.is-lazy; values.repeated(|c).lazy-if($laze) }

proto sub sort(|) {*}
multi sub sort(&by, @values) { @values.sort(&by) }
multi sub sort(&by, +values) { values.sort(&by) }
multi sub sort(@values)      { @values.sort }
multi sub sort(+values)      { values.sort }

# vim: ft=perl6 expandtab sw=4