# necessary stubs
my class X::Range::CannotIterate { ... }
my class X::Numeric::DivideByZero { ... }

# This class contains generally usable methods creating Iterators.
# There are two reasons for having this in a separate class:
#
# 1. Nice to have a separate file for similar stuff.  Rakudo::Internals
#    has become a hodgepodge of stuff of late.
# 2. Improve readability/searchability of code using these iterators, as
#    many already have a long name, and having them prefixed with the more
#    general Rakudo::Internals in the code, as opposed for the definite
#    Rakudo::Iterator, feels better.

class Rakudo::Iterator {
    my $empty := nqp::list;   # an empty list for nqp::splice
    sub always-IterationEnd(--> IterationEnd) { }
    sub always-False(--> False) { }

#-------------------------------------------------------------------------------
# Roles that are used by iterators in the rest of the core settings, in
# alphabetical order for easier perusal.

    # Generic role for iterating over a Blob / Buf.  You need to
    # supply at least a .pull-one.  Takes a Blob / Buf as the only
    # parameter to .new.
    our role Blobby does PredictiveIterator {
        has $!blob;
        has Int $!i;   # sadly, this can not be a native int yet  :-(

        method !SET-SELF(\blob) {
            $!blob := blob;   # something to iterator over
            $!i     = -1;
            self
        }
        method new(\blob) {
            nqp::isgt_i(nqp::elems(blob),0)
              ?? nqp::create(self)!SET-SELF(blob)
              !! Rakudo::Iterator.Empty    # nothing to iterate
        }

        # We can provide a generic push-all to the iterator as the
        # result of a push-all is always immutable, so we can use
        # the atpos_i here in both cases.
        method push-all(\target --> IterationEnd) {
            my $blob := $!blob;     # attribute access is slower
            my int $elems = nqp::elems($blob);
            my int $i = $!i;

            nqp::while(
              nqp::islt_i(++$i,$elems),
              target.push(nqp::atpos_i($blob,$i))
            );
            $!i = $i;
        }
        method count-only(--> Int:D) {
            nqp::elems($!blob) - $!i - nqp::islt_i($!i,nqp::elems($!blob))
        }
        method sink-all(--> IterationEnd) { $!i = nqp::elems($!blob) }
    }

    # Generic role for iterating over a Map / Hash.  You must
    # at least provide your own .pull-one.  Takes a Map / Hash
    # as the only parameter to .new.
    our role Mappy does Iterator {
        has $!hash;
        has $!iter;

        method !SET-SELF(\hash) {
            nqp::if(
              ($!hash := nqp::if(
                nqp::istype(hash,Rakudo::Internals::IterationSet),
                hash,
                nqp::getattr(hash,Map,'$!storage')
              )) && ($!iter := nqp::iterator($!hash)),
              self,
              Rakudo::Iterator.Empty   # nothing to iterate
            )
        }
        method new(\hash) { nqp::create(self)!SET-SELF(hash) }
        method skip-one() { nqp::if($!iter,nqp::stmts(nqp::shift($!iter),1)) }
        method sink-all(--> IterationEnd) { $!iter := nqp::null }
        method is-deterministic(--> False) { }
    }

    # Generic role for iterating over a Map / Hash that has pairs
    # for values providing the "real" key and value.  A default
    # .pull-one and .push-all is provided.  Takes a Map / Hash as
    # the only parameter to .new.
    our role Mappy-kv-from-pairs does Iterator {
        has $!hash;
        has $!iter;
        has $!on;

        method !SET-SELF(\hash) {
            nqp::if(
              ($!hash := nqp::if(
                nqp::istype(hash,Rakudo::Internals::IterationSet),
                hash,
                nqp::getattr(hash,Map,'$!storage')
              )) && ($!iter := nqp::iterator($!hash)),
              self,
              Rakudo::Iterator.Empty   # nothing to iterate
            )
        }
        method new(\hash) { nqp::create(self)!SET-SELF(hash) }

        method pull-one() is raw {
            nqp::if(
              $!on,
              nqp::stmts(
                ($!on = 0),
                nqp::getattr(nqp::iterval($!iter),Pair,'$!value')
              ),
              nqp::if(
                $!iter,
                nqp::stmts(
                  ($!on = 1),
                  nqp::getattr(nqp::iterval(nqp::shift($!iter)),Pair,'$!key')
                ),
                IterationEnd
              )
            )
        }
        method push-all(\target --> IterationEnd) {
            nqp::while(
              $!iter,
              nqp::stmts(  # doesn't sink
                (my $pair := nqp::decont(nqp::iterval(nqp::shift($!iter)))),
                target.push(nqp::getattr($pair,Pair,'$!key')),
                target.push(nqp::getattr($pair,Pair,'$!value'))
              )
            )
        }
        method skip-one() {               # must define our own skip-one
            nqp::if(
              $!on,
              nqp::not_i($!on = 0),
              nqp::if(
                $!iter,
                nqp::stmts(
                  nqp::shift($!iter),
                  ($!on = 1)
                )
              )
            )
        }
        method is-deterministic(--> False) { }
        method sink-all(--> IterationEnd) { $!iter := nqp::null }
    }

    # Generic role for iterating over a >1 dimensional shaped list
    # for its lowest branches.  The default .new method takes a List
    # to iterate over.  A consuming class needs to provide a .process
    # method, which will be called with each iteration with the
    # $!indices attribute set to the coordinates of the branch being
    # iterated for this time (with the highest element index set to 0).
    # Consuming class can optionally provide a .done method that will
    # be called just before the iterator returns IterationEnd.
    our role ShapeBranch does Iterator {
        has $!dims;
        has $!indices;
        has Mu $!list;
        has int $!maxdim;
        has int $!maxind;
        has int $!level;

        # Every time process() gets called, the following attributes are set:
        # $!indices  a list_i with current position, with the highest elem 0
        # $!level    level at which exhaustion happened
        # $!dims     a list_i with dimensions
        # $!maxdim   maximum element number in $!dims
        # $!maxind   maximum element number in lowest level list
        method process { ... }           # consumer needs to supply a .process
        method done(--> Nil) { }         # by default no action at end

        method dims() {                  # HLL version of $!dims
            my $buffer :=
              nqp::setelems(nqp::create(IterationBuffer),nqp::elems($!dims));
            my int $i = -1;

            nqp::while(                # convert list_i to list
              nqp::isle_i(++$i,$!maxdim),
              nqp::bindpos($buffer,$i,nqp::atpos_i($!dims,$i))
            );
            $buffer.List
        }

        method !SET-SELF(Mu \list) {
            nqp::if(
              nqp::istype(list,List),
              nqp::stmts(                                 # List like
                ($!list := nqp::getattr(list,List,'$!reified')),
                (my $shape := list.shape),
                (my int $dims = $shape.elems),     # reifies
                ($!dims := nqp::setelems(nqp::list_i,$dims)),
                (my int $i = -1),
                nqp::while(
                  nqp::islt_i(++$i,$dims),
                  nqp::bindpos_i($!dims,$i,
                    nqp::atpos(nqp::getattr($shape,List,'$!reified'),$i))
                )
              ),
              ($dims = nqp::elems($!dims := nqp::dimensions($!list := list)))
            );

            $!indices := nqp::setelems(nqp::list_i,$dims);
            $!maxdim = nqp::sub_i($dims,1);
            $!maxind = nqp::sub_i(nqp::atpos_i($!dims,$!maxdim),1);

            self
        }
        method new(Mu \list) { nqp::create(self)!SET-SELF(list) }

        method pull-one() is raw {
            nqp::if(
              nqp::isge_i($!level,0),
              nqp::stmts(                      # still iterating
                (my $result := self.process),  # do the processing
                (my int $level = $!maxdim),
                nqp::until(                    # update indices
                  nqp::islt_i(--$level,0)      # exhausted ??
                    || nqp::stmts(             # next level
                    nqp::bindpos_i($!indices,nqp::add_i($level,1),0),  # reset
                    nqp::islt_i(
                      nqp::bindpos_i($!indices,$level, # increment this level
                        nqp::add_i(nqp::atpos_i($!indices,$level),1)),
                      nqp::atpos_i($!dims,$level)      # out of range?
                    ),
                  ),
                  nqp::null
                ),
                ($!level = $level),            # set level for next call
                $result                        # what we found
              ),
              nqp::stmts(
                nqp::if(
                  nqp::iseq_i($!level,-1),
                  nqp::stmts(                  # first time telling we're done
                    self.done,                 # notify we're done
                    ($!level = -2)             # do this only once
                  )
                ),
                IterationEnd                   # done iterating
              )
            )
        }
    }

    # Generic role for iterating over a >1 dimensional shaped list
    # for its values (leaves).  The default .new method takes a List
    # to iterate over.  A consuming class needs to provide a .result
    # method, which will be called with each iteration with the
    # $!indices attribute set to the coordinates of the element being
    # iterated for this time.  In some cases, the iterator is iterated
    # over for the side-effects in .result only.  Which is why this
    # role supplies an optimized .sink-all.
    our role ShapeLeaf does Iterator {
        has $!dims;
        has $!indices;
        has Mu $!list;
        has int $!maxdim;
        has int $!max;

        # Every time .result gets called, the following attributes are set:
        # $!indices  a list_i with current coordinate
        # $!dims     a list_i with dimensions
        # $!maxdim   maximum element number in $!dims
        method result { ... }            # consumer needs to supply a .result

        method indices() {               # HLL version of $!indices
            my $result := nqp::setelems(nqp::list,nqp::elems($!indices));
            my int $i = -1;

            nqp::while(                # convert list_i to list
              nqp::isle_i(++$i,$!maxdim),
              nqp::bindpos($result,$i,nqp::atpos_i($!indices,$i))
            );
            $result
        }
        method !SET-SELF(Mu \list) {
            nqp::if(
              nqp::istype(list,List),
              nqp::stmts(                                 # List like
                ($!list := nqp::getattr(list,List,'$!reified')),
                (my $shape := list.shape),
                (my int $dims = $shape.elems),            # reifies
                ($!dims := nqp::setelems(nqp::list_i,$dims)),
                (my int $i = -1),
                nqp::while(
                  nqp::islt_i(++$i,$dims),
                  nqp::bindpos_i($!dims,$i,
                    nqp::atpos(nqp::getattr($shape,List,'$!reified'),$i))
                )
              ),
              ($dims = nqp::elems($!dims := nqp::dimensions($!list := list)))
            );

            $!indices := nqp::setelems(nqp::list_i,$dims);
            $!maxdim = nqp::sub_i($dims,1);
            $!max    = nqp::atpos_i($!dims,$!maxdim);

            self
        }
        method new(Mu \list) { nqp::create(self)!SET-SELF(list) }

        method pull-one() is raw {
            nqp::if(
              $!indices,
              nqp::stmts(                                 # still iterating
                (my $result := self.result),              # process
                nqp::if(
                  nqp::islt_i(
                    (my int $i =
                      nqp::add_i(nqp::atpos_i($!indices,$!maxdim),1)),
                    $!max
                  ),
                  nqp::bindpos_i($!indices,$!maxdim,$i),  # ready for next
                  nqp::stmts(                             # done for now
                    (my int $level = $!maxdim),
                    nqp::until(                           # update indices
                      nqp::islt_i(--$level,0)             # exhausted ??
                        || nqp::stmts(
                        nqp::bindpos_i($!indices,nqp::add_i($level,1),0),
                        nqp::islt_i(
                          nqp::bindpos_i($!indices,$level,
                            nqp::add_i(nqp::atpos_i($!indices,$level),1)),
                          nqp::atpos_i($!dims,$level)
                        ),
                      ),
                      nqp::null
                    ),
                    nqp::if(
                      nqp::islt_i($level,0),
                      $!indices := nqp::null              # done next time
                    )
                  )
                ),
                $result                                   # what we found
              ),
              IterationEnd                                # done now
            )
        }

        method push-all(\target --> IterationEnd) {
            nqp::while(
              $!indices,
              nqp::stmts(                                   # still iterating
                (my int $i = nqp::atpos_i($!indices,$!maxdim)),
                nqp::while(
                  nqp::isle_i(++$i,$!max),
                  nqp::stmts(
                    target.push(self.result),              # process
                    nqp::bindpos_i($!indices,$!maxdim,$i),  # ready for next
                  )
                ),
                (my int $level = $!maxdim),                 # done for now
                nqp::until(                                 # update indices
                  nqp::islt_i(--$level,0)                   # exhausted ??
                    || nqp::stmts(
                    nqp::bindpos_i($!indices,nqp::add_i($level,1),0),
                    nqp::islt_i(
                      nqp::bindpos_i($!indices,$level,
                        nqp::add_i(nqp::atpos_i($!indices,$level),1)),
                      nqp::atpos_i($!dims,$level)
                    ),
                  ),
                  nqp::null
                ),
                nqp::if(
                  nqp::islt_i($level,0),
                  $!indices := nqp::null                    # done
                )
              )
            )
        }

        method sink-all(--> IterationEnd) {
            nqp::while(
              $!indices,
              nqp::stmts(                                   # still iterating
                (my int $i = nqp::atpos_i($!indices,$!maxdim)),
                nqp::while(
                  nqp::isle_i(++$i,$!max),
                  nqp::stmts(
                    self.result,                            # process
                    nqp::bindpos_i($!indices,$!maxdim,$i),  # ready for next
                  )
                ),
                (my int $level = $!maxdim),                 # done for now
                nqp::until(                                 # update indices
                  nqp::islt_i(--$level,0)                   # exhausted ??
                    || nqp::stmts(
                    nqp::bindpos_i($!indices,nqp::add_i($level,1),0),
                    nqp::islt_i(
                      nqp::bindpos_i($!indices,$level,
                        nqp::add_i(nqp::atpos_i($!indices,$level),1)),
                      nqp::atpos_i($!dims,$level)
                    ),
                  ),
                  nqp::null
                ),
                nqp::if(
                  nqp::islt_i($level,0),
                  $!indices := nqp::null                    # done
                )
              )
            )
        }
    }

#-------------------------------------------------------------------------------
# Methods that generate an Iterator (in alphabetical order)

    # Return iterator that produces all but the first value
    method AllButFirst(\iterator) { iterator.skip-one; iterator }

    # Create iterator that produces all values *except* the last of a given
    # iterator.  Returns an empty iterator if the given iterator did not
    # produce any value
    my role AllButLastRole {
        has $!iterator;
        has $!value;

        method !SET-SELF(\iterator) {
            $!iterator := iterator;
            nqp::eqaddr(($!value := iterator.pull-one),IterationEnd)
              ?? Rakudo::Iterator.Empty
              !! self
        }
        method new(\iterator) { nqp::create(self)!SET-SELF(iterator) }
        method pull-one() is raw {
            my \this := $!value;
            nqp::eqaddr(($!value := $!iterator.pull-one),IterationEnd)
              ?? $!value
              !! this
        }
    }
    my class AllButLast does Iterator does AllButLastRole {

        # Override default from Iterator
        method is-deterministic(--> Bool:D) { $!iterator.is-deterministic }
        method is-monotonically-increasing(--> Bool:D) {
            $!iterator.is-monotonically-increasing
        }
    }
    my class AllButLastPredictive does PredictiveIterator does AllButLastRole {
        method count-only() { ($!iterator.count-only || 1) - 1 }
        method bool-only()  {  $!iterator.count-only > 1       }

        # Override default from Iterator
        method is-deterministic(--> Bool:D) { $!iterator.is-deterministic }
        method is-monotonically-increasing(--> Bool:D) {
            $!iterator.is-monotonically-increasing
        }
    }

    proto method AllButLast(|) {*}
    multi method AllButLast(PredictiveIterator:D \iterator) {
        AllButLastPredictive.new(iterator)
    }
    multi method AllButLast(Iterator:D \iterator) {
        AllButLast.new(iterator)
    }

    # Create iterator that produces all values *except* the last N values
    # of a given iterator.  Returns an empty iterator if the given iterator
    # produced fewer than N values.
    my class AllButLastNValues does Iterator {
        has $!iterator;
        has $!buffered;
        has int $!size;
        has int $!index;

        method !SET-SELF(\iterator, int $size) {
            my int $i = -1;
            my \buffered := nqp::setelems(nqp::list,$size);

            nqp::while(                      # fill buffer to produce from
              nqp::islt_i(++$i,$size)
                && nqp::not_i(nqp::eqaddr(
                     (my \pulled := iterator.pull-one),
                     IterationEnd
                   )),
              nqp::bindpos(buffered,$i,pulled)
            );

            nqp::if(
              nqp::islt_i($i,$size),
              Rakudo::Iterator.Empty,        # didn't produce enough
              nqp::stmts(                    # we're in business
                ($!iterator := iterator),
                ($!buffered := buffered),
                ($!size = $size),
                self
              )
            )
        }
        method new(\iterator,\n) {
            nqp::isle_i(n,0)
              ?? iterator                      # we wants it all
              !! nqp::create(self)!SET-SELF(iterator,n)
        }
        method pull-one() is raw {
            nqp::if(
              nqp::eqaddr((my \pulled := $!iterator.pull-one),IterationEnd),
              pulled,                          # we're done
              nqp::stmts(                      # produce/update buffer
                (my \value := nqp::atpos($!buffered,$!index)),
                nqp::bindpos($!buffered,$!index,pulled),
                ($!index = nqp::mod_i(nqp::add_i($!index,1),$!size)),
                value
              )
            )
        }
        method is-deterministic(--> Bool:D) {
            $!iterator.is-deterministic
        }
        method is-monotonically-increasing(--> Bool:D) {
            $!iterator.is-monotonically-increasing
        }
    }
    method AllButLastNValues(\iterator, \n) {
        n == 1
          ?? AllButLast.new(iterator)
          !! AllButLastNValues.new(iterator,n)
    }

    # Return an iterator that will generate a pair with the value as the
    # key and as value the key of the given iterator, basically the
    # .antipairs functionality on 1 dimensional lists.
    my class AntiPair does Iterator {
        has Mu $!iter;
        has int $!key;

        method !SET-SELF(\iter) { $!iter := iter; $!key = -1; self }
        method new(\iter) { nqp::create(self)!SET-SELF(iter) }

        method pull-one() is raw {
            nqp::eqaddr((my \pulled := $!iter.pull-one),IterationEnd)
              ?? IterationEnd
              !! Pair.new(pulled,+(++$!key))
        }
        method push-all(\target --> IterationEnd) {
            my int $key = -1;
            nqp::until(
              nqp::eqaddr((my \pulled := $!iter.pull-one),IterationEnd),
              target.push(Pair.new(pulled,+(++$key)))
            )
        }
        method is-lazy() { $!iter.is-lazy }
        method is-deterministic(--> Bool:D) { $!iter.is-deterministic }
    }
    method AntiPair(\iterator) { AntiPair.new(iterator) }

    # Return an iterator that takes an Associative and an Iterable that
    # generates keys, to call the AT-KEY method on the Associative.
    my class AssociativeIterableKeys does Iterator {
        has $!associative;
        has $!iterator;
        method !SET-SELF(\associative, \iterator) {
            $!associative := associative;
            $!iterator    := iterator;
            self
        }
        method new(\asso,\iter) { nqp::create(self)!SET-SELF(asso,iter) }

        method pull-one() is raw {
            nqp::eqaddr((my \key := $!iterator.pull-one),IterationEnd)
              ?? IterationEnd
              !! $!associative.AT-KEY(key)
        }
        method push-all(\target --> IterationEnd) {
            my \iterator    := $!iterator;
            my \associative := $!associative;
            nqp::until(
              nqp::eqaddr((my \key := iterator.pull-one),IterationEnd),
              target.push(associative.AT-KEY(key))
            )
        }
        method is-deterministic(--> Bool:D) {
            $!iterator.is-deterministic
        }
        method is-monotonically-increasing(--> Bool:D) {
            $!iterator.is-monotonically-increasing
        }
        method List() {
            self.push-all(my \buffer := nqp::create(IterationBuffer));
            buffer.List
        }
    }
    method AssociativeIterableKeys(\asso, \iterable) {
        AssociativeIterableKeys.new(asso,iterable.iterator)
    }

    # Return an iterator that batches the given source iterator in
    # batches of the given size.  The third parameter indicates whether
    # a partial batch should be returned when the source iterator has
    # exhausted.  The returned iterator is as lazy as the source iterator.
    my class Batch does Iterator {
        has $!iterator;
        has int $!size;
        has int $!complete;
        has int $!is-exhausted;
        method !SET-SELF(\iterator,\size,\partial) {
            $!iterator := iterator;

            nqp::if(
              nqp::istype(size,Whatever),
              ($!size = -1),        # set to never stop and ok partial
              nqp::if(
                size < 1,
                X::OutOfRange.new(
                  what    => "Batching sublist length is",
                  got     => size,
                  range   => "1..^Inf",
                ).throw,
                nqp::if(
                  (nqp::istype(size,Int)
                    && nqp::isbig_I(nqp::decont(size)))
                    || size == Inf,
                  ($!size = -1),    # set to never stop and ok partial
                  nqp::stmts(
                    ($!size     = size),
                    ($!complete = !partial),
                  )
                )
              )
            );
            self
        }
        method new(\it,\si,\pa) { nqp::create(self)!SET-SELF(it,si,pa) }
        method pull-one() is raw {
          nqp::if(
            $!is-exhausted,
            IterationEnd,
            nqp::stmts(
              (my \reified := nqp::create(IterationBuffer)),
              nqp::until(
                nqp::iseq_i(nqp::elems(reified),$!size)
                  || nqp::eqaddr(
                       (my \pulled := $!iterator.pull-one),
                       IterationEnd
                     ),
                nqp::push(reified,pulled)
              ),
              nqp::if(
                nqp::eqaddr(pulled,IterationEnd)
                  && ($!is-exhausted = 1) # set the flag
                  && ($!complete || nqp::not_i(nqp::elems(reified))),
                IterationEnd,
                reified.List
              )
            )
          )
        }
        method is-lazy(--> Bool:D)          { $!iterator.is-lazy          }
        method is-deterministic(--> Bool:D) { $!iterator.is-deterministic }
        method is-monotonically-increasing(--> Bool:D) {
            $!iterator.is-monotonically-increasing
        }
    }
    method Batch(\iterator,\size,\partial) { Batch.new(iterator,size,partial) }

    # Return an iterator for a given Callable.  The Callable is supposed
    # to return a value for the iterator, or IterationEnd to indicate the
    # data from the Callable is exhausted.  No checks for Slips are done,
    # so they will be passed on as is.  Also optionally takes a flag to
    # mark the iterator as lazy or not: default is False (not lazy)
    proto method Callable(|) {*}
    my class CallableEager does Iterator {
        has &!callable;
        method new(&callable) {
            nqp::p6bindattrinvres(
              nqp::create(self),self,'&!callable',&callable)
        }
        method pull-one() is raw { &!callable() }
    }
    multi method Callable(&callable) { CallableEager.new(&callable) }

    my class CallableLazy does Iterator {
        has &!callable;
        method new(&callable) {
            nqp::p6bindattrinvres(
              nqp::create(self),self,'&!callable',&callable)
        }
        method pull-one() is raw { &!callable() }
        method is-lazy(--> True) { }
    }
    multi method Callable(&callable, Bool() $lazy) {
        $lazy
          ?? CallableLazy.new(&callable)
          !! CallableEager.new(&callable)
    }

    # Return an iterator for the "thunk xx 42" functionality.
    my class Callable-xx-Times does Iterator {
        has @!slipped;
        has $!code;
        has $!times;
        method !SET-SELF(\code,\times) {
            $!code := code;
            $!times = times;
            self
        }
        method new(\code,\times) {
            times > 0
              ?? nqp::create(self)!SET-SELF(code,times)
              !! Rakudo::Iterator.Empty
        }
        method pull-one() {
            nqp::if(
              @!slipped,
              @!slipped.shift,
              nqp::if(
                $!times > 0,
                nqp::stmts(
                  --$!times,             # consumed a value
                  nqp::if(
                    nqp::istype((my \pulled := $!code()),Slip),
                    nqp::if(
                      (@!slipped = pulled),
                      @!slipped.shift,
                      IterationEnd
                    ),
                    nqp::if(
                      nqp::istype(pulled,Seq),
                      pulled.cache,
                      pulled
                    )
                  )
                ),
                IterationEnd
              )
            )
        }
    }
    method Callable-xx-Times(&code, Int:D \times) {
        Callable-xx-Times.new(&code,times)
    }

