### ----------------------------------------------------
### -- Sequence::Generator
### -- Licenses: Artistic-2.0
### -- Authors: Elizabeth Mattijsen
### -- File: lib/Sequence/Generator.rakumod
### ----------------------------------------------------
### -- Chunk 1 of 2
# We need to be naughty for speed
use nqp;

class Sequence::Generator is repr('Uninstantiable') {

#- copy of Rakudo::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 new($iterator is raw --> Iterator:D) {
            if nqp::eqaddr((my $value := $iterator.pull-one),IterationEnd) {
                ().iterator
            }
            else {
                my $self := nqp::create(self);
                nqp::bindattr($self,$?CLASS,'$!iterator',$iterator);
                nqp::bindattr($self,$?CLASS,'$!value',   $value);
                $self
            }
        }
        method pull-one() is raw {
            my $this := $!value;
            nqp::eqaddr(($!value := $!iterator.pull-one),IterationEnd)
              ?? IterationEnd
              !! $this
        }
    }
    my class AllButLast does Iterator does AllButLastRole { }
    my class AllButLastPredictive does PredictiveIterator does AllButLastRole {
        method count-only() { ($!iterator.count-only || 1) - 1 }
        method bool-only()  {  $!iterator.count-only > 1       }
    }

    # Return an iterator that combines two iterators
    my class TwoIterators does Iterator {
        has $!active;
        has $!spare;

        method new($one is raw, $two is raw --> Iterator:D) {
            my $self := nqp::create(self);
            nqp::bindattr($self,TwoIterators,'$!active',$one);
            nqp::bindattr($self,TwoIterators,'$!spare', $two);
            $self
        }
        method pull-one() is raw {
            my $pulled := $!active.pull-one;
            if nqp::eqaddr($pulled,IterationEnd) {
                if nqp::isnull($!spare) {
                    IterationEnd
                }
                else {            # switching to spare
                    $!active := $!spare;
                    $!spare  := nqp::null;
                    $!active.pull-one
                }
            }
            else {
                $pulled
            }
        }
    }

    # Role for iterators that need to handle Slips
    my role Slipper does Iterator {
        has $!slipping;  # the slip we are iterating now

        proto method start-slip(|) {*}
        multi method start-slip(Slip:U $slip) {
            $slip
        }
        multi method start-slip(Slip:D $slip is raw) {
            nqp::if(
              nqp::eqaddr($slip,Empty),
              self.pull-one,                 # nothing, so recurse
              nqp::if(
                nqp::eqaddr(
                  (my $result := ($!slipping := $slip.iterator).pull-one),
                  IterationEnd
                ),
                nqp::stmts(                  # determined there is nothing
                  ($!slipping := nqp::null),
                  IterationEnd
                ),
                $result                       # started a Slip
              )
            )
        }

        method slip-one() {
            my $result := $!slipping.pull-one;
            $!slipping := nqp::null if nqp::eqaddr($result,IterationEnd);
            $result
        }
    }

