# A List is a (potentially infinite) immutable list. The immutability is not # deep; a List may contain Scalar containers that can be assigned to. However, # it is not possible to shift/unshift/push/pop/splice/bind. A List is also # Positional, and so may be indexed. my class Array { ... } my class List does Iterable does Positional { # declared in BOOTSTRAP # class List is Cool # The reified elements in the list so far (that is, those that we already # have produced the values for). # has $!reified; # # Object that reifies the rest of the list. We don't just inline it into # the List class itself, because a STORE on Array can clear things and # upset an ongoing iteration. (An easy way to create such a case is to # assign an array with lazy parts into itself.) # has $!todo; # The object that goes into $!todo. class Reifier { # Our copy of the reified elements in the list so far. has $!reified; # The current iterator, if any, that we're working our way through in # order to lazily reify values. Must be depleted before $!future is # considered. has Iterator $!current-iter; # The (possibly lazy) values we've not yet incorporated into the list. The # only thing we can't simply copy from $!future into $!reified is a Slip # (and so the only reason to have a $!future is that there is at least one # Slip). has $!future; # The reification target (what .reify-* will .push to). Exists so we can # share the reification code between List/Array. List just uses its own # $!reified buffer; the Array one shoves stuff into Scalar containers # first. has $!reification-target; method reify-at-least(int $elems) { nqp::if( (nqp::isconcrete($!current-iter) && nqp::eqaddr( $!current-iter.push-at-least( $!reification-target, nqp::sub_i($elems,nqp::elems($!reified)) ), IterationEnd )), $!current-iter := Iterator ); # there is a future nqp::if( nqp::isconcrete($!future), # still need and can get something from the future nqp::stmts( nqp::while( (nqp::islt_i(nqp::elems($!reified),$elems) && nqp::elems($!future)), nqp::if( (nqp::istype((my $current := nqp::shift($!future)),Slip) && nqp::isconcrete($current)), nqp::stmts( (my $iter := $current.iterator), nqp::unless( nqp::eqaddr( $iter.push-at-least( $!reification-target, nqp::sub_i($elems,nqp::elems($!reified)) ), IterationEnd ), # The iterator produced enough values to fill the need, # but did not reach its end. We save it for next time. # We know we'll exit the loop, since the < $elems check # must be False (unless the iterator broke contract). ($!current-iter := $iter) ) ), $!reification-target.push($current) ) ), # that was the future nqp::unless( nqp::elems($!future), ($!future := Mu) ) ) ); nqp::elems($!reified) } method reify-until-lazy() { nqp::if( (nqp::isconcrete($!current-iter) && nqp::eqaddr( $!current-iter.push-until-lazy($!reification-target), IterationEnd ) ), $!current-iter := Iterator ); nqp::if( nqp::isconcrete($!future) && nqp::not_i(nqp::isconcrete($!current-iter)), nqp::stmts( nqp::while( nqp::elems($!future), nqp::if( (nqp::istype((my $current := nqp::shift($!future)),Slip) && nqp::isconcrete($current)), nqp::unless( nqp::eqaddr( (my $iter := $current.iterator).push-until-lazy( $!reification-target), IterationEnd ), nqp::stmts( ($!current-iter := $iter), last ) ), $!reification-target.push($current) ) ), nqp::unless( nqp::elems($!future), $!future := Mu ) ) ); nqp::elems($!reified) } method reify-all() { nqp::if( nqp::isconcrete($!current-iter), nqp::stmts( $!current-iter.push-all($!reification-target), $!current-iter := Iterator ) ); nqp::if( nqp::isconcrete($!future), nqp::stmts( nqp::while( nqp::elems($!future), nqp::if( (nqp::istype((my $current := nqp::shift($!future)),Slip) && nqp::isconcrete($current)), $current.iterator.push-all($!reification-target), $!reification-target.push($current) ) ), ($!future := Mu) ), nqp::elems($!reified) ) } method fully-reified() { nqp::hllbool(nqp::not_i( nqp::isconcrete($!current-iter) || nqp::isconcrete($!future) )) } method is-lazy() { nqp::isconcrete($!current-iter) ?? $!current-iter.is-lazy !! False } } method from-iterator(List:U: Iterator $iter --> List:D) { my \buffer := nqp::create(IterationBuffer); nqp::bindattr( (my \result := nqp::create(self)),List,'$!reified',buffer); nqp::bindattr( (my \todo := nqp::create(Reifier)),Reifier,'$!reified',buffer); nqp::bindattr(todo,Reifier,'$!current-iter',$iter); # since Array has its own from-iterator, we don't need to # call reification-target, because it is the same as buffer nqp::bindattr(todo,Reifier,'$!reification-target',buffer); nqp::p6bindattrinvres(result,List,'$!todo',todo) } method from-slurpy(|) { my \result := nqp::create(self); my Mu \vm-tuple := nqp::captureposarg(nqp::usecapture,1); nqp::if( nqp::isgt_i(nqp::elems(vm-tuple),0), nqp::stmts( nqp::bindattr(result,List,'$!reified', my \buffer := nqp::create(IterationBuffer)), nqp::bindattr(result,List,'$!todo', my \todo := nqp::create(List::Reifier)), nqp::bindattr(todo,List::Reifier,'$!reified', buffer), nqp::bindattr(todo,List::Reifier,'$!reification-target', result.reification-target), nqp::bindattr(todo,List::Reifier,'$!future',vm-tuple) ) ); result } method from-slurpy-onearg(|) { my Mu \vm-tuple := nqp::captureposarg(nqp::usecapture, 1); my $result; my $buffer; my $todo; my $consider; nqp::if( nqp::isgt_i(nqp::elems(vm-tuple),1), nqp::stmts( # handle as slurpy nqp::bindattr(($result := nqp::create(self)),List,'$!reified', $buffer := nqp::create(IterationBuffer)), nqp::bindattr($result,List,'$!todo', $todo := nqp::create(List::Reifier)), nqp::bindattr($todo,List::Reifier,'$!reified', $buffer), nqp::bindattr($todo,List::Reifier,'$!reification-target', $result.reification-target), nqp::bindattr($todo,List::Reifier,'$!future',vm-tuple), $result ), nqp::if( nqp::iseq_i(nqp::elems(vm-tuple),1), nqp::if( # single arg semantics active nqp::istype(($consider := nqp::atpos(vm-tuple,0)),Seq), nqp::if( # a single Seq nqp::istype(self,Array), $consider.cache, $consider ), nqp::stmts( # something else nqp::bindattr(($result := nqp::create(self)),List,'$!reified', $buffer := nqp::create(IterationBuffer)), nqp::bindattr($result,List,'$!todo', $todo := nqp::create(List::Reifier)), nqp::bindattr($todo,List::Reifier,'$!reified', $buffer), nqp::bindattr($todo,List::Reifier,'$!reification-target', $result.reification-target), nqp::if( nqp::iscont($consider) || nqp::not_i(nqp::istype($consider,Iterable)) || nqp::not_i(nqp::p6definite($consider)), nqp::bindattr($todo,List::Reifier,'$!future', vm-tuple), nqp::bindattr($todo,List::Reifier,'$!future', nqp::list($consider.list.Slip)) ), $result ) ), nqp::create(self) # no args, so just a bare object ) ) } method from-slurpy-flat(|) { nqp::if( (my int $elems = nqp::elems( (my Mu $vm-tuple := nqp::captureposarg(nqp::usecapture,1)) )), nqp::stmts( (my $future := nqp::setelems(nqp::create(IterationBuffer),$elems)), (my int $i = -1), (my int $b = 0), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iscont(my $consider := nqp::atpos($vm-tuple,$i)), nqp::bindpos($future,$i,$consider), nqp::if( nqp::isconcrete($consider) && nqp::istype($consider,Iterable), nqp::if( nqp::istype($consider,PositionalBindFailover), nqp::bindpos($future,$i,$consider.cache.flat.Slip), nqp::bindpos($future,$i,$consider.flat.Slip) ), nqp::stmts( nqp::bindpos($future,$i,$consider), ++$b ) ) ) ), nqp::if( nqp::iseq_i($b,$elems), # we already reified everything nqp::p6bindattrinvres(nqp::create(self),List,'$!reified',$future), # need full fledged List with a $todo nqp::stmts( (my $result := nqp::p6bindattrinvres(nqp::create(self),List,'$!reified', (my $buffer := nqp::create(IterationBuffer)) ) ), (my $todo := nqp::create(List::Reifier)), nqp::bindattr($todo,List::Reifier,'$!reified', $buffer ), nqp::bindattr($todo,List::Reifier,'$!reification-target', $result.reification-target ), nqp::bindattr($todo,List::Reifier,'$!future', $future ), $todo.reify-until-lazy, nqp::unless( $todo.fully-reified, nqp::bindattr($result,List,'$!todo', $todo), ), $result ) ) ), # no args, an empty list suffices nqp::create(self) ) } method new(List: **@things is raw --> List:D) { nqp::p6bindattrinvres( nqp::create(self), List, '$!reified', nqp::if( @things.elems, # reifies nqp::splice( nqp::create(IterationBuffer), nqp::getattr(@things,List,'$!reified'), 0, 0 ), nqp::create(IterationBuffer), ) ) } multi method Bool(List:D: --> Bool:D) { nqp::hllbool( nqp::unless( nqp::isconcrete($!reified) && nqp::elems($!reified), nqp::isconcrete($!todo) && $!todo.reify-at-least(1) ) ) } multi method Int(List:D: --> Int:D) { self.elems } multi method end(List:D: --> Int:D) { self.elems - 1 } multi method Numeric(List:D: --> Int:D) { self.elems } multi method Str(List:D: --> Str:D) { self.join(' ') } # Pretend we're a Match assuming we're a list of Matches method to(List:D:) { self.elems ?? self[self.end].to !! Nil } method from(List:D:) { self.elems ?? self[0].from !! Nil } multi method sum(List:D:) { nqp::if( self.is-lazy, self.fail-iterator-cannot-be-lazy('.sum'), nqp::if( (my int $elems = self.elems), # reify nqp::stmts( (my $sum := 0), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), ($sum := $sum + nqp::ifnull(nqp::atpos($!reified,$i),0)) ), $sum ) ) ) } multi method fmt(List:D: --> Str:D) { nqp::if( self.elems, # reifies nqp::stmts( (my $list := nqp::clone($!reified)), (my $strings := nqp::list_s), nqp::while( nqp::elems($list), nqp::push_s($strings,nqp::shift($list).Str) ), nqp::join(' ',$strings) ), '' ) } multi method fmt(List:D: Str(Cool) $format --> Str:D) { nqp::iseq_s($format,'%s') ?? self.fmt !! self.fmt($format,' ') } multi method fmt(List:D: Str(Cool) $format, $separator --> Str:D) { nqp::if( nqp::iseq_s($format,'%s') && nqp::iseq_s($separator,' '), self.fmt, nqp::if( self.elems, # reifies nqp::stmts( (my $list := nqp::clone($!reified)), (my $strings := nqp::list_s), nqp::if( nqp::iseq_i( # only one % in format? nqp::elems(nqp::split('%',$format)), 2 ) && nqp::iseq_i( # only one %s in format nqp::elems(my $parts := nqp::split('%s',$format)), 2 ), nqp::while( # only a single %s nqp::elems($list), nqp::push_s($strings, nqp::if( nqp::istype((my $elem := nqp::shift($list)),List), $elem.fmt($format, $separator), nqp::join($elem.Str,$parts) ) ) ), nqp::while( # something else nqp::elems($list), nqp::push_s($strings, nqp::if( nqp::istype(($elem := nqp::shift($list)),List), $elem.fmt($format, $separator), $elem.fmt($format) ) ) ) ), nqp::p6box_s(nqp::join($separator,$strings)) ), '' ) ) } multi method elems(List:D:) { nqp::if( nqp::isconcrete($!todo), nqp::stmts( $!todo.reify-until-lazy, nqp::if( $!todo.fully-reified, nqp::stmts( ($!todo := nqp::null), nqp::elems($!reified) ), self.fail-iterator-cannot-be-lazy('.elems',"") ) ), nqp::isconcrete($!reified) && nqp::elems($!reified), ) } multi method head(List:D:) is raw { nqp::isconcrete($!reified) ?? nqp::ifnull( nqp::atpos($!reified,0), self!AT_POS_SLOW(0) ) !! self!AT_POS_SLOW(0) } multi method AT-POS(List:D: uint $pos) is raw { nqp::isconcrete($!reified) ?? nqp::ifnull( nqp::atpos($!reified,$pos), self!AT_POS_SLOW($pos) ) !! self!AT_POS_SLOW($pos) } multi method AT-POS(List:D: Int:D $pos) is raw { nqp::isge_i($pos,0) && nqp::isconcrete($!reified) ?? nqp::ifnull( # should just redispatch to uint when that inlines nqp::atpos($!reified,$pos), self!AT_POS_SLOW($pos) ) !! self!AT_POS_SLOW($pos) } method !AT_POS_SLOW(int $pos) is raw { nqp::if( nqp::islt_i($pos,0), X::OutOfRange.new( :what($*INDEX // 'Index'), :got($pos), :range<0..^Inf> ).Failure, nqp::if( nqp::isconcrete($!reified), nqp::ifnull( nqp::atpos($!reified,$pos), nqp::if( nqp::isconcrete($!todo) && $!todo.reify-at-least(nqp::add_i($pos,1)), nqp::ifnull(nqp::atpos($!reified,$pos),Nil), Nil ) ), Nil ) ) } method ASSIGN-POS(List:D: Int:D $pos, \what) is raw { nqp::isrwcont(my $target := self.AT-POS($pos)) ?? ($target = what) !! X::Assignment::RO.new(value => self).throw } method BIND-POS(List:D: Int:D $, \what) { X::Bind.new.throw } multi method EXISTS-POS(List:D: uint $pos --> Bool:D) { nqp::hllbool( nqp::if( nqp::isconcrete($!reified) && nqp::islt_i($pos,nqp::elems($!reified)), nqp::existspos($!reified,$pos), nqp::if( nqp::isconcrete($!todo), nqp::stmts( $!todo.reify-at-least(nqp::add_i($pos,1)), nqp::existspos($!reified,$pos) ) ) ) ) } multi method EXISTS-POS(List:D: Int:D $pos --> Bool:D) { nqp::hllbool( nqp::if( nqp::isge_i($pos,0), nqp::if( # should just refer to uint candidate when that inlines nqp::isconcrete($!reified) && nqp::islt_i($pos,nqp::elems($!reified)), nqp::existspos($!reified,$pos), nqp::if( nqp::isconcrete($!todo), nqp::stmts( $!todo.reify-at-least(nqp::add_i($pos,1)), nqp::existspos($!reified,$pos) ) ) ) ) ) } method reification-target(List:D:) { nqp::ifnull( $!reified, $!reified := nqp::create(IterationBuffer) ) } my class Todo does Iterator { has int $!i; has $!list; has $!reified; has $!todo; method !SET-SELF(\list) { nqp::stmts( ($!i = -1), ($!list := list), ($!reified := nqp::if( nqp::isconcrete(nqp::getattr(list,List,'$!reified')), # we already have a place to put values in nqp::getattr(list,List,'$!reified'), # create a place here and there to put values in nqp::bindattr(list,List,'$!reified', nqp::create(IterationBuffer)) )), ($!todo := nqp::getattr(list,List,'$!todo')), self ) } method new(\list) { nqp::create(self)!SET-SELF(list) } method pull-one() is raw { nqp::ifnull( nqp::atpos($!reified,++$!i), nqp::if( nqp::isconcrete($!todo), nqp::if( nqp::islt_i( $!i, $!todo.reify-at-least(nqp::add_i($!i,1)) ), nqp::atpos($!reified,$!i), self!done ), IterationEnd ) ) } method !done() is raw { $!todo := nqp::bindattr($!list,List,'$!todo',nqp::null); IterationEnd } method push-until-lazy(\target) { nqp::if( nqp::isconcrete($!todo), nqp::stmts( # something to reify still (my int $elems = $!todo.reify-until-lazy), nqp::while( # doesn't sink nqp::islt_i(++$!i,$elems), target.push(nqp::atpos($!reified,$!i)) ), nqp::if( $!todo.fully-reified, self!done, nqp::stmts( ($!i = nqp::sub_i($elems,1)), Mu ) ) ), nqp::stmts( # already fully reified ($elems = nqp::elems($!reified)), nqp::while( # doesn't sink nqp::islt_i(++$!i,$elems), target.push(nqp::atpos($!reified,$!i)) ), IterationEnd ) ) } method is-lazy() { $!todo.DEFINITE && $!todo.is-lazy } } multi method iterator(List:D: --> Iterator:D) { nqp::isconcrete($!todo) ?? Todo.new(self) # something to iterate in the future !! nqp::isconcrete($!reified) # everything we need is already there ?? Rakudo::Iterator.ReifiedList(self) !! Rakudo::Iterator.Empty } multi method ACCEPTS(List:D: Iterable:U --> False) { } multi method ACCEPTS(List:D: Iterable:D $topic --> Bool:D) { CATCH { default { return False } } # .elems on lazies throws return True if nqp::eqaddr(self, nqp::decont($topic)); my $sseq = self; my $tseq = $topic; sub tailmatch($s,$t) { my int $spos = $s; my int $tpos = $t; while $spos < $sseq { # if the next element is Whatever if nqp::istype($sseq[$spos], HyperWhatever) { # skip over all of the Whatevers $spos = $spos + 1 while $spos <= $sseq && nqp::istype($sseq[$spos], HyperWhatever); # if nothing left, we're done return True if $spos == $sseq; # find a target matching our new target while $tpos < $tseq { my $result = tailmatch($spos,$tpos); return True if $result; $tpos = $tpos + 1 } # return false if we ran out return False; } elsif $tpos == $tseq or not $sseq[$spos].ACCEPTS($tseq[$tpos] ) { return False; } # skip matching elements $spos = $spos + 1; $tpos = $tpos + 1; } # If nothing left to match, we're successful. $tpos >= $tseq; } tailmatch(0,0) } # Note that ACCEPTS not always returning Bool here is what powers m:g/ / # smartmatch behaviour multi method ACCEPTS(List:D: $topic) { self.elems ?? nqp::istype(self[0], Match) ?? self !! False !! self } multi method list(List:D:) { self } # We don't sink contents by design https://github.com/rakudo/rakudo/issues/1393 method sink(--> Nil) { } multi method values(List:D: --> Seq:D) { Seq.new(self.iterator) } multi method keys(List:D: --> Seq:D) { Seq.new(nqp::if( self.is-lazy, nqp::stmts( (my int $i = -1), Rakudo::Iterator.Callable( { ++$i }, True ) ), Rakudo::Iterator.IntRange(0, self.elems - 1) )) } multi method kv(List:D: --> Seq:D) { Seq.new(Rakudo::Iterator.KeyValue(self.iterator)) } multi method pairs(List:D: --> Seq:D) { Seq.new(Rakudo::Iterator.Pairs(self.iterator)) } multi method antipairs(List:D: --> Seq:D) { Seq.new(Rakudo::Iterator.AntiPair(self.iterator)) } multi method invert(List:D: --> Seq:D) { Seq.new(Rakudo::Iterator.Invert(self.iterator)) } # Store in List targets containers with in the list. This handles list # assignments, like ($a, $b) = foo(). proto method STORE(List:D: |) {*} multi method STORE(List:D: Iterable:D \iterable, :INITIALIZE($)! --> List:D) { my \buffer := nqp::create(IterationBuffer); iterable.iterator.push-all(buffer); nqp::p6bindattrinvres(self,List,'$!reified',buffer) } multi method STORE(List:D: Mu \item, :INITIALIZE($)! --> List:D) { self.STORE((item,), :INITIALIZE); } multi method STORE(List:D: Iterable:D \iterable --> List:D) { # First pass -- scan lhs containers and pick out scalar versus list # assignment. This also reifies the RHS values we need, and deconts # them. The decont is needed so that we can do ($a, $b) = ($b, $a). my \cv = nqp::list(); my \lhs-iter = self.iterator; my \rhs-iter = iterable.iterator; my int $rhs-done; my Mu $v; my Mu $c; my Mu $sub-iter; my Mu $sc; nqp::until( nqp::eqaddr(($c := lhs-iter.pull-one),IterationEnd), nqp::if( # Container: scalar assignment nqp::iscont($c), nqp::stmts( nqp::push(cv,$c), nqp::if( ($rhs-done || ($rhs-done = nqp::eqaddr(($v := rhs-iter.pull-one),IterationEnd))), nqp::push(cv,Nil), nqp::push(cv,nqp::decont($v)), ) ), nqp::if( # Whatever: skip assigning value nqp::istype($c,Whatever), nqp::if( (nqp::not_i($rhs-done) && nqp::eqaddr(rhs-iter.pull-one,IterationEnd)), ($rhs-done = 1) ), nqp::if( # List splice into current lhs (nqp::istype($c,List) && nqp::not_i(nqp::istype($c,Array))), nqp::stmts( ($sub-iter := $c.iterator), nqp::until( nqp::eqaddr(($sc := $sub-iter.pull-one),IterationEnd), nqp::stmts( nqp::push(cv,$sc); nqp::if( ($rhs-done = nqp::eqaddr( ($v := rhs-iter.pull-one),IterationEnd )), nqp::push(cv,Nil), nqp::push(cv,nqp::decont($v)) ) ) ) ), nqp::stmts( # Non-container: store entire remaining rhs nqp::push(cv,$c), nqp::push(cv,List.from-iterator(rhs-iter)), ($rhs-done = 1) ) ) ) ) ); # Second pass, perform the assignments. nqp::shift(cv) = nqp::shift(cv) while nqp::elems(cv); self } multi method STORE(List:D: Mu \item --> List:D) { self.STORE((item,)); } multi method gist(List:D: --> Str:D) { self.gistseen(self.^name, { (nqp::istype(self,Array) ?? '[' !! '(') ~ self.map( -> $elem { given ++$ { when 101 { '...' } when 102 { last } default { $elem.gist } } }).join(' ') ~ (nqp::istype(self,Array) ?? ']' !! ')') }) } multi method raku(List:D \SELF: --> Str:D) { SELF.rakuseen('List', { my $prefix := nqp::iscont(SELF) ?? '$(' !! '('; if self.is-lazy { my @elements = self.head(101); if @elements > 100 { @elements.pop; $prefix ~ @elements.map({.raku}).join(', ') ~ '...).lazy'; } else { $prefix ~ @elements.map({.raku}).join(', ') ~ ').lazy'; } } elsif self.elems -> $elems { $prefix ~ ( $elems == 1 ?? self[0].raku ~ ',)' !! self.map( {.raku} ).join(', ') ~ ')' ) } else { $prefix ~ (nqp::iscont(SELF) ?? ' )' !! ')') } }) } multi method List(List:D:) { self } multi method Slip(List:D: --> Slip:D) { nqp::isconcrete($!todo) # We're not fully reified, and so have internal mutability still. # The safe thing to do is to take an iterator of ourself and build # the Slip out of that. ?? Slip.from-iterator(self.iterator) # We're fully reified - and so immutable inside and out! Just make # a Slip that shares our reified buffer. !! nqp::p6bindattrinvres(nqp::create(Slip),List,'$!reified',$!reified) } multi method Array(List:D: --> Array:D) { # We need to populate the Array slots with Scalar containers nqp::isconcrete($!todo) ?? Array.from-iterator(self.iterator) !! Array.from-list(self) } method eager(List:D: --> List:D) { nqp::stmts( nqp::if( nqp::isconcrete($!todo), nqp::stmts( $!todo.reify-all, ($!todo := nqp::null) ) ), self ) } method Capture(List:D: --> Capture:D) { # too lazy if self.is-lazy { self.fail-iterator-cannot-be-lazy('create a Capture from'); } # we have something to work with elsif nqp::isconcrete($!reified) && nqp::elems($!reified) -> int $elems { my $capture := nqp::create(Capture); my $list := nqp::create(IterationBuffer); my $hash := nqp::hash; my int $i = -1; my $v; nqp::istype(($v := nqp::atpos($!reified, $i)),Pair) ?? nqp::bindkey($hash, $v.key.Str, $v.value) !! nqp::push($list,$v) while nqp::islt_i(++$i,$elems); nqp::bindattr($capture,Capture,'@!list',$list) if nqp::elems($list); nqp::bindattr($capture,Capture,'%!hash',$hash) if nqp::elems($hash); $capture } # nothing to work with else { nqp::create(Capture) } } method FLATTENABLE_LIST() is implementation-detail { nqp::if( nqp::isconcrete($!todo), nqp::stmts( $!todo.reify-all, $!reified ), nqp::if( nqp::isconcrete($!reified), $!reified, nqp::bindattr(self,List,'$!reified',nqp::create(IterationBuffer)) ) ) } method FLATTENABLE_HASH() is implementation-detail { nqp::hash() } multi method Supply(List:D: --> Supply:D) { Supply.from-list(self) } multi method is-lazy(List:D: --> Bool:D) { nqp::if( nqp::isconcrete($!todo), nqp::stmts( $!todo.reify-until-lazy, nqp::if( $!todo.fully-reified, nqp::hllbool(nqp::istrue($!todo := nqp::null)), True ) ), False ) } multi method pick(List:D:) { self.is-lazy ?? self.fail-iterator-cannot-be-lazy('.pick from') !! (my int $elems = self.elems) # reifies ?? nqp::atpos($!reified,nqp::floor_n(nqp::rand_n($elems))) !! Nil } multi method pick(List:D: Callable:D $calculate) { self.is-lazy ?? self.fail-iterator-cannot-be-lazy('.pick from') !! self.pick( $calculate(self.elems) ) } my class PickN does Iterator { has $!list; has int $!elems; has int $!number; method !SET-SELF(\list, $!elems, $!number) { $!list := nqp::clone(nqp::getattr(list,List,'$!reified')); self } method new(\list, $elems, $number) { nqp::create(self)!SET-SELF(list, $elems, $number + 1) } method pull-one() { nqp::if( --$!number, nqp::stmts( (my \tmp = nqp::atpos( $!list, my int $i = nqp::floor_n(nqp::rand_n($!elems)) )), nqp::bindpos($!list,$i,nqp::atpos($!list,--$!elems)), tmp ), IterationEnd ) } method push-all(\target --> IterationEnd) { nqp::stmts( (my $list := $!list), (my int $number = $!number), (my int $elems = $!elems), nqp::while( --$number, nqp::stmts( # doesn't sink target.push(nqp::atpos( $list, (my int $i = nqp::floor_n(nqp::rand_n($elems))) )), nqp::bindpos($list,$i,nqp::atpos($list,--$elems)) ) ), ($!number = $number), ($!elems = $elems) ) } method is-deterministic(--> False) { } } multi method pick(List:D: $number) { self.is-lazy ?? self.fail-iterator-cannot-be-lazy('.pick from') !! (my Int $elems = self.elems) ?? Seq.new(PickN.new( self, $elems, nqp::istype($number,Whatever) || $number == Inf ?? $elems !! $number.UInt min $elems )) !! () } proto method roll(|) is nodal {*} multi method roll(List:D:) { self.is-lazy ?? self.fail-iterator-cannot-be-lazy('.roll from') !! (my int $elems = self.elems) # reify ?? nqp::atpos($!reified,nqp::floor_n(nqp::rand_n($elems))) !! Nil } multi method roll(List:D: Whatever) { nqp::if( self.is-lazy, self.fail-iterator-cannot-be-lazy('.roll from'), Seq.new(nqp::if( (my int $elems = self.elems), nqp::stmts( (my $reified := $!reified), Rakudo::Iterator.Callable( { nqp::atpos($reified,nqp::floor_n(nqp::rand_n($elems))) }, True ) ), Rakudo::Iterator.Empty )) ) } my class RollN does Iterator { has $!list; has int $!elems; has int $!todo; method !SET-SELF(\list, $todo) { $!list := nqp::getattr(list,List,'$!reified'); $!elems = nqp::elems($!list); $!todo = $todo + 1; self } method new(\list, $todo) { nqp::create(self)!SET-SELF(list, $todo) } method pull-one() is raw { --$!todo ?? nqp::atpos($!list,nqp::floor_n(nqp::rand_n($!elems))) !! IterationEnd } method push-all(\target --> IterationEnd) { nqp::stmts( (my int $todo = $!todo), (my int $elems = $!elems), nqp::while( --$todo, target.push(nqp::atpos( $!list, nqp::floor_n(nqp::rand_n($elems)) )) ), ($!todo = $todo) ) } method is-deterministic(--> False) { } } multi method roll(List:D: $number) { $number == Inf ?? self.roll(*) !! self.is-lazy ?? self.fail-iterator-cannot-be-lazy('.roll from').throw !! Seq.new(self.elems # this allocates/reifies ?? RollN.new(self, $number.Int) !! Rakudo::Iterator.Empty ) } method reverse(List:D: --> Seq:D) is nodal { self.is-lazy # reifies ?? self.fail-iterator-cannot-be-lazy('reverse') !! Seq.new: $!reified ?? Rakudo::Iterator.ReifiedReverse(self, Mu) !! Rakudo::Iterator.Empty } method rotate(List:D: Int(Cool) $rotate = 1 --> Seq:D) is nodal { self.is-lazy # reifies ?? self.fail-iterator-cannot-be-lazy('rotate') !! Seq.new: $!reified ?? Rakudo::Iterator.ReifiedRotate($rotate, self, Mu) !! Rakudo::Iterator.Empty } proto method combinations(|) is nodal {*} multi method combinations(--> Seq:D) { my int $elems = self.elems; # reifies my int $i = -1; Seq.new( Rakudo::Iterator.SequentialIterators( Rakudo::Iterator.Callable( { nqp::islt_i(++$i,$elems) ?? Rakudo::Iterator.ListIndexes( # .combinations($i) self, Rakudo::Iterator.Combinations($elems, $i, 1) ) !! nqp::iseq_i($i,$elems) ?? Rakudo::Iterator.OneValue( # last one is self nqp::p6bindattrinvres( # but must be a (new) List nqp::create(List), # so transplant innards List, '$!reified', nqp::getattr(self,List,'$!reified') ) ) !! IterationEnd } ) ) ) } multi method combinations(Int() $of --> Seq:D) { Seq.new( Rakudo::Iterator.ListIndexes( self, Rakudo::Iterator.Combinations( self.elems, $of, 1) ) ) } multi method combinations(Range:D $ofrange --> Seq:D) { nqp::stmts( (my int $elems = self.elems), # reifies nqp::if( $ofrange.is-int, $ofrange.int-bounds(my int $i, my int $to), nqp::stmts( nqp::unless( $ofrange.min < 0, # $i already 0 if not ($i = ($ofrange.min + $ofrange.excludes-min).Int) ), nqp::if( $ofrange.max > $elems, ($to = $elems), ($to = ($ofrange.max - $ofrange.excludes-max).Int) ) ) ), ($i = nqp::if(nqp::islt_i($i,0),-1,nqp::sub_i($i,1))), nqp::if(nqp::isgt_i($to,$elems),($to = $elems)), Seq.new( Rakudo::Iterator.SequentialIterators( Rakudo::Iterator.Callable( { nqp::if( nqp::isle_i(++$i,$to), Rakudo::Iterator.ListIndexes( # basically .combinations($i) self, Rakudo::Iterator.Combinations($elems, $i, 1) ), IterationEnd ) } ) ) ) ) } proto method permutations(|) is nodal {*} multi method permutations(--> Seq:D) { Seq.new: Rakudo::Iterator.ListIndexes: self, Rakudo::Iterator.Permutations: self.elems, 1 } method join(List:D: Str(Cool) $separator = '') is nodal { nqp::stmts( nqp::if( nqp::isconcrete($!todo), nqp::stmts( # need to reify first $!todo.reify-until-lazy, nqp::if( $!todo.fully-reified, ($!todo := nqp::null), # all reified (my int $infinite = 1) # still stuff left to do ) ) ), nqp::if( nqp::isconcrete($!reified) && (my int $elems = nqp::elems($!reified)), nqp::stmts( # something to join (my $strings := nqp::list_s), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::stmts( # something left to check (my $tmp := nqp::ifnull( nqp::atpos($!reified,$i), nqp::if( nqp::isconcrete(my $default), $default, # seen before ($default := nqp::if( # first time we see null nqp::can(self,'default'), self.default.Str, '' )) ) )), nqp::if( nqp::isconcrete($tmp), nqp::if( # not a type object nqp::istype($tmp,Junction), (return self!JUNCTIONIZE( # follow Junction path $separator, $strings, $i, $elems, $tmp )), nqp::push_s( # no special action needed $strings, nqp::if( nqp::istype($tmp,Str), $tmp, nqp::if( nqp::can($tmp,'Str'), $tmp.Str, nqp::box_s($tmp,Str) ) ) ) ), nqp::push_s($strings,$tmp.Str) # type object ) ) ), nqp::if($infinite,nqp::push_s($strings,'...')), nqp::box_s( # done nqp::join($separator,$strings), Str ) ), nqp::if($infinite,'...','') # nothing to join ) ) } # When we find a Junction in the list, start handling the rest # of the list as junctions, and stringify the parts between Junctions # normally, for performance. method !JUNCTIONIZE(\sep, Mu \strings, \i, \elems, Mu \initial) { nqp::stmts( nqp::if( nqp::elems(strings), nqp::stmts( # some strings on left (my $junction := infix:<~>( nqp::concat(nqp::join(sep,strings),sep), initial )), nqp::setelems(strings,0) ), ($junction := initial) # just start with this one ), nqp::while( nqp::islt_i((i = nqp::add_i(i,1)),elems), nqp::stmts( # something left in list (my $tmp := nqp::ifnull( nqp::atpos($!reified,i), nqp::if( nqp::isconcrete(my $default), $default, # seen before ($default := nqp::if( # first time we have a null nqp::can(self,'default'), self.default.Str, '' )) ) )), nqp::if( nqp::isconcrete($tmp), nqp::if( # not a type object nqp::istype($tmp,Junction), nqp::stmts( # found another Junction nqp::if( nqp::elems(strings), nqp::stmts( # process string on left ($junction := infix:<~>( $junction, nqp::concat(sep,nqp::join(sep,strings)) )), nqp::setelems(strings,0) ) ), ($junction := infix:<~>($junction, $tmp)) ), nqp::push_s(strings,nqp::if( # not a Junction nqp::istype($tmp,Str), $tmp, nqp::if( nqp::can($tmp,'Str'), $tmp.Str, nqp::box_s($tmp,Str) ) )) ), nqp::push_s(strings,$tmp.Str) # type object ) ) ), nqp::if( nqp::elems(strings), infix:<~>( # need to concat right $junction, nqp::concat(sep,nqp::join(sep,strings)) ), $junction # nothing left to concat ) ) } # helper method for .sort method !reify-for-sort(--> Nil) { nqp::if( nqp::isconcrete($!todo), nqp::stmts( $!todo.reify-until-lazy, nqp::if( $!todo.fully-reified, ($!todo := nqp::null), self.throw-iterator-cannot-be-lazy('.sort') ) ) ); } method !deep-clone() { nqp::p6bindattrinvres( nqp::create(List),List,'$!reified',nqp::clone($!reified) ) } # https://en.wikipedia.org/wiki/Merge_sort#Bottom-up_implementation multi method sort(List:D: --> Seq:D) { self!reify-for-sort; Seq.new: nqp::isconcrete($!reified) && nqp::elems($!reified) ?? Rakudo::Iterator.ReifiedList( Rakudo::Sorting.MERGESORT-REIFIED-LIST(self!deep-clone) ) !! Rakudo::Iterator.Empty } multi method sort(List:D: &by?, :$k!) { if $k { self!reify-for-sort; nqp::isconcrete($!reified) && nqp::elems($!reified) ?? &by && &by.arity < 2 ?? Rakudo::Sorting.MERGESORT-REIFIED-LIST-AS( self!deep-clone, &by, :indices ) !! Rakudo::Sorting.MERGESORT-REIFIED-LIST-INDICES( self!deep-clone, &by // &[cmp] ) !! () } # no keys requested else { &by ?? self.sort(&by) !! self.sort } } multi method sort(List:D: &by --> Seq:D) { nqp::eqaddr(&by,&infix:) ?? (return self.sort) !! self!reify-for-sort; Seq.new: nqp::isconcrete($!reified) && nqp::elems($!reified) ?? Rakudo::Iterator.ReifiedList(&by.arity < 2 ?? Rakudo::Sorting.MERGESORT-REIFIED-LIST-AS( self.clone, &by ) !! Rakudo::Sorting.MERGESORT-REIFIED-LIST-WITH( self!deep-clone, &by ) ) !! Rakudo::Iterator.Empty } multi method tail(List:D:) is raw { nqp::isconcrete($!todo) ?? self.Any::tail !! nqp::isconcrete($!reified) && nqp::elems($!reified) ?? nqp::atpos($!reified,nqp::sub_i(nqp::elems($!reified),1)) !! Nil } multi method tail(List:D: $n --> Seq:D) { nqp::if( nqp::isconcrete($!todo), self.Any::tail($n), Seq.new( nqp::if( nqp::isconcrete($!reified) && nqp::elems($!reified), nqp::stmts( (my $iterator := Rakudo::Iterator.ReifiedList(self)), nqp::if( nqp::istype($n,Callable), nqp::if( nqp::isgt_i((my $skip := -($n(0).Int)),0), $iterator.skip-at-least($skip) ), nqp::unless( nqp::istype($n,Whatever) || $n == Inf, $iterator.skip-at-least(nqp::elems($!reified) - $n.Int) ) ), $iterator ), Rakudo::Iterator.Empty ) ) ) } method push(|) is nodal { X::Immutable.new(:typename,:method).throw } method append(|) is nodal { X::Immutable.new(:typename,:method).throw } method unshift(|) is nodal { X::Immutable.new(:typename,:method).throw } method prepend(|) is nodal { X::Immutable.new(:typename,:method).throw } method shift(|) is nodal { X::Immutable.new(:typename,:method).throw } method pop(|) is nodal { X::Immutable.new(:typename, :method).throw } } # The , operator produces a List. proto sub infix:<,>(|) is pure {*} multi sub infix:<,>(--> List:D) { nqp::create(List) } multi sub infix:<,>(Slip:D \a, Slip:D \b --> List:D) { # now set up the List with a future Rakudo::Internals.INFIX_COMMA_SLIP_HELPER(nqp::create(IterationBuffer), nqp::list(a,b)) } multi sub infix:<,>(Any \a, Slip:D \b --> List:D) { nqp::stmts( # Slip seen, first copy non-slippy thing (my $reified := nqp::create(IterationBuffer)), nqp::bindpos($reified,0,a), # now set up the List with a future Rakudo::Internals.INFIX_COMMA_SLIP_HELPER($reified, nqp::list(b)) ) } multi sub infix:<,>(Slip:D \a, Any \b --> List:D) { # now set up the List with a future Rakudo::Internals.INFIX_COMMA_SLIP_HELPER(nqp::create(IterationBuffer), nqp::list(a,b)) } multi sub infix:<,>(Any \a, Any \b --> List:D) { nqp::p6bindattrinvres(nqp::create(List),List,'$!reified',nqp::list(a,b)) } multi sub infix:<,>(|) { # look for a Slip in the parameters my \in := nqp::p6argvmarray(); my int $i = -1; my int $elems = nqp::elems(in); nqp::while( (nqp::islt_i(++$i,$elems) && nqp::not_i(nqp::istype(nqp::atpos(in,$i),Slip))), nqp::null ); nqp::if( nqp::iseq_i($i,$elems), # no Slip seen, so just alias input params nqp::p6bindattrinvres(nqp::create(List),List,'$!reified',in), nqp::stmts( # Slip seen, first copy non-slippy things ($elems = $i), ($i = -1), (my $reified := nqp::setelems(nqp::create(IterationBuffer),$elems)), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos($reified,$i,nqp::shift(in)) ), # now set up the List with a future Rakudo::Internals.INFIX_COMMA_SLIP_HELPER($reified, in) ) ) } proto sub combinations($, $?, *%) {*} multi sub combinations(Int() $n, Int() $k --> Seq:D) { Seq.new: Rakudo::Iterator.Combinations($n, $k, 0) } multi sub combinations(Int() $n, Range:D \k --> Seq:D) { ^$n .combinations: k } multi sub combinations(Iterable \n, \k --> Seq:D) is default { n.combinations: k } multi sub combinations(\n --> Seq:D) { combinations n, 0..* } proto sub permutations($, *%) {*} multi sub permutations(Int() $n --> Seq:D) { Seq.new: Rakudo::Iterator.Permutations($n, 0) } multi sub permutations(Iterable \n --> Seq:D) { n.permutations } proto sub list(|) {*} multi sub list(+l) { l } proto sub cache(|) {*} multi sub cache(+@l) { @l } # Use **@list and then .flat it, otherwise we'll end up remembering all the # things we flatten, which would be different semantics to .flat which gives # back a Seq. We also add an Iterable candidate, to preserve .is-lazy # of an Iterable whenever we can. proto sub flat(|) {*} multi flat(**@list is raw) { @list.flat } multi flat(Iterable \a) { a.flat } proto sub infix:(Mu $?, $?, *%) {*} multi sub infix:() { "infix:".no-zero-arg } multi sub infix:(Mu \x) { x } multi sub infix:(&x, Num:D() $n) { infix:(&x, $n == Inf ?? Whatever !! $n.Int); } multi sub infix:(&x, Whatever) { Seq.new(Rakudo::Iterator.Callable-xx-Whatever(&x)) } multi sub infix:(&x, Bool:D $b) { $b ?? infix:(&x, 1) !! Seq.new(Rakudo::Iterator.Empty) } multi sub infix:(&x, Int:D $n) { my int $todo = $n; my Mu $list := nqp::create(IterationBuffer); nqp::while( nqp::isgt_i(--$todo,-1), nqp::if( nqp::istype((my $pulled := x()),Slip), $pulled.iterator.push-all($list), nqp::if( nqp::istype($pulled,Seq), nqp::push($list,$pulled.cache), nqp::push($list,nqp::decont($pulled)) ) ) ); Seq.new(Rakudo::Iterator.ReifiedList($list)) } multi sub infix:(Mu \x, Num:D() $n) { Seq.new( $n == Inf ?? Rakudo::Iterator.UnendingValue(x) !! Rakudo::Iterator.OneValueTimes(x,$n.Int) ) } multi sub infix:(Mu \x, Whatever) { Seq.new(Rakudo::Iterator.UnendingValue(x)) } multi sub infix:(Mu \x, Bool:D $b) { Seq.new( $b ?? Rakudo::Iterator.OneValue(x) !! Rakudo::Iterator.Empty ) } multi sub infix:(Mu \x, Int:D $n) is pure { Seq.new(Rakudo::Iterator.OneValueTimes(x,$n)) } proto sub reverse(|) {*} multi sub reverse() { "reverse()".no-zero-arg } multi sub reverse(@a) { @a.reverse } multi sub reverse(+@a) { @a.reverse } proto sub rotate($, $?, *%) {*} multi sub rotate(@a) { @a.rotate } multi sub rotate(@a, Int:D $n) { @a.rotate($n) } proto sub prefix:<|>($, *%) {*} multi sub prefix:<|>(\x --> Slip:D) { x.Slip } proto sub infix:(|) is pure {*} multi sub infix:(+lol, :$with! --> Seq:D) { Seq.new(Rakudo::Iterator.CrossIterablesOp(lol,$with)) } multi sub infix:(+lol --> Seq:D) { Seq.new(Rakudo::Iterator.CrossIterablesOp(lol,&infix:<,>)) } my constant &cross := &infix:; proto sub infix:(|) is pure {*} multi sub infix:(+lol, :&with! --> Seq:D) { Seq.new(Rakudo::Iterator.ZipIterablesOp(lol,&with)) } multi sub infix:(+lol --> Seq:D) { Seq.new(Rakudo::Iterator.ZipIterables(lol)) } my constant &zip := &infix:; proto sub roundrobin(|) {*} multi sub roundrobin(+lol, :$slip --> Seq:D) { Seq.new($slip ?? Rakudo::Iterator.RoundrobinIterablesSlipped(lol) !! Rakudo::Iterator.RoundrobinIterables(lol) ) } # vim: expandtab shiftwidth=4