    # Return an iterator for the "thunk xx *" functionality.
    my class Callable-xx-Whatever does Iterator {
        has @!slipped;
        has $!code;
        method new(\code) {
            nqp::p6bindattrinvres(nqp::create(self),self,'$!code',code)
        }
        method pull-one() {
            nqp::if(
              @!slipped,
              @!slipped.shift,
              nqp::if(
                nqp::istype((my \pulled := $!code()),Slip),
                nqp::if(
                  (@!slipped = pulled),
                  @!slipped.shift,
                  IterationEnd
                ),
                nqp::if(
                  nqp::istype(pulled,Seq),
                  pulled.cache,
                  pulled
                )
              )
            )
        }
        method is-lazy(--> True) { }
    }
    method Callable-xx-Whatever(&code) { Callable-xx-Whatever.new(&code) }

    # Returns an iterator for a one character range of values.  Takes the
    # first, last character, and whether the first / last should be
    # excluded.
    my class CharFromTo does PredictiveIterator {
        has int $!i;
        has int $!n;

        method !SET-SELF(\min,\max,\excludes-min,\excludes-max) {
            $!i = nqp::ord(nqp::unbox_s(min))
                - (excludes-min ?? 0 !! 1);
            $!n = nqp::ord(nqp::unbox_s(max))
                - (excludes-max ?? 1 !! 0);
            self
        }
        method new(\min,\max,\excludes-min,\excludes-max) {
            nqp::create(self)!SET-SELF(
               min,max,excludes-min,excludes-max)
        }
        method pull-one() {
            ( $!i = $!i + 1 ) <= $!n
              ?? nqp::chr($!i)
              !! IterationEnd
        }
        method skip-one() { ( $!i = $!i + 1 ) <= $!n }
        method push-all(\target --> IterationEnd) {
            my int $i = $!i;
            my int $n = $!n;
            target.push(nqp::chr($i)) while ($i = $i + 1) <= $n;
            $!i = $i;
        }
        method count-only(--> Int:D) { $!n - $!i }
        method is-monotonically-increasing(--> True) { }
        method sink-all(--> IterationEnd) { $!i = $!n }
    }
    method CharFromTo(\min,\max,\excludes-min,\excludes-max) {
        CharFromTo.new(min,max,excludes-min,excludes-max)
    }

    # Return an iterator for a range of 0..^N with a number of elements.
    # The third parameter indicates whether an IterationBuffer should be
    # returned (1) for each combinatin, or a fully reified List (0).
    # Has a highly optimized count-only, for those cases when one is only
    # interested in the number of combinations, rather than the actual
    # combinations.  The workhorse of combinations().
    my class Combinations does PredictiveIterator {
        has int $!pulled-count = 0;
        has int $!n;
        has int $!k;
        has int $!b;
        has Mu $!stack;
        has Mu $!combination;
        method !SET-SELF(\n,\k,\b) {
            $!n = n;
            $!k = k;
            $!b = b;
            $!stack := nqp::list_i(0);
            $!combination := nqp::create(IterationBuffer);
            self
        }
        method new(\n,\k,\b) { nqp::create(self)!SET-SELF(n,k,b) }

        method pull-one() {
            my int $n        = $!n;          # lexicals faster
            my int $k        = $!k;
            my int $running  = 1;
            my $combination := $!combination;
            my $stack       := $!stack;

            nqp::while(
              ($running && (my int $elems = nqp::elems($stack))),
              nqp::stmts(
                (my int $index = $elems - 1),
                (my int $value = nqp::pop_i($stack)),
                nqp::while(
                  nqp::islt_i($value,$n) && nqp::islt_i($index,$k),
                  nqp::stmts(
                    nqp::bindpos($combination,$index,nqp::clone($value)),
                    ++$index,
                    ++$value,
                    nqp::push_i($stack,$value)
                  )
                ),
                ($running = nqp::isne_i($index,$k)),
              )
            );

            nqp::if(
              nqp::iseq_i($index,$k),
              nqp::stmts(
                ++$!pulled-count,
                nqp::if(
                  $!b,
                  nqp::clone($combination),
                  nqp::clone($combination).List
                )
              ),
              IterationEnd
            )
        }

        method count-only(--> Int:D) {
            (([*] ($!n ... 0) Z/ 1 .. min($!n - $!k, $!k)).Int)
            - $!pulled-count
        }
    }
    method Combinations($n, $k, int $b) {
        nqp::if(
          $n > 0 && nqp::isbig_I(nqp::decont($n)),    # must be HLL comparison
          X::OutOfRange.new(
            :what("First parameter"),
            :got($n),
            :range("-Inf^..{$?BITS == 32 ?? 2**28-1 !! 2**31-1}")
          ).throw,
          nqp::if(
            # k = 0 → can pick just 1 combination (empty list); return ((),)
            $k == 0,                                  # Must be HLL comparison
            Rakudo::Iterator.OneValue(
              nqp::create(nqp::if($b,IterationBuffer,List))
            ),
            nqp::if(
              # n < 1 → we have an empty list to pick from
              # n < k → not enough items to pick combination of k items
              $n < 1 || $n < $k || $k < 0,            # must be HLL comparisons
              Rakudo::Iterator.Empty,                 # nothing to return
              Combinations.new($n,$k,$b)
            )
          )
        )
    }

    # Return an iterator that will cross the given iterables (with &[,])
    # Basically the functionality of @a X @b
    my class CrossIterables does Iterator {
        has $!iterators;  # iterator per iterable, if any
        has $!reifieds;   # cached values (either complete, or so far)
        has $!indices;    # indices of virtual matrix of crossed values
        has $!next;       # IterationBuffer with next values to return
        has int $!lazy;   # whether the outer iterator is lazy
        has int $!top;    # index of top reified/iterator

        method !SET-SELF(\iterables) {
            my $iterables := nqp::getattr(iterables,List,'$!reified');
            my int $elems  = nqp::elems($iterables);
            $!iterators   := nqp::setelems(nqp::list,$elems);
            $!reifieds    := nqp::setelems(nqp::list,$elems);
            $!next := nqp::setelems(nqp::create(IterationBuffer),$elems);

            # loop over all iterables
            my int $i = -1;
            nqp::while(
              nqp::islt_i(++$i,$elems),

              # set up initial value of index $i with...
              nqp::bindpos($!next,$i,nqp::if(
                nqp::iscont(my $elem := nqp::atpos($iterables,$i))
                  || nqp::not_i(nqp::istype($elem,Iterable)),

                # single value same as reified list of 1
                nqp::bindpos(
                  nqp::bindpos($!reifieds,$i,nqp::list),
                  0,
                  $elem
                ),

                # something more elaborate
                nqp::if(
                  nqp::istype($elem,List)
                    && nqp::not_i(nqp::isconcrete(
                         nqp::getattr($elem,List,'$!todo')
                  )),

                  # it's a List, may have a reified we can use directly
                  nqp::if(
                    nqp::isconcrete(
                      $elem := nqp::getattr($elem,List,'$!reified')
                    ) && nqp::isgt_i(nqp::elems($elem),0),

                    # use the available reified directly
                    nqp::stmts(
                      nqp::bindpos($!reifieds,$i,$elem),
                      nqp::atpos($elem,0)
                    ),

                    # cross with an empty list is always an empty list
                    return Rakudo::Iterator.Empty
                  ),

                  # need to set up an iterator
                  nqp::stmts(
                    nqp::if($elem.is-lazy,($!lazy = 1)),
                    nqp::if(
                      nqp::eqaddr(
                        (my $pulled :=
                          ($elem := $elem.iterator).pull-one),
                        IterationEnd
                      ),

                      # cross with an empty list is an empty list
                      (return Rakudo::Iterator.Empty),

                      # set up the iterator stuff
                      nqp::stmts(
                        nqp::bindpos($!iterators,$i,$elem),
                        nqp::bindpos($!reifieds,$i,nqp::list($pulled)),
                        $pulled
                      )
                    )
                  )
                )
              ))
            );

            # indices start with 0 xx $elems
            $!indices := nqp::setelems(nqp::list_i,$elems);
            $!top = nqp::sub_i($elems,1);
            self
        }
        method new(\iterables) { nqp::create(self)!SET-SELF(iterables) }
        method pull-one() {
            nqp::if(
              nqp::eqaddr($!next,IterationEnd),
              IterationEnd,
              nqp::stmts(

                # set up result of this pull
                (my $result := nqp::clone($!next).List),

                # start working on next result
                nqp::unless(
                  nqp::isnull(nqp::atpos($!iterators,$!top)),

                  # top level is still iterator, fetch
                  nqp::if(
                    nqp::eqaddr(
                      (my $pulled :=
                        nqp::atpos($!iterators,$!top).pull-one),
                      IterationEnd
                    ),
                    # iterator no more
                    nqp::bindpos($!iterators,$!top,nqp::null),

                    # push value, let normal reifier handler handle
                    nqp::push(
                      nqp::atpos($!reifieds,$!top),
                      $pulled
                    )
                  )
                ),

                # no iterator, must use reified list
                nqp::if(
                  nqp::islt_i(
                    (my int $index =
                      nqp::add_i(nqp::atpos_i($!indices,$!top),1)),
                    nqp::elems(nqp::atpos($!reifieds,$!top))
                  ),

                  # within range, update next result and index
                  nqp::bindpos($!next,$!top,
                    nqp::atpos(
                      nqp::atpos($!reifieds,$!top),
                      nqp::bindpos_i($!indices,$!top,$index)
                    )
                  ),

                  # need to update lower levels
                  nqp::stmts(

                    # update topmost value (go back to first)
                    nqp::bindpos($!next,$!top,
                      nqp::atpos(
                        nqp::atpos($!reifieds,$!top),
                        nqp::bindpos_i($!indices,$!top,0)
                      )
                    ),

                    # until we're at the bottom
                    (my int $level = $!top),
                    nqp::while(
                      nqp::isge_i(--$level,0),
                      nqp::if(
                        nqp::isnull(nqp::atpos($!iterators,$level)),

                        # can use reified list at this level
                        nqp::if(
                          nqp::islt_i(
                            ($index = nqp::add_i(
                              nqp::atpos_i($!indices,$level),1)),
                            nqp::elems(nqp::atpos($!reifieds,$level))
                          ),

                          # within range, update next result and index
                          nqp::stmts(
                            nqp::bindpos($!next,$level,
                              nqp::atpos(
                                nqp::atpos($!reifieds,$level),
                                nqp::bindpos_i($!indices,$level,$index)
                              )
                            ),
                            ($level = -1)  # done searching
                          ),

                          # reset this level
                          nqp::bindpos($!next,$level,
                            nqp::atpos(
                              nqp::atpos($!reifieds,$level),
                              nqp::bindpos_i($!indices,$level,0)
                            )
                          )
                        ),

                        # still an iterator at this level
                        nqp::if(
                          nqp::eqaddr(
                            ($pulled :=
                              nqp::atpos($!iterators,$level).pull-one),
                            IterationEnd
                          ),

                          # exhausted iterator, reset to reified
                          nqp::stmts(
                            nqp::bindpos($!iterators,$level,nqp::null),
                            nqp::bindpos($!next,$level,
                              nqp::atpos(
                                nqp::atpos($!reifieds,$level),
                                nqp::bindpos_i($!indices,$level,0)
                              )
                            )
                          ),

                          # new value, add to reified, update indices
                          nqp::stmts(
                            nqp::bindpos(
                              $!next,
                              $level,
                              nqp::bindpos(
                                nqp::atpos($!reifieds,$level),
                                nqp::bindpos_i(
                                  $!indices,
                                  $level,
                                  nqp::add_i(
                                    nqp::atpos_i($!indices,$level),
                                    1
                                  )
                                ),
                                $pulled
                              )
                            ),
                            ($level = -1)  # done searching
                          )
                        )
                      )
                    ),
                    nqp::if(
                      nqp::iseq_i($level,-1),
                      # was last iteration, free up everything now
                      ($!next := $!iterators := $!reifieds := $!indices :=
                        IterationEnd)
                    )
                  )
                ),
                $result
              )
            )
        }
        method is-lazy(--> Bool:D) { nqp::hllbool($!lazy) }
    }
    method CrossIterables(@iterables) {
        nqp::isgt_i(@iterables.elems,0)  # reifies
          ?? CrossIterables.new(@iterables)
          !! Rakudo::Iterator.Empty
    }

    # Return an iterator that will cross the given iterables and map
    # the result with the given mapper Callable.  Basically the
    # functionality of @a Xop @b (with the op  -> mapper functionality
    # to be supplied externally).
    my class CrossIterablesMap does Iterator {
        has $!iterators;  # iterator per iterable, if any
        has $!reifieds;   # cached values (either complete, or so far)
        has $!indices;    # indices of virtual matrix of crossed values
        has $!next;       # IterationBuffer with next values to return
        has $!mapper;     # Callable to do final result mapping
        has int $!lazy;   # whether the outer iterator is lazy
        has int $!top;    # index of top reified/iterator

        method !SET-SELF(\iterables,\mapper) {
            my $iterables := nqp::getattr(iterables,List,'$!reified');
            my int $elems  = nqp::elems($iterables);
            $!iterators   := nqp::setelems(nqp::list,$elems);
            $!reifieds    := nqp::setelems(nqp::list,$elems);
            $!next := nqp::setelems(nqp::create(IterationBuffer),$elems);

            # loop over all iterables
            my int $i = -1;
            nqp::while(
              nqp::islt_i(++$i,$elems),

              # set up initial value of index $i with...
              nqp::bindpos($!next,$i,nqp::if(
                nqp::iscont(my $elem := nqp::atpos($iterables,$i))
                  || nqp::not_i(nqp::istype($elem,Iterable)),

                # single value same as reified list of 1
                nqp::bindpos(
                  nqp::bindpos($!reifieds,$i,nqp::list),
                  0,
                  $elem
                ),

                # something more elaborate
                nqp::if(
                  nqp::istype($elem,List)
                    && nqp::not_i(nqp::isconcrete(
                         nqp::getattr($elem,List,'$!todo')
                  )),

                  # it's a List, may have a reified we can use directly
                  nqp::if(
                    nqp::isconcrete(
                      $elem := nqp::getattr($elem,List,'$!reified')
                    ) && nqp::isgt_i(nqp::elems($elem),0),

                    # use the available reified directly
                    nqp::stmts(
                      nqp::bindpos($!reifieds,$i,$elem),
                      nqp::atpos($elem,0)
                    ),

                    # cross with an empty list is always an empty list
                    (return Rakudo::Iterator.Empty)
                  ),

                  # need to set up an iterator
                  nqp::stmts(
                    nqp::if($elem.is-lazy,($!lazy = 1)),
                    nqp::if(
                      nqp::eqaddr(
                        (my $pulled :=
                          ($elem := $elem.iterator).pull-one),
                        IterationEnd
                      ),

                      # cross with an empty list is an empty list
                      (return Rakudo::Iterator.Empty),

                      # set up the iterator stuff
                      nqp::stmts(
                        nqp::bindpos($!iterators,$i,$elem),
                        nqp::bindpos($!reifieds,$i,nqp::list($pulled)),
                        $pulled
                      )
                    )
                  )
                )
              ))
            );

            # indices start with 0 xx $elems
            $!indices := nqp::setelems(nqp::list_i,$elems);
            $!top = nqp::sub_i($elems,1);
            $!mapper := mapper;
            self
        }
        method new(\its,\map) { nqp::create(self)!SET-SELF(its,map) }
        method pull-one() {
            nqp::if(
              nqp::eqaddr($!next,IterationEnd),
              IterationEnd,
              nqp::stmts(

                # set up result of this pull
                # we *MUST* clone here, because we cannot be sure
                # the mapper isn't going to throw the buffer away.
                (my $result := $!mapper(nqp::clone($!next))),

                # start working on next result
                nqp::unless(
                  nqp::isnull(nqp::atpos($!iterators,$!top)),

                  # top level is still iterator, fetch
                  nqp::if(
                    nqp::eqaddr(
                      (my $pulled :=
                        nqp::atpos($!iterators,$!top).pull-one),
                      IterationEnd
                    ),
                    # iterator no more
                    nqp::bindpos($!iterators,$!top,nqp::null),

                    # push value, let normal reifier handler handle
                    nqp::push(
                      nqp::atpos($!reifieds,$!top),
                      $pulled
                    )
                  )
                ),

                # no iterator, must use reified list
                nqp::if(
                  nqp::islt_i(
                    (my int $index =
                      nqp::add_i(nqp::atpos_i($!indices,$!top),1)),
                    nqp::elems(nqp::atpos($!reifieds,$!top))
                  ),

                  # within range, update next result and index
                  nqp::bindpos($!next,$!top,
                    nqp::atpos(
                      nqp::atpos($!reifieds,$!top),
                      nqp::bindpos_i($!indices,$!top,$index)
                    )
                  ),

                  # need to update lower levels
                  nqp::stmts(

                    # update topmost value (go back to first)
                    nqp::bindpos($!next,$!top,
                      nqp::atpos(
                        nqp::atpos($!reifieds,$!top),
                        nqp::bindpos_i($!indices,$!top,0)
                      )
                    ),

                    # until we're at the bottom
                    (my int $level = $!top),
                    nqp::while(
                      nqp::isge_i(--$level,0),
                      nqp::if(
                        nqp::isnull(nqp::atpos($!iterators,$level)),

                        # can use reified list at this level
                        nqp::if(
                          nqp::islt_i(
                            ($index = nqp::add_i(
                              nqp::atpos_i($!indices,$level),1)),
                            nqp::elems(nqp::atpos($!reifieds,$level))
                          ),

                          # within range, update next result and index
                          nqp::stmts(
                            nqp::bindpos($!next,$level,
                              nqp::atpos(
                                nqp::atpos($!reifieds,$level),
                                nqp::bindpos_i($!indices,$level,$index)
                              )
                            ),
                            ($level = -1)  # done searching
                          ),

                          # reset this level
                          nqp::bindpos($!next,$level,
                            nqp::atpos(
                              nqp::atpos($!reifieds,$level),
                              nqp::bindpos_i($!indices,$level,0)
                            )
                          )
                        ),

                        # still an iterator at this level
                        nqp::if(
                          nqp::eqaddr(
                            ($pulled :=
                              nqp::atpos($!iterators,$level).pull-one),
                            IterationEnd
                          ),

                          # exhausted iterator, reset to reified
                          nqp::stmts(
                            nqp::bindpos($!iterators,$level,nqp::null),
                            nqp::bindpos($!next,$level,
                              nqp::atpos(
                                nqp::atpos($!reifieds,$level),
                                nqp::bindpos_i($!indices,$level,0)
                              )
                            )
                          ),

                          # new value, add to reified, update indices
                          nqp::stmts(
                            nqp::bindpos(
                              $!next,
                              $level,
                              nqp::bindpos(
                                nqp::atpos($!reifieds,$level),
                                nqp::bindpos_i(
                                  $!indices,
                                  $level,
                                  nqp::add_i(
                                    nqp::atpos_i($!indices,$level),
                                    1
                                  )
                                ),
                                $pulled
                              )
                            ),
                            ($level = -1)  # done searching
                          )
                        )
                      )
                    ),
                    nqp::if(
                      nqp::iseq_i($level,-1),
                      # was last iteration, free up everything now
                      ($!next := $!iterators := $!reifieds := $!indices :=
                        IterationEnd)
                    )
                  )
                ),
                $result
              )
            )
        }
        method is-lazy() { nqp::hllbool($!lazy) }
    }
    method CrossIterablesMap(@iterables,&mapper) {
        nqp::isgt_i((my int $n = @iterables.elems),1)  # reifies
          # actually need to do some crossing (probably)
          ?? CrossIterablesMap.new(@iterables,&mapper)
          # simpler cases
          !! nqp::iseq_i($n,0)
            # nothing to cross, so return an empty list
            ?? Rakudo::Iterator.Empty
            # only 1 list to cross, which is the list itself
            !! nqp::atpos(nqp::getattr(@iterables,List,'$!reified'),0).iterator
    }

    # Return an iterator that will cross the given iterables and operator.
    # Basically the functionality of @a Z=> @b, with &[=>] being the op.
    method CrossIterablesOp(@iterables,\op) {
        nqp::eqaddr(op,&infix:<,>)
          ?? Rakudo::Iterator.CrossIterables(@iterables)
          !! Rakudo::Iterator.CrossIterablesMap(
               @iterables,
               Rakudo::Metaops.MapperForOp(op)
             )
    }

    # Returns an iterator that handles all properties of a C-style loop with
    # a condition.  Takes a Callable to be considered the body of the loop,
    # another Callable for the condition, and a third Callable to be executed
    # after each iteration.
    my class CStyleLoop does Rakudo::SlippyIterator {
        has &!body;
        has &!cond;
        has &!afterwards;
        has $!label;
        has int $!seen-first;

        method !SET-SELF(\body,\cond,\afterwards,\label) {
            nqp::bindattr(self,self.WHAT,'$!slipper',nqp::null);
            &!body := body;
            &!cond := cond;
            &!afterwards := afterwards;
            $!label := nqp::decont(label);
            self
        }
        method new(\body,\cond,\afterwards,\label) {
            nqp::create(self)!SET-SELF(body,cond,afterwards,label)
        }

        method pull-one() {
            if nqp::not_i(nqp::isnull($!slipper)) && nqp::not_i(
                nqp::eqaddr((my $result := self.slip-one),IterationEnd)
            ) {
                $result
            }
            else {
                $!seen-first
                  ?? &!afterwards()
                  !! ($!seen-first = 1);

                nqp::if(
                  &!cond(),
                  nqp::stmts(
                    nqp::until(
                      (my int $stopped),
                      nqp::stmts(
                        ($stopped = 1),
                        nqp::handle(
                          nqp::if(
                            nqp::istype(($result := &!body()),Slip),
                            nqp::if(
                              nqp::eqaddr(
                                ($result := self.start-slip($result)),
                                IterationEnd
                              ),
                              nqp::stmts(
                                &!afterwards(),
                                ($stopped = nqp::if(&!cond(),0,1))
                              )
                            )
                          ),
                          'LABELED', $!label,
                          'NEXT', nqp::if(
                            nqp::eqaddr(
                              ($result := self.control-payload),
                              IterationEnd
                            ),
                            nqp::stmts(          # bare next or empty Slip
                              &!afterwards(),
                              ($stopped = nqp::if(&!cond(),0,1))
                            )
                          ),
                          'REDO', ($stopped = 0),
                          'LAST', nqp::if(
                            nqp::eqaddr(
                              ($result := self.control-payload),
                              IterationEnd
                            ),
                            ($!seen-first = 0),  # bare 'last' or empty Slip
                            (&!cond := &always-False)  # end later
                          )
                        )
                      ),
                      :nohandler
                    ),
                    $result
                  ),
                  IterationEnd
                )
            }
        }
    }
    method CStyleLoop(&body, &cond, &afterwards, $label) {
        CStyleLoop.new(&body, &cond, &afterwards, $label)
    }

    # Returns an iterator for iterating file system directories, producing
    # IO::Path objects for a directory entry if it smart matches the given
    # tester.  Takes an IO::Path object and a prefix to be added to each
    # director entry produced.
    class DirTest does Iterator {
        has Mu  $!path;      # IO::Path object to do dir for
        has str $!prefix;    # string to prefix to elements found
        has Mu  $!CWD;       # IO::Path object for $*CWD when testing
        has Mu  $!tester;    # object to call .ACCEPTS on to accept entry
        has Mu  $!dirhandle; # low level directory handle
#?if !js
        has $!dots;  # Moar & JVM don't produce "." and "..", so we need to fake them
#?endif

