# The Iterator role defines the API for an iterator and provides simple # fallback implementations for most of it, so any given iterator can pick # and choose what bits it can implement better for performance and/or # correctness reasons. my role Iterator { # Pulls one value from the iterator. If there's nothing more to pull, # returns the constant IterationEnd. If you don't override any other # methods in this role, they'll all end up falling back to using this. method pull-one() { ... } # Skip one value from the iterator. Should return a true-like value to # indicate the skip was successful. Override this method if you can # make an iterator that has significantly less to do when skipping a # generated value. method skip-one() { nqp::not_i(nqp::eqaddr(self.pull-one,IterationEnd)) } # Has the iterator produce a certain number of values and push them into # the target. The only time the iterator may push less values than asked # for is when it reaches the end of the iteration. It may never push more # values than are requested. Iterators that can do something smarter than # the default implementation here should override this method. Should # return how many things were pushed. Note that if the iterator does any # side-effects as a result of producing values then up to $n of them will # occur; you must be sure this is desired. Returns the number of things # pushed, or IterationEnd if it reached the end of the iteration. method push-exactly(\target, int $n) { my int $todo = nqp::add_i($n,1); nqp::until( # doesn't sink nqp::not_i(--$todo) || nqp::eqaddr((my $pulled := self.pull-one),IterationEnd), target.push($pulled) # don't .sink $pulled here, it can be a Seq ); nqp::eqaddr($pulled,IterationEnd) ?? IterationEnd !! $n } # Has the iteration push at least a certain number of values into the # target buffer. For iterators that do side-effects, this should always # be the same as push-exactly. Those that know they can safely work ahead # to achieve better throughput may do so. Returns the number of things # pushed, or IterationEnd if it reached the end of the iteration. method push-at-least(\target, int $n) { self.push-exactly(target, $n) } # Has the iterator produce all of its values into the target. Typically # called in .STORE if the iterator is non-lazy. Returns IterationEnd. method push-all(\target --> IterationEnd) { nqp::until( # we may not .sink $pulled here, since it can be a Seq nqp::eqaddr((my \pulled := self.pull-one),IterationEnd), target.push(pulled) ) } # Pushes things until we hit a lazy iterator (one whose is-lazy method # returns True). The default works well for non-composite iterators (that # is, those that don't trigger the evaluation of other iterators): it # looks at the lazy property of itself, and if it's true, does nothing, # otherwise it calls push-all. If all values the iterator can produce are # pushed, then IterationEnd should be returned. Otherwise, return # something else (Mu will do fine). method push-until-lazy(\target) { nqp::unless( self.is-lazy, self.push-all(target) ) } # Skip the given number of values. Return true if succesful in # skipping that many values. method skip-at-least(int $toskip) { my int $left = $toskip; nqp::while( nqp::isge_i(--$left,0) && self.skip-one, nqp::null ); nqp::islt_i($left,0) } # Skip the given number of values produced before returning the next # pulled value. Given 0 it is an expensive way to do .pull-one method skip-at-least-pull-one(int $toskip) { self.skip-at-least($toskip) ?? self.pull-one !! IterationEnd } # Consumes all of the values in the iterator for their side-effects only. # May be overridden by iterators to either warn about use of things in # sink context that should not be used that way, or to process things in # a more efficient way when we know we don't need the results. method sink-all(--> IterationEnd) { nqp::while( self.skip-one, nqp::null ) } # Whether the iterator is lazy (True if yes, False if no). # If True, the iterator must *never* try to evaluate more than the # user absolutely asks for. This has e.g. effect on the behaviour # on .STORE: a lazy iterator would not reify, a non-lazy would. method is-lazy(--> False) { } # Whether the iterator will produce values in a deterministic way (always # the same for a given data source). This is True for most iterators, # but *not* true for iterators that typically return keys and/or values # from a hash. method is-deterministic(--> True) { } # Whether the iterator will produce values in a monotonically increasing # manner, when being compared with infix cmp. method is-monotonically-increasing(--> False) { } } # The PredictiveIterator role is a refinement of the Iterator role for those # cases when the number of values to be generated (still) can be determined # *without* actually generating those values. my role PredictiveIterator does Iterator { # The "count-only" method in a PredictiveIterator class returns the number # of elements that the iterator would still be able to generate but # *without* actually generating any values. This can e.g. be the case # when an iterator for all the characters in a string, of which the number # elements is already known and the number of values generated as well. method count-only(--> Int:D) { ... } # The "bool-only" method in a PredictiveIterator class returns a Bool # to indicate whether the generator is (still) able to generate at least # one value, *without* actually generating that value. method bool-only(--> Bool:D) { self.count-only.Bool } # Since PredictiveIterators are not supposed to be lazy, we can skip # the step checking for laziness. method push-until-lazy(\target) { self.push-all(target) } } # vim: expandtab shiftwidth=4