#-- classes for creating actual iterators --------------------------------------

    my class UnendingValue does Iterator {
        has Mu $!value;
        method new(Mu $value is raw --> Iterator:D) {
            nqp::p6bindattrinvres(
              nqp::create(self),UnendingValue,'$!value',$value
            )
        }
        method pull-one() is raw { $!value }
        method skip-one(--> True) { }
        method sink-all(--> IterationEnd) { }
        method is-lazy(--> True) { }
    }

    # Unending iterator for constant numeric increment/decrement
    class UnendingStep does Iterator {
        has $!value;
        has $!step;
        method new($first is raw, $step is raw --> Iterator:D) {
            my $self := nqp::create(self);
            nqp::bindattr($self,UnendingStep,'$!value',$first);
            nqp::bindattr($self,UnendingStep,'$!step', $step);
            $self
        }
        method pull-one() is raw {
            my $current := $!value;
            $!value := $current + $!step;
            $current
        }
        method is-lazy(--> True) { }
    }

    # Unending iterator for constant numeric multiplication
    class UnendingMult does Iterator {
        has $!value;
        has $!mult;
        method new($first is raw, $mult is raw --> Iterator:D) {
            my $self := nqp::create(self);
            nqp::bindattr($self,UnendingMult,'$!value',$first);
            nqp::bindattr($self,UnendingMult,'$!mult', $mult);
            $self
        }
        method pull-one() is raw {
            my $current := $!value;
            $!value := $current * $!mult;
            $current
        }
        method is-lazy(--> True) { }
    }

    # Iterator for constant numeric negative increment to given numeric
    class StepDownto does PredictiveIterator {
        has $!value;
        has $!step;
        has $!downto;

        method new($first is raw, $step is raw, $downto is raw --> Iterator:D) {
            my $self := nqp::create(self);
            nqp::bindattr($self,StepDownto,'$!value', $first);
            nqp::bindattr($self,StepDownto,'$!step',  $step);
            nqp::bindattr($self,StepDownto,'$!downto',$downto);
            $self
        }
        method pull-one() is raw {
            if (my $current := $!value) < $!downto {
                IterationEnd
            }
            else {
                $!value := $current + $!step;
                $current
            }
        }
        method count-only(--> Int:D) { (($!downto - $!value) / $!step).Int }
        method bool-only(--> Bool:D) { $!value + $!step >= $!downto }
    }

    # Iterator for constant numeric multiplier < 1
    class MultDownto does Iterator {
        has $!value;
        has $!mult;
        has $!downto;

        method new($first is raw, $mult is raw, $downto is raw --> Iterator:D) {
            my $self := nqp::create(self);
            nqp::bindattr($self,MultDownto,'$!value', $first);
            nqp::bindattr($self,MultDownto,'$!mult',  $mult);
            nqp::bindattr($self,MultDownto,'$!downto',$downto);
            $self
        }
        method pull-one() is raw {
            if (my $current := $!value) < $!downto {
                IterationEnd
            }
            else {
                $!value := $current * $!mult;
                $current
            }
        }
    }

    # Iterator for constant numeric increment upto given numeric
    class StepUpto does PredictiveIterator {
        has $!value;
        has $!step;
        has $!upto;

        method new($first is raw, $step is raw, $upto is raw --> Iterator:D) {
            my $self := nqp::create(self);
            nqp::bindattr($self,StepUpto,'$!value',$first);
            nqp::bindattr($self,StepUpto,'$!step', $step);
            nqp::bindattr($self,StepUpto,'$!upto', $upto);
            $self
        }
        method pull-one() is raw {
            if (my $current := $!value) > $!upto {
                IterationEnd
            }
            else {
                $!value := $current + $!step;
                $current
            }
        }
        method count-only(--> Int:D) { (($!upto - $!value) / $!step).Int }
        method bool-only(--> Bool:D) { $!value + $!step <= $!upto }
        method is-monotonically-increasing(--> True) { }
    }

    # Iterator for constant numeric multiplier > 1
    class MultUpto does Iterator {
        has $!value;
        has $!mult;
        has $!upto;

        method new($first is raw, $mult is raw, $upto is raw --> Iterator:D) {
            my $self := nqp::create(self);
            nqp::bindattr($self,MultUpto,'$!value',$first);
            nqp::bindattr($self,MultUpto,'$!mult', $mult);
            nqp::bindattr($self,MultUpto,'$!upto', $upto);
            $self
        }
        method pull-one() is raw {
            if (my $current := $!value) > $!upto {
                IterationEnd
            }
            else {
                $!value := $current * $!mult;
                $current
            }
        }
        method is-monotonically-increasing(--> True) { }
    }

    # Iterator for stepping for a single codepoint
    class StepCodepoint does PredictiveIterator {
        has int $!codepoint;
        has int $!step;
        has int $!todo;

        method !SET-SELF(
          int $first, int $last, int $no-first, int $no-last
        --> Iterator:D) {
            if $last > $first {
                $!step      = 1;
                $!codepoint = $first - 1 + $no-first;
                $!todo      = $last - $first + 2 - $no-first - $no-last;
                self
            }
            elsif $first > $last {
                $!step      = -1;
                $!codepoint = $first + 1 - $no-first;
                $!todo      = $first - $last + 2 - $no-first - $no-last;
                self
            }
            else {
                $no-first || $no-last
                  ?? ().iterator
                  !! nqp::chr($first).iterator
            }
        }
        method new(str $first, str $last, int $no-first, int $no-last) {
            nqp::create(self)!SET-SELF(
              nqp::ord($first), nqp::ord($last), $no-first, $no-last
            )
        }
        method pull-one() is raw {
            --$!todo
              ?? nqp::chr($!codepoint = nqp::add_i($!codepoint,$!step))
              !! IterationEnd
        }
        method push-all($target is raw --> IterationEnd) {
            my int $todo = $!todo;
            my int $cp   = $!codepoint;
            my int $step = $!step;

            nqp::if(
              $todo,
              nqp::while(
                --$todo,
                $target.push(nqp::chr($cp = nqp::add_i($cp,$step)))
              )
            );
            $!todo = $todo;  # make sure count/bool-only are correct
        }
        method count-only() { $!todo }
        method bool-only() { nqp::hllbool($!todo) }
    }

    # Class to return an iterator for 2 strings of equal length in chars
    # as well as codepoints.  Each string is split into graphemes, and
    # associated graphemes are built into list_s of all the graphemes
    # basically $a.comb Z.. $b.comb.  The list_s of the last grapheme of
    # each string is then separated and the remaining list_s are equipped
    # with indices.  The result list_s is seeded with the first element
    # of each of the list_s.  Pulling a value will increment the pointer
    # for the last grapheme: if it is out of range, then the next list_s
    # index will be incremented, the result seeded and the pointer for the
    # last grapheme reset.  Until all combinations have been produced.
    class StepMultipleCodepoints does Iterator {
        has int $!i;    # current index
        has $!lists;    # list of list_s with chars (except most iterated)
        has $!indices;  # list_i of indices of $!lists
        has $!lowest;   # lowest list_s with chars (most iterated)
        has $!result;   # list_s of result chars to be joined

        method !SET-SELF(
          str $first, str $last, int $no-first, int $no-last
        --> Iterator:D) {

            # set up source lists
            my $lists  := nqp::create(IterationBuffer);
            my $result := nqp::list_s;
            my int $i  = -1;
            nqp::while(
              nqp::islt_i(++$i,nqp::chars($first)),
              nqp::stmts(
                (my $chars :=
                  cps2list_s(nqp::ordat($first,$i),nqp::ordat($last,$i))),
                nqp::push($lists,$chars),
                nqp::push_s($result,nqp::atpos_s($chars,0))
              )
            );

            # set up attributes
            $!i = -1;
            $!lowest  := nqp::pop($lists);
            $!indices := nqp::setelems(nqp::list_i(),nqp::elems($lists));
            $!lists   := $lists;
            $!result  := $result;

            # set up iterator wrt to endpoints
            self.skip-one if $no-first;
            $no-last ?? all-but-last(self) !! self
        }
        method new(str $first, str $last, int $no-first, int $no-last) {
            nqp::create(self)!SET-SELF($first, $last, $no-first, $no-last)
        }

        method pull-one() is raw {
            nqp::if(
              nqp::islt_i(++$!i,nqp::elems($!lowest)),
              nqp::stmts(
                nqp::bindpos_s($!result,-1,nqp::atpos_s($!lowest,$!i)),
                nqp::join('',$!result)
              ),
              self!lowest-has-run-out
            )
        }

        method !lowest-has-run-out() {

            # reset lowest char and index
            nqp::bindpos_s($!result,-1,nqp::atpos_s($!lowest,($!i = 0)));

            # loop for higher lists
            my int $index = nqp::elems($!indices);
            nqp::while(
              nqp::isge_i(--$index,0),
              nqp::if(         # still lists to check
                nqp::islt_i(
                  (my int $j = nqp::bindpos_i($!indices,$index,
                    nqp::add_i(nqp::atpos_i($!indices,$index),1)
                  )),
                  nqp::elems(nqp::atpos($!lists,$index))
                ),
                nqp::stmts(    # still available in this list, return that
                  nqp::bindpos_s(
                    $!result,$index,nqp::atpos_s(nqp::atpos($!lists,$index),$j)
                  ),
                  (return nqp::join('',$!result))
                ),
                nqp::stmts(    # not available in this list, move to next one
                  nqp::bindpos_s(
                    $!result,$index,nqp::atpos_s(nqp::atpos($!lists,$index),0)
                  ),
                  nqp::bindpos_i($!indices,$index,0)
                )
              )
            );

            IterationEnd
        }
    }

    # Unending iterator calling .succ
    class UnendingSucc does Iterator {
        has $!value;
        method new(\first) {
            nqp::p6bindattrinvres(nqp::create(self),self,'$!value',first)
        }
        method pull-one() is raw {
            $!value := (my $this := $!value).succ;
            $this
        }
        method is-lazy(--> True) { }
    }

    # Unending iterator calling .pred, stopping when exhausted
    class UnendingPred does Iterator {
        has $!value;
        method new(\first) {
            nqp::p6bindattrinvres(nqp::create(self),self,'$!value',first)
        }
        method pull-one() is raw {
            nqp::unless(
              nqp::eqaddr((my $this := $!value),IterationEnd),
              nqp::if(
                nqp::istype(($!value := $this.pred),Failure),
                nqp::stmts(
                  $!value.Bool,  # mark Failure as handled
                  ($!value := IterationEnd)
                )
              )
            );
            $this
        }
        method is-lazy(--> True) { }
    }