        method !SET-SELF(\path, Mu \tester) {
            $!path      := path;
            $!prefix     = path.prefix-for-dir;
            $!CWD       := path.CWD.IO;
            $!tester    := tester;
            $!dirhandle := nqp::opendir(path.absolute);
#?if !js
            $!dots   := nqp::list_s(".","..");
#?endif

            self
        }

        method new(\path, Mu \tester) { nqp::create(self)!SET-SELF(path, tester) }

        method pull-one() {
            my str $entry;
            my $*CWD := $!CWD;

            nqp::while(
#?if !js
              ($entry = nqp::if(
                nqp::elems($!dots),
                nqp::shift_s($!dots),
                nqp::nextfiledir($!dirhandle)
              )),
#?endif
#?if js
              ($entry = nqp::nextfiledir($!dirhandle)),
#?endif
              nqp::if(
                $!tester.ACCEPTS($entry),
                return nqp::clone(
                  $!path
                ).cloned-with-path(nqp::concat($!prefix,$entry))
              )
            );

            nqp::closedir($!dirhandle);
            IterationEnd
        }

        method push-all(\target --> IterationEnd) {
#?if !js
            my $dots      := $!dots;
#?endif
            my $path      := $!path;
            my str $prefix = $!prefix;
            my $tester    := $!tester;
            my $dirhandle := $!dirhandle;
            my str $entry;

            my $*CWD := $!CWD;
            nqp::while(
#?if !js
              ($entry = nqp::if(
                nqp::elems($dots),
                nqp::shift_s($dots),
                nqp::nextfiledir($dirhandle)
              )),
#?endif
#?if js
              ($entry = nqp::nextfiledir($dirhandle)),
#?endif
              nqp::if(
                $tester.ACCEPTS($entry),
                target.push: nqp::clone(
                  $path
                ).cloned-with-path(nqp::concat($prefix,$entry))
              )
            );

            nqp::closedir($dirhandle);
        }
    }

    # Returns an iterator for iterating file system directories, producing
    # IO::Path objects for *each* directory entry found (except "." and "..").
    # Takes an IO::Path and a prefix to be added to each directory entry
    # produced.
    class Dir does Iterator {
        has Mu  $!path;      # IO::Path object to do dir for
        has str $!prefix;    # string to prefix to elements found
        has Mu  $!dirhandle; # low level directory handle

        method !SET-SELF(\path) {
            $!path  := path;
            $!prefix = path.prefix-for-dir;

#?if js
            my $dirhandle := nqp::opendir(path.absolute);
            # skipping . and .. worked, JVM never produces them
            if nqp::iseq_s(nqp::nextfiledir($dirhandle),'.')
              && nqp::iseq_s(nqp::nextfiledir($dirhandle),'..') {
                $!dirhandle  := $dirhandle;
                self
            }

            # strange, no '.' or '..' at start, run with tester
            else {
                DirTest.new(
                  path,
                  -> str $d { nqp::isne_s($d,'.') && nqp::isne_s($d,'..') },
                )
            }
#?endif
#?if !js
            $!dirhandle := nqp::opendir(path.absolute);
            self
#?endif
        }

        method new(\path) { nqp::create(self)!SET-SELF(path) }

        method pull-one() {
            nqp::if(
              (my str $entry = nqp::nextfiledir($!dirhandle)),
              nqp::clone($!path).cloned-with-path(nqp::concat($!prefix,$entry)),
              nqp::stmts(
                nqp::closedir($!dirhandle),
                IterationEnd
              )
            )
        }

        method push-all(\target --> IterationEnd) {
            my $path      := $!path;
            my str $prefix = $!prefix;
            my $dirhandle := $!dirhandle;
            my str $entry;

            nqp::while(
              ($entry = nqp::nextfiledir($dirhandle)),
              target.push:
                nqp::clone($path).cloned-with-path(nqp::concat($prefix,$entry))
            );

            nqp::closedir($dirhandle);
        }
    }

    proto method Dir(|) {*}
    multi method Dir(IO::Path:D \path)          {     Dir.new(path)         }
    multi method Dir(IO::Path:D \path, Mu \tester) { DirTest.new(path, tester) }

    # Create an iterator from a source iterator that will repeat the
    # values of the source iterator indefinitely *unless* a Whatever
    # was encountered, in which case it will repeat the last seen value
    # indefinitely (even if the source iterator wasn't actually exhausted).
    # Only if the source iterator did not produce any values at all, then
    # the resulting iterator will not produce any either.
    my class DWIM does Iterator {
        has $!source;
        has $!buffer;
        has int $!ended;
        has int $!whatever;
        has int $!i;
        method !SET-SELF(\source) {
            $!source := source;
            $!buffer := nqp::create(IterationBuffer);
            self
        }
        method new(\source) { nqp::create(self)!SET-SELF(source) }

        method pull-one() is raw {
            nqp::if(
              $!ended,
              nqp::if(                          # source exhausted
                $!whatever,
                nqp::if(                        # seen a Whatever
                  nqp::elems($!buffer),
                  nqp::atpos($!buffer,          # last value seen
                    nqp::sub_i(nqp::elems($!buffer),1)),
                  Nil                           # no last value seen
                ),
                nqp::atpos($!buffer,            # not seen, so modulo repeat
                  nqp::mod_i(
                    nqp::sub_i(++$!i,1),
                    nqp::elems($!buffer)
                  )
                )
              ),
              nqp::if(                          # source not exhausted
                nqp::eqaddr((my \value := $!source.pull-one),IterationEnd),
                nqp::stmts(                     # exhausted now
                  ($!ended = 1),
                  nqp::if(
                    nqp::iseq_i(nqp::elems($!buffer),0),
                    IterationEnd,               # nothing to repeat, done
                    self.pull-one               # last or repeat
                  )
                ),
                nqp::if(                        # got a value
                  nqp::istype(value,Whatever),
                  nqp::stmts(                   # done, repeat last value
                    ($!whatever = $!ended = 1),
                    self.pull-one,
                  ),
                  nqp::stmts(                   # save / return value
                    nqp::push($!buffer,value),
                    value
                  )
                )
              )
            )
        }

        # Is the source iterator considered exhausted?
        method ended() { nqp::hllbool($!ended) }

        # Eat the iterator trying to find out the number of elements
        # produced by the iterator.  Intended to provide information
        # for error messages.
        method count-elems() {
            nqp::if(
              $!ended,
              nqp::elems($!buffer),
              nqp::stmts(
                (my int $elems = nqp::elems($!buffer)),
                nqp::until(
                  nqp::eqaddr($!source.pull-one,IterationEnd),
                  ++$elems
                ),
                $elems
              )
            )
        }
        method is-deterministic(--> Bool:D) { $!source.is-deterministic }
    }
    method DWIM(\source) { DWIM.new(source) }

    # Returns a sentinel Iterator object that will never generate any value.
    # Does not take a parameter.
    my class Empty does PredictiveIterator {
        method new() { nqp::create(self) }
        method pull-one(--> IterationEnd)  { }
        method push-all($ --> IterationEnd) { }
        method sink-all(--> IterationEnd)  { }
        method skip-one(--> 0) { }
        method skip-at-least($ --> 0) { }
        method count-only(--> 0) { }
        method bool-only(--> False) { }
    }
    my constant EmptyIterator = Empty.new;
    method Empty() { EmptyIterator }

    # Returns at most N items, then calls .sink-all on source. Optionally,
    # executes a Callable when either N items were returned or original iterator
    # got exhausted. N can be negative to ask for "all values".
    # This is used in several places in IO::Handle, e.g. in
    # .lines to read N lines and then close the filehandle via .sink-all
    my class FirstNThenSinkAllN does Iterator {
        has $!source;
        has int $!n;
        has int $!i;
        has &!callable;
        method pull-one() is raw {
            nqp::if(
              nqp::isle_i($!n,++$!i),
              nqp::if(
                nqp::iseq_i($!i, $!n),
                nqp::stmts(
                  (my \got1 := $!source.pull-one),
                  self!FINISH-UP(
                    nqp::isfalse(nqp::eqaddr(got1, IterationEnd))),
                  got1),
                nqp::stmts(
                  $!n || self!FINISH-UP(1),
                  IterationEnd)),
              nqp::if(
                nqp::eqaddr((my \got2 := $!source.pull-one),IterationEnd),
                nqp::stmts(
                  self!FINISH-UP(0),
                  IterationEnd),
                got2))
        }
        method is-deterministic(--> Bool:D) { $!source.is-deterministic }
        method sink-all(--> IterationEnd) { self.FINISH-UP }
        method new(\s,\n,\c) { nqp::create(self)!SET-SELF(s,n,c) }
        method !SET-SELF($!source,$!n,&!callable) { self }
        method !FINISH-UP(\do-sink) {
            do-sink    && $!source.sink-all;
            &!callable && &!callable();
            1
        }
    }
    my class FirstNThenSinkAllCallable  does Iterator {
        has $!source;
        has &!callable;
        method pull-one() is raw {
            nqp::eqaddr((my \got := $!source.pull-one),IterationEnd)
              && (&!callable() || 1)
              ?? IterationEnd
              !! got
        }
        method sink-all(--> IterationEnd) {
            $!source.sink-all;
            &!callable();
        }
        method new(\s,\c) { nqp::create(self)!SET-SELF(s,c) }
        method !SET-SELF($!source,&!callable) { self }
        method is-deterministic(--> Bool:D) { $!source.is-deterministic }
    }
    method FirstNThenSinkAll(\source,\n,&callable?) {
        # XXX TODO: Make this code DRYer by moving common bits to a role,
        # but currently (2017-04) assigning to `int $!n` attribute from SET-SELF
        # signature complains about immutable ints if done in a role, and
        # private methods **in roles** are slow, so we duplicated stuff here

        nqp::isge_i(n,0)
          # only want N pull's
          ?? FirstNThenSinkAllN.new(source,n,&callable)
          # want it all
          !! &callable
            # want it all with callable
            ?? FirstNThenSinkAllCallable.new(source,&callable)
            # want it all without callable
            !! source
    }

    # Return an iterator that flattens all embedded Iterables into a single
    # iterator, producing a single sequence of non-Iterable values
    my class Flat does Iterator {
        has      $!iterator;
        has      @!next;
        has uint $!levels;
        has uint $!decont;

        method new($iterator, uint $levels, uint $decont) {
            my $self := nqp::create(self);
            nqp::bindattr_i($self,Flat,'$!levels',$levels);
            nqp::bindattr_i($self,Flat,'$!decont',$decont);
            nqp::p6bindattrinvres($self,Flat,'$!iterator',$iterator)
        }

        method pull-one() is raw {
            nqp::while(
              nqp::eqaddr((my $pulled := $!iterator.pull-one),IterationEnd),
              nqp::if(
                @!next,
                ($!iterator := @!next.pop),
                (return IterationEnd)
              )
            );

            nqp::if(
              nqp::isconcrete($pulled)
                && nqp::istype($pulled,Iterable)
                && @!next.elems < $!levels
                && $!decont >= nqp::iscont($pulled),
              nqp::stmts(
                @!next.push($!iterator),
                ($!iterator := $pulled.iterator),
                self.pull-one
              ),
              $pulled
            )
        }

        method is-lazy(--> Bool:D) {
            $!iterator.is-lazy
        }
        method is-deterministic(--> Bool:D) {
            $!iterator.is-deterministic
        }
        method is-monotonically-increasing(--> Bool:D) {
            $!iterator.is-monotonically-increasing
        }
    }
    method Flat(\iterator, $levels, $decont) {
        Flat.new(
          iterator,
#?if !jvm
          nqp::istype($levels,Whatever) || $levels == Inf ?? -1 !! $levels.Int,
#?endif
#?if jvm
          nqp::istype($levels,Whatever) || $levels == Inf ?? int.Range.max !! $levels.Int,
#?endif
          $decont.Int
        )
    }

    # Return an iterator that will cache a source iterator for the index
    # values that the index iterator provides, from a given offest in the
    # cached source iterator.  Values from the index iterator below the
    # offset, are considered to be illegal and will throw.  Also takes an
    # optional block to be called when an otherwise out-of-bounds index
    # value is given by the index iterator: if not given, Nil will be
    # returned for such index values.
    my class FromIndexes does Iterator {
        has $!source;
        has $!indexes;
        has int $!offset;
        has &!out;
        has $!cache;
        method !SET-SELF(\source, \indexes, \offset, \out) {
            $!source  := source;
            $!indexes := indexes,
            $!cache   := nqp::setelems(nqp::list,$!offset = offset);
            &!out     := out;
            self
        }
        method new(\s,\i,\o,\out) { nqp::create(self)!SET-SELF(s,i,o,out) }
        method pull-one() is raw {
            nqp::if(
              nqp::eqaddr((my $got := $!indexes.pull-one),IterationEnd),
              IterationEnd,
              nqp::if(
                nqp::istype(                      # doesn't look like int
                  (my $number = +$got),Failure),
                $number.throw,
                nqp::if(                          # out of range
                  nqp::islt_i((my int $index = $number.Int),$!offset),
                  X::OutOfRange.new(:$got,:range("$!offset..^Inf")).throw,
                  nqp::if(
                    nqp::existspos($!cache,$index),
                    nqp::atpos($!cache,$index),   # it's in the cache
                    nqp::if(
                      nqp::defined($!source),
                      nqp::stmts(                 # can still search it
                        nqp::until(
                          nqp::existspos($!cache,$index)
                            || nqp::eqaddr(
                                 (my \pulled := $!source.pull-one),
                                 IterationEnd
                               ),
                          nqp::push($!cache,pulled)
                        ),
                        nqp::if(
                          nqp::eqaddr(pulled,IterationEnd),
                          nqp::stmts(
                            ($!source := Mu),
                            nqp::if(
                              $!indexes.is-lazy,
                              IterationEnd,       # not going to be any more
                              nqp::stmts(         # didn't find it
                                nqp::if(&!out,&!out($index)),
                                Nil
                              )
                            )
                          ),
                          pulled                  # found it
                        )
                      ),
                      nqp::stmts(                 # cannot be found
                        nqp::if(&!out,&!out($index)),
                        Nil
                      )
                    )
                  )
                )
              )
            )
        }
        method is-lazy() { $!source.is-lazy && $!indexes.is-lazy }
        method is-deterministic(--> Bool:D) { $!source.is-deterministic }
    }
    method FromIndexes(\source,\indexes,\offset,&out?) {
        FromIndexes.new(source,indexes,offset,&out)
    }

    # Return an iterator for the basic "gather" functionality on the
    # given block.
    my class Gather does Rakudo::SlippyIterator {
        has &!resumption;
        has $!push-target;
        has int $!wanted;

        my constant PROMPT = nqp::create(Mu);

        method new(&block) {
            my \iter = nqp::create(self);
            nqp::bindattr(iter, self, '$!slipper', nqp::null);
            my int $wanted;
            my $taken;
            my $taker := {
                $taken := nqp::getpayload(nqp::exception());
                nqp::if(
                  nqp::istype($taken, Slip),
                  nqp::stmts(
                    iter!start-slip-wanted($taken),
                    ($wanted = nqp::getattr_i(iter, self, '$!wanted'))
                  ),
                  nqp::stmts(  # doesn't sink
                    nqp::getattr(iter, self, '$!push-target').push($taken),
                    ($wanted = nqp::bindattr_i(iter,self,'$!wanted',
                      nqp::sub_i(nqp::getattr_i(iter,self,'$!wanted'),1)))
                  )
                );

                nqp::if(
                  nqp::iseq_i($wanted,0),
                  nqp::continuationcontrol(0, PROMPT, nqp::getattr(-> Mu \c {
                      nqp::bindattr(iter, self, '&!resumption', c);
                  }, Code, '$!do'))
                );
                nqp::resume(nqp::exception())
            }
            nqp::bindattr(iter, self, '&!resumption', nqp::getattr({
                nqp::stmts(  # doesn't sink
                  nqp::handle(&block(), 'TAKE', $taker()),
                  nqp::continuationcontrol(0, PROMPT, nqp::getattr(-> | {
                      nqp::bindattr(iter, self, '&!resumption', Callable)
                  }, Code, '$!do'))
                )
            }, Code, '$!do'));
            iter
        }

        method pull-one() is raw {
            nqp::if(
              nqp::not_i(nqp::isnull($!slipper)) && nqp::not_i(
                nqp::eqaddr((my \result = self.slip-one),IterationEnd)
              ),
              result,
              nqp::stmts(
                nqp::unless(
                  nqp::isconcrete($!push-target),
                  ($!push-target := nqp::create(IterationBuffer))
                ),
                ($!wanted = 1),
                nqp::continuationreset(PROMPT, &!resumption),
                nqp::if(
                  nqp::isconcrete(&!resumption),
                  nqp::shift($!push-target),
                  IterationEnd
                )
              )
            )
        }

        method push-exactly(\target, int $n) {
            nqp::if(
              nqp::isgt_i($n,0),
              nqp::stmts(
                ($!wanted = $n),
                ($!push-target := target),
                nqp::if(
                  nqp::not_i(nqp::isnull($!slipper)) && nqp::not_i(
                    nqp::eqaddr(self!slip-wanted,IterationEnd)
                  ),
                  nqp::stmts(
                    ($!push-target := nqp::null),
                    $n
                  ),
                  nqp::stmts(
                    nqp::continuationreset(PROMPT, &!resumption),
                    ($!push-target := nqp::null),
                    nqp::if(
                      nqp::isconcrete(&!resumption),
                      ($n - $!wanted),
                      IterationEnd
                    )
                  )
                )
              )
            )
        }

        method !start-slip-wanted(\slip --> Nil) {
            my $value := self.start-slip(slip);
            nqp::unless(
              nqp::eqaddr($value,IterationEnd),
              nqp::stmts(  # doesn't sink
                $!push-target.push($value),
                (my int $i = 0),
                (my int $n = $!wanted),
                nqp::while(  # doesn't sink
                  nqp::islt_i(++$i,$n),
                  nqp::if(
                    nqp::eqaddr(($value := self.slip-one),IterationEnd),
                    last   #  XXX this probably doesn't do what is intended
                  ),
                  $!push-target.push($value)
                ),
                ($!wanted = $!wanted - $i)
              )
            )
        }

        method !slip-wanted() {
            my int $i = -1;
            my int $n = $!wanted;
            my $value;
            nqp::while(
              nqp::islt_i(++$i,$n),
              nqp::stmts(  # doesn't sink
                nqp::if(
                  nqp::eqaddr(($value := self.slip-one),IterationEnd),
                  last
                ),
                $!push-target.push($value)
              )
            );
            $!wanted = nqp::sub_i($!wanted,$i);

            nqp::eqaddr($value,IterationEnd) ?? IterationEnd !! $n
        }
    }
    method Gather(&block) { Gather.new(&block) }

    # Return an iterator for the given low/high integer value (inclusive).
    # Has dedicated .push-all for those cases one needs to fill a list
    # with consecutive numbers quickly.  Handle int ranges with an Inf as
    # endpoint in separate Iterator class.
    my class IntRangeUnending does Iterator {
        has int $!i;

        method !SET-SELF(int $i) { $!i = nqp::sub_i($i,1); self }
        method new(int $from) { nqp::create(self)!SET-SELF($from) }

        method pull-one() { ++$!i }
        method push-exactly(\target, int $batch-size) {
            target.push(self.pull-one) for ^$batch-size;
            $batch-size
        }
        method is-lazy(--> True) { }
        method is-monotonically-increasing(--> True) { }
        method sink-all(--> IterationEnd) { }
    }
    my class IntRange does PredictiveIterator {
        has int $!i;
        has int $!last;

        method !SET-SELF(int $i, $last) {
            $!i    = nqp::sub_i($i,1);
            $!last = $last;
            self
        }
        method new(\f,\t) { nqp::create(self)!SET-SELF(f,t) }

        method pull-one() {
            nqp::isle_i(++$!i,$!last)
              ?? $!i
              !! IterationEnd
        }
        method skip-one() { nqp::isle_i(++$!i,$!last) }
        method push-exactly(\target, int $batch-size) {
            my int $todo = nqp::add_i($batch-size,1);
            my int $i    = $!i;      # lexicals are faster than attrs
            my int $last = $!last;

            nqp::while(
              --$todo && nqp::isle_i(++$i,$last),
              target.push(nqp::p6box_i($i))
            );

            $!i = $i;                # make sure pull-one ends
            nqp::isgt_i($i,$last) ?? IterationEnd !! $batch-size
        }
        method push-all(\target --> IterationEnd) {
            my int $i    = $!i;      # lexicals are faster than attrs
            my int $last = $!last;

            nqp::while(
              nqp::isle_i(++$i,$last),
              target.push(nqp::p6box_i($i))
            );
            $!i = $i;                # make sure pull-one ends
        }
        method count-only(--> Int:D) { $!last - $!i + nqp::isgt_i($!i,$!last) }
        method is-monotonically-increasing(--> True) { }
        method sink-all(--> IterationEnd) { $!i = $!last }
    }
    method IntRange(\from,\to) {
        to == Inf
          ?? IntRangeUnending.new(from)
          !! IntRange.new(from,to)
    }

    # Return an iterator from a given iterator producing Pairs, in which
    # each .value is checked for iterability: if Iterable, produce Pairs
    # with the original key as its value, and key with the values produced
    # by the Iterable.  Otherwise, just produce an antipair.
    my class Invert does Iterator {
        has $!iterator;   # source iterator
        has $!value;      # original key to repeat for Iterable
        has $!slipper;    # iterator if Iterable value in source

        method new(\iterator) {
            nqp::p6bindattrinvres(
              nqp::create(self),self,'$!iterator',iterator)
        }
        method pull-one() {
            nqp::if(
              $!slipper,                            # we have a slipper
              nqp::if(
                nqp::eqaddr(
                  (my $pulled := $!slipper.pull-one),
                  IterationEnd
                ),
                nqp::stmts(                         # slipper exhausted
                  ($!slipper := nqp::null),         # deny all knowledge
                  self.pull-one                     # rinse and repeat
                ),
                Pair.new($pulled,$!value)           # not the end, slip it
              ),
              nqp::if(                              # no slipper
                nqp::eqaddr(
                  ($pulled := nqp::decont($!iterator.pull-one)),
                  IterationEnd
                ),
                IterationEnd,                       # source exhausted
                nqp::if(                            # still in business
                  nqp::istype($pulled,Pair),
                  nqp::if(                          # it's a Pair, whee!
                    nqp::istype(
                      (my $key := nqp::getattr($pulled,Pair,'$!value')),
                      Iterable
                    ),
                    nqp::stmts(                     # need to slip it!
                      ($!slipper := $key.iterator), # set up the slipper
                      ($!value := nqp::getattr($pulled,Pair,'$!key')),
                      self.pull-one                 # rinse and repeat
                    ),
                    Pair.new(                       # just needs swapping
                      $key,
                      nqp::getattr($pulled,Pair,'$!key')
                    )
                  ),
                  X::TypeCheck.new(                 # naughty, slap it!
                    operation => 'invert',
                    got       => $pulled,
                    expected  => Pair
                  ).throw
                )
              )
            )
        }
        method is-lazy() { $!iterator.is-lazy }
        method is-deterministic(--> Bool:D) { $!iterator.is-deterministic }
        method is-monotonically-increasing(--> Bool:D) {
            $!iterator.is-monotonically-increasing
        }
        method sink-all(--> IterationEnd) {
            nqp::until(
              nqp::eqaddr((my \pulled := $!iterator.pull-one),IterationEnd),
              nqp::unless(
                nqp::istype(pulled,Pair),
                X::TypeCheck.new(                   # naughty, slap it!
                  operation => 'invert',
                  got       => pulled,
                  expected  => Pair
                ).throw
              )
            )
        }
    }
    method Invert(\iterator) { Invert.new(iterator) }

    # Return an iterator that will alternately generate an index value,
    # and the value of the given iterator, basically the .kv functionality
    # for 1 dimensional lists.
    my class KeyValue does Iterator {
        has Mu $!iter;
        has Mu $!pulled;
        has int $!on-key;
        has int $!key;

        method !SET-SELF(\iter) { $!iter := iter; $!key = -1; self }
        method new(\iter) { nqp::create(self)!SET-SELF(iter) }

        method pull-one() is raw {
            ($!on-key = nqp::not_i($!on-key))
              ?? nqp::eqaddr(($!pulled := $!iter.pull-one),IterationEnd)
                ?? IterationEnd
                !! nqp::p6box_i(++$!key)
              !! $!pulled
        }
        method push-all(\target --> IterationEnd) {
            my int $key = -1;
            nqp::until(
              nqp::eqaddr(
                (my \pulled := $!iter.pull-one),
                IterationEnd
              ),
              nqp::stmts(
                target.push(nqp::p6box_i(++$key)),
                target.push(pulled),
              )
            )
        }
        method is-lazy() { $!iter.is-lazy }
        method is-deterministic(--> Bool:D) { $!iter.is-deterministic }
    }
    method KeyValue(\iterator) { KeyValue.new(iterator) }

    # Create iterator for the last N values of a given iterator.  Needs
    # to specify the :action part of X::Cannot::Lazy in case the given
    # iterator is lazy.  Optionally returns an empty iterator if the
    # given iterator produced fewer than N values.
    my class LastNValues does Iterator {
        has $!iterator;
        has int $!size;
        has int $!full;
        has $!lastn;
        has int $!todo;
        has int $!index;
        method !SET-SELF(\iterator, \size, \full) {
            $!iterator := iterator;
            $!full = full;
            $!lastn := nqp::setelems(nqp::list, $!size = size);
            nqp::setelems($!lastn, 0);
            self
        }
        method new(\iterator,\n,\action,\f) {
            nqp::if(
              iterator.is-lazy,
              Any.throw-iterator-cannot-be-lazy(action,''),
              nqp::if(
                nqp::istype(n,Whatever),
                iterator,                   # * just give back itself
                nqp::if(
                  n <= 0,                   # must be HLL comparison
                  Rakudo::Iterator.Empty,   # negative is just nothing
                  nqp::if(
                    (nqp::istype(n,Int)
                      && nqp::isbig_I(nqp::decont(n)))
                      || n == Inf,
                    iterator,               # big value = itself
                    nqp::create(self)!SET-SELF(iterator,n,f)
                  )
                )
              )
            )
        }
        method next() is raw {
            my int $index = $!index;
            $!index = nqp::mod_i(nqp::add_i($!index,1),$!size);
            --$!todo;
            nqp::atpos($!lastn,$index)
        }
        method pull-one() is raw {
            nqp::if(
              $!todo,
              self.next,
              nqp::if(
                nqp::defined($!iterator),
                nqp::stmts(
                  (my int $index),
                  (my int $size = $!size),
                  nqp::until(
                    nqp::eqaddr(
                      (my \pulled := $!iterator.pull-one),IterationEnd),
                    nqp::stmts(
                      nqp::bindpos($!lastn,$index,pulled),
                      ($index = nqp::mod_i(nqp::add_i($index,1),$size))
                    )
                  ),
                  nqp::if(
                    nqp::iseq_i(nqp::elems($!lastn),$size),   # full set
                    nqp::stmts(
                      ($!index = $index),
                      ($!todo  = $!size)
                    ),
                    ($!todo =       # not a full set, $!index still at 0
                      nqp::if($!full,0,nqp::elems($!lastn))),
                  ),
                  ($!iterator := Mu),                   # done iterating
                  nqp::if($!todo, self.next, IterationEnd)
                ),
                IterationEnd
              )
            )
        }
        method is-deterministic(--> Bool:D) { $!iterator.is-deterministic }
        method is-monotonically-increasing(--> Bool:D) {
            $!iterator.is-monotonically-increasing
        }
    }
    method LastNValues(\iterator, \n, \action, $full = 0) {
        LastNValues.new(iterator, n, action, $full)
    }

    # Return the last value of the given source iterator (if any).
    # Also needs the action string to be used in X::Cannot::Lazy if
    # the source iterator turns out to be lazy.
    method LastValue(\iterator, $action) is raw {
        nqp::if(
          iterator.is-lazy,
          Any.throw-iterator-cannot-be-lazy($action,''),
          nqp::stmts(
            (my $result := IterationEnd),
            nqp::if(
              nqp::istype(iterator,PredictiveIterator),
              nqp::if(
                (my \count := iterator.count-only)
                  && iterator.skip-at-least(count - 1),
                $result := iterator.pull-one
              ),
              nqp::until(
                nqp::eqaddr((my \pulled := iterator.pull-one),IterationEnd),
                ($result := pulled)
              ),
            ),
            $result
          )
        )
    }

    # Return an iterator that is always lazy, by wrapping it inside another
    # iterator that indicates to be lazy.  Does not actually call the
    # iterator until it is really  necessary, preventing any unnecessary
    # work or external resource usage.
    my class Lazy does Iterator {
        has $!iterable;
        has $!iterator;

        method new(\iterable) {
            my \iter := nqp::create(self);
            nqp::bindattr(iter,self,'$!iterable',iterable);
            nqp::bindattr(iter,self,'$!iterator',nqp::null);
            iter
        }

        method !iterator() {
            nqp::ifnull($!iterator, $!iterator := $!iterable.iterator)
        }

        method pull-one() is raw {
            self!iterator.pull-one
        }

        method push-exactly(\target, int $n) {
            self!iterator.push-exactly(target, $n)
        }

        method is-lazy(--> True) { }
        method is-deterministic(--> Bool:D) { self!iterator.is-deterministic }
        method is-monotonically-increasing(--> Bool:D) {
            self!iterator.is-monotonically-increasing
        }
    }
    method Lazy(\iterable) { Lazy.new(iterable) }

    # Return an iterator given a List and an iterator that generates
    # an IterationBuffer of indexes for each pull.  Each value is
    # is a List with the mapped elements.
    my role ListIndex {
        has $!list;
        has $!indexes;
        method !SET-SELF(\list,\indexes) {
            $!list := nqp::getattr(list,List,'$!reified');
            $!indexes := indexes;
            self
          }
        method new(\l,\i) { nqp::create(self)!SET-SELF(l,i) }
        method pull-one() {
            nqp::if(
              nqp::eqaddr(
                (my \buffer := $!indexes.pull-one),
                IterationEnd
              ),
              IterationEnd,
              nqp::stmts(
                (my int $elems = nqp::elems(buffer)),
                (my int $i = -1),
                (my $list := $!list),
                nqp::while(           # repurpose buffer for result
                  nqp::islt_i(++$i,$elems),
                  nqp::bindpos(buffer,$i,
                    nqp::atpos($list,nqp::atpos(buffer,$i))
                  )
                ),
                nqp::p6bindattrinvres(
                  nqp::create(List),List,'$!reified',buffer)
              )
            )
        }
    }
    my class ListIndexes does ListIndex does Iterator { }
    my class ListPredictiveIndexes does ListIndex does PredictiveIterator {
        method count-only() { $!indexes.count-only }
        method bool-only()  { $!indexes.bool-only  }
    }
    method ListIndexes(\list,\indexes) {
        list.elems            # reifies
          ?? nqp::istype(indexes,PredictiveIterator)
            ?? ListPredictiveIndexes.new(list,indexes)
            !! ListIndexes.new(list,indexes)
          !! Rakudo::Iterator.OneValue(nqp::create(List)) # only one
    }

    # Returns an iterator that handles all properties of a bare -loop-
    # Takes a Callable to be considered the body of the loop.
    my class Loop does Rakudo::SlippyIterator {
        has &!body;
        has $!label;

        method !SET-SELF(\body,\label) {
            nqp::bindattr(self,self.WHAT,'$!slipper',nqp::null);
            &!body := body;
            $!label := nqp::decont(label);
            self
        }

        method new(\body,\label) {
            nqp::create(self)!SET-SELF(body,label)
        }

        method pull-one() {
            my $result;
            my int $stopped;
            nqp::if(
              nqp::not_i(nqp::isnull($!slipper)) && nqp::not_i(
                nqp::eqaddr(($result := self.slip-one),IterationEnd)
              ),
              $result,
              nqp::stmts(
                nqp::until(
                  $stopped,
                  nqp::stmts(
                    ($stopped = 1),
                    nqp::handle(
                      nqp::if(
                        nqp::istype(($result := &!body()),Slip),
                        ($stopped = nqp::eqaddr(
                          ($result := self.start-slip($result)),
                          IterationEnd
                        ))
                      ),
                      'LABELED', $!label,
                      'NEXT', nqp::if(
                        nqp::eqaddr(
                          ($result := self.control-payload),
                          IterationEnd
                        ),
                        ($stopped = 0)           # bare next or empty Slip
                      ),
                      'REDO', ($stopped = 0),
                      'LAST', nqp::unless(
                        nqp::eqaddr(
                          ($result := self.control-payload),
                          IterationEnd
                        ),
                        (&!body := &always-IterationEnd)  # end later
                      )
                    )
                  ),
                  :nohandler
                ),
                $result
              )
            )
        }

        method is-lazy(--> True) { }
    }
    method Loop(&body, $label) { Loop.new(&body, $label) }

    # An often occurring use of the Mappy role to generate all of the
    # keys of a Map / Hash.  Takes a Map / Hash as the only parameter.
    my class Mappy-keys does Rakudo::Iterator::Mappy {
        method pull-one() {
            $!iter
              ?? nqp::iterkey_s(nqp::shift($!iter))
              !! IterationEnd
        }
        method push-all(\target --> IterationEnd) {
            nqp::while(
              $!iter,
              target.push(nqp::iterkey_s(nqp::shift($!iter)))
            )
        }
    }
    method Mappy-keys(\map) { Mappy-keys.new(map) }

    # An often occurring use of the Mappy role to generate alternating
    # key and values of a Map/Hash in which each value is a Pair to
    # be interpreted as the actual key/value.  Takes a Map / Hash as
    # the only parameter.  Make sure class gets created at compile time,
    # to avoid global de-opt at run-time
    my class Mkvp does Mappy-kv-from-pairs { }
    method Mappy-kv-from-pairs(\map) { Mkvp.new(map) }

    # An often occurring use of the Mappy role to generate all of the
    # values of a Map / Hash.  Takes a Map / Hash as the only parameter.
    my class Mappy-values does Mappy {
        method pull-one() is raw {
            $!iter
              ?? nqp::iterval(nqp::shift($!iter))
              !! IterationEnd
        }
        method push-all(\target --> IterationEnd) {
            nqp::while(  # doesn't sink
              $!iter,
              target.push(nqp::iterval(nqp::shift($!iter)))
            )
        }
    }
    method Mappy-values(\map) { Mappy-values.new(map) }

    # cache cursor initialization lookup
    my $initialize-cursor := Match.^lookup("!cursor_init");

    my &POPULATE := Match.^lookup("MATCH" );  # fully populate Match object

    my $movers := nqp::list(
      Match.^lookup("CURSOR_MORE"),     # :g
      Match.^lookup("CURSOR_OVERLAP"),  # :ov
      Match.^lookup("CURSOR_NEXT")      # :ex
    );

    # Iterate a cursor according to a given regex, string and mover
    my class MatchCursor does Iterator {
        has Mu $!cursor;
        has Mu $!mover;
        method !SET-SELF(&regex, \string, int $mover) {
            $!cursor := regex($initialize-cursor(Match, string, :0c));
            $!mover  := nqp::atpos($movers,$mover);
            self
        }
        method new(\regex, \string, \mover) {
            nqp::create(self)!SET-SELF(regex, string, mover)
        }
        method pull-one() is raw {
            nqp::if(
              nqp::isge_i(nqp::getattr_i($!cursor,Match,'$!pos'),0),
              nqp::stmts(
                (my $current := $!cursor),
                ($!cursor := $!mover($!cursor)),
                $current
              ),
              IterationEnd
            )
        }
        method skip-one() is raw {
            nqp::if(
              nqp::isge_i(nqp::getattr_i($!cursor,Match,'$!pos'),0),
              ($!cursor := $!mover($!cursor)),
            )
        }
    }
    method MatchCursor(\regex, \string, \mover) {
        MatchCursor.new(regex, string, mover)
    }

    # Iterate a cursor according to a given regex, string, limit and mover
    my class MatchCursorLimit does Iterator {
        has Mu $!cursor;
        has Mu $!mover;
        has int $!todo;
        method !SET-SELF(&regex, \string, int $todo, int $mover) {
            $!cursor := regex($initialize-cursor(Match, string, :0c));
            $!mover  := nqp::atpos($movers,$mover);
            $!todo    = $todo + 1;
            self
        }
        method new(\regex, \string, \todo, \mover) {
            nqp::create(self)!SET-SELF(regex, string, todo, mover)
        }
        method pull-one() is raw {
            nqp::if(
              --$!todo
                && nqp::isge_i(nqp::getattr_i($!cursor,Match,'$!pos'),0),
              nqp::stmts(
                (my $current := $!cursor),
                ($!cursor := $!mover($!cursor)),
                $current
              ),
              IterationEnd
            )
        }
        method skip-one() is raw {
            nqp::if(
              --$!todo
                && nqp::isge_i(nqp::getattr_i($!cursor,Match,'$!pos'),0),
              ($!cursor := $!mover($!cursor)),
            )
        }
    }
    method MatchCursorLimit(\regex, \string, \limit, \mover) {
        MatchCursorLimit.new(regex, string, limit, mover)
    }

    # generate full blown Match objects for given regex, string and limit
    my class MatchMatch does Iterator {
        has Mu $!iterator;

        method new(\regex, \string, \limit) {
            my \iterator := nqp::istype(limit,Whatever) || limit == Inf
              ?? MatchCursor.new(regex, string, 0)
              !! limit < 1
                ?? (return Rakudo::Iterator.Empty)
                !! MatchCursorLimit.new(regex, string, limit.Int, 0);

            nqp::p6bindattrinvres(nqp::create(self),self,'$!iterator',iterator)
        }
        method pull-one() is raw {
            nqp::eqaddr((my $cursor := $!iterator.pull-one),IterationEnd)
              ?? IterationEnd
              !! $cursor.MATCH
        }
        method skip-one() {
            nqp::not_i(nqp::eqaddr($!iterator.pull-one,IterationEnd))
        }
        method push-all(\target --> IterationEnd) {
            my $iterator := $!iterator;
            nqp::until(
              nqp::eqaddr((my $cursor := $iterator.pull-one),IterationEnd),
              target.push($cursor.MATCH)
            );
        }
    }
    method MatchMatch(\regex, \string, \limit) {
        MatchMatch.new(regex, string, limit)
    }

    # generate strings for given regex, string and limit
    my class MatchStr does Iterator {
        has Mu $!iterator;
        has Mu $!what;

        method new(\regex, \string, \limit) {
            my \iterator := nqp::istype(limit,Whatever) || limit == Inf
              ?? MatchCursor.new(regex, string, 0)
              !! limit < 1
                ?? (return Rakudo::Iterator.Empty)
                !! MatchCursorLimit.new(regex, string, limit.Int, 0);

            my $self := nqp::create(self);
            nqp::bindattr($self,self,'$!iterator',iterator);
            nqp::bindattr($self,self,'$!what',string.WHAT);
            $self
        }
        method pull-one() is raw {
            nqp::eqaddr((my $cursor := $!iterator.pull-one),IterationEnd)
              ?? IterationEnd
              !! nqp::box_s($cursor.MATCH.Str,$!what)
        }
        method skip-one() {
            nqp::not_i(nqp::eqaddr($!iterator.pull-one,IterationEnd))
        }
        method push-all(\target --> IterationEnd) {
            my $iterator := $!iterator;
            my $what     := $!what;
            nqp::until(
              nqp::eqaddr((my $cursor := $iterator.pull-one),IterationEnd),
              target.push(nqp::box_s($cursor.MATCH.Str,$!what))
            );
        }
    }
    method MatchStr(\regex, \string, \limit) {
        MatchStr.new(regex, string, limit)
    }

    # basic split iterator functionality, with optional limiting
    my class MatchSplit does Iterator {
        has Mu $!string;
        has Mu $!iterator;
        has int $!last-pos;

        method !SET-SELF(\iterator, \string) {
            $!iterator := iterator;
            $!string   := string;
            self
        }
        method new(\regex, \string, \limit) {
            my \iterator := nqp::istype(limit,Whatever) || limit == Inf
              ?? MatchCursor.new(regex, string, 0)
              !! limit < 1
                ?? (return Rakudo::Iterator.Empty)
                !! limit == 1
                  ?? (return Rakudo::Iterator.OneValue(string))
                  !! MatchCursorLimit.new(regex, string, limit.Int - 1, 0);

            nqp::create(self)!SET-SELF(iterator, string)
        }

        method pull-one() is raw {
            nqp::if(
              nqp::islt_i($!last-pos,0),
              IterationEnd,                     # last part also done
              nqp::if(
                nqp::eqaddr((my $cursor := $!iterator.pull-one),IterationEnd),
                nqp::stmts(                     # need to do last part still
                  (my $result := nqp::substr($!string,$!last-pos)),
                  ($!last-pos = -1),
                  $result
                ),
                nqp::stmts(                     # produce next
                  ($result := nqp::substr(
                    $!string,
                    $!last-pos,
                    nqp::sub_i(nqp::getattr_i($cursor,Match,'$!from'),$!last-pos)
                  )),
                  ($!last-pos = nqp::getattr_i($cursor,Match,'$!pos')),
                  nqp::box_s($result,$!string)
                )
              )
            )
        }
        method push-all(\target --> IterationEnd) {
            if nqp::isge_i($!last-pos,0) {
                my $string      := $!string;
                my $iterator    := $!iterator;
                my int $last-pos = $!last-pos;

                nqp::until(
                  nqp::eqaddr((my $cursor := $iterator.pull-one),IterationEnd),
                  nqp::stmts(
                    target.push(nqp::substr(
                      $string,
                      $last-pos,
                      nqp::sub_i(
                        nqp::getattr_i($cursor,Match,'$!from'),
                        $last-pos
                      )
                    )),
                    ($last-pos = nqp::getattr_i($cursor,Match,'$!pos'))
                  )
                );
                target.push(nqp::box_s(
                  nqp::substr($string,$last-pos),
                  $string
                ));
            }
        }
    }

    method MatchSplit(\regex, \string, \limit) {
        MatchSplit.new(regex, string, limit)
    }

    # split iterator functionality with mapper for extra values and skip-empty
    my class MatchSplitMap does Iterator {
        has Mu $!string;
        has Mu $!iterator;
        has Mu $!mapper;
        has int $!skip-empty;
        has int $!last-pos;
        has Mu $!slipped;

        method !SET-SELF(\iterator, \string, \mapper, int $skip-empty) {
            $!iterator  := iterator;
            $!string    := string;
            $!mapper    := mapper;
            $!skip-empty = $skip-empty;
            $!slipped   := nqp::list;
            self
        }
        method new(\regex, \string, \mapper, \limit, \skip-empty) {
            my \iterator := nqp::istype(limit,Whatever) || limit == Inf
              ?? MatchCursor.new(regex, string, 0)
              !! limit < 1 || (skip-empty && nqp::not_i(nqp::chars(string)))
                ?? (return Rakudo::Iterator.Empty)
                !! limit == 1
                  ?? (return Rakudo::Iterator.OneValue(string))
                  !! MatchCursorLimit.new(regex, string, limit.Int - 1, 0);

            nqp::create(self)!SET-SELF(
              iterator, string, mapper, nqp::istrue(skip-empty))
        }

        method pull-one() is raw {
            nqp::if(
              nqp::elems($!slipped),
              nqp::shift($!slipped),                   # produce slipped
              nqp::if(                                 # nothing to slip
                nqp::eqaddr((my $cursor := $!iterator.pull-one),IterationEnd),
                nqp::if(                               # last cursor seen
                  $!skip-empty && nqp::iseq_i($!last-pos,nqp::chars($!string)),
                  IterationEnd,                        # last one empty, done
                  nqp::stmts(
                    nqp::push($!slipped,IterationEnd), # do last part still
                    nqp::substr($!string,$!last-pos)   # produce final string
                  )
                ),
                nqp::stmts(                            # produce next
                  (my $result := nqp::substr(
                    $!string,
                    $!last-pos,
                    nqp::sub_i(
                      nqp::getattr_i($cursor,Match,'$!from'),
                      $!last-pos
                    )
                  )),
                  ($!last-pos = nqp::getattr_i($cursor,Match,'$!pos')),
                  nqp::if(                             # preset slipped
                    nqp::istype((my $mapped := $!mapper($cursor)),List),
                    ($!slipped := nqp::getattr($mapped,List,'$!reified')),
                    nqp::push($!slipped,$mapped)
                  ),
                  nqp::if(
                    $!skip-empty && nqp::not_i(nqp::chars($result)),
                    nqp::shift($!slipped),             # skipping, so slip
                    $result                            # produce string
                  )
                )
              )
            )
        }
    }

    method MatchSplitMap(\regex, \string, \mapper, \limit, \skip-empty) {
        MatchSplitMap.new(regex, string, mapper, limit, skip-empty)
    }

    # Return an iterator that will iterate over a source iterator and an
    # iterator generating monotonically increasing index values from a
    # given offset.  Optionally, call block if an out-of-sequence index
    # value is obtained, or simply ignore out of sequence index values.
    my class MonotonicIndexes does Iterator {
        has $!source;     # source iterator
        has $!indexes;    # iterator providing index values
        has int $!next;   # virtual index of next source value
        has &!out;        # callable for out of sequence values
        method !SET-SELF(\source, \indexes, \offset, \out) {
            $!source  := source;
            $!indexes := indexes;
            $!next     = offset;
            &!out     := out;
            self
        }
        method new(\s,\i,\o,\out) { nqp::create(self)!SET-SELF(s,i,o,out) }
        method pull-one() is raw {
            nqp::until(
              nqp::eqaddr(
                (my $got := $!indexes.pull-one),
                IterationEnd
              ),
              nqp::if(
                nqp::istype((my $number = +$got),Failure),
                $number.throw,
                nqp::if(
                  nqp::isle_i($!next,(my int $index = $number.Int)),
                  nqp::stmts(                      # possibly valid index
                    nqp::while(
                      nqp::islt_i($!next,$index) && $!source.skip-one,
                      ++$!next
                    ),
                    (return-rw nqp::if(
                      nqp::iseq_i($!next,$index),
                      nqp::stmts(
                        ++$!next,
                        $!source.pull-one
                      ),
                      IterationEnd
                    ))
                  ),
                  nqp::if(&!out,&!out($index,$!next)) # out of sequence
                )
              )
            );
            IterationEnd
        }
        method is-deterministic(--> Bool:D) {
            $!source.is-deterministic && $!indexes.is-deterministic
        }
    }
    method MonotonicIndexes(\source,\indexes,\offset,&out?) {
        MonotonicIndexes.new(source,indexes,offset,&out)
    }

    # Class for iterating native str arrays and nqp::list_s
    my class native_s does PredictiveIterator {
        has $!list;
        has int $!i;

        method !SET-SELF(Mu \list) {
            $!list := nqp::decont(list);
            $!i     = -1;
            self
        }
        method new(Mu \list) { nqp::create(self)!SET-SELF(list) }

        method pull-one() is raw {
            nqp::islt_i(++$!i,nqp::elems($!list))
              ?? nqp::atposref_s($!list,$!i)
              !! IterationEnd
        }

        method push-all(\target --> IterationEnd) {
            my $list := $!list;
            my int $i = $!i;
            nqp::while(
              nqp::islt_i(++$i,nqp::elems($list)),
              target.push(nqp::atposref_s($list,$i))
            );
            $!i = $i;
        }

        method skip-one() {
            nqp::islt_i(++$!i,nqp::elems($!list))
        }

        method skip-at-least(int $toskip) {
            nqp::unless(
              nqp::islt_i(($!i = nqp::add_i($!i,$toskip)),nqp::elems($!list)),
              nqp::stmts(
                ($!i = nqp::elems($!list)),
                0
              )
            )
        }

        method sink-all(--> IterationEnd) { $!i = nqp::elems($!list) }

        method count-only(--> Int:D) {
            nqp::elems($!list) - $!i - nqp::islt_i($!i,nqp::elems($!list))
        }
    }
    method native_s(Mu \list --> Iterator:D) { native_s.new(list) }