#-- iterators taking one or more lambdas ---------------------------------------

    # Iterator calling a lambda without any parameters
    class LambdaNone does Slipper {
        has $!producer; # lambda to produce value

        method new($seed, $producer, int $no-last) {
            my $self := nqp::create(self);
            nqp::bindattr($self,LambdaNone,'$!slipping',$seed.iterator);
            nqp::bindattr($self,LambdaNone,'$!producer',$producer);
            $no-last ?? all-but-last($self) !! $self
        }
        method pull-one() is raw {
            my $result;

            if nqp::isnull($!slipping) {
                nqp::handle(                # not slipping
                  ($result := $!producer()),
                  'LAST', (return IterationEnd)
                );
                nqp::istype($result,Slip)
                  ?? self.start-slip($result)
                  !! $result
            }
            else {                          # slipping
                nqp::eqaddr(($result := self.slip-one),IterationEnd)
                  ?? self.pull-one          # recurse to handle potential Slip
                  !! $result
            }
        }
        method is-lazy(--> True) is raw { }
    }

    # Iterator calling a lambda without any parameters with ACCEPTS endpoint
    class LambdaNoneAccepts does Slipper {
        has $!producer;    # lambda to produce value, null to mark ended
        has $!endpoint;    # value to call ACCEPTS on with produce value
        has int $!no-last; # flag to indicate skipping last produced value

        method new($seed, $producer, $endpoint, int $no-last) {
            my $self := nqp::create(self);
            nqp::bindattr($self,LambdaNoneAccepts,'$!slipping',$seed.iterator);
            nqp::bindattr($self,LambdaNoneAccepts,'$!producer',$producer);
            nqp::bindattr($self,LambdaNoneAccepts,'$!endpoint',$endpoint);
            nqp::bindattr_i($self,LambdaNoneAccepts,'$!no-last', $no-last);
            $self
        }
        method pull-one() is raw {
            my $result;

            # not slipping
            if nqp::isnull($!slipping) {
                nqp::handle(
                  ($result := nqp::ifnull($!producer,(return IterationEnd))()),
                  'LAST', (return IterationEnd)
                );
                $result := self.start-slip($result)
                  if nqp::istype($result,Slip);

                if $!endpoint.ACCEPTS($result) {
                    $!no-last  # do not bother to produce last value?
                      ?? ($result    := IterationEnd)
                      !! ($!producer := nqp::null)
                }
            }

            # slipping
            elsif nqp::eqaddr(($result := self.slip-one),IterationEnd) {
                self.pull-one  # recurse to handle potential Slip
            }

            $result
        }
    }

    # Iterator calling a lambda with 1 parameter
    class Lambda1 does Slipper {
        has $!value;    # value to be passed to lambda to produce next value
        has $!producer; # lambda to produce value

        method new($seed, $producer, int $no-last) {
            my $self := nqp::create(self);
            nqp::bindattr($self,Lambda1,'$!slipping',$seed.iterator);
            nqp::bindattr($self,Lambda1,'$!producer',$producer);
            $no-last ?? all-but-last($self) !! $self
        }

        method pull-one() is raw {
            my $result;

            $!value := do if nqp::isnull($!slipping) {
                CATCH {
                    return IterationEnd  # ignore .pred failing
                      if .message eq 'Decrement out of range';
                }
                # not slipping
                nqp::handle(
                  ($result := $!producer($!value)),
                  'LAST', (return IterationEnd)
                );

                $result := nqp::istype($result,Slip)
                  ?? self.start-slip($result)
                  !! $result
            }
            else {
                # slipping
                nqp::eqaddr(($result := self.slip-one),IterationEnd)
                  ?? (return self.pull-one)  # recurse to handle potential Slip
                  !! $result
            }
        }
        method is-lazy(--> True) is raw { }
    }

    # Iterator calling a lambda with 1 parameter with ACCEPTS endpoint
    class Lambda1Accepts does Slipper {
        has $!value;       # value to be passed to lambda to produce next value
        has $!producer;    # lambda to produce value
        has $!endpoint;    # value to call ACCEPTS on with produce value
        has int $!no-last; # flag to indicate skipping last produced value

        method new($seed, $producer, $endpoint, int $no-last) {
            my $self := nqp::create(self);
            nqp::bindattr($self,Lambda1Accepts,'$!slipping',$seed.iterator);
            nqp::bindattr($self,Lambda1Accepts,'$!producer',$producer);
            nqp::bindattr($self,Lambda1Accepts,'$!endpoint',$endpoint);
            nqp::bindattr_i($self,Lambda1Accepts,'$!no-last',$no-last);
            $self
        }
        method pull-one() is raw {
            my $result;

            if nqp::isnull($!slipping) {
                nqp::handle(
                  ($result := nqp::ifnull(
                    $!producer,(return IterationEnd)
                  )($!value)),
                  'LAST', (return IterationEnd)
                );
                $result := self.start-slip($result)
                  if nqp::istype($result,Slip);
            }
            elsif nqp::eqaddr(($result := self.slip-one),IterationEnd) {
                return self.pull-one;  # recurse to handle potential Slip
            }

            if $!endpoint.ACCEPTS($result) {
                if $!no-last {
                    IterationEnd       # do not bother to produce last value
                }
                else {                 # after this we're done
                    $!producer := nqp::null;
                    $result
                }
            }
            else {
                $!value := $result
            }
        }
    }

    # Iterator calling a lambda with 2 parameters
    class Lambda2 does Slipper {
        has $!value1;   # first value to be passed to produce next value
        has $!value2;   # second value to be passed to produce next value
        has $!producer; # lambda to produce value

        method new($seed, $producer, int $no-last) {
            my $self := nqp::create(self);
            nqp::bindattr($self,Lambda2,'$!slipping',$seed.iterator);
            nqp::bindattr($self,Lambda2,'$!producer',$producer);
            $no-last ?? all-but-last($self) !! $self
        }

        method pull-one() is raw {
            my $result;

            # not slipping
            if nqp::isnull($!slipping) {
                nqp::handle(
                  ($result := $!producer($!value1,$!value2)),
                  'LAST', (return IterationEnd)
                );
                $result := self.start-slip($result)
                  if nqp::istype($result,Slip);
            }

            # slipping
            elsif nqp::eqaddr(($result := self.slip-one),IterationEnd) {
                return self.pull-one;   # recurse to handle potential Slip
            }

            $!value1 := $!value2;
            $!value2 := $result;
        }
        method is-lazy(--> True) is raw { }
    }

    # Iterator calling a lambda with 2 parameters with ACCEPTS endpoint
    class Lambda2Accepts does Slipper {
        has $!value1;      # first value to be passed to produce next value
        has $!value2;      # second value to be passed to produce next value
        has $!producer;    # lambda to produce value
        has $!endpoint;    # value to call ACCEPTS on with produce value
        has int $!no-last; # flag to indicate skipping last produced value

        method new($seed, $producer, $endpoint, int $no-last) {
            my $self := nqp::create(self);
            nqp::bindattr($self,Lambda2Accepts,'$!slipping',$seed.iterator);
            nqp::bindattr($self,Lambda2Accepts,'$!producer',$producer);
            nqp::bindattr($self,Lambda2Accepts,'$!endpoint',$endpoint);
            nqp::bindattr_i($self,Lambda2Accepts,'$!no-last',$no-last);
            $self
        }

        method pull-one() is raw {
            my $result;

            # not slipping
            if nqp::isnull($!slipping) {
                nqp::handle(
                  ($result := nqp::ifnull(
                    $!producer,(return IterationEnd)
                  )($!value1,$!value2)),
                  'LAST', (return IterationEnd)
                );
                $result := self.start-slip($result)
                  if nqp::istype($result,Slip);
            }

            # slipping
            elsif nqp::eqaddr(($result := self.slip-one),IterationEnd) {
                return self.pull-one;  # recurse to handle potential Slip
            }

            if $!endpoint.ACCEPTS($result) {
                if $!no-last {
                    IterationEnd       # do not bother to produce last value
                }
                else {                 # after this we're done
                    $!producer := nqp::null;
                    $result
                }
            }
            else {
                $!value1 := $!value2;
                $!value2 := $result
            }
        }
    }

    # Iterator calling a lambda with last N values
    class LambdaN does Slipper {
        has $!producer;  # lambda to produce value
        has $!values;    # IterationBuffer with values to be passed to producer
        has $!list;      # HLL wrapper around $!values

        method new($seed, $producer, int $no-last, int $elems) {
            my $self := nqp::create(self);
            nqp::bindattr($self,LambdaN,'$!slipping',$seed.iterator);
            nqp::bindattr($self,LambdaN,'$!producer',$producer);
            nqp::bindattr($self,LambdaN,'$!list',
              nqp::bindattr($self,LambdaN,'$!values',
                set-buffer-size(nqp::clone($seed),$elems)
              ).List
            );
            $no-last ?? all-but-last($self) !! $self
        }

        method pull-one() is raw {
            my $result;

            # not slipping
            if nqp::isnull($!slipping) {
                nqp::handle(
                  ($result := $!producer(|$!list)),
                  'LAST', (return IterationEnd)
                );
                $result := self.start-slip($result)
                  if nqp::istype($result,Slip);
            }

            # slipping
            elsif nqp::eqaddr(($result := self.slip-one),IterationEnd) {
                return self.pull-one;  # recurse to handle potential Slip
            }

            nqp::shift($!values);
            nqp::push($!values,$result)
        }
        method is-lazy(--> True) is raw { }
    }

    # Iterator calling a lambda with last N values with ACCEPTS endpoint
    class LambdaNAccepts does Slipper {
        has $!producer;    # lambda to produce value
        has $!endpoint;    # value to call ACCEPTS on with produce value
        has int $!no-last; # flag to indicate skipping last produced value
        has $!values;      # IterationBuffer with values to pass to producer
        has $!list;        # HLL wrapper around $!values

        method new($seed, $producer, $endpoint, int $no-last, int $elems) {
            my $self := nqp::create(self);
            nqp::bindattr($self,LambdaNAccepts,'$!slipping',$seed.iterator);
            nqp::bindattr($self,LambdaNAccepts,'$!producer',$producer);
            nqp::bindattr($self,LambdaNAccepts,'$!endpoint',$endpoint);
            nqp::bindattr_i($self,LambdaNAccepts,'$!no-last',$no-last);
            nqp::bindattr($self,LambdaNAccepts,'$!list',
              nqp::bindattr($self,LambdaNAccepts,'$!values',
                set-buffer-size(nqp::clone($seed),$elems)
              ).List
            );
            $self
        }

        method pull-one() is raw {
            my $result;

            # not slipping
            if nqp::isnull($!slipping) {
                nqp::handle(
                  ($result := nqp::ifnull(
                    $!producer,(return IterationEnd)
                  )(|$!list)),
                  'LAST', (return IterationEnd)
                );
                $result := self.start-slip($result)
                  if nqp::istype($result,Slip);
            }

            # slipping
            elsif nqp::eqaddr(($result := self.slip-one),IterationEnd) {
                return self.pull-one;  # recurse to handle potential Slip
            }

            if $!endpoint.ACCEPTS($result) {
                if $!no-last {
                    IterationEnd       # do not bother to produce last value
                }
                else {                 # after this we're done
                    $!producer := nqp::null;
                    $result
                }
            }
            else {
                nqp::shift($!values);
                nqp::push($!values,$result)
            }
        }
    }

    # Iterator calling a lambda with *all* values
    class LambdaAll does Slipper {
        has $!producer;  # lambda to produce value
        has $!values;    # IterationBuffer with values to be passed to producer
        has $!list;      # HLL wrapper around $!values

        method new($seed, $producer, int $no-last) {
            my $self := nqp::create(self);
            nqp::bindattr($self,LambdaAll,'$!slipping',$seed.iterator);
            nqp::bindattr($self,LambdaAll,'$!producer',$producer);
            nqp::bindattr($self,LambdaAll,'$!list',
              nqp::bindattr(
                $self,LambdaAll,'$!values',nqp::create(IterationBuffer)
              ).List
            );
            $no-last ?? all-but-last($self) !! $self
        }

        method pull-one() is raw {
            my $result;

            # not slipping
            if nqp::isnull($!slipping) {
                nqp::push(
                  $!values,
                  nqp::stmts(
                    nqp::handle(
                      ($result := $!producer(|$!list)),
                      'LAST', (return IterationEnd)
                    ),
                    nqp::if(
                      nqp::istype($result,Slip),
                      self.start-slip($result),
                      $result
                    )
                  )
                );
            }

            # slipping
            else {
                nqp::eqaddr(($result := self.slip-one),IterationEnd)
                  ?? self.pull-one  # recurse to handle potential Slip
                  !! nqp::push($!values,$result)
            }
        }
        method is-lazy(--> True) is raw { }
    }

    # Iterator calling a lambda with *all* values with ACCEPTS endpoint
    class LambdaAllAccepts does Slipper {
        has $!producer;    # lambda to produce value
        has $!endpoint;    # value to call ACCEPTS on with produce value
        has int $!no-last; # flag to indicate skipping last produced value
        has $!values;      # IterationBuffer with values to pass to producer
        has $!list;        # HLL wrapper around $!values

        method new($seed, $producer, $endpoint, int $no-last) {
            my $self := nqp::create(self);
            nqp::bindattr($self,LambdaAllAccepts,'$!slipping',$seed.iterator);
            nqp::bindattr($self,LambdaAllAccepts,'$!producer',$producer);
            nqp::bindattr($self,LambdaAllAccepts,'$!endpoint',$endpoint);
            nqp::bindattr_i($self,LambdaAllAccepts,'$!no-last',$no-last);
            nqp::bindattr($self,LambdaAllAccepts,'$!list',
              nqp::bindattr(
                $self,LambdaAllAccepts,'$!values',nqp::create(IterationBuffer)
              ).List
            );
            $self
        }

        method pull-one() is raw {
            my $result;

            # not slipping
            if nqp::isnull($!slipping) {
                nqp::handle(
                  ($result := nqp::ifnull(
                    $!producer,(return IterationEnd)
                  )(|$!list)),
                  'LAST', (return IterationEnd)
                );
                $result := self.start-slip($result)
                  if nqp::istype($result,Slip);
            }

            # slipping
            elsif nqp::eqaddr(($result := self.slip-one),IterationEnd) {
                return self.pull-one;  # recurse to handle potential Slip
            }

            if $!endpoint.ACCEPTS($result) {
                if $!no-last {
                    IterationEnd       # do not bother to produce last value
                }
                else {                 # after this we're done
                    $!producer := nqp::null;
                    $result
                }
            }
            else {
                nqp::push($!values,$result)
            }
        }
    }

#-- the actual iterator dispatch -----------------------------------------------
    proto method iterator(|) {*}

    # Iterator for 2 numeric endpoints
    multi method iterator(
      Real:D $first, Real:D $endpoint;; int :$no-first, int :$no-last
    --> Iterator:D) {
        my $iterator := $endpoint < $first
          ?? $endpoint == -Inf
            ?? (return UnendingStep.new($first - $no-first, -1))
            !! StepDownto.new($first - $no-first, -1, $endpoint)
          !! $endpoint == Inf
            ?? (return UnendingStep.new($first + $no-first, +1))
            !! StepUpto.new($first + $no-first, +1, $endpoint);

        $no-last ?? all-but-last($iterator) !! $iterator
    }

    # Iterator for numeric and an Iterable endpoint
    multi method iterator(
      Real:D $first, Iterable:D $endpoint;; int :$no-first, int :$no-last
    --> Iterator:D) {
        my $rhs-iterator := $endpoint.iterator;
        my $last         := $rhs-iterator.pull-one;
        $last := Inf if nqp::istype($last,Whatever);

        TwoIterators.new(
          self.iterator($first, $last, :$no-first, :$no-last),
          $rhs-iterator
       