    # Class for iterating native int arrays and nqp::list_i
    my class native_i does PredictiveIterator {
        has $!list;
        has int $!i;

        method !SET-SELF(Mu \list) {
            $!list := nqp::decont(list);
            $!i     = -1;
            self
        }
        method new(Mu \list) { nqp::create(self)!SET-SELF(list) }

        method pull-one() is raw {
            nqp::islt_i(++$!i,nqp::elems($!list))
              ?? nqp::atposref_i($!list,$!i)
              !! IterationEnd
        }

        method push-all(\target --> IterationEnd) {
            my $list := $!list;
            my int $i = $!i;
            nqp::while(
              nqp::islt_i(++$i,nqp::elems($list)),
              target.push(nqp::atposref_i($list,$i))
            );
            $!i = $i;
        }

        method skip-one() {
            nqp::islt_i(++$!i,nqp::elems($!list))
        }

        method skip-at-least(int $toskip) {
            nqp::unless(
              nqp::islt_i(($!i = nqp::add_i($!i,$toskip)),nqp::elems($!list)),
              nqp::stmts(
                ($!i = nqp::elems($!list)),
                0
              )
            )
        }

        method sink-all(--> IterationEnd) { $!i = nqp::elems($!list) }

        method count-only(--> Int:D) {
            nqp::elems($!list) - $!i - nqp::islt_i($!i,nqp::elems($!list))
        }
    }
    method native_i(Mu \list --> Iterator:D) { native_i.new(list) }

    # Class for iterating native int arrays and nqp::list_u
    my class native_u does PredictiveIterator {
        has $!list;
        has int $!i;

        method !SET-SELF(Mu \list) {
            $!list := nqp::decont(list);
            $!i     = -1;
            self
        }
        method new(Mu \list) { nqp::create(self)!SET-SELF(list) }

        method pull-one() is raw {
            nqp::islt_i(++$!i,nqp::elems($!list))
              ?? nqp::atposref_u($!list,$!i)
              !! IterationEnd
        }

        method push-all(\target --> IterationEnd) {
            my $list := $!list;
            my int $i = $!i;
            nqp::while(
              nqp::islt_i(++$i,nqp::elems($list)),
              target.push(nqp::atposref_u($list,$i))
            );
            $!i = $i;
        }

        method skip-one() {
            nqp::islt_i(++$!i,nqp::elems($!list))
        }

        method skip-at-least(int $toskip) {
            nqp::unless(
              nqp::islt_i(($!i = nqp::add_i($!i,$toskip)),nqp::elems($!list)),
              nqp::stmts(
                ($!i = nqp::elems($!list)),
                0
              )
            )
        }

        method sink-all(--> IterationEnd) { $!i = nqp::elems($!list) }

        method count-only(--> Int:D) {
            nqp::elems($!list) - $!i - nqp::islt_i($!i,nqp::elems($!list))
        }
    }
    method native_u(Mu \list --> Iterator:D) { native_u.new(list) }

    # Class for iterating native num arrays and nqp::list_n
    my class native_n does PredictiveIterator {
        has $!list;
        has int $!i;

        method !SET-SELF(Mu \list) {
            $!list := nqp::decont(list);
            $!i     = -1;
            self
        }
        method new(Mu \list) { nqp::create(self)!SET-SELF(list) }

        method pull-one() is raw {
            nqp::islt_i(++$!i,nqp::elems($!list))
              ?? nqp::atposref_n($!list,$!i)
              !! IterationEnd
        }

        method push-all(\target --> IterationEnd) {
            my $list := $!list;
            my int $i = $!i;
            nqp::while(
              nqp::islt_i(++$i,nqp::elems($list)),
              target.push(nqp::atposref_n($list,$i))
            );
            $!i = $i;
        }

        method skip-one() {
            nqp::islt_i(++$!i,nqp::elems($!list))
        }

        method skip-at-least(int $toskip) {
            nqp::unless(
              nqp::islt_i(($!i = nqp::add_i($!i,$toskip)),nqp::elems($!list)),
              nqp::stmts(
                ($!i = nqp::elems($!list)),
                0
              )
            )
        }

        method sink-all(--> IterationEnd) { $!i = nqp::elems($!list) }

        method count-only(--> Int:D) {
            nqp::elems($!list) - $!i - nqp::islt_i($!i,nqp::elems($!list))
        }
    }
    method native_n(Mu \list --> Iterator:D) { native_n.new(list) }

    # Returns an iterator for the next N values of given iterator.
    my class NextNValues does Iterator {
        has $!iterator;
        has int $!times;
        method !SET-SELF(\iterator, int $times) {
            $!iterator := iterator;
            $!times     = nqp::add_i($times,1);
            self
        }
        method new(\iterator, \times) {
            nqp::istype(times,Whatever)
              ?? iterator                   # * just give back itself
              !! times <= 0                 # must be HLL comparison
                ?? Rakudo::Iterator.Empty   # negative is just nothing
                !! nqp::istype(times,Int)
                  ?? nqp::isbig_I(nqp::decont(times))
                    ?? iterator             # big value = itself
                    !! nqp::create(self)!SET-SELF(iterator,times)
                  !! times == Inf           # big value = itself
                    ?? iterator
                    !! nqp::create(self)!SET-SELF(iterator,times.Int)
        }
        method pull-one() is raw {
            --$!times
              ?? $!iterator.pull-one
              !! IterationEnd
        }
        method push-all(\target --> IterationEnd) {
            my $iterator := $!iterator;
            my int $times = $!times;
            nqp::until(
              nqp::not_i(--$times)
                || nqp::eqaddr((my \pulled := $iterator.pull-one),IterationEnd),
              target.push(pulled)
            );
        }
        method is-deterministic(--> Bool:D) { $!iterator.is-deterministic }
        method is-monotonically-increasing(--> Bool:D) {
            $!iterator.is-monotonically-increasing
        }
    }
    method NextNValues(\iterator, \times) { NextNValues.new(iterator, times) }

    # Return an iterator that will produce N-grams of a given string,
    # essentially a superset of capabilities that is needed for .comb(N)
    # in 6.d, with 6.e allowing full access to these capabilities with
    # .comb(size => step, :partial)
    my class NGrams does PredictiveIterator {
        has str $!str;
        has Mu  $!what;
        has int $!size;
        has int $!step;
        has int $!pos;
        has int $!todo;
        method !SET-SELF($string, $size, $limit, $step, $partial) {
            $!str   = $string;
            $!what := $string.WHAT;
            $!size  = $size < 1 ?? 1 !! $size;
            $!step  = $step < 1 ?? 1 !! $step;
            $!pos   = -$!step;
            $!todo  = (
              nqp::chars($!str) + $!step - ($partial ?? 1 !! $!size)
            ) div $!step;
            $!todo  = $limit
              unless nqp::istype($limit,Whatever) || $limit > $!todo;
            ++$!todo;  # starting with --
            self
        }
        method new($string, $size, $limit, $step, $partial) {
            $string
              ?? nqp::create(self)!SET-SELF($string,$size,$limit,$step,$partial)
              !! Rakudo::Iterator.Empty
        }
        method pull-one() {
            --$!todo
              ?? nqp::box_s(
                   nqp::substr($!str,($!pos = $!pos + $!step),$!size),
                   $!what
                 )
              !! IterationEnd
        }
        method push-all(\target --> IterationEnd) {
            my str $str   = $!str;
            my int $todo  = $!todo;
            my int $pos   = $!pos;
            my int $size  = $!size;
            my int $step  = $!step;
            my Mu  $what := $!what;

            nqp::while(
              --$todo,
              target.push(
                nqp::box_s(
                  nqp::substr($str,($pos = $pos + $step),$size),
                  $what
                )
              )
            );
            $!todo = 0;
        }
        method count-only(--> Int:D) {
            nqp::sub_i($!todo,nqp::isgt_i($!todo,0))
        }
        method sink-all(--> IterationEnd) { $!pos = nqp::chars($!str) }
    }
    method NGrams($string, $size, $limit, $step, $partial) {
        nqp::istype($limit,Whatever) || $limit > 0
          ?? NGrams.new($string, $size, $limit, $step, $partial)
          !! EmptyIterator
    }

    # Return an iterator that only will return the given value once.
    # Basically the same as 42 xx 1.
    my class OneValue does PredictiveIterator {
        has Mu $!value;
        method new(Mu \value) {
            nqp::p6bindattrinvres(nqp::create(self),self,'$!value',value)
        }
        method pull-one() is raw {
            nqp::if(
              nqp::eqaddr($!value,IterationEnd),
              IterationEnd,
              nqp::stmts(
                (my Mu $value := $!value),
                ($!value := IterationEnd),
                $value
              )
            )
        }
        method push-all(\target --> IterationEnd) {
            nqp::unless(
              nqp::eqaddr($!value,IterationEnd),
              target.push($!value)
            );
            $!value := IterationEnd;
        }
        method skip-one() {
            nqp::if(
              nqp::not_i(nqp::eqaddr($!value,IterationEnd)),
              nqp::isfalse($!value := IterationEnd)
            )
        }
        method sink-all(--> IterationEnd) {
            $!value := IterationEnd
        }
        method count-only() {
            nqp::not_i(nqp::eqaddr($!value,IterationEnd))
        }
    }
    method OneValue(Mu \value) { OneValue.new(value) }

    # Return an iterator that only will return the given value for the
    # given number of times.  Basically the same as 42 xx N.
    my class OneValueTimes does PredictiveIterator {
        has Mu $!value;
        has Int $!times;
        has int $!is-lazy;

        method !SET-SELF(Mu \value,\times) {
            $!value := value;
            $!times := times;
            $!is-lazy = nqp::isbig_I(nqp::decont(times));
            self
        }
        method new(Mu \value,\times) {
            times > 0
              ?? nqp::create(self)!SET-SELF(value,times)
              !! Rakudo::Iterator.Empty
        }
        method pull-one() is raw {
            nqp::if(
              $!times,
              nqp::stmts(
                ($!times := nqp::sub_I($!times,1,Int)),
                $!value
              ),
              IterationEnd
            )
        }
        method push-all(\target --> IterationEnd) {
            nqp::while(
              $!times,
              nqp::stmts(
                ($!times := nqp::sub_I($!times,1,Int)),
                target.push($!value)
              )
            )
        }
        method skip-one() {
            nqp::if(
              $!times,
              nqp::stmts(
                (my $times := $!times),
                ($!times   := nqp::sub_I($!times,1,Int)),
                $times
              )
            )
        }
        method is-lazy() { nqp::hllbool($!is-lazy) }
        method count-only(--> Int:D) { $!times }
        method sink-all(--> IterationEnd) { $!times := 0 }
    }
    method OneValueTimes(Mu \value, Int() \times) { OneValueTimes.new(value,times) }

    # Return an iterator that will generate a pair with the index as the
    # key and as value the value of the given iterator, basically the
    # .pairs functionality on 1 dimensional lists.
    my class Pairs does Iterator {
        has Mu $!iter;
        has int $!key;

        method !SET-SELF(\iter) { $!iter := iter; $!key = -1; self }
        method new(\iter) { nqp::create(self)!SET-SELF(iter) }

        method pull-one() is raw {
            nqp::eqaddr((my $pulled := $!iter.pull-one),IterationEnd)
              ?? IterationEnd
              !! Pair.new(++$!key,$pulled)
        }
        method push-all(\target --> IterationEnd) {
            my $pulled;
            my int $key = -1;
            nqp::until(
              nqp::eqaddr(($pulled := $!iter.pull-one),IterationEnd),
              target.push(Pair.new(++$key,$pulled))
            )
        }
        method is-lazy() { $!iter.is-lazy }
        method is-deterministic(--> Bool:D) { $!iter.is-deterministic }
    }
    method Pairs(\iterator) { Pairs.new(iterator) }

    # Return an iterator for a given number of permutations.  Also specify
    # whether an IterationBuffer should be returned for each iteration (1),
    # or a List (0).  Basically the workhorse of permutations.
    my class Permutations does PredictiveIterator {
        has int $!n;
        has int $!b;
        has int $!todo;
        has $!next;
        method !SET-SELF(int $n, int $b) {
            $!n = $n;
            $!b = $b;
            $!todo = 1;
            my int $i = 1;

            nqp::while(
              nqp::isle_i(++$i,$n),
              ($!todo = nqp::mul_i($!todo,$i))
            );

            $!next := nqp::setelems(nqp::create(IterationBuffer),$n);

            $i = -1;
            nqp::while(
              nqp::islt_i(++$i,$n),
              nqp::bindpos($!next,$i,nqp::clone($i))
            );
            self
        }
        method new(\n,\b) { nqp::create(self)!SET-SELF(n,b) }
        method pull-one {
            my int $n = $!n;          # lexicals faster
            my $next := $!next;

            nqp::if(
              nqp::isge_i(--$!todo,0),
              nqp::stmts(
                (my $permuted := nqp::clone($next)),
                nqp::if(
                  $!todo,     # need to calculate next one
                  nqp::stmts( # largest index k such that a[k] < a[k+1]
                    (my int $k = nqp::sub_i($n,2)),
                    nqp::until(
                      nqp::islt_i(
                        nqp::atpos($next,$k),
                        nqp::atpos($next,nqp::add_i($k,1))
                      ),
                      --$k
                    ),
                    (my int $l = nqp::sub_i($n,1)),
                    nqp::until(
                      nqp::islt_i( # largest index l>k where a[k] < a[l]
                        nqp::atpos($next,$k),
                        nqp::atpos($next,$l)
                      ),
                      --$l,
                    ),
                    (my $tmp := nqp::atpos($next,$k)),
                    nqp::bindpos($next,$k,nqp::atpos($next,$l)),
                    nqp::bindpos($next,$l,$tmp)
                  )
                ),
                ($l = $n),
                nqp::until(
                  nqp::isge_i(++$k,--$l),
                  nqp::stmts(
                    ($tmp := nqp::atpos($next,$k)),
                    nqp::bindpos($next,$k,nqp::atpos($next,$l)),
                    nqp::bindpos($next,$l,$tmp)
                  )
                ),
                nqp::if(
                  $!b,
                  $permuted,
                  $permuted.List
                )
              ),
              IterationEnd
            )
        }
        method count-only(--> Int:D) { nqp::isgt_i($!todo,0) && $!todo }
    }
    my class CowardlyRefusing does PredictiveIterator {
        has Int $.n;
        method pull-one() is hidden-from-backtrace {
            die "Cowardly refusing to permutate more than {
                $?BITS == 32 ?? 13 !! 20
            } elements, tried $!n";
        }
        method count-only { [*] 1 .. $!n }
        method bool-only(--> True) { }
    }
    method Permutations($n, int $b) {
        $n > nqp::if(nqp::iseq_i($?BITS,32),13,20)  # must be HLL comparison
          ?? CowardlyRefusing.new(:$n)
          !! $n < 1                                 # must be HLL comparison
            ?? Rakudo::Iterator.OneValue(
                 nqp::create(nqp::if($b,IterationBuffer,List))
              )
            # See: https://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order
            !! Permutations.new($n,$b)
    }

    # helper sub to reduce footprint for fast path
    my sub divide-by-zero($numerator --> Nil) {
        X::Numeric::DivideByZero.new(:using<polymod>, :$numerator).Failure
    }

    # A role for handling the .polymod functionality, with the actual pull
    # logic to be provided by the client
    my role Polymod does Iterator {
        has $!numerator;
        has $!iterator;

        method new($numerator, $iterator) {
            my $self := nqp::create(self);
            nqp::bindattr($self,$?CLASS,'$!numerator',nqp::decont($numerator));
            nqp::bindattr($self,$?CLASS,'$!iterator', nqp::decont($iterator));
            $self
        }

        method done() {
            my $value   := $!numerator;
            $!numerator := nqp::null;
            $value || nqp::if(
              $!iterator.is-lazy,
              IterationEnd,
              0
            )
        }

        method pull-one() { ... }
    }

    # Return an iterator for handling Int.polymod
    my class IntPolymod does Polymod {
        method pull-one() {
            nqp::if(
              nqp::isnull($!numerator),
              IterationEnd,
              nqp::if(                          # not yet done
                nqp::eqaddr(
                  (my $mod := nqp::decont($!iterator.pull-one)),
                  IterationEnd
                ),
                self.done,                      # no more mods, so done
                nqp::if(                        # got a mod
                  $mod > 1,
                  nqp::stmts(                   # an actable mod
                    (my $value := $!numerator mod $mod),
                    nqp::bindattr(self,IntPolymod,'$!numerator',
                      ($!numerator div $mod)
                    ),
                    $value || nqp::if(
                      $!numerator,
                      0,
                      nqp::if(
                        $!iterator.is-lazy,
                        IterationEnd,
                        0
                      )
                    )
                  ),
                  nqp::if(                      # not an actable mod
                    $mod,
                    self.done,                  # 1
                    divide-by-zero($!numerator) # kaboom
                  )
                )
              )
            )
        }
    }

    # Return an iterator for handling Real.polymod
    my class RealPolymod does Polymod {
        method pull-one() {
            nqp::if(
              nqp::isnull($!numerator),
              IterationEnd,
              nqp::if(                          # not yet done
                nqp::eqaddr(
                  (my $mod := nqp::decont($!iterator.pull-one)),
                  IterationEnd
                ),
                self.done,                      # no more mods, so done
                nqp::if(                        # got a mod
                  $mod == 1,
                  self.done,                    # done
                  nqp::if(                      # possibly an actable mod
                    $mod,
                    nqp::stmts(                 # an actable mod
                      (my $value     := $!numerator % $mod),
                      (my $numerator := $!numerator - $value),
                      nqp::bindattr(self,RealPolymod,'$!numerator',
                        ($numerator / $mod)
                      ),
                      $value || nqp::if(
                        $!numerator,
                        0,
                        nqp::if(
                          $!iterator.is-lazy,
                          IterationEnd,
                          0
                        )
                      )
                    ),
                    divide-by-zero($!numerator) # kaboom
                  )
                )
              )
            )
        }
    }

    # Access method, checking for valid value to work with
    method Polymod(Numeric:D $got, @mods) {
        $got < 0
          ?? X::OutOfRange.new(
               :what('invocant to polymod'), :$got, :range<0..^Inf>
             ).Failure
          !! Seq.new:
               (nqp::istype($got,Int) ?? IntPolymod !! RealPolymod).new(
                 $got, @mods.iterator
               )
    }

    # Return an iterator for an Array that has been completely reified
    # already.  Returns a assignable container for elements don't exist
    # before the end of the reified array.
    my class ReifiedArrayIterator does PredictiveIterator {
        has $!reified;
        has $!descriptor;
        has int $!i;

        method !SET-SELF(\array, Mu \des) {
            $!reified    := nqp::getattr(array, List,  '$!reified');
            $!descriptor := des;
            $!i = -1;
            self
        }
        method new(\arr, Mu \des) { nqp::create(self)!SET-SELF(arr, des) }

        method !hole(int $i) is raw {
            nqp::p6scalarfromdesc(ContainerDescriptor::BindArrayPos.new(
                $!descriptor, $!reified, $i))
        }
        method pull-one() is raw {
            nqp::ifnull(
              nqp::atpos($!reified,++$!i),
              nqp::if(
                nqp::islt_i($!i,nqp::elems($!reified)), # found a hole
                self!hole($!i),
                IterationEnd
              )
            )
        }
        method push-exactly(\target, int $batch-size) {
            my int $todo = nqp::add_i($batch-size,1);
            my int $i    = $!i;      # lexicals are faster than attrs
            my int $elems = nqp::elems($!reified);

            nqp::while(
              --$todo && nqp::islt_i(++$i,$elems),
              target.push(
                nqp::ifnull(nqp::atpos($!reified,$i),self!hole($i))
              )
            );

            $!i = $i;                # make sure pull-one ends

            nqp::isge_i($i,$elems) ?? IterationEnd !! $batch-size
        }

        method push-all(\target --> IterationEnd) {
            my int $elems = nqp::elems($!reified);
            my int $i = $!i;

            nqp::while(   # doesn't sink
              nqp::islt_i(++$i,$elems),
              target.push(
                nqp::ifnull(nqp::atpos($!reified,$i),self!hole($i))
              )
            );
            $!i = $i;
        }
        method skip-one() {
            nqp::islt_i(++$!i,nqp::elems($!reified))
        }
        method skip-at-least(Int:D $toskip) {
            nqp::unless(
              $toskip <= 0,  # must be HLL
              nqp::stmts(
                ($!i = nqp::if(
                  $!i + $toskip < nqp::elems($!reified),  # must be HLL
                  nqp::add_i($!i,$toskip),
                  nqp::elems($!reified)
                )),
                nqp::islt_i($!i,nqp::elems($!reified))
              )
            )
        }
        method count-only(--> Int:D) {
            nqp::elems($!reified) - $!i - nqp::islt_i($!i,nqp::elems($!reified))
        }
        method sink-all(--> IterationEnd) { $!i = nqp::elems($!reified) }
    }
    method ReifiedArray(\array, Mu \descriptor) {
        ReifiedArrayIterator.new(array, descriptor)
    }

    # Return an iterator for a List that has been completely reified
    # already.  Returns an nqp::null for elements that don't exist
    # before the end of the reified list.
    my class ReifiedListIterator does PredictiveIterator {
        has $!reified;
        has int $!i;

        method !SET-SELF(\list) {
            $!reified := nqp::if(
              nqp::istype(list,List),
              nqp::getattr(list,List,'$!reified'),
              list
            );
            $!i = -1;
            self
        }
        method new(\list) { nqp::create(self)!SET-SELF(list) }

        method pull-one() is raw {
            nqp::ifnull(
              nqp::atpos($!reified,++$!i),
              nqp::if(
                nqp::islt_i($!i,nqp::elems($!reified)), # found a hole
                nqp::null,                              # it's a hole
                IterationEnd                            # it's the end
              )
            )
        }
        method push-exactly(\target, int $batch-size) {
            my int $todo = nqp::add_i($batch-size,1);
            my int $i    = $!i;      # lexicals are faster than attrs
            my int $elems = nqp::elems($!reified);

            nqp::while(
              --$todo && nqp::islt_i(++$i,$elems),
              target.push(nqp::atpos($!reified,$i))
            );

            $!i = $i;                # make sure pull-one ends

            nqp::isge_i($i,$elems) ?? IterationEnd !! $batch-size
        }
        method push-all(\target --> IterationEnd) {
            my int $elems = nqp::elems($!reified);
            my int $i = $!i;   # lexicals are faster than attributes

            nqp::while(  # doesn't sink
              nqp::islt_i(++$i,$elems),
              target.push(nqp::atpos($!reified,$i))
            );
            $!i = $i;
        }
        method skip-one() {
            nqp::islt_i(++$!i,nqp::elems($!reified))
        }
        method skip-at-least(Int:D $toskip) {
            nqp::unless(
              $toskip <= 0,  # must be HLL
              nqp::stmts(
                ($!i = nqp::if(
                  $!i + $toskip < nqp::elems($!reified),  # must be HLL
                  nqp::add_i($!i,$toskip),
                  nqp::elems($!reified)
                )),
                nqp::islt_i($!i,nqp::elems($!reified))
              )
            )
        }
        method count-only(--> Int:D) {
            nqp::elems($!reified) - $!i - nqp::islt_i($!i,nqp::elems($!reified))
        }
        method sink-all(--> IterationEnd) { $!i = nqp::elems($!reified) }
    }
    method ReifiedList(\list) { ReifiedListIterator.new(list) }

    # Return an iterator for a List that has been completely reified
    # already *AND* is sorted with increasing values.  Returns an nqp::null
    # for elements that don't exist before the end of the reified list.
    class ReifiedListIteratorMonotonicallyIncreasing is ReifiedListIterator {
        method is-monotonically-increasing(--> True) { }
    }
    method ReifiedListMonotonicallyIncreasing(\list) {
        ReifiedListIteratorMonotonicallyIncreasing.new(list)
    }

    # Return an iterator for a List/Array that has been completely reified,
    # or an IterationBuffer, that will produce values in reverse order.  Takes
    # a descriptor to create correct values for holes in the List/Array, use
    # Mu to have holes returned as Nil.
    my class ReifiedReverseIterator does PredictiveIterator {
        has $!reified;
        has $!descriptor;
        has int $!i;

        method !SET-SELF(\list, Mu \descriptor) {
            $!reified := nqp::istype(list,List)
              ?? nqp::getattr(list,List,'$!reified')
              !! list;
            $!descriptor := nqp::eqaddr(descriptor,Mu)
              ?? nqp::null()
              !! descriptor;
            ($!i = nqp::elems($!reified))
              ?? self
              !! Rakudo::Iterator.Empty
        }
        method new(\list, Mu \des) { nqp::create(self)!SET-SELF(list, des) }

        method !hole(int $i) is raw {
            nqp::isnull($!descriptor)
              ?? Nil
              !! nqp::p6scalarfromdesc(
                   ContainerDescriptor::BindArrayPos.new(
                     $!descriptor, $!reified, $i
                   )
                 )
        }

        method pull-one() is raw {
            nqp::isge_i(--$!i,0)
              ?? nqp::ifnull(nqp::atpos($!reified,$!i),self!hole($!i))
              !! IterationEnd
        }
        method push-all(\target --> IterationEnd) {
            my $reified := $!reified; # lexicals are faster than attributes
            my int $i    = $!i;
            nqp::while(  # doesn't sink
              nqp::isge_i(--$i,0),
              target.push(nqp::ifnull(nqp::atpos($reified,$i),self!hole($i)))
            );
            $!i = $i;
        }
        method skip-one() { nqp::isge_i(--$!i,0) }
        method count-only(--> Int:D) { $!i + nqp::islt_i($!i,0) }
        method sink-all(--> IterationEnd) { $!i = -1 }
    }
    method ReifiedReverse(\list, Mu \descriptor) {
        ReifiedReverseIterator.new(list, descriptor)
    }

    # Return an iterator for a List/Array that has been completely reified,
    # or an IterationBuffer, that will produce values in according to a given
    # rotation value.  Takes a descriptor to create correct values for holes
    # in the List/Array, use Mu to have holes returned as Nil.
    my class ReifiedRotateIterator does PredictiveIterator {
        has $!reified;
        has $!descriptor;
        has int $!todo;
        has int $!i;

        method !SET-SELF(int $rotate, \list, Mu \descriptor) {
            $!reified := nqp::istype(list,List)
              ?? nqp::getattr(list,List,'$!reified')
              !! list;
            $!descriptor := nqp::eqaddr(descriptor,Mu)
              ?? nqp::null()
              !! descriptor;

            nqp::if(
              ($!todo = my int $elems = nqp::elems($!reified)),
              nqp::stmts(
                ($!i = nqp::sub_i(nqp::mod_i(
                  nqp::add_i(nqp::mod_i($rotate,$elems),$elems),
                  $elems
                ),1)),
                self
              ),
              Rakudo::Iterator.Empty
            )
        }
        method new(\rotate, \list, Mu \des) {
            nqp::create(self)!SET-SELF(rotate, list, des)
        }

        method !hole(int $i) is raw {
            nqp::isnull($!descriptor)
              ?? Nil
              !! nqp::p6scalarfromdesc(
                   ContainerDescriptor::BindArrayPos.new(
                     $!descriptor, $!reified, $i
                   )
                 )
        }

        method pull-one() is raw {
            nqp::isge_i(--$!todo,0)
              ?? nqp::ifnull(
                   nqp::atpos(
                     $!reified,
                     ($!i = nqp::mod_i(nqp::add_i($!i,1),nqp::elems($!reified)))
                   ),
                   self!hole($!i)
                 )
              !! IterationEnd
        }
        method push-all(\target --> IterationEnd) {
            my $reified  := $!reified; # lexicals are faster than attributes
            my int $elems = nqp::elems($reified);
            my int $todo  = $!todo;
            my int $i     = $!i;

            nqp::while(  # doesn't sink
              nqp::isge_i(--$todo,0),
              target.push(
                nqp::ifnull(
                  nqp::atpos(
                    $reified,
                    ($i = nqp::mod_i(nqp::add_i($i,1),$elems))
                  ),
                  self!hole($i)
                )
              )
            );
            $!todo = $todo;
        }
        method skip-one() {
            $!i = nqp::mod_i(nqp::add_i($!i,1),nqp::elems($!reified));
            nqp::isge_i(--$!todo,0)
        }
        method count-only(--> Int:D) { $!todo + nqp::islt_i($!todo,0) }
        method sink-all(--> IterationEnd) { $!todo = -1 }
    }
    method ReifiedRotate(\rotate, \list, Mu \descriptor) {
        ReifiedRotateIterator.new(rotate, list, descriptor)
    }

    # Return a lazy iterator that takes a Callable that returns an iterator,
    # produces the values of that iterator until it is exhausted, then gets
    # an iterator by calling the Callable again, ad infinitum.
    my class Reiterate does Iterator {
        has &!reiterator;
        has $!iterator;

        method !SET-SELF(&reiterator) {
            &!reiterator := &reiterator;
            $!iterator   := reiterator();
            self
        }
        method new(\reiterator) { nqp::create(self)!SET-SELF(reiterator) }
        method pull-one() {
            nqp::if(
              nqp::eqaddr((my \pulled := $!iterator.pull-one),IterationEnd),
              ($!iterator := &!reiterator()).pull-one,
              pulled
            )
        }
        method is-lazy(--> True) { }     # we're lazy, always
    }
    method Reiterate(\reiterator) { Reiterate.new(reiterator) }

    # Return a lazy iterator that will repeat the values of a given
    # source iterator indefinitely.  Even when given a lazy iterator,
    # it will cache the values seen to handle case that the iterator
    # will exhaust after all.  Only if the source iterator did not
    # produce any values at all, then the returned iterator will not
    # produce any either.
    my class Repeat does Iterator {
        has $!iterator;
        has $!reified;
        has int $!i;
        method !SET-SELF(\iterator) {
            $!iterator := iterator;
            $!reified  := nqp::create(IterationBuffer);
            self
        }
        method new(\iter) { nqp::create(self)!SET-SELF(iter) }
        method pull-one() is raw {
            nqp::if(
              nqp::eqaddr($!iterator,IterationEnd),
              nqp::atpos(                # supplying from cache
                $!reified,
                nqp::mod_i(++$!i,nqp::elems($!reified))
              ),
              nqp::if(                   # supplying from iterator
                nqp::eqaddr(
                  (my \pulled := $!iterator.pull-one),
                  IterationEnd
                ),
                nqp::if(
                  nqp::elems($!reified),
                  nqp::stmts(            # exhausted, something in cache
                    ($!iterator := IterationEnd),
                    nqp::atpos($!reified,0)
                  ),
                  IterationEnd           # exhausted, nothing in cache
                ),
                nqp::push(               # cache and supply
                  $!reified,
                  pulled
                )
              )
            )
        }
        method is-lazy(--> True) { }     # we're lazy, always
        method is-deterministic(--> Bool:D) { $!iterator.is-deterministic }
    }
    method Repeat(\iterator) { Repeat.new(iterator) }

    # Returns an iterator that handles all properties of a -repeat- with
    # a condition.  Takes a Callable to be considered the body of the loop,
    # and a Callable for the condition..
    my class RepeatLoop does Rakudo::SlippyIterator {
        has &!body;
        has &!cond;
        has $!label;
        has int $!skip;

        method !SET-SELF(\body,\cond,\label) {
            nqp::bindattr(self,self.WHAT,'$!slipper',nqp::null);
            &!body  := body;
            &!cond  := cond;
            $!label := nqp::decont(label);
            $!skip   = 1;
            self
        }
        method new(\body,\cond,\label) {
            nqp::create(self)!SET-SELF(body,cond,label)
        }

        method pull-one() {
            if nqp::not_i(nqp::isnull($!slipper)) && nqp::not_i(
                nqp::eqaddr((my $result := self.slip-one),IterationEnd)
            ) {
                $result
            }
            else {
                nqp::if(
                  $!skip || &!cond(),
                  nqp::stmts(
                    ($!skip = 0),
                    nqp::until(   # XXX perhaps repeat_until?
                      (my int $stopped),
                      nqp::stmts(
                        ($stopped = 1),
                        nqp::handle(
                          nqp::if(
                            nqp::istype(($result := &!body()),Slip),
                            ($stopped = nqp::eqaddr(
                              ($result := self.start-slip($result)),
                              IterationEnd
                            ) && nqp::if(&!cond(),0,1))
                          ),
                          'LABELED', $!label,
                          'NEXT', nqp::if(
                            nqp::eqaddr(
                              ($result := self.control-payload),
                              IterationEnd
                            ),
                            ($stopped = nqp::if(&!cond(),0,1))
                          ),
                          'REDO', ($stopped = 0),
                          'LAST', nqp::unless(
                            nqp::eqaddr(
                              ($result := self.control-payload),
                              IterationEnd
                            ),
                            (&!cond  := &always-False)  # end later
                          )
                        )
                      ),
                      :nohandler
                    ),
                    $result
                  ),
                  IterationEnd
                )
            }
        }
    }
    method RepeatLoop(&body, &cond, $label) {
        RepeatLoop.new(&body, &cond, $label)
    }

    # Return an iterator for a non-lazy iterator that rotates values for a
    # given positive amount.  This will cache the given amount, and produce
    # them at the end after the given iterator is exhausted.
    my class RotateIterator does Iterator {
        has $!iterator;
        has $!rotated;

        method !SET-SELF(int $rotate, \iterator) {

            # set up values to be rotated
            my $rotated := nqp::create(IterationBuffer);
            nqp::until(
              nqp::iseq_i(nqp::elems($rotated),$rotate),
              nqp::if(
                nqp::eqaddr((my \pulled := iterator.pull-one),IterationEnd),
                (return ReifiedRotateIterator.new($rotate, $rotated, Mu)),
                nqp::push($rotated,pulled)
              )
            );

            $!iterator := iterator;
            $!rotated  := $rotated;
            self
        }
        method new(\rot, \iter) { nqp::create(self)!SET-SELF(rot, iter) }

        method !exhausted() is raw {
            $!iterator := nqp::null;
            nqp::shift($!rotated)
        }

        method pull-one() is raw {
            nqp::isnull($!iterator)
              ?? nqp::elems($!rotated)
                ?? nqp::shift($!rotated)
                !! IterationEnd
              !! nqp::eqaddr((my \pulled := $!iterator.pull-one),IterationEnd)
                ?? self!exhausted()
                !! pulled
        }
        method is-deterministic(--> Bool:D) { $!iterator.is-deterministic }
    }
    method Rotate(\rotate, \iterator) {
        RotateIterator.new(rotate, iterator)
    }

    # Return an iterator that rotorizes the given iterator with the
    # given cycle.  If the cycle is a Cool, then it is assumed to
    # be a single Int value to R:It.Batch with.  Otherwise it is
    # considered to be something Iterable that will be repeated
    # until the source iterator is exhausted.  The third parameter
    # indicates whether a partial result is acceptable when the
    # source iterator is exhausted.
    my class Rotor does Iterator {
        has $!iterator;
        has $!cycle;
        has $!buffer;
        has int $!complete;
        has int $!is-exhausted;
        method !SET-SELF(\iterator,\cycle,\partial) {
            $!iterator := iterator;
            $!cycle    := Rakudo::Iterator.Repeat(cycle.iterator);
            $!buffer   := nqp::create(IterationBuffer);
            $!complete  = !partial;
            self
        }
        method new(\iterator,\cycle,\partial) {
            nqp::istype(cycle,Iterable)
              ?? nqp::create(self)!SET-SELF(iterator,cycle,partial)
              !! Rakudo::Iterator.Batch(iterator,cycle,partial)
        }
        method pull-one() is raw {
          nqp::if(
            $!is-exhausted,
            IterationEnd,
            nqp::stmts(
              nqp::if(
                nqp::istype((my $todo := $!cycle.pull-one),Pair),
                nqp::stmts(
                  (my $size := $todo.key),
                  nqp::if(
                    nqp::istype($size,Whatever),
                    nqp::stmts(                    # eat everything
                      (my int $elems = -1),
                      ($!complete = 0)
                    ),
                    nqp::if(
                      $size < 1,                   # must be HLL comparison
                      X::OutOfRange.new(
                        what    => "Rotorizing sublist length is",
                        got     => $size,
                        range   => "1..^Inf",
                      ).throw,
                      nqp::if(
                        $size == Inf || (
                          nqp::istype($size,Int)
                            && nqp::isbig_I(nqp::decont($size))
                        ),
                        nqp::stmts(                # eat everything
                          ($elems = -1),
                          ($!complete = 0)
                        ),
                        nqp::if(
                          nqp::isle_i(
                            nqp::add_i(
                              ($elems = $size.Int),
                              (my int $gap = $todo.value.Int)
                            ),
                            -1
                          ),
                          X::OutOfRange.new(       # gap out of range
                            what    => "Rotorizing gap is",
                            got     => $gap,
                            range   => "-$elems..^Inf",
                            comment => "\nEnsure a negative gap is not larger than the length of the sublist",
                          ).throw
                        )
                      )
                    )
                  )
                ),
                nqp::if(                           # just a size
                  nqp::istype($todo,Whatever),
                  nqp::stmts(                      # eat everything
                    ($elems = -1),
                    ($!complete = 0)
                  ),
                  nqp::if(
                    $todo < 1,                     # must be HLL comparison
                    X::OutOfRange.new(             # size out of range
                      what    => "Rotorizing sublist length is",
                      got     => $todo,
                      range   => "1..^Inf",
                      comment => "\nDid you mean to specify a Pair with => $todo?"
                    ).throw,
                    nqp::if(
                      (nqp::istype($todo,Int)
                        && nqp::isbig_I(nqp::decont($todo)))
                        || $todo == Inf,
                      nqp::stmts(                  # eat everything
                        ($elems = -1),
                        ($!complete = 0)
                      ),
                      ($elems = $todo.Int)
                    )
                  )
                )
              ),
              nqp::until(                          # fill the buffer
                (nqp::isge_i(nqp::elems($!buffer),$elems)
                  && nqp::isne_i($elems,-1))       # eat everything
                  || nqp::eqaddr(
                       (my $pulled := $!iterator.pull-one),
                       IterationEnd
                     ),
                nqp::push($!buffer,$pulled)
              ),
              nqp::if(
                  nqp::iseq_i($elems,-1),
                  ($elems = nqp::elems($!buffer))
              ),
              nqp::if(
                nqp::not_i(nqp::elems($!buffer))
                  || (nqp::eqaddr($pulled,IterationEnd)
                       && ($!is-exhausted = 1)
                       && $!complete
                       && nqp::islt_i(nqp::elems($!buffer),$elems)
                     ),
                IterationEnd,                      # done
                nqp::if(
                  nqp::islt_i($gap,0),
                  nqp::stmts(                      # keep some for next
                    (my $result := nqp::clone($!buffer).List),
                    nqp::if(
                      nqp::islt_i(nqp::elems($!buffer),$elems),
                      nqp::setelems($!buffer,0),   # was :partial, now done
                      nqp::splice($!buffer,$empty,0,nqp::add_i($elems,$gap))
                    ),
                    $result
                  ),
                  nqp::stmts(
                    nqp::if(
                      nqp::isgt_i($gap,0),
                      $!iterator.skip-at-least($gap) # need to skip a few
                    ),
                    nqp::if(
                      nqp::isle_i(nqp::elems($!buffer),$elems),
                      nqp::stmts(                    # whole buffer ok
                        ($result := $!buffer.List),
                        ($!buffer := nqp::create(IterationBuffer))
                      ),
                      nqp::stmts(                    # partial buffer ok
                        ($result := nqp::p6bindattrinvres(
                          nqp::create(List),List,'$!reified',
                          nqp::splice(
                            nqp::clone($!buffer),
                            $empty,
                            $elems,
                            nqp::sub_i(nqp::elems($!buffer),$elems)
                          )
                        )),
                        nqp::splice($!buffer,$empty,0,$elems)
                      )
                    ),
                    $result
                  )
                )
              )
            )
          )
        }
        method is-lazy() { $!iterator.is-lazy }
        method is-deterministic(--> Bool:D) { $!iterator.is-deterministic }
    }
    method Rotor(\iterator,\cycle,\partial) {
        Rotor.new(iterator,cycle,partial)
    }

    # Return an iterator that will roundrobin the given iterables
    # (with &[,]).  Basically the functionality of roundrobin(@a,@b)
    my class RoundrobinIterables does Iterator {
        has $!iters;
        has $.is-lazy;
        method !SET-SELF(\iterables) {
            my $iterables := nqp::getattr(iterables,List,'$!reified');
            my int $elems = nqp::elems($iterables);
            $!iters   := nqp::setelems(nqp::list,$elems);
            $!is-lazy := False;

            my int $i = -1;
            nqp::while(
              nqp::islt_i(++$i,$elems),
              nqp::bindpos($!iters,$i,
                nqp::if(
                  nqp::iscont(my $elem := nqp::atpos($iterables,$i)),
                  Rakudo::Iterator.OneValue($elem),
                  nqp::stmts(
                    nqp::if($elem.is-lazy,($!is-lazy := True)),
                    $elem.iterator
                  )
                )
              )
            );
            self
        }
        method new(\iterables) { nqp::create(self)!SET-SELF(iterables) }
        method pull-one() {
            nqp::if(
              nqp::eqaddr($!iters,IterationEnd),
              IterationEnd,
              nqp::stmts(
                (my int $i = -1),
                (my int $elems = nqp::elems($!iters)),
                (my $buf := nqp::create(IterationBuffer)),
                nqp::until(
                  nqp::iseq_i(++$i,$elems),
                  nqp::if(
                    nqp::eqaddr(
                      (my $pulled := nqp::atpos($!iters,$i).pull-one),
                      IterationEnd
                    ),
                    nqp::stmts(          # remove exhausted iterator
                      nqp::splice($!iters,$empty,$i,1),
                      --$i,
                      --$elems
                    ),
                    nqp::push($buf,$pulled)
                  )
                ),
                nqp::if(
                  nqp::elems($buf),
                  $buf.List,
                  ($!iters := IterationEnd),            # we're done
                )
              )
            )
        }
    }
    method RoundrobinIterables(@iterables) {
        nqp::isgt_i(@iterables.elems,0)  # reifies
          ?? RoundrobinIterables.new(@iterables)
          !! Rakudo::Iterator.Empty
    }

    # Return an iterator that will slip all values of the given iterables
    # roundrobinly.  Basically the functionality of roundrobin(@a,@b, :slip)
    my class RoundrobinIterablesSlipped does Iterator {
        has $!iters;
        has $.is-lazy;
        has int $!i;
        method !SET-SELF(\iterables) {
            my $iterables := nqp::getattr(iterables,List,'$!reified');
            my int $elems = nqp::elems($iterables);
            $!iters   := nqp::setelems(nqp::list,$elems);
            $!is-lazy := False;

            my int $i = -1;
            nqp::while(
              nqp::islt_i(++$i,$elems),
              nqp::bindpos($!iters,$i,
                nqp::if(
                  nqp::iscont(my $elem := nqp::atpos($iterables,$i)),
                  Rakudo::Iterator.OneValue($elem),
                  nqp::stmts(
                    nqp::if($elem.is-lazy,($!is-lazy := True)),
                    $elem.iterator
                  )
                )
              )
            );
            self
        }
        method new(\iterables) { nqp::create(self)!SET-SELF(iterables) }
        method pull-one() {
            nqp::while(
              nqp::islt_i($!i,nqp::elems($!iters))
                && nqp::eqaddr(
                     (my $pulled := nqp::atpos($!iters,$!i).pull-one),
                     IterationEnd
                   ),
              nqp::stmts(
                nqp::splice($!iters,$empty,$!i,1),       # remove exhausted
                nqp::if(                                 # check at same index
                  nqp::isge_i($!i,nqp::elems($!iters)),  # unless it was last
                  ($!i = 0)
                )
              )
            );
            $!i = 0 if nqp::isge_i(++$!i,nqp::elems($!iters));
            $pulled
        }
    }
    method RoundrobinIterablesSlipped(@iterables) {
        nqp::isgt_i(@iterables.elems,0)  # reifies
          ?? RoundrobinIterablesSlipped.new(@iterables)
          !! Rakudo::Iterator.Empty
    }

    # Return an iterator from a source iterator that is supposed to
    # generate iterators. As soon as an iterator is exhausted, the next iterator
    # will be fetched and iterated over until exhausted.
    my class SequentialIterators does Iterator {
        has $!source;
        has $!current;
        has $!is-lazy;
        method !SET-SELF(\source, \is-lazy) {
            ($!current := ($!source := source).pull-one);
            $!is-lazy  := is-lazy;
            self
        }
        method new(\source, \is-lazy) {
            nqp::create(self)!SET-SELF(source, is-lazy)
        }
        method pull-one() {
            nqp::if(
              nqp::eqaddr($!current,IterationEnd),
              IterationEnd,
              nqp::if(
                nqp::eqaddr(
                  (my \pulled := $!current.pull-one),
                  IterationEnd
                ),
                nqp::stmts(
                  ($!current := $!source.pull-one),
                  self.pull-one
                ),
                pulled
              )
            )
        }
        method is-lazy() { $!is-lazy }
    }
    method SequentialIterators(\source, Bool() $is-lazy = False) {
        SequentialIterators.new(source, $is-lazy)
    }

    # Return an iterator that generates all possible keys of the
    # given shape.  Each value generated is a reified List.  This is
    # basically a copy of the internal engine of ShapeLeaf and
    # ShapeBranchi roles, but without any additional processing.
    # Intended for ad-hoc iterators that feed .AT-POS on shaped lists.
    my class ShapeIndex does Iterator {
        has $!dims;
        has $!indices;
        has int $!maxdim;
        has int $!max;

        method !SET-SELF(\shape) {
            $!dims := nqp::getattr(nqp::decont(shape),List,'$!reified');
            my int $dims = nqp::elems($!dims);
            $!indices := nqp::setelems(nqp::create(IterationBuffer),$dims);
            my int $i = -1;

            nqp::while(
              nqp::islt_i(++$i,$dims),
              nqp::bindpos($!indices,$i,0)
            );

            $!maxdim = nqp::sub_i($dims,1);
            $!max    = nqp::atpos($!dims,$!maxdim);
            self
        }
        method new(\shape) { nqp::create(self)!SET-SELF(shape) }

        method pull-one() is raw {
            nqp::if(
              $!indices,
              nqp::stmts(                      # still iterating
                (my $buf := nqp::clone($!indices)),
                nqp::if(
                  nqp::islt_i(                        (my int $i =
                      nqp::add_i(nqp::atpos($!indices,$!maxdim),1)),
                    $!max
                  ),
                  nqp::bindpos($!indices,$!maxdim,$i),    # ready for next
                  nqp::stmts(                             # done for now
                    (my int $level = $!maxdim),
                    nqp::until(                           # update indices
                      nqp::islt_i(--$level,0)             # exhausted ??
                        || nqp::stmts(
                        nqp::bindpos($!indices,nqp::add_i($level,1),0),
                        nqp::islt_i(
                          nqp::bindpos($!indices,$level,
                            nqp::add_i(nqp::atpos($!indices,$level),1)),
                          nqp::atpos($!dims,$level)
                        ),
                      ),
                      nqp::null
                    ),
                    nqp::if(                   # this was the last value
                      nqp::islt_i($level,0),
                      $!indices := nqp::null
                    )
                  )
                ),
                $buf.List                      # what we found
              ),
              IterationEnd                     # done iterating
            )
        }
    }
    method ShapeIndex(\shape) {
        shape.elems == 1
          ?? IntRange.new(0,shape.AT-POS(0) - 1)
          !! ShapeIndex.new(shape)
    }

    # Returns an iterator for an unbounded sequence of generic values that
    # have a .succ method to indicate the next logical value.  Takes the
    # initial.
    my class SuccFromInf does Iterator {
        has Mu $!i;

        method !SET-SELF(Mu $!i) { self }
        method new(\i) { nqp::create(self)!SET-SELF(i) }

        method pull-one() {
            my Mu $i = $!i;
            $!i = $i.succ;
            $i
        }
        method is-lazy(--> True) { }
        method is-monotonically-increasing(--> True) { }
    }
    method SuccFromInf(\i) {
        i.can('succ')
          ?? SuccFromInf.new(i)
          !! X::Range::CannotIterate.new(:min(i)).throw
    }

    # Returns an iterator for a range of generic values that have a
    # .succ method to indicate the next logical value.  Takes the initial
    # value, whether the final value should be excluded and the final value.
    my class SuccFromTo does Iterator {
        has Mu $!i;
        has Mu $!e;
        has int $!exclude;

        method !SET-SELF(Mu $!i, Int() $!exclude, Mu $!e) { self }
        method new(\i,\exclude,\e) {
            nqp::create(self)!SET-SELF(i,exclude,e)
        }

        method pull-one() {
            if $!exclude ?? $!i before $!e !! not $!i after $!e {
                my Mu $i = $!i;
                $!i = $i.succ;
                $i
            }
            else {
                IterationEnd
            }
        }
        method push-all(\target --> IterationEnd) {
            my Mu $i = $!i;
            my Mu $e = $!e;
            if $!exclude {
                while $i before $e {
                    target.push(nqp::clone($i));
                    $i = $i.succ;
                }
            }
            else {
                while not $i after $e {
                    target.push(nqp::clone($i));
                    $i = $i.succ;
                }
            }
            $!i = $e.succ;
        }
        method is-monotonically-increasing(--> True) { }
        method sink-all(--> IterationEnd) { $!i = $!e.succ }
    }
    method SuccFromTo(\i,\exclude,\e) {
        i.can('succ')
          ?? SuccFromTo.new(i,exclude,e)
          !! X::Range::CannotIterate.new(:min(i)).throw
    }

    # Returns an iterator that takes a source iterator and a value, and
    # produces a lazy iterator that will first produce decontainerized
    # values of the given iterator, and then starts producing the given
    # value indefinitely.
    my class TailWith {
        has $!iterator;
        has $!tail;
        method !SET-SELF(\iterator, \tail) {
            $!iterator := iterator;
            $!tail     := tail;
            self
        }
        method new(\iter, \tail) { nqp::create(self)!SET-SELF(iter, tail) }

        method pull-one() {
            nqp::if(
              nqp::isnull($!iterator),
              $!tail,
              nqp::if(
                nqp::eqaddr((my \pulled := $!iterator.pull-one),IterationEnd),
                nqp::stmts(
                  ($!iterator := nqp::null),
                  $!tail
                ),
                pulled
              )
            )
        }
        method lazy(--> True) { }

        # Destructive test to see whether the original iterator was
        # exhausted.  Will act as a skip-one otherwise.  Returns 1 for
        # exhausted, and 0 for not exhausted yet.
        method exhausted() {
            self.pull-one unless nqp::isnull($!iterator);
            nqp::isnull($!iterator)
        }
    }
    method TailWith(\iter, \tail) { TailWith.new(iter, tail) }

    # Returns an iterator that takes a source iterator, an iterator producing
    # Callable blocks producing trueish/falsish values, and a flag indicating
    # the initial state.  Iteration begins with taking the next Callable from
    # the iterator taking Callables.  If there's no Callable found (anymore),
    # either the result iterator will end (if the state is falsish), or the
    # iterator will pass on all future values of the source iterator (if the
    # state is truish).  Then values from the source iterator will be taken
    # and fed to the block as long as the returned values matches the state.
    # If the state if trueish, then values will be passed along.  If the
    # state if falsish, then values will be dropped.  If the value returned
    # by the Callable does not match the state, the next Callable will be
    # taken (if any) and the process will be repeated until either the source
    # iterator is exhausted, or the Callable block iterator is.
    my class Toggle does Iterator {
        has $!iter;
        has $!conds;
        has int $!on;
        has $!current; # null if passing on
        has $!done;    # IterationEnd if done

        method !SET-SELF(\iter, \conds, \on) {
            $!iter  := iter;
            $!conds := conds;
            $!on = nqp::istrue(on);
            $!done := nqp::null;

            nqp::eqaddr((my $next := conds.pull-one),IterationEnd)
              ?? $!on
                ?? ($!current := nqp::null)
                !! ($!done := IterationEnd)
              !! ($!current := $next);
            self
        }
        method new(\iter, \conds, \on) {
            nqp::create(self)!SET-SELF(iter, conds, on)
        }

        method pull-one() is raw {
            nqp::ifnull(
              $!done,                        # done if not null

              nqp::if(                       # source not exhausted yet
                nqp::eqaddr((my $pulled := $!iter.pull-one),IterationEnd),
                ($!done := IterationEnd),    # source exhausted now

                nqp::if(
                  nqp::isnull($!current),
                  $pulled,                   # passing through rest

                  nqp::if(                   # need to check if ok
                    $!on,
                    nqp::if(                 # passing on until off
                      $!current($pulled),
                      $pulled,               # still on

                      nqp::if(               # was on, off now
                        nqp::eqaddr(
                          (my $next := $!conds.pull-one),
                          IterationEnd
                        ),
                        ($!done := IterationEnd), # no next checker, done

                        nqp::stmts(          # use next checker
                          nqp::until(
                           nqp::eqaddr(
                              ($pulled := $!iter.pull-one),
                              IterationEnd
                            ) || $next($pulled),
                            nqp::null
                          ),
                          nqp::if(           # ended looping, why?
                            nqp::eqaddr($pulled,IterationEnd),
                            ($!done := IterationEnd), # exhausted now

                            nqp::stmts(      # on, passed off, on again
                              ($!current := nqp::if(
                                nqp::eqaddr(
                                  ($next := $!conds.pull-one),
                                  IterationEnd
                                ),
                                nqp::null,   # pass rest on
                                $next        # set next checker
                              )),
                              $pulled

                            )
                          )
                        )
                      )
                    ),
                    nqp::if(                 # off now (first time)
                      $!current($pulled),
                      nqp::stmts(
                        nqp::if(             # on for first elem
                          nqp::eqaddr(
                            ($!current := $!conds.pull-one),
                            IterationEnd
                          ),
                          ($!current := nqp::null), # no next, passing on
                          ($!on = 1)         # there's next, keep going
                        ),
                        $pulled              # first hit is ok

                      ),
                      nqp::stmts(            # still off for first
                        nqp::until(
                         nqp::eqaddr(
                            ($pulled := $!iter.pull-one),
                            IterationEnd
                          ) || $!current($pulled),
                          nqp::null
                        ),
                        nqp::if(             # ended looping, why?
                          nqp::eqaddr($pulled,IterationEnd),
                          ($!done := IterationEnd), # exhausted now

                          nqp::stmts(        # found ok
                            nqp::if(
                              nqp::eqaddr(
                                ($!current := $!conds.pull-one),
                                IterationEnd
                              ),
                              ($!current := nqp::null), # no next, pass on
                              ($!on = 1)     # there's next, keep going
                            ),
                            $pulled

                          )
                        )
                      )
                    )
                  )
                )
              )
            )
        }
        method sink-all(--> IterationEnd) { $!iter.sink-all }
        method is-deterministic(--> Bool:D) { $!iter.is-deterministic }
    }
    method Toggle(\iter, \conds, $on) { Toggle.new(iter, conds, $on) }

    # Return an iterator for the Truthy values of an iterator
    my class Truthy does Iterator {
        has Mu $!iterator;
        method new(\iterator) {
            nqp::p6bindattrinvres(
              nqp::create(self),self,'$!iterator',iterator)
        }
        method pull-one() is raw {
            nqp::until(
              nqp::eqaddr((my $pulled := $!iterator.pull-one),IterationEnd),
              nqp::if(
                $pulled,
                return $pulled
              )
            );
            IterationEnd
        }
        method is-deterministic(--> Bool:D) { $!iterator.is-deterministic }
    }
    method Truthy(\iterator) { Truthy.new(iterator) }

    # Return an iterator that only will return the two given values.
    my class TwoValues does Iterator {
        has Mu $!val1;
        has Mu $!val2;
        method new(Mu \val1, Mu \val2) {
            nqp::p6bindattrinvres(
              nqp::p6bindattrinvres(
                nqp::create(self),self,'$!val1',val1),
              self,'$!val2',val2
            )
        }
        method pull-one() is raw {
            nqp::if(
              nqp::eqaddr($!val1,IterationEnd),
              nqp::if(
                nqp::eqaddr($!val2,IterationEnd),
                IterationEnd,
                nqp::stmts(
                  (my Mu $val2 := $!val2),
                  ($!val2 := IterationEnd),
                  $val2
                )
              ),
              nqp::stmts(
                (my $val1 := $!val1),
                ($!val1 := IterationEnd),
                $val1
              )
            )
        }
        method push-all(\target --> IterationEnd) {
            nqp::if(
              nqp::eqaddr($!val1,IterationEnd),
              nqp::unless(nqp::eqaddr($!val2,Mu),target.push($!val2)),
              nqp::stmts(
                target.push($!val1),
                target.push($!val2)
              )
            );
            $!val1 := $!val2 := IterationEnd;
        }
        method skip-one() {
            nqp::if(
              nqp::not_i(nqp::eqaddr($!val1,IterationEnd)),
              nqp::isfalse($!val1 := IterationEnd),
              nqp::if(
                nqp::not_i(nqp::eqaddr($!val2,IterationEnd)),
                nqp::isfalse($!val2 := IterationEnd)
              )
            )
        }
        method sink-all(--> IterationEnd) {
            $!val1 := $!val2 := IterationEnd
        }
    }
    method TwoValues(Mu \val1, Mu \val2) { TwoValues.new(val1, val2) }

    # Return a lazy iterator that will keep producing the given value.
    # Basically the functionality of 42 xx *
    my class UnendingValue does Iterator {
        has Mu $!value;
        method new(Mu \value) {
            nqp::p6bindattrinvres(nqp::create(self),self,'$!value',value)
        }
        method pull-one() is raw { $!value }
        method skip-one(--> True) { }
        method sink-all(--> IterationEnd) { }
        method is-lazy(--> True) { }
    }
    method UnendingValue(Mu \value) { UnendingValue.new(value) }

    # Return an iterator from a given iterator with a given mapper callable
    # and a compare callable, producing values either with unique or repeated
    # semantics.
    my class UniqueRepeatedAsWith does Iterator {
        has Mu $!iter;
        has &!as;
        has &!with;
        has int $!unique;
        has $!seen;
        method !SET-SELF(\iterator, \as, \with, \unique) {
            $!iter := iterator;
            &!as := as;
            &!with := with;
            $!unique = nqp::istrue(unique);
            $!seen := nqp::list;
            self
        }
        method new( \iterator, \as, \with, \union) {
            nqp::create(self)!SET-SELF(iterator, as, with, union)
        }
        method pull-one() is raw {
            my &as := &!as;      # lexicals are faster than attributes
            my &with := &!with;
            my $seen := $!seen;

            nqp::until(
              nqp::eqaddr((my $needle := $!iter.pull-one),IterationEnd),
              nqp::stmts(
                (my int $i = -1),
                (my int $elems = nqp::elems($!seen)),
                (my $target := as($needle)),
                nqp::if(
                  nqp::istype($target,Seq),
                  $target := $target.cache
                ),
                nqp::until(
                  nqp::iseq_i(++$i,$elems)
                    || with($target,nqp::atpos($seen,$i)),
                  nqp::null
                ),
                nqp::if(                         # done searching
                  $!unique,
                  nqp::if(                       # need unique semantics
                    nqp::iseq_i($i,$elems),
                    nqp::stmts(                  # new, so add and produce
                      nqp::push($!seen,$target),
                      (return-rw $needle)
                    )
                  ),
                  nqp::if(                       # need repeated semantics
                    nqp::iseq_i($i,$elems),
                    nqp::push($!seen,$target),   # new, just add
                    (return-rw $needle)          # not new, produce
                  )
                )
              )
            );
            IterationEnd
        }
        method is-lazy() { $!iter.is-lazy }
        method is-deterministic(--> Bool:D) { $!iter.is-deterministic }
        method sink-all(--> IterationEnd) { $!iter.sink-all }
    }
    method UniqueRepeatedAsWith(\iterator, \as, \with, \unique) {
        UniqueRepeatedAsWith.new(iterator, as, with, unique)
    }

    # Return an iterator from a given iterator with a given compare
    # callable, producing values either with unique or repeated semantics.
    my class UniqueRepeatedWith does Iterator {
        has Mu $!iter;
        has &!with;
        has int $!unique;
        has $!seen;
        method !SET-SELF(\iterator, \with, \unique) {
            $!iter := iterator;
            &!with := with;
            $!unique = nqp::istrue(unique);
            $!seen := nqp::list;
            self
        }
        method new( \iterator, \with, \union) {
            nqp::create(self)!SET-SELF(iterator, with, union)
        }
        method pull-one() is raw {
            my &with := &!with;  # lexicals are faster than attributes
            my $seen := $!seen;

            nqp::until(
              nqp::eqaddr((my $needle := $!iter.pull-one),IterationEnd),
              nqp::stmts(
                (my int $i = -1),
                (my int $elems = nqp::elems($!seen)),
                nqp::until(
                  nqp::iseq_i(++$i,$elems)
                    || with($needle,nqp::atpos($seen,$i)),
                  nqp::null
                ),
                nqp::if(                         # done searching
                  $!unique,
                  nqp::if(                       # need unique semantics
                    nqp::iseq_i($i,$elems),
                    nqp::stmts(                  # new, so add and produce
                      nqp::push($!seen,$needle),
                      (return-rw $needle)
                    )
                  ),
                  nqp::if(                       # need repeated semantics
                    nqp::iseq_i($i,$elems),
                    nqp::push($!seen,$needle),   # new, just add
                    (return-rw $needle)          # not new, produce
                  )
                )
              )
            );
            IterationEnd
        }
        method is-lazy() { $!iter.is-lazy }
        method is-deterministic(--> Bool:D) { $!iter.is-deterministic }
        method sink-all(--> IterationEnd) { $!iter.sink-all }
    }
    method UniqueRepeatedWith(\iterator, \with, \unique) {
        UniqueRepeatedWith.new(iterator, with, unique)
    }

    # Returns an iterator that takes a source iterator and a Callable.  It
    # passes on all values from the source iterator from the moment the
    # Callable returns a trueish value.
    my class Until does Iterator {
        has $!iter;
        has $!cond;

        method !SET-SELF(\iter, \cond) {
            $!iter := iter;
            $!cond := cond;
            self
        }
        method new(\iter,\cond) { nqp::create(self)!SET-SELF(iter,cond) }

        method pull-one() is raw {
            nqp::if(
              $!cond,
              nqp::stmts(
                nqp::until(
                  nqp::eqaddr((my $pulled := $!iter.pull-one),IterationEnd)
                    || $!cond($pulled),
                  nqp::null
                ),
                ($!cond := nqp::null),
                $pulled
              ),
              $!iter.pull-one
            )
        }
        method is-deterministic(--> Bool:D) { $!iter.is-deterministic }
        method sink-all(--> IterationEnd) { $!iter.sink-all }
    }
    method Until(\iter, &cond) { Until.new(iter, &cond) }

    # Returns an iterator from a given iterator where the occurrence of
    # a Whatever value indicates that last value seen from the source
    # iterator should be repeated indefinitely until either another
    # non-Whatever value is seen from the source iterator, or the source
    # iterator is exhausted.
    my class WhateverIterator does Iterator {
        has $!source;
        has $!last;
        has int $!whatever;
        method new(\source) {
            nqp::p6bindattrinvres(nqp::create(self),self,'$!source',source)
        }
        method pull-one() is raw {
            nqp::if(
              $!whatever,
              nqp::if(                          # we're repeating
                nqp::iseq_i($!whatever,2),      # source exhausted, repeat
                $!last,
                nqp::if(
                  nqp::eqaddr(
                    (my $value := $!source.pull-one),
                    IterationEnd
                  ),
                  nqp::stmts(                   # exhausted now, repeat
                    ($!whatever = 2),
                    $!last
                  ),
                  nqp::if(
                    nqp::istype($value,Whatever),
                    $!last,                     # another Whatever, repeat
                    nqp::stmts(                 # something else, no repeat
                      ($!whatever = 0),
                      ($!last := $value)
                    )
                  )
                )
              ),
              nqp::if(                          # not repeating
                nqp::eqaddr(
                  ($value := $!source.pull-one),
                  IterationEnd
                ),
                IterationEnd,                   # exhausted, stop
                nqp::if(
                  nqp::istype($value,Whatever), # start repeating
                  nqp::stmts(
                    ($!whatever = 1),
                    $!last
                  ),
                  ($!last := $value)            # keep value for repeat
                )
              )
            )
        }
        method is-deterministic(--> Bool:D) { $!source.is-deterministic }
    }
    method Whatever(\source) { WhateverIterator.new(source) }

    # Returns an iterator that takes a source iterator and a Callable.  It
    # passes on values from the source iterator while the Callable returns
    # a trueish value.  Once a falsish value is returned, the iterator ends.
    my class While does Iterator {
        has $!iter;
        has &!cond;

        method !SET-SELF(\iter, \cond) {
            $!iter := iter;
            &!cond := cond;
            self
        }
        method new(\iter,\cond) { nqp::create(self)!SET-SELF(iter,cond) }

        method pull-one() is raw {
            nqp::eqaddr((my \pulled := $!iter.pull-one),IterationEnd)
              || nqp::isfalse(&!cond(pulled))
              ?? IterationEnd
              !! pulled
        }
        method is-deterministic(--> Bool:D) { $!iter.is-deterministic }
        method sink-all(--> IterationEnd) { $!iter.sink-all }
    }
    method While(\iter, &cond) { While.new(iter, &cond) }

    # Returns an iterator that handles all properties of a -while- with
    # a condition.  Takes a Callable to be considered the body of the loop,
    # and a Callable for the condition.
    my class WhileLoop does Rakudo::SlippyIterator {
        has &!body;
        has &!cond;
        has $!label;

        method !SET-SELF(\body,\cond,\label) {
            nqp::bindattr(self,self.WHAT,'$!slipper',nqp::null);
            &!body := body;
            &!cond := cond;
            $!label := nqp::decont(label);
            self
        }
        method new(\body,\cond,\label) {
            nqp::create(self)!SET-SELF(body,cond,label)
        }

        method pull-one() {
            if nqp::not_i(nqp::isnull($!slipper)) && nqp::not_i(
                nqp::eqaddr((my $result := self.slip-one),IterationEnd)
            ) {
                $result
            }
            else {
                nqp::if(
                  &!cond(),
                  nqp::stmts(
                    nqp::until(
                      (my int $stopped),
                      nqp::stmts(
                        ($stopped = 1),
                        nqp::handle(
                          nqp::if(
                            nqp::istype(($result := &!body()),Slip),
                            ($stopped = nqp::eqaddr(
                              ($result := self.start-slip($result)),
                              IterationEnd
                            ) && nqp::if(&!cond(),0,1))
                          ),
                          'LABELED', $!label,
                          'NEXT', nqp::if(
                            nqp::eqaddr(
                              ($result := self.control-payload),
                              IterationEnd
                            ),
                            ($stopped = nqp::if(&!cond(),0,1))
                          ),
                          'REDO', ($stopped = 0),
                          'LAST', nqp::unless(
                            nqp::eqaddr(
                              ($result := self.control-payload),
                              IterationEnd
                            ),
                            (&!cond  := &always-False)  # end later
                          )
                        )
                      ),
                      :nohandler
                    ),
                    $result
                  ),
                  IterationEnd
                )
            }
        }

    }
    method WhileLoop(&body, &cond, $label) {
        WhileLoop.new(&body, &cond, $label)
    }

    # Return an iterator that will zip the given iterables (with &[,])
    # Basically the functionality of @a Z @b
    my class ZipIterables does Iterator {
        has $!iters;
        has int $!lazy;
        method !SET-SELF(\iters) {
            my \iterables := nqp::getattr(iters,List,'$!reified');
            my int $elems = nqp::elems(iterables);
            $!iters := nqp::setelems(nqp::list,$elems);
            $!lazy = 1;
            my int $i = -1;

            nqp::while(
              nqp::islt_i(++$i,$elems),
              nqp::bindpos($!iters,$i,
                nqp::if(
                  nqp::iscont(my \elem := nqp::atpos(iterables,$i)),
                  nqp::stmts(
                    ($!lazy = 0),
                    Rakudo::Iterator.OneValue(elem)
                  ),
                  nqp::stmts(
                    nqp::unless(elem.is-lazy,($!lazy = 0)),
                    Rakudo::Iterator.Whatever(elem.iterator)
                  )
                )
              )
            );
            self
        }
        method new(\iterables) { nqp::create(self)!SET-SELF(iterables) }
        method pull-one() {
            nqp::if(
              nqp::eqaddr($!iters,IterationEnd),
              IterationEnd,
              nqp::stmts(
                (my int $i = -1),
                (my int $elems = nqp::elems($!iters)),
                (my int $is_iterend = 0),
                (my \buf := nqp::setelems(nqp::create(IterationBuffer),$elems)),
                nqp::while(
                  nqp::islt_i(++$i,$elems),
                  nqp::if(
                    nqp::eqaddr(
                      (my \pulled := nqp::atpos($!iters,$i).pull-one), IterationEnd
                    ),
                    $is_iterend = 1,
                    nqp::bindpos(buf,$i,pulled)
                  )
                ),
                nqp::if(
                  $is_iterend,  # at least one exhausted
                  nqp::stmts(
                    ($!iters := IterationEnd),
                    IterationEnd
                  ),
                  buf.List
                )
              )
            )
        }
        method is-lazy() { nqp::hllbool($!lazy) }
    }
    method ZipIterables(@iterables) {
        nqp::isgt_i(@iterables.elems,0)  # reifies
          ?? ZipIterables.new(@iterables)
          !! Rakudo::Iterator.Empty
    }

    # Same as ZipIterablesOp, but takes a mapper Callable instead of
    # an op.  This is the underlying workhorse of ZipIterablesOp.
    my class ZipIterablesMap does Iterator {
        has $!iters;
        has $!mapper;
        has int $!lazy;
        method !SET-SELF(\iters,\mapper) {
            my \iterables := nqp::getattr(iters,List,'$!reified');
            my int $elems = nqp::elems(iterables);
            $!iters  := nqp::setelems(nqp::list,$elems);
            $!mapper := mapper;
            $!lazy = 1;
            my int $i = -1;

            nqp::while(
              nqp::islt_i(++$i,$elems),
              nqp::bindpos($!iters,$i,
                nqp::if(
                  nqp::iscont(my \elem := nqp::atpos(iterables,$i)),
                  nqp::stmts(
                    ($!lazy = 0),
                    Rakudo::Iterator.OneValue(elem)
                  ),
                  nqp::stmts(
                    nqp::unless(elem.is-lazy,($!lazy = 0)),
                    Rakudo::Iterator.Whatever(elem.iterator)
                  )
                )
              )
            );
            self
        }
        method new(\iters,\map) { nqp::create(self)!SET-SELF(iters,map) }
        method pull-one() {
            nqp::if(
              nqp::eqaddr($!iters,IterationEnd),
              IterationEnd,
              nqp::stmts(
                (my int $i = -1),
                (my int $elems = nqp::elems($!iters)),
                (my int $is_iterend = 0),
                (my $list :=
                  nqp::setelems(nqp::create(IterationBuffer),$elems)),
                nqp::while(
                  nqp::islt_i(++$i,$elems),
                  nqp::if(
                    nqp::eqaddr(
                      (my \pulled := nqp::atpos($!iters,$i).pull-one),
                      IterationEnd
                    ),
                    $is_iterend = 1,
                    nqp::bindpos($list,$i,pulled)
                  )
                ),
                nqp::if(
                  $is_iterend,  # at least one exhausted
                  ($!iters := IterationEnd),
                  $!mapper($list)
                )
              )
            )
        }
        method is-lazy() { nqp::hllbool($!lazy) }
    }
    method ZipIterablesMap(@iterables,&mapper) {
        nqp::isgt_i((my int $n = @iterables.elems),1)  # reifies
          ?? ZipIterablesMap.new(@iterables,&mapper)
          !! nqp::iseq_i($n,0)
            ?? Rakudo::Iterator.Empty
            !! nqp::atpos(nqp::getattr(@iterables,List,'$!reified'),0).iterator
    }

    # Return an iterator that will zip the given iterables and operator.
    # Basically the functionality of @a Z=> @b, with &[=>] being the op.
    method ZipIterablesOp(@iterables,\op) {
        nqp::eqaddr(op,&infix:<,>)
          ?? Rakudo::Iterator.ZipIterables(@iterables)
          !! Rakudo::Iterator.ZipIterablesMap(
               @iterables,
               Rakudo::Metaops.MapperForOp(op)
             )
    }
}

my class Rakudo::IterateOneWithPhasers does Rakudo::SlippyIterator {
    has &!block;
    has $!source;
    has $!label;
    has $!pulled;
    has $!NEXT;
    has $!LAST;

    method new(&block, Iterator:D $source, $label) {
        nqp::if(
          nqp::eqaddr((my $pulled := $source.pull-one),IterationEnd),
          Rakudo::Iterator.Empty,  # nothing to do
          nqp::stmts(              # iterate at least once
            (my $iter := nqp::create(self)),
            nqp::bindattr($iter,self,'$!slipper',nqp::null),
            nqp::bindattr($iter,self,'$!pulled',$pulled),
            nqp::if(       # set up FIRST phaser execution if needed
              &block.has-phaser('FIRST'),
              nqp::p6setfirstflag(nqp::getattr(&block, Code, '$!do'))
            ),
            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.callable_for_phaser('NEXT') // nqp::null),
            nqp::bindattr($iter,self,'$!LAST',
              &block.callable_for_phaser('LAST')),
            $iter
          )
        )
    }

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

    method pull-one() is raw {
        my $value := nqp::null;

        # handle slipping
        nqp::unless(
          nqp::isnull($!slipper),
          nqp::if(
            nqp::eqaddr(($value := self.slip-one),IterationEnd),
            ($value := nqp::null)
          )
        );

        nqp::while(
          nqp::isnull($value)
            && nqp::not_i(nqp::eqaddr($!pulled,IterationEnd)),
          nqp::handle(       # still something to do
            nqp::stmts(
              ($value   := &!block($!pulled)),
              ($!pulled := $!source.pull-one),
              nqp::unless(
                nqp::isnull($!NEXT),
                nqp::handle(
                  $!NEXT(),  # control ops inside NEXT phaser
                  'REDO', self!improper-control('redo', 'NEXT'),
                  'NEXT', nqp::null,
                  'LAST', ($!pulled := IterationEnd)
                )
              ),
              nqp::if(       # check for Slip
                nqp::istype($value,Slip)
                  && nqp::eqaddr(
                       ($value := self.start-slip($value)),
                       IterationEnd
                     ),
                ($value := nqp::null)  # nothing in the slip
              ),
            ),
            'LABELED', $!label,
            'REDO', nqp::null,   # a 'redo' in the block
            'NEXT', nqp::stmts(  # a 'next' in the block
              ($!pulled := $!source.pull-one),
              nqp::if(
                nqp::eqaddr(($value := self.control-payload),IterationEnd),
                ($value := nqp::null)
              ),
              nqp::unless(
                nqp::isnull($!NEXT),
                nqp::handle(
                  $!NEXT(),  # control ops inside a NEXT phaser
                  'REDO', self!improper-control('redo', 'NEXT'),
                  'NEXT', nqp::null,
                  'LAST', ($!pulled := IterationEnd)
                )
              )
            ),
            'LAST', nqp::if(  # a 'last' in the block
              nqp::eqaddr(($value := self.control-payload),IterationEnd),
              self!fire-any-LAST,
              ($!pulled := IterationEnd)  # done later
            )
          )
        );

        nqp::ifnull($value,self!fire-any-LAST)
    }

    method !improper-control(str $control, str $phaser --> Nil) {
        # XXX make it a proper Exception
        die "Cannot call '$control' inside a $phaser phaser";
    }

    # Fire any LAST phaser, making sure that any invalid control
    # exceptions will be cause an exception.  Returns IterationEnd
    # for convenience.
    method !fire-any-LAST(--> IterationEnd) {
        if $!LAST -> &LAST {
            nqp::handle(
              LAST(),
              'REDO', self!improper-control('redo', 'LAST'),
              'NEXT', self!improper-control('next', 'LAST'),
              'LAST', nqp::null  # ok to last inside a LAST phaser
            )
        }
    }

    method push-all(\target) {
        my $source := $!source;
        my &block  := &!block;
        my $label  := $!label;
        my $pulled := $!pulled;

        self.push-rest(target) unless nqp::isnull($!slipper);

        if $!NEXT -> &NEXT {
            nqp::until(
              nqp::eqaddr($pulled,IterationEnd),
              nqp::handle(
                nqp::stmts(
                  (my $value := block($pulled)),
                  nqp::if(
                    nqp::istype($value,Slip),
                    self.slip-all($value,target),
                    target.push($value)
                  ),
                  nqp::handle(
                    NEXT(),
                    'REDO', self!improper-control('redo', 'NEXT')
                  ),
                  ($pulled := $source.pull-one)
                ),
                'LABELED', $label,
                'NEXT', nqp::stmts(
                  self.push-control-payload(target),
                  ($pulled := $source.pull-one),
                  nqp::handle(
                    NEXT(),
                    'REDO', self!improper-control('redo', 'NEXT'),
                    'NEXT', nqp::null,
                    'LAST', ($pulled := self!fire-any-LAST)
                  )
                ),
                'REDO', nqp::null,
                'LAST', nqp::stmts(
                  self.push-control-payload(target),
                  ($pulled := self!fire-any-LAST)
                )
              ),
              :nohandler
            );
        }

        # no NEXT phaser
        else {
            nqp::until(
              nqp::eqaddr($pulled,IterationEnd),
              nqp::handle(
                nqp::stmts(
                  (my $value := block($pulled)),
                  nqp::if(
                    nqp::istype($value,Slip),
                    self.slip-all($value,target),
                    target.push($value)
                  ),
                  ($pulled := $source.pull-one)
                ),
                'LABELED', $label,
                'NEXT', nqp::stmts(
                  self.push-control-payload(target),
                  ($pulled := $source.pull-one)
                ),
                'REDO', nqp::null,
                'LAST', nqp::stmts(
                  self.push-control-payload(target),
                  ($pulled := IterationEnd)
                )
              ),
              :nohandler
            );
        }

        self!fire-any-LAST
    }

    method sink-all() {
        my $source := $!source;
        my &block  := &!block;
        my $label  := $!label;
        my $pulled := $!pulled;

        self.sink-rest unless nqp::isnull($!slipper);

        if $!NEXT -> &NEXT {
            nqp::until(
              nqp::eqaddr($pulled,IterationEnd),
              nqp::handle(
                nqp::stmts(
                  block($pulled),
                  nqp::handle(
                    NEXT(),
                    'REDO', self!improper-control('redo', 'NEXT')
                  ),
                  ($pulled := $source.pull-one)
                ),
                'LABELED', $label,
                'NEXT', nqp::stmts(
                  ($pulled := $source.pull-one),
                  nqp::handle(
                    NEXT(),
                    'REDO', self!improper-control('redo', 'NEXT'),
                    'NEXT', nqp::null,
                    'LAST', ($pulled := IterationEnd)
                  )
                ),
                'REDO', nqp::null,
                'LAST', ($pulled := IterationEnd)
              ),
              :nohandler
            );
        }

        # no NEXT phaser
        else {
            nqp::until(
              nqp::eqaddr($pulled,IterationEnd),
              nqp::handle(
                nqp::stmts(
                  block($pulled),
                  ($pulled := $source.pull-one)
                ),
                'LABELED', $label,
                'NEXT', ($pulled := $source.pull-one),
                'REDO', nqp::null,
                'LAST', ($pulled := IterationEnd)
              ),
              :nohandler
            );
        }

        self!fire-any-LAST
    }
}

my class Rakudo::IterateOneWithoutPhasers does Rakudo::SlippyIterator {
    has &!block;
    has $!source;
    has $!label;

    method new(&block, Iterator:D $source, $label) {
        my $iter := nqp::create(self);
        nqp::bindattr($iter, self, '$!slipper', nqp::null);
        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 $pulled := nqp::isnull($!slipper)
          || nqp::eqaddr((my $value := self.slip-one),IterationEnd)
          ?? $!source.pull-one
          !! IterationEnd;

        nqp::until(
          nqp::eqaddr($pulled,IterationEnd),
          nqp::handle(
            ($pulled := nqp::if(
              nqp::istype(($value := &!block($pulled)),Slip)
                && nqp::eqaddr(
                     ($value := self.start-slip($value)),
                     IterationEnd
                   ),
                $!source.pull-one,
                IterationEnd
            )),
            'LABELED', $!label,
            'NEXT', ($pulled := nqp::if(
              nqp::eqaddr(($value := self.control-payload),IterationEnd),
              $!source.pull-one,
              IterationEnd
            )),
            'REDO', nqp::null,
            'LAST', nqp::stmts(
              ($pulled := IterationEnd),
              nqp::unless(
                nqp::eqaddr(($value := self.control-payload),IterationEnd),
                ($!source := Rakudo::Iterator.Empty)   # also done later
              )
            )
          ),
          :nohandler
        );

        nqp::ifnull($value,IterationEnd)
    }

    method push-all(\target --> IterationEnd) {
        self.push-rest(target) unless nqp::isnull($!slipper);

        my $source := $!source;
        my $label  := $!label;
        my &block  := &!block;

        my $pulled := $source.pull-one;
        nqp::until(
          nqp::eqaddr($pulled,IterationEnd),
          nqp::handle(
            nqp::stmts(
              nqp::if(
                nqp::istype((my $value := &block($pulled)),Slip),
                self.slip-all($value, target),
                target.push($value)
              ),
              ($pulled := $source.pull-one),
            ),
            'LABELED', $label,
            'REDO', nqp::null,
            'NEXT', nqp::stmts(
              self.push-control-payload(target),
              ($pulled := $source.pull-one)
            ),
            'LAST', nqp::stmts(
              self.push-control-payload(target),
              ($pulled := IterationEnd)
            )
          ),
          :nohandler
        );
    }

    method sink-all(--> IterationEnd) {
        self.sink-rest unless nqp::isnull($!slipper);

        my $source := $!source;
        my $label  := $!label;
        my &block  := &!block;

        my $pulled := $source.pull-one;
        nqp::until(
          nqp::eqaddr($pulled,IterationEnd),
          nqp::handle(
            nqp::stmts(
              &!block($pulled),
              ($pulled := $source.pull-one)
            ),
            'LABELED', $label,
            'NEXT', ($pulled := $source.pull-one),
            'REDO', nqp::null,
            'LAST', ($pulled := IterationEnd)
          ),
          :nohandler
        );
    }
}

my class Rakudo::IterateTwoWithoutPhasers does Rakudo::SlippyIterator {
    has &!block;
    has $!source;
    has $!label;

    method new(&block, Iterator:D $source, $label) {
        my $iter := nqp::create(self);
        nqp::bindattr($iter, self, '$!slipper', nqp::null);
        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 {
        nqp::if(
          nqp::isnull(
            nqp::if(
              nqp::isnull($!slipper)
                || nqp::eqaddr((my $value := self.slip-one),IterationEnd),
              ($value := nqp::null),
              $value
            )
          ),
          nqp::unless(
            nqp::eqaddr((my $a := $!source.pull-one),IterationEnd),
            (my $b := $!source.pull-one)
          )
        );

        nqp::while(
          nqp::isnull($value)
            && nqp::not_i(nqp::eqaddr($a,IterationEnd)),
          nqp::if(
            nqp::eqaddr($b,IterationEnd),
            nqp::handle(  # iterator exhausted
              nqp::stmts(
                ($!source := Rakudo::Iterator.Empty),
                ($value := &!block($a)),
                nqp::if(
                  nqp::istype($value,Slip),
                  ($value := self.start-slip($value))
                )
              ),
              'LABELED', $!label,
              'NEXT', ($value := self.control-payload),
              'REDO', ($value := nqp::null),
              'LAST', ($value := self.control-payload)
            ),
            nqp::handle(  # iterator still good
              nqp::stmts(
                ($value := &!block($a, $b)),
                nqp::if(
                  nqp::istype($value,Slip) && nqp::eqaddr(
                    ($value := self.start-slip($value)),
                    IterationEnd
                  ),
                  nqp::stmts(             # set up next iteration
                    ($value := nqp::null),
                    nqp::unless(
                      nqp::eqaddr(($a := $!source.pull-one),IterationEnd),
                      ($b := $!source.pull-one)
                    )
                  )
                )
              ),
              'LABELED', $!label,
              'NEXT', nqp::if(
                nqp::eqaddr(($value := self.control-payload),IterationEnd),
                nqp::stmts(
                  ($value := nqp::null),  # set up next iteration
                  nqp::unless(
                    nqp::eqaddr(($a := $!source.pull-one),IterationEnd),
                    ($b := $!source.pull-one)
                  )
                )
              ),
              'REDO', nqp::null,
              'LAST', nqp::if(
                nqp::eqaddr(($value := self.control-payload),IterationEnd),
                ($value   := IterationEnd),           # end now
                ($!source := Rakudo::Iterator.Empty)  # end later
              )
            )
          )
        );

        nqp::ifnull($value,IterationEnd)
    }

    method push-all(\target --> IterationEnd) {
        self.push-rest(target) unless nqp::isnull($!slipper);

        my $source := $!source;
        my $label  := $!label;
        my &block  := &!block;

        nqp::unless(
          nqp::eqaddr((my $a := $source.pull-one),IterationEnd),
          (my $b := $source.pull-one)
        );

        nqp::until(
          nqp::eqaddr($a,IterationEnd),
          nqp::handle(
            nqp::stmts(
              nqp::if(
                nqp::eqaddr($b,IterationEnd),
                nqp::stmts(
                  (my $value := &block($a)),
                  ($a := IterationEnd)
                ),
                nqp::stmts(
                  ($value := &block($a, $b)),
                  nqp::unless(
                    nqp::eqaddr(($a := $source.pull-one),IterationEnd),
                    ($b := $source.pull-one)
                  )
                )
              ),
              nqp::if(
                nqp::istype($value,Slip),
                self.slip-all($value,target),
                target.push($value)
              )
            ),
            'LABELED', $label,
            'NEXT', nqp::stmts(
              self.push-control-payload(target),
              nqp::unless(
                nqp::eqaddr(($a := $source.pull-one),IterationEnd),
                ($b := $source.pull-one)
              )
            ),
            'REDO', nqp::null,
            'LAST', nqp::stmts(
              self.push-control-payload(target),
              ($a := IterationEnd)
            )
          ),
          :nohandler
        );
    }

    method sink-all(--> IterationEnd) {
        self.sink-rest unless nqp::isnull($!slipper);

        my $source := $!source;
        my $label  := $!label;
        my &block  := &!block;

        nqp::unless(
          nqp::eqaddr((my $a := $source.pull-one),IterationEnd),
          (my $b := $source.pull-one)
        );

        nqp::until(
          nqp::eqaddr($a,IterationEnd),
          nqp::handle(  # doesn't sink
            nqp::if(
              nqp::eqaddr($b,IterationEnd),
              nqp::stmts(
                &block($a),
                ($a := IterationEnd)
              ),
              nqp::stmts(
                &block($a, $b),
                nqp::unless(
                  nqp::eqaddr(($a := $source.pull-one),IterationEnd),
                  ($b := $source.pull-one)
                )
              )
            ),
            'LABELED', $label,
            'NEXT', nqp::unless(
              nqp::eqaddr(($a := $source.pull-one),IterationEnd),
              ($b := $source.pull-one)
            ),
            'REDO', nqp::null,
            'LAST', ($a := IterationEnd)
          ),
          :nohandler
        );
    }
}

my class Rakudo::IterateMoreWithoutPhasers does Rakudo::SlippyIterator {
    has &!block;
    has $!source;
    has $!label;
    has int $!count;

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

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

    method pull-one() is raw {
        nqp::if(
          nqp::isnull(
            nqp::if(
              nqp::isnull($!slipper)
                || nqp::eqaddr((my $value := self.slip-one),IterationEnd),
              ($value := nqp::null),
              $value
            )
          ),
          (my $pulled := $!source.pull-one)
        );

        my $params := nqp::list;
        nqp::while(
          nqp::isnull($value)
            && nqp::not_i(nqp::eqaddr($pulled,IterationEnd)),
          nqp::handle(
            nqp::stmts(
              nqp::unless(
                nqp::elems($params),
                nqp::push($params,$pulled)
              ),
              nqp::until(
                nqp::iseq_i(nqp::elems($params),$!count)
                  || nqp::eqaddr(($pulled := $!source.pull-one),IterationEnd),
                nqp::push($params,$pulled)
              ),
              ($value := nqp::p6invokeflat(&!block,$params)),
              nqp::if(
                nqp::istype($value,Slip) && nqp::eqaddr(
                  ($value := self.start-slip($value)),
                  IterationEnd
                ),
                nqp::stmts(                # set up next iteration
                  ($value  := nqp::null),
                  ($pulled := $!source.pull-one),
                  nqp::setelems($params,0)
                )
              )
            ),
            'LABELED', $!label,
            'NEXT', nqp::if(
              nqp::eqaddr(($value := self.control-payload),IterationEnd),
              nqp::stmts(                   # set up next iteration
                ($value  := nqp::null),
                ($pulled := $!source.pull-one),
                nqp::setelems($params,0)
              )
            ),
            'REDO', ($value := $pulled := nqp::null),
            'LAST', nqp::unless(
              nqp::eqaddr(($value := self.control-payload),IterationEnd),
              ($!source := Rakudo::Iterator.Empty)  # end later
            )
          )
        );

        nqp::ifnull($value,IterationEnd)
    }

    method push-all(\target --> IterationEnd) {
        self.push-rest(target) unless nqp::isnull($!slipper);

        my     $source := $!source;
        my     $label  := $!label;
        my     &block  := &!block;
        my int $count   = $!count;

        my $pulled := $source.pull-one;
        my $params := nqp::list;
        nqp::until(
          nqp::eqaddr($pulled,IterationEnd),
          nqp::handle(
            nqp::stmts(
              nqp::unless(
                nqp::elems($params),
                nqp::push($params,$pulled)
              ),
              nqp::until(
                nqp::iseq_i(nqp::elems($params),$count)
                  || nqp::eqaddr(($pulled := $source.pull-one),IterationEnd),
                nqp::push($params,$pulled)
              ),
              (my $value := nqp::p6invokeflat(&!block,$params)),
              nqp::if(
                nqp::istype($value,Slip),
                self.slip-all($value,target),
                target.push($value)
              ),
              ($pulled := $source.pull-one),
              nqp::setelems($params,0)
            ),
            'LABELED', $label,
            'NEXT', nqp::stmts(
              self.push-control-payload(target),
              ($pulled := $source.pull-one),
              nqp::setelems($params,0)
            ),
            'REDO', nqp::null,
            'LAST', nqp::stmts(
              self.push-control-payload(target),
              ($pulled := IterationEnd)
            )
          )
        );
    }

    method sink-all(--> IterationEnd) {
        self.sink-rest unless nqp::isnull($!slipper);

        my     $source := $!source;
        my     $label  := $!label;
        my     &block  := &!block;
        my int $count   = $!count;

        my $pulled := $source.pull-one;
        my $params := nqp::list;
        nqp::until(
          nqp::eqaddr($pulled,IterationEnd),
          nqp::handle(
            nqp::stmts(
              nqp::unless(
                nqp::elems($params),
                nqp::push($params,$pulled)
              ),
              nqp::until(
                nqp::iseq_i(nqp::elems($params),$count)
                  || nqp::eqaddr(($pulled := $source.pull-one),IterationEnd),
                nqp::push($params,$pulled)
              ),
              nqp::p6invokeflat(&block,$params),
              ($pulled := $source.pull-one),
              nqp::setelems($params,0)
            ),
            'LABELED', $label,
            'NEXT', nqp::stmts(
              ($pulled := $source.pull-one),
              nqp::setelems($params,0)
            ),
            'REDO', nqp::null,
            'LAST', ($pulled := IterationEnd)
          )
        );
    }
}

my class Rakudo::IterateMoreWithPhasers does Rakudo::SlippyIterator {
    has     &!block;
    has     $!source;
    has     $!count;
    has     $!label;
    has     $!value-buffer;
    has int $!did-init;
    has int $!did-iterate;
    has int $!CAN_FIRE_PHASERS;
    has     $!NEXT;

    method new(&block, Iterator:D $source, $count, $label) {
        my $iter := nqp::create(self);
        nqp::bindattr($iter, self, '$!slipper', nqp::null);
        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::isconcrete($!value-buffer)
          ?? nqp::setelems($!value-buffer,0)
          !! ($!value-buffer := nqp::create(IterationBuffer));

        my &block := &!block;
        my $result;

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

        if nqp::not_i(nqp::isnull($!slipper)) && 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 {
            my     $source := $!source;
            my     $label  := $!label;
            my     $NEXT   := $!NEXT;
            my int $count   = $!count;
            my int $redo    = 1;

            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::if(
                      nqp::isnull($result := nqp::getpayload(nqp::exception)),
                      nqp::stmts(
                        nqp::if(
                          nqp::eqaddr(
                            $source.push-exactly($!value-buffer, $count),
                            IterationEnd
                          ) && nqp::elems($!value-buffer) == 0,
                          ($result := IterationEnd),
                          ($redo = 1)
                        )
                      ),
                      nqp::if(                        # next with value
                        nqp::istype($result,Slip)
                          && nqp::eqaddr(             # it's a Slip
                               ($result := self.start-slip($result)),
                               IterationEnd
                             )
                          && nqp::not_i(nqp::eqaddr(  # an empty Slip
                               $source.push-exactly($!value-buffer, $!count),
                               IterationEnd
                             )),
                        ($redo = 1)                   # process these values
                      )
                    )
                  ),
                  'REDO', $redo = 1,
                  'LAST', nqp::stmts(
                    ($!did-iterate = 1),
                    nqp::if(
                      nqp::isnull($result := nqp::getpayload(nqp::exception)),
                      ($result := IterationEnd),
                      nqp::stmts(
                        nqp::if(
                          nqp::istype($result,Slip),
                          ($result := self.start-slip($result))
                        ),
                        ($!source := Rakudo::Iterator.Empty)
                      )
                    )
                  )
                )
              ),
            :nohandler);
        }
        &block.fire_if_phasers('LAST')
          if $!CAN_FIRE_PHASERS
          && $!did-iterate
          && nqp::eqaddr($result, IterationEnd);
        $result
    }
}

# vim: expandtab shiftwidth=4
