# This file automatically generated by /friends/.rakubrew/versions/moar-main/tools/build/gen-cat.nqp #line 1 SETTING::src/core.c/core_prologue.rakumod # Stub a few things the compiler wants to have really early on. my class Pair { ... } # must be first for some reason my class Block { ... } my class HyperWhatever { ... } my class List { ... } my class Map { ... } my class Match { ... } my class Failure { ... } my class RakuAST::Deparse { ... } my class Rakudo::Deprecations { ... } my class Rakudo::Internals { ... } my class Rakudo::Internals::JSON { ... } my class Rakudo::Internals::RegexBoolification6cMarker { ... } my class Rakudo::Iterator { ... } my class ThreadPoolScheduler { ... } my class Whatever { ... } my class WhateverCode { ... } my class X::Attribute::Required { ... } my class X::Numeric::Overflow { ... } my class X::Numeric::Underflow { ... } my class X::TypeCheck::Attribute::Default { ... } # Stub these or we can't use any sigil other than $. my role Positional { ... } my role Associative { ... } my role Callable { ... } my role Iterable { ... } my role Enumeration { ... } my role PositionalBindFailover { ... } my role Hash::Typed { ... } my role Hash::Object { ... } # Make Iterable available for the code-gen. BEGIN nqp::bindhllsym('Raku', 'Iterable', Iterable); nqp::bindhllsym('Raku', 'Iterable', Iterable); nqp::bindhllsym('Raku', 'Failure', Failure); # Make deparsing possible with the .DEPARSE method from NQP nqp::bindhllsym('Raku','DEPARSE',RakuAST::Deparse); BEGIN { # Ensure routines with traits using mixins applied to them typecheck as Callable. Code.^add_role: Callable; # Compose routine types used in the setting so traits using mixins can be # applied to them. Sub.^compose; Method.^compose; Submethod.^compose; } # Set up Empty, which is a Slip created with an empty IterationBuffer (which # is stubbed in bootstrap). This is needed in a bunch of simple constructs # (like if with only one branch). my constant Empty = nqp::p6bindattrinvres(nqp::create(Slip), List, '$!reified', nqp::create(IterationBuffer)); # We use a sentinel value to mark the end of an iteration. my constant IterationEnd = nqp::create(Mu); # To allow passing of nqp::hash without being HLLized, we create a HLL class # with the same low level REPR as nqp::hash. my class Rakudo::Internals::IterationSet is repr('VMHash') { method raku() { nqp::p6bindattrinvres(nqp::create(Map),Map,'$!storage',self) } } # The value for \n. my constant $?NL = "\x0A"; # Make sure we have an environment PROCESS::<%ENV> := do { my $env := nqp::hash; my $iter := nqp::iterator(nqp::getenvhash); nqp::while( $iter, nqp::bindkey( $env, nqp::iterkey_s(nqp::shift($iter)), nqp::assign( nqp::p6scalarfromdesc(nqp::null), val(nqp::box_s(nqp::iterval($iter),Str)) ) ) ); nqp::p6bindattrinvres(nqp::create(Hash),Map,'$!storage',$env) } # This thread pool scheduler will be the default one. PROCESS::<$SCHEDULER> = ThreadPoolScheduler.new(); #line 1 SETTING::src/core.c/traits.rakumod # for errors my class X::Syntax::ParentAsHash { ... } my class X::Inheritance::Unsupported { ... } my class X::Inheritance::UnknownParent { ... } my class X::Export::NameClash { ... } my class X::Composition::NotComposable { ... } my class X::Import::MissingSymbols { ... } my class X::Redeclaration { ... } my class X::Inheritance::SelfInherit { ... } my class X::Comp::Trait::Unknown { ... } my class X::Comp::Trait::Invalid { ... } my class X::Experimental { ... } my class Pod::Block::Declarator { ... } proto sub trait_mod:(Mu $, |) {*} multi sub trait_mod:(Mu:U $child, Mu:U $parent) { if $parent.HOW.archetypes.inheritable() || ($child.HOW.archetypes.parametric && $parent.^archetypes.generic) { $child.^add_parent($parent); } elsif $parent.HOW.archetypes.inheritalizable() { if nqp::can($parent.HOW, 'methods') && my @required-methods = $parent.^methods.grep({$_.yada}) { my $type = $child.HOW.archetypes.inheritable() ?? 'Class ' !! $child.HOW.archetypes.inheritalizable() ?? 'Role ' !! ''; die $type ~ "{$child.^name} can't pun role {$parent.^name} because it has required methods: " ~ @required-methods.map({$_.name}).join(', ') ~ '. Did you mean to use "does" instead?'; } else { $child.^add_parent($parent.^inheritalize) } } else { X::Inheritance::Unsupported.new( :child-typename($child.^name), :$parent, ).throw; } } multi sub trait_mod:(Mu:U \child, Mu:U \parent, @subtypes) { # re-dispatch properly parameterized R#2611 trait_mod:(child,parent.^parameterize(|@subtypes)) } multi sub trait_mod:(Mu:U $child, :DEPRECATED($)!) { # add COMPOSE phaser for this child, which will add an ENTER phaser to an # existing "new" method, or create a "new" method with a call to DEPRECATED # and a nextsame. } multi sub trait_mod:(Mu:U $type, :rw($)!) { $type.^set_rw; } multi sub trait_mod:(Mu:U $type, :$nativesize!) { $type.^set_nativesize($nativesize); } multi sub trait_mod:(Mu:U $type, :$ctype!) { $type.^set_ctype($ctype); } multi sub trait_mod:(Mu:U $type, :$unsigned!) { $type.^set_unsigned($unsigned); } multi sub trait_mod:(Mu:U $type, :hidden($)!) { $type.^set_hidden; } multi sub trait_mod:(Mu:U $type, Mu :$array_type!) { $type.^set_array_type($array_type); } multi sub trait_mod:(Mu:U $type, Mu:U $parent, Block) { X::Syntax::ParentAsHash.new( :type($type.^name), :parent($parent.^name), :what ).throw; } multi sub trait_mod:(Mu:U $type, Mu:U $parent, Hash) { X::Syntax::ParentAsHash.new( :type($type.^name), :parent($parent.^name), :what ).throw; } multi sub trait_mod:(Mu:U $type, :$implementation-detail!) { my role is-implementation-detail { method is-implementation-detail(Mu --> 1) { } } $type.HOW.^mixin(is-implementation-detail) if $implementation-detail; } multi sub trait_mod:(Mu:U $type, *%fail) { if %fail.keys[0] !eq $type.^name { X::Inheritance::UnknownParent.new( :child($type.^name), :parent(%fail.keys[0]), :suggestions([]) ).throw; } else { X::Inheritance::SelfInherit.new( :name(%fail.keys[0]) ).throw; } } multi sub trait_mod:(Attribute:D $attr, |c ) { X::Comp::Trait::Unknown.new( file => $?FILE, line => $?LINE, type => 'is', subtype => c.hash.keys[0], declaring => 'an attribute', highexpect => , ).throw; } multi sub trait_mod:(Attribute:D $attr, :rw($)!) { $attr.set_rw(); warn "useless use of 'is rw' on $attr.name()" unless $attr.has_accessor; } multi sub trait_mod:(Attribute:D $attr, :readonly($)!) { $attr.set_readonly(); warn "useless use of 'is readonly' on $attr.name()" unless $attr.has_accessor; } multi sub trait_mod:(Attribute:D $attr, :$required!) { die "'is required' must be Cool" unless nqp::istype($required,Cool); $attr.set_required( nqp::istype($required,Bool) ?? +$required !! $required ); } multi sub trait_mod:(Attribute:D $attr, Mu :$default!) { $attr.container_descriptor.set_default(nqp::decont($default)); $attr.container = nqp::decont($default) if nqp::isrwcont($attr.container); } multi sub trait_mod:(Attribute:D $attr, :box_target($)!) { $attr.set_box_target(); } multi sub trait_mod:(Attribute:D $attr, :$DEPRECATED!) { my $new := nqp::istype($DEPRECATED,Bool) ?? "something else" !! $DEPRECATED; my role is-DEPRECATED { has $.DEPRECATED } $attr does is-DEPRECATED($new); } multi sub trait_mod:(Attribute:D $attr, :$leading_docs!) { Rakudo::Internals.SET_LEADING_DOCS($attr, $leading_docs); } multi sub trait_mod:(Attribute:D $attr, :$trailing_docs!) { Rakudo::Internals.SET_TRAILING_DOCS($attr, $trailing_docs); } multi sub trait_mod:(Routine:D $r, |c) { my $subtype = c.hash.keys[0]; when $subtype eq 'cached' { # Return early for cached die X::Experimental.new: :feature :use } my @traits = &trait_mod:.candidates.grep({.signature.params.[0].type ~~ Routine}); sub trait-name(&t) { &t.signature.params[1].named_names[0] } my %info = :file($?FILE), :line($?LINE), :type, :$subtype, :declaring($r.^name.split('+').head.lc); with @traits.first({.&trait-name eq $subtype}) -> &t { my $reason = do { try t($r, |c); $!.message } die X::Comp::Trait::Invalid.new: |%info, :$reason, :name($r.gist) } else { my @expected = @traits.map(&trait-name).unique.Str.naive-word-wrapper.lines, ('',"or did you forget to 'use NativeCall'?" if $subtype eq 'native'); die X::Comp::Trait::Unknown.new: |%info, :highexpect(@expected) } } multi sub trait_mod:(Routine:D $r, :rw($)!) { $r.set_rw(); } multi sub trait_mod:(Routine:D $r, :raw($)!) { $r.set_rw(); # for now, until we have real raw handling } multi sub trait_mod:(Routine:D $r, :default($)!) { $r.^mixin: role { method default(--> True) { } } } multi sub trait_mod:(Routine:D $r, :$DEPRECATED!) { my $new := nqp::istype($DEPRECATED,Bool) ?? "something else" !! $DEPRECATED; my role is-DEPRECATED { has $.DEPRECATED } $r does is-DEPRECATED($new); $r.add_phaser( 'ENTER', -> { Rakudo::Deprecations.DEPRECATED($new) } ); } multi sub trait_mod:(Routine:D $r, Mu :$inlinable!) { $r.set_inline_info(nqp::decont($inlinable)); } multi sub trait_mod:(Routine:D $r, :onlystar($)!) { $r.set_onlystar(); } # Since trait_mod: to set onlystar isn't there at the # point we wrote its proto, we do it manually here. BEGIN &trait_mod:.set_onlystar(); multi sub trait_mod:(Parameter:D $param, |c ) { X::Comp::Trait::Unknown.new( file => $?FILE, line => $?LINE, type => 'is', subtype => c.hash.keys[0], declaring => 'a parameter', highexpect => , ).throw; } multi sub trait_mod:(Parameter:D $param, :readonly($)!) { # This is the default. } multi sub trait_mod:(Parameter:D $param, :rw($)!) { $param.set_rw(); } multi sub trait_mod:(Parameter:D $param, :copy($)!) { $param.set_copy(); } multi sub trait_mod:(Parameter:D $param, :required($)!) { $param.set_required(); } multi sub trait_mod:(Parameter:D $param, :raw($)!) { $param.set_raw(); } multi sub trait_mod:(Parameter:D $param, :onearg($)!) { $param.set_onearg(); } multi sub trait_mod:(Parameter:D $param, :$leading_docs!) { Rakudo::Internals.SET_LEADING_DOCS($param, $leading_docs); } multi sub trait_mod:(Parameter:D $param, :$trailing_docs!) { Rakudo::Internals.SET_TRAILING_DOCS($param, $trailing_docs); } # Declare these, as setting mainline doesn't get them automatically (as the # Mu/Any/Scalar are not loaded). my $!; my $/; my $_; multi sub trait_mod:(Routine:D $r, :$export!, :$SYMBOL = '&' ~ $r.name) { my $to_export := $r.multi ?? $r.dispatcher !! $r; my @tags = flat 'ALL', ( nqp::istype($export,Pair) ?? $export.key() !! nqp::istype($export,Positional) ?? @($export)>>.key !! nqp::istype($export,Bool) && $export ?? 'DEFAULT' !! die "Invalid value '$export.gist()' of type " ~ "'$export.^name()' in trait 'is export'. Use a Pair " ~ 'or a list of Pairs, with keys as tag names.' ); Rakudo::Internals.EXPORT_SYMBOL($SYMBOL, @tags, $to_export); } multi sub trait_mod:(Mu:U \type, :$export!) { my $exp_name := type.^shortname; my @tags = flat 'ALL', ( nqp::istype($export,Pair) ?? $export.key() !! nqp::istype($export,Positional) ?? @($export)>>.key !! nqp::istype($export,Bool) && $export ?? 'DEFAULT' !! die "Invalid value '$export.gist()' of type " ~ "'$export.^name()' in trait 'is export'. Use a Pair " ~ 'or a list of Pairs, with keys as tag names.' ); # If a role is being exported export its respective group instead. my \export_type := nqp::istype(type.HOW, Metamodel::ParametricRoleHOW) ?? type.^group !! type; Rakudo::Internals.EXPORT_SYMBOL($exp_name, @tags, export_type); if nqp::istype(type.HOW, Metamodel::EnumHOW) { type.^set_export_callback( { for type.^enum_values.keys -> $value_name { Rakudo::Internals.EXPORT_SYMBOL( $value_name, @tags, type.WHO{$value_name}); } }); } } # for constants multi sub trait_mod:(Mu \sym, :$export!, :$SYMBOL!) { my @tags = flat 'ALL', ( nqp::istype($export,Pair) ?? $export.key() !! nqp::istype($export,Positional) ?? @($export)>>.key !! nqp::istype($export,Bool) && $export ?? 'DEFAULT' !! die "Invalid value '$export.gist()' of type " ~ "'$export.^name()' in trait 'is export'. Use a Pair " ~ 'or a list of Pairs, with keys as tag names.' ); Rakudo::Internals.EXPORT_SYMBOL($SYMBOL, @tags, sym); } multi sub trait_mod:(Block:D $r, :$leading_docs!) { Rakudo::Internals.SET_LEADING_DOCS($r, $leading_docs); } multi sub trait_mod:(Block:D $r, :$trailing_docs!) { Rakudo::Internals.SET_TRAILING_DOCS($r, $trailing_docs); } # this should be identical to Mu:D, :leading_docs, otherwise the fallback Block:D, |c # will catch it and declare "leading_docs" to be an unknown trait. This is why # we need this redundant form in spite of having a Block:D candidate above multi sub trait_mod:(Routine:D $r, :$leading_docs!) { Rakudo::Internals.SET_LEADING_DOCS($r, $leading_docs); } multi sub trait_mod:(Routine:D $r, :$trailing_docs!) { Rakudo::Internals.SET_TRAILING_DOCS($r, $trailing_docs); } multi sub trait_mod:(Mu:U $docee, :$leading_docs!) { Rakudo::Internals.SET_LEADING_DOCS($docee, $leading_docs); } multi sub trait_mod:(Mu:U $docee, :$trailing_docs!) { Rakudo::Internals.SET_TRAILING_DOCS($docee.HOW, $trailing_docs); } proto sub trait_mod:(Mu, Mu, *%) {*} multi sub trait_mod:(Mu:U $doee, Mu:U $role) { my $how := $role.HOW; if $how.archetypes.parametric() || ($doee.HOW.archetypes.parametric && $how.archetypes.generic) { $doee.^add_role($role) } elsif $how.archetypes.composalizable() { $doee.^add_role($how.composalize($role)) } else { X::Composition::NotComposable.new( target-name => $doee.^name, composer => $role, ).throw; } } proto sub trait_mod:(Mu, Mu, *%) {*} multi sub trait_mod:(Mu:U $target, Mu:U $type) { # XXX Ensure we can do this, die if not. $target.^set_of($type); } multi sub trait_mod:(Routine:D $target, Mu:U $type) { my $sig := $target.signature; X::Redeclaration.new(what => 'return type for', symbol => $target, postfix => " (previous return type was {$sig.returns.^name})").throw if $sig.has_returns; $sig.set_returns($type); $target.^mixin(Callable.^parameterize($type)) } multi sub trait_mod:(Routine:D $r, :$implementation-detail!) { $r.^mixin( role is-implementation-detail { method is-implementation-detail(--> True) { } }) if $implementation-detail; } multi sub trait_mod:(Routine:D $r, :$hidden-from-backtrace!) { $r.^mixin( role is-hidden-from-backtrace { method is-hidden-from-backtrace(--> True) { } }) if $hidden-from-backtrace; } multi sub trait_mod:(Routine:D $r, :$hidden-from-USAGE!) { $r.^mixin( role is-hidden-from-USAGE { method is-hidden-from-USAGE(--> True) { } }) if $hidden-from-USAGE; } multi sub trait_mod:(Routine:D $r, :$pure!) { $r.^mixin( role is-pure { method is-pure (--> True) { } }) if $pure; } multi sub trait_mod:(Routine:D $r, :$nodal!) { $r.^mixin( role is-nodal { method nodal(--> True) { } }) if $nodal; } proto sub trait_mod:($, Mu, *%) {*} multi sub trait_mod:(Routine:D $target, Mu:U $type) { my $sig := $target.signature; X::Redeclaration.new(what => 'return type for', symbol => $target, postfix => " (previous return type was {$sig.returns.^name})").throw if $sig.has_returns; $sig.set_returns($type); $target.^mixin(Callable.^parameterize($type)) } proto sub trait_mod:($, $, *%) {*} multi sub trait_mod:(Attribute:D $target, $thunk) { $target does role { has $.handles; method set_handles($expr) { $!handles := $expr; } method add_delegator_method($attr: Mu $pkg, $meth_name, $call_name) { my $meth := anon method (|c) is rw { (nqp::isconcrete(self) ?? $attr.get_value(self) !! nqp::decont(nqp::getattr( nqp::decont($attr),Attribute,'$!auto_viv_container' )) )."$call_name"(|c) }; $meth.set_name($meth_name); $pkg.^add_method($meth_name, $meth); } method apply_handles($attr: Mu $pkg) { sub applier($expr) { if $expr.defined() { if nqp::istype($expr,Str) { self.add_delegator_method($pkg, $expr, $expr); } elsif nqp::istype($expr,Pair) { self.add_delegator_method($pkg, $expr.key, $expr.value); } elsif nqp::istype($expr,Positional) { for $expr.list { applier($_); } 0; } elsif nqp::istype($expr, Whatever) { $pkg.^add_fallback( -> $obj, $name { nqp::can(nqp::decont($attr.get_value: $obj), nqp::decont($name)) }, -> $obj, $name { -> $self, |c { $attr.get_value($self)."$name"(|c) } }); } elsif nqp::istype($expr, HyperWhatever) { $pkg.^add_fallback( -> $, $ --> True { }, -> $obj, $name { -> $self, |c { $attr.get_value($self)."$name"(|c) } }); } else { $pkg.^add_fallback( -> $obj, $name { ?($name ~~ $expr) }, -> $obj, $name { -> $self, |c { $attr.get_value($self)."$name"(|c) } }); } } else { $pkg.^add_fallback( -> $obj, $name { nqp::can(nqp::decont($expr), nqp::decont($name)) }, -> $obj, $name { -> $self, |c { $attr.get_value($self)."$name"(|c) } }); } } applier($!handles); } }; $target.set_handles($thunk()); } multi sub trait_mod:(Method:D $m, &thunk) { $m does role { has $.handles; has $!delegator_name; method set_handles($expr) { $!handles := $expr; } method add_delegator_method(&code_obj: Mu $pkg, $meth_name, $call_name) { my $meth := nqp::defined(my $delegator_name = $!delegator_name) ?? anon method (|c) is raw { self."$delegator_name"()."$call_name"(|c) } !! anon method (|c) is raw { &code_obj(self)."$call_name"(|c) }; $meth.set_name($meth_name); $pkg.^add_method($meth_name, $meth); } method !fallback-code(&code_obj: $name) { nqp::defined(my $delegator_name = $!delegator_name) ?? -> \SELF, |c is raw { SELF."$delegator_name"()."$name"(|c) } !! -> \SELF, |c is raw { &code_obj(SELF)."$name"(|c) } } method apply_handles(&code_obj: Mu $pkg is raw) { $!delegator_name := ($pkg.^language_revision // nqp::getcomp("Raku").language_revision) < 3 ?? &code_obj.name !! Nil; sub applier($expr) { if $expr.defined() { if nqp::istype($expr,Str) { self.add_delegator_method($pkg, $expr, $expr); } elsif nqp::istype($expr,Pair) { self.add_delegator_method($pkg, $expr.key, $expr.value); } elsif nqp::istype($expr,Positional) { for $expr.list { applier($_); } 0; } elsif nqp::istype($expr, Whatever) { $pkg.^add_fallback( -> $obj, $name { nqp::can(nqp::decont(&code_obj($obj)), nqp::decont($name)) }, -> $obj, $name { self!fallback-code($name) } ); } elsif nqp::istype($expr, HyperWhatever) { $pkg.^add_fallback( -> $, $ --> True { }, -> $obj, $name { self!fallback-code($name) } ); } else { $pkg.^add_fallback( -> $obj, $name { ?($name ~~ $expr) }, -> $obj, $name { self!fallback-code($name) } ); } } else { $pkg.^add_fallback( -> $obj, $name { nqp::can(nqp::decont($expr), nqp::decont($name)) }, -> $obj, $name { self!fallback-code($name) } ); } } applier($!handles); } } $m.set_handles(&thunk()); } proto sub trait_mod:(Mu $, |) {*} multi sub trait_mod:(Attribute:D $attr, |c ) { X::Comp::Trait::Unknown.new( file => $?FILE, line => $?LINE, type => 'will', subtype => c.hash.keys[0], declaring => 'an attribute', highexpect => , ).throw; } multi sub trait_mod:(Attribute $attr, Mu :$build!) { # internal usage $attr.set_build($build) } proto sub trait_mod:(Mu, Mu, *%) {*} multi sub trait_mod:(Mu:U $truster, Mu:U $trustee) { $truster.^add_trustee($trustee); } proto sub trait_mod:(Mu, Mu, *%) {*} multi sub trait_mod:(Mu:U $child, Mu:U $parent) { if $parent.HOW.archetypes.inheritable() { $child.^add_parent($parent, :hides); } elsif $parent.HOW.archetypes.inheritalizable() { $child.^add_parent($parent.^inheritalize, :hides) } else { X::Inheritance::Unsupported.new( :child-typename($child.^name), :$parent, ).throw; } } #line 1 SETTING::src/core.c/Positional.rakumod my role Positional[::T = Mu] { method of() { T } # These methods must be implemented by any object performing the Positional # role. The reason this is not actually activated, is that there are some # chicken-and-egg issues with building the core if we do. # method elems() { ... } # method AT-POS($) { ... } # method EXISTS-POS($) { ... } } #line 1 SETTING::src/core.c/Associative.rakumod my role Associative[::TValue = Mu, ::TKey = Str(Any)] { method of() { TValue } method keyof() { TKey } # These methods must be implemented by any object performing the Associative # role. The reason this is not actually activated, is that there are some # chicken-and-egg issues with building the core if we do. # method AT-KEY($) { ... } # method EXISTS-KEY($) { ... } } #line 1 SETTING::src/core.c/Callable.rakumod my class X::Cannot::Capture { ... } my role Callable[::T = Mu] { method of() { T } method returns() { T } method Capture() { X::Cannot::Capture.new( :what(self) ).throw } } #line 1 SETTING::src/core.c/natives.rakumod my native int is repr('P6int') is Int { } my native int8 is repr('P6int') is Int is nativesize( 8) { } my native int16 is repr('P6int') is Int is nativesize(16) { } my native int32 is repr('P6int') is Int is nativesize(32) { } my native int64 is repr('P6int') is Int is nativesize(64) { } my native uint is repr('P6int') is Int is unsigned { } my native uint8 is repr('P6int') is Int is nativesize( 8) is unsigned { } my native byte is repr('P6int') is Int is nativesize( 8) is unsigned { } my native uint16 is repr('P6int') is Int is nativesize(16) is unsigned { } my native uint32 is repr('P6int') is Int is nativesize(32) is unsigned { } my native uint64 is repr('P6int') is Int is nativesize(64) is unsigned { } my native num is repr('P6num') is Num { } my native num32 is repr('P6num') is Num is nativesize(32) { } my native num64 is repr('P6num') is Num is nativesize(64) { } my native str is repr('P6str') is Str { } #line 1 SETTING::src/core.c/stubs.rakumod # This file contains various stubs. Note that a few are created already # outside of the setting, such as Mu/Any/Cool, Attribute, Signature/Parameter, # Code/Block/Routine/Sub/Method and Str/Int/Num. They are built in BOOTSTRAP.nqp # in Perl6::Metamodel for now, though should be a BEGIN block in CORE.setting # in the end. my class Exception { ... } my class X::AdHoc { ... } my class FatRat { ... } my class Pair { ... } my class Promise { ... } my class Channel { ... } my class X::OutOfRange { ... } my class X::Dynamic::NotFound { ... } my class X::SecurityPolicy::Eval { ... } my class X::Channel::ReceiveOnClosed { ... } my role QuantHash { ... } my role Setty { ... } my class Set { ... } my class SetHash { ... } my role Baggy { ... } my class Bag { ... } my class BagHash { ... } my role Mixy { ... } my class Mix { ... } my class MixHash { ... } my class Lock is repr('ReentrantMutex') { ... } my class Lock::Async { ... } # Used by current compiler. sub DYNAMIC(str $name, @deprecation?) is raw { # is implementation-detail # Please leave this code here to be enable only for tracing calls to # dynamic variables in the setting and during setting compilation. #my $frame := callframe(1); #nqp::say($name ~ ": " ~ $frame.file ~ "(" ~ $frame.line ~ ")"); nqp::ifnull( nqp::getlexdyn($name), nqp::stmts( nqp::unless( nqp::isnull(my \promise := nqp::getlexdyn('$*PROMISE')), (my Mu \value := nqp::getlexreldyn( nqp::getattr(promise,Promise,'$!dynamic_context'),$name) ) ), nqp::ifnull( value, nqp::stmts( (my str $pkgname = nqp::replace($name,1,1,'')), nqp::ifnull( nqp::atkey(GLOBAL.WHO,$pkgname), nqp::ifnull( nqp::atkey(PROCESS.WHO,$pkgname), Rakudo::Internals.INITIALIZE-DYNAMIC($name, @deprecation) ) ) ) ) ) ) } # Used by RakuAST-based compiler. sub DYNAMIC-FALLBACK(str $name-with-star, str $name-without-star) is raw { # is implementation-detail nqp::unless( nqp::isnull(my \promise := nqp::getlexdyn('$*PROMISE')), (my Mu \value := nqp::getlexreldyn( nqp::getattr(promise,Promise,'$!dynamic_context'),$name-with-star) ) ); nqp::ifnull( value, nqp::stmts( nqp::ifnull( nqp::atkey(GLOBAL.WHO,$name-without-star), nqp::ifnull( nqp::atkey(PROCESS.WHO,$name-without-star), Rakudo::Internals.INITIALIZE-DYNAMIC($name-with-star) ) ) ) ) } # Set up ClassHOW's auto-gen proto (nested scope so it won't # actually appear in the setting). { my class Dummy { our proto method AUTOGEN-METHOD(::T $: |) {*} our proto submethod AUTOGEN-SUBMETHOD(::T $: |) {*} } Dummy.HOW.set_autogen_proto(&Dummy::AUTOGEN-METHOD, &Dummy::AUTOGEN-SUBMETHOD); } #line 1 SETTING::src/core.c/control.rakumod my class X::ControlFlow::Return { ... } my class X::Eval::NoSuchLang { ... } my class X::Multi::NoMatch { ... } my class X::NYI { ... } my class PseudoStash { ... } my class Label { ... } class CompUnit::DependencySpecification { ... } sub THROW(int $type, Mu \arg) is raw { # is implementation-detail my Mu $ex := nqp::newexception(); nqp::setpayload($ex, arg); nqp::setextype($ex, $type); nqp::throw($ex); arg; } sub THROW-NIL(int $type --> Nil) { # is implementation-detail my Mu $ex := nqp::newexception(); # nqp::setpayload($ex, Nil); nqp::setextype($ex, $type); nqp::throw($ex); } sub RETURN-LIST(Mu \list) is raw { # is implementation-detail my \reified := nqp::getattr(list, List, '$!reified'); nqp::isgt_i(nqp::elems(reified),1) ?? list !! nqp::elems(reified) ?? nqp::shift(reified) !! Nil } proto sub return-rw(|) {*} multi sub return-rw(--> Nil) { nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, Nil); } multi sub return-rw(Mu \x --> Nil) { nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, x); } multi sub return-rw(**@x is raw --> Nil) { nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, @x); } proto sub return(|) {*} multi sub return(--> Nil) { nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, Nil); } multi sub return(Mu \x --> Nil) { nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, nqp::p6recont_ro(x)); } multi sub return(**@x is raw --> Nil) { nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, @x); } proto sub take-rw(|) {*} multi sub take-rw() { die "take-rw without parameters doesn't make sense" } multi sub take-rw(\value) { my Mu $ex := nqp::newexception(); nqp::setpayload($ex,value); nqp::setextype($ex,nqp::const::CONTROL_TAKE); nqp::throw($ex); value } multi sub take-rw(|) { nqp::setpayload( (my Mu $ex := nqp::newexception), (my \out := nqp::isgt_i(nqp::elems(my $positionals := nqp::p6argvmarray),1) ?? nqp::p6bindattrinvres( nqp::create(List),List,'$!reified',$positionals) !! nqp::elems($positionals) ?? nqp::shift($positionals) !! Nil ) ); nqp::setextype($ex,nqp::const::CONTROL_TAKE); nqp::throw($ex); out } proto sub take(|) {*} multi sub take() { die "take without parameters doesn't make sense" } multi sub take(\value) { my Mu $ex := nqp::newexception(); nqp::setpayload($ex,my \out := nqp::p6recont_ro(value)); nqp::setextype($ex,nqp::const::CONTROL_TAKE); nqp::throw($ex); out } multi sub take(|) { nqp::setpayload( (my Mu $ex := nqp::newexception), (my \out := nqp::p6recont_ro( nqp::isgt_i(nqp::elems(my $positionals := nqp::p6argvmarray),1) ?? nqp::p6bindattrinvres( nqp::create(List),List,'$!reified',$positionals) !! nqp::elems($positionals) ?? nqp::shift($positionals) !! Nil )) ); nqp::setextype($ex,nqp::const::CONTROL_TAKE); nqp::throw($ex); out } proto sub goto($, *%) {*} multi sub goto(Label:D $x --> Nil) { $x.goto } proto sub last($?, *%) {*} multi sub last(--> Nil) { nqp::throwextype(nqp::const::CONTROL_LAST); Nil } multi sub last(Label:D $x --> Nil) { $x.last } proto sub next($?, *%) {*} multi sub next(--> Nil) { nqp::throwextype(nqp::const::CONTROL_NEXT); Nil } multi sub next(Label:D $x --> Nil) { $x.next } proto sub redo($?, *%) {*} multi sub redo(--> Nil) { nqp::throwextype(nqp::const::CONTROL_REDO); Nil } multi sub redo(Label:D $x --> Nil) { $x.redo } proto sub succeed(|) {*} multi sub succeed(--> Nil) { THROW-NIL(nqp::const::CONTROL_SUCCEED) } multi sub succeed(\x --> Nil) { THROW(nqp::const::CONTROL_SUCCEED, x) } multi sub succeed(| --> Nil) { THROW(nqp::const::CONTROL_SUCCEED,RETURN-LIST(nqp::p6argvmarray)) } sub proceed(--> Nil) { THROW-NIL(nqp::const::CONTROL_PROCEED) } sub callwith(|c) is raw { $/ := nqp::getlexcaller('$/'); # TODO Future mechanism to avoid having to flatten here nqp::dispatch('boot-resume-caller', nqp::const::DISP_CALLWITH, |c) } sub nextwith(|c) is raw { $/ := nqp::getlexcaller('$/'); # TODO Future mechanism to avoid having to flatten here nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, nqp::dispatch('boot-resume-caller', nqp::const::DISP_CALLWITH, |c)) } sub callsame() is raw { $/ := nqp::getlexcaller('$/'); nqp::dispatch('boot-resume-caller', nqp::const::DISP_CALLSAME) } sub nextsame() is raw { $/ := nqp::getlexcaller('$/'); nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, nqp::dispatch('boot-resume-caller', nqp::const::DISP_CALLSAME)) } sub lastcall(--> True) { nqp::dispatch('boot-resume-caller', nqp::const::DISP_LASTCALL) } sub nextcallee() { nqp::dispatch('boot-resume-caller', nqp::const::DISP_NEXTCALLEE) } sub samewith(|c) { $/ := nqp::getlexcaller('$/'); my Mu $ctx := nqp::ctxcaller(nqp::ctx()); until nqp::isnull($ctx) { my $caller := nqp::getcodeobj(nqp::ctxcode($ctx)); if nqp::istype($caller, Routine) { if $caller.multi { die "Could not find dispatcher" unless my $dispatcher := nqp::can($caller,"dispatcher") && $caller.dispatcher; return nqp::istype($caller, Method) ?? $dispatcher(nqp::atkey($ctx, 'self') // $caller.package,|c) !! $dispatcher(|c); } else { return $caller(|c); } } $ctx := nqp::ctxouter($ctx); } die "Cannot use samewith outside of a routine"; } sub leave(|) { NYI('leave').throw; } sub emit(Mu \value --> Nil) { my Mu $ex := nqp::newexception(); nqp::setpayload($ex,nqp::p6recont_ro(value)); nqp::setextype($ex,nqp::const::CONTROL_EMIT); nqp::throw($ex); } proto sub done(|) {*} multi sub done(--> Nil) { THROW-NIL(nqp::const::CONTROL_DONE); } multi sub done(Mu \value --> Nil) { emit value; done; } proto sub die(|) {*}; multi sub die(--> Nil) { my $stash := CALLER::LEXICAL::; my $payload = $stash<$!>.DEFINITE ?? $stash<$!> !! "Died"; $payload ~~ Exception ?? $payload.throw !! X::AdHoc.new(:$payload).throw } multi sub die(Exception:U $e --> Nil) { X::AdHoc.new(:payload("Died with undefined " ~ $e.^name)).throw; } multi sub die($payload --> Nil) { $payload ~~ Exception ?? $payload.throw !! X::AdHoc.new(:$payload).throw } multi sub die(|cap ( *@msg ) --> Nil) { X::AdHoc.from-slurpy(|cap).throw } proto sub warn(|) {*} multi sub warn(*@msg) { my $msg := @msg.join || "Warning: something's wrong"; my $ex := nqp::newexception(); nqp::setmessage($ex, nqp::unbox_s($msg)); nqp::setextype($ex, nqp::const::CONTROL_WARN); nqp::throw($ex); 0; } multi sub warn(Junction:D $j) { $j.THREAD: &warn } constant Inf = nqp::p6box_n(nqp::inf()); constant NaN = nqp::p6box_n(nqp::nan()); # For some reason, we cannot move this to Rakudo::Internals as a class # method, because then the return value is always HLLized :-( sub CLONE-HASH-DECONTAINERIZED(\hash) { # is implementation-detail my \clone := nqp::hash; my \iter := nqp::iterator(nqp::getattr(hash,Map,'$!storage')); nqp::while( iter, nqp::bindkey(clone, nqp::iterkey_s(nqp::shift(iter)), nqp::if( nqp::defined(nqp::iterval(iter)), nqp::decont(nqp::iterval(iter)).Str, '' ) ) ); clone } sub CLONE-LIST-DECONTAINERIZED(*@list) { # is implementation-detail my Mu \list-without := nqp::list(); nqp::push(list-without, nqp::decont(~$_)) for @list.eager; list-without; } #line 1 SETTING::src/core.c/Mu.rakumod my class X::Constructor::Positional { ... } my class X::Method::NotFound { ... } my class X::Method::InvalidQualifier { ... } my class X::Attribute::Required { ... } my class WalkList { ... } my class Mu { # declared in BOOTSTRAP method self { self } method sink(--> Nil) { } proto method perl(|) {*} multi method perl(Mu \SELF: |c) { SELF.raku(|c) } # although technically not a documented method, some module authors have # used this in the ecosystem. method perlseen(Mu \SELF: |c) { SELF.rakuseen(|c) } proto method ACCEPTS(|) {*} multi method ACCEPTS(Mu:U: Mu \topic) { nqp::hllbool(nqp::istype(topic, self)) } # Typically, junctions shouldn't be typechecked literally. There are # exceptions though, such as Junction in particular, so this probably # shouldn't be handled by the compiler itself. Having a default ACCEPTS # candidate to handle junctions allows them to get threaded as they should # while preserving compatibility with existing code that has any ACCEPTS # candidates for Mu or Junction. multi method ACCEPTS(Mu:U \SELF: Junction:D \topic) is default { topic.THREAD: { SELF.ACCEPTS: $_ } } # Mostly for introspection purposes, and to allow foo."$bar"() syntax method HOW(Mu \SELF:) { nqp::how(SELF) } method VAR(Mu \SELF:) { nqp::p6var(SELF) } method WHAT(Mu \SELF:) { nqp::what(SELF) } method WHERE(Mu \SELF:) { nqp::where(SELF) } method WHO(Mu \SELF:) { nqp::who(SELF) } proto method WHICH(|) {*} multi method WHICH(Mu:U: --> ValueObjAt:D) { nqp::box_s( nqp::concat( nqp::concat(nqp::unbox_s(self.^name), '|U'), nqp::objectid(self) ), ValueObjAt ) } multi method WHICH(Mu:D: --> ObjAt:D) { nqp::box_s( nqp::concat( nqp::concat(nqp::unbox_s(self.^name), '|'), nqp::objectid(self) ), ObjAt ) } proto method iterator(|) {*} multi method iterator(Mu:) { Rakudo::Iterator.OneValue(self) } proto method split(|) {*} method emit { emit self; } method take { take self; } method return-rw(Mu \SELF: |) { # same code as control.rakumod's return-rw my $list := RETURN-LIST(nqp::p6argvmarray()); nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, $list); $list; } method return(|) { # same code as control.rakumod's return my $list := RETURN-LIST(nqp::p6argvmarray()); nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, nqp::p6recont_ro($list)); $list; } proto method WHY(|) {*} multi method WHY(Mu:) { my Mu $why; my role Suggestion[\object] { method gist { my $what; my $where; if nqp::isconcrete(object) && nqp::can(object,"name") { $what := object.name; $where := "routine/$what".subst('<', '<', :g).subst('>', '>', :g); } else { $what := object.^name; $where := "type/$what"; } (CORE::{$what}:exists ?? "Sorry, no documentation is attached to $what." ~ " Perhaps it can be found at https://docs.raku.org/$where.html" !! "Sorry, no documentation is attached to $what." ).naive-word-wrapper } } if nqp::can(self.HOW, 'WHY') { $why := self.HOW.WHY; } if $why.defined && !$.defined #`(ie. we're a type object) { $why.set_docee(self); } $why // Nil but Suggestion[self] } method set_why($why) { self.HOW.set_why($why); } proto method Bool() {*} multi method Bool(Mu:U: --> False) { } multi method Bool(Mu:D:) { self.defined } method so() { self.Bool } method not() { self ?? False !! True } proto method defined(|) {*} multi method defined(Mu:U: --> False) { } multi method defined(Mu:D: --> True) { } proto method new(|) {*} multi method new(*%attrinit) { nqp::eqaddr((my $bless := nqp::tryfindmethod(self,'bless')), nqp::findmethod(Mu,'bless')) ?? nqp::create(self).BUILDALL(Empty, %attrinit) !! $bless(self,|%attrinit) } multi method new($, *@) { X::Constructor::Positional.new(:type( self )).throw(); } proto method is-lazy (|) {*} multi method is-lazy(Mu: --> False) { } method CREATE() { nqp::create(self) } method bless(*%attrinit) { nqp::create(self).BUILDALL(Empty, %attrinit); } method BUILDALL(Mu:D: @autovivs, %attrinit) { my $init := nqp::getattr(%attrinit,Map,'$!storage'); # Get the build plan. Note that we do this "low level" to # avoid the NQP type getting mapped to a Rakudo one, which # would get expensive. my $bp := nqp::findmethod(self.HOW,'BUILDALLPLAN')(self.HOW, self); my int $count = nqp::elems($bp); my int $i = -1; nqp::while( nqp::islt_i(++$i,$count), nqp::if( nqp::istype((my $task := nqp::atpos($bp,$i)),Callable), nqp::if( # BUILD/TWEAK nqp::istype( (my $build := nqp::if( nqp::elems($init), $task(self,|%attrinit), $task(self) )), Failure ), return $build ), nqp::if( # not just calling (my int $code = nqp::atpos($task,0)), nqp::if( # >0 nqp::islt_i($code,100), nqp::if( # 1|2|3|10 nqp::existskey($init,nqp::atpos($task,3)), nqp::if( # can initialize nqp::iseq_i($code,1), nqp::bindattr_i(self, # 1 nqp::atpos($task,1), nqp::atpos($task,2), nqp::decont(%attrinit.AT-KEY(nqp::atpos($task,3))) ), nqp::if( nqp::iseq_i($code,2), nqp::bindattr_n(self, # 2 nqp::atpos($task,1), nqp::atpos($task,2), nqp::decont(%attrinit.AT-KEY(nqp::atpos($task,3))) ), nqp::if( # can initialize nqp::iseq_i($code,10), nqp::bindattr_u(self, # 10 nqp::atpos($task,1), nqp::atpos($task,2), nqp::decont(%attrinit.AT-KEY(nqp::atpos($task,3))) ), nqp::bindattr_s(self, # 3 nqp::atpos($task,1), nqp::atpos($task,2), nqp::decont(%attrinit.AT-KEY(nqp::atpos($task,3))) ) ) ) ) ), nqp::if( nqp::iseq_i($code,400), nqp::unless( # 400 nqp::p6attrinited( nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) ) ), nqp::if( nqp::istype(nqp::atpos($task,3),Block), nqp::stmts( (my \attr := nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) )), (attr = nqp::atpos($task,3)(self,attr)) ), nqp::getattr(self,nqp::atpos($task,1),nqp::atpos($task,2)) = nqp::atpos($task,3) ) ), nqp::if( nqp::iseq_i($code,401), nqp::if( # 401 nqp::iseq_i(my int $int = nqp::getattr_i(self, nqp::atpos($task,1), nqp::atpos($task,2) ), 0), nqp::bindattr_i(self, nqp::atpos($task,1), nqp::atpos($task,2), nqp::if( nqp::istype(nqp::atpos($task,3),Block), (nqp::atpos($task,3)(self,$int)), nqp::atpos($task,3) ) ) ), nqp::if( nqp::iseq_i($code,402), nqp::if( # 402 nqp::iseq_n(my num $num = nqp::getattr_n(self, nqp::atpos($task,1), nqp::atpos($task,2) ), 0e0), nqp::bindattr_n(self, nqp::atpos($task,1), nqp::atpos($task,2), nqp::if( nqp::istype(nqp::atpos($task,3),Block), (nqp::atpos($task,3)(self,$num)), nqp::atpos($task,3) ) ) ), nqp::if( nqp::iseq_i($code,403), nqp::if( # 403 nqp::isnull_s(my str $str = nqp::getattr_s(self, nqp::atpos($task,1), nqp::atpos($task,2) )), nqp::bindattr_s(self, nqp::atpos($task,1), nqp::atpos($task,2), nqp::if( nqp::istype(nqp::atpos($task,3),Block), (nqp::atpos($task,3)(self,$str)), nqp::atpos($task,3) ) ) ), nqp::if( nqp::iseq_i($code,410), nqp::if( # 410 nqp::iseq_i(my int $uint = nqp::getattr_u(self, nqp::atpos($task,1), nqp::atpos($task,2) ), 0), nqp::bindattr_i(self, nqp::atpos($task,1), nqp::atpos($task,2), nqp::if( nqp::istype(nqp::atpos($task,3),Block), (nqp::atpos($task,3)(self,$uint)), nqp::atpos($task,3) ) ) ), nqp::if( nqp::iseq_i($code,800), nqp::unless( # 800 nqp::p6attrinited( nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) ) ), X::Attribute::Required.new( name => nqp::atpos($task,2), why => nqp::atpos($task,3) ).throw ), nqp::if( nqp::iseq_i($code,900), nqp::bindattr(self, # 900 nqp::atpos($task,1), nqp::atpos($task,2), (nqp::atpos($task,3)()) ), nqp::if( nqp::iseq_i($code,1100), nqp::if( # 1100 nqp::existskey($init,nqp::atpos($task,3)), (nqp::getattr(self, nqp::atpos($task,1),nqp::atpos($task,2)) = %attrinit.AT-KEY(nqp::atpos($task,3))), nqp::bindattr(self, nqp::atpos($task,1),nqp::atpos($task,2), nqp::list ) ), nqp::if( nqp::iseq_i($code,1200), nqp::if( # 1200 nqp::existskey($init,nqp::atpos($task,3)), (nqp::getattr(self, nqp::atpos($task,1),nqp::atpos($task,2)) = %attrinit.AT-KEY(nqp::atpos($task,3))), nqp::bindattr(self, nqp::atpos($task,1),nqp::atpos($task,2), nqp::hash ) ), nqp::if( nqp::iseq_i($code,1300), nqp::if( # 1300 nqp::existskey($init,nqp::atpos($task,3)), nqp::bindattr(self, nqp::atpos($task,1),nqp::atpos($task,2), nqp::if( nqp::elems($task) == 5, nqp::p6bindassert( %attrinit.AT-KEY(nqp::atpos($task,3)), nqp::atpos($task,4)), %attrinit.AT-KEY(nqp::atpos($task,3)) ) ) ), nqp::if( nqp::iseq_i($code,1400), nqp::unless( # 1400 nqp::p6attrinited( nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) ) ), nqp::bindattr(self, nqp::atpos($task,1), nqp::atpos($task,2), nqp::if( nqp::istype(nqp::atpos($task,3),Block), nqp::if( nqp::elems($task) == 5, nqp::p6bindassert( nqp::atpos($task,3)(self, nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) )), nqp::atpos($task,4) ), nqp::atpos($task,3)(self, nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) )), ), nqp::atpos($task,3) ) ) ), nqp::if( nqp::iseq_i($code,1501), nqp::unless( # 1501 nqp::getattr_i(self, nqp::atpos($task,1), nqp::atpos($task,2) ), X::Attribute::Required.new( name => nqp::atpos($task,2), why => nqp::atpos($task,3) ).throw ), nqp::if( nqp::iseq_i($code,1502), nqp::unless( # 1502 nqp::getattr_n(self, nqp::atpos($task,1), nqp::atpos($task,2) ), X::Attribute::Required.new( name => nqp::atpos($task,2), why => nqp::atpos($task,3) ).throw ), nqp::if( nqp::iseq_i($code,1503), nqp::if( # 1503 nqp::isnull_s(nqp::getattr_s(self, nqp::atpos($task,1), nqp::atpos($task,2) )), X::Attribute::Required.new( name => nqp::atpos($task,2), why => nqp::atpos($task,3) ).throw ), nqp::if( nqp::iseq_i($code,1510), nqp::unless( # 1510 nqp::getattr_u(self, nqp::atpos($task,1), nqp::atpos($task,2) ), X::Attribute::Required.new( name => nqp::atpos($task,2), why => nqp::atpos($task,3) ).throw ), die('Invalid ' ~ self.^name ~ ".BUILDALL plan: $code"), )))))))))))))))), nqp::if( # 0 nqp::existskey($init,nqp::atpos($task,3)), (nqp::getattr(self,nqp::atpos($task,1),nqp::atpos($task,2)) = %attrinit.AT-KEY(nqp::atpos($task,3))) ) ) ) ); self } method BUILD_LEAST_DERIVED(%attrinit) { my $init := nqp::getattr(%attrinit,Map,'$!storage'); # Get the build plan for just this class. my $bp := nqp::findmethod(self.HOW,'BUILDPLAN')(self.HOW,self); my int $count = nqp::elems($bp); my int $i = -1; nqp::while( nqp::islt_i(++$i,$count), nqp::if( nqp::istype((my $task := nqp::atpos($bp,$i)),Callable), nqp::if( # BUILD/TWEAK nqp::istype( (my $build := nqp::if( nqp::elems($init), $task(self,|%attrinit), $task(self) )), Failure ), return $build ), nqp::if( # not just calling (my int $code = nqp::atpos($task,0)), nqp::if( # >0 nqp::islt_i($code,100), nqp::if( # 1|2|3|10 nqp::existskey($init,nqp::atpos($task,3)), nqp::if( # can initialize nqp::iseq_i($code,1), nqp::bindattr_i(self, # 1 nqp::atpos($task,1), nqp::atpos($task,2), nqp::decont(%attrinit.AT-KEY(nqp::atpos($task,3))) ), nqp::if( nqp::iseq_i($code,2), nqp::bindattr_n(self, # 2 nqp::atpos($task,1), nqp::atpos($task,2), nqp::decont(%attrinit.AT-KEY(nqp::atpos($task,3))) ), nqp::if( nqp::iseq_i($code,10), nqp::bindattr_u(self, # 10 nqp::atpos($task,1), nqp::atpos($task,2), nqp::decont(%attrinit.AT-KEY(nqp::atpos($task,3))) ), nqp::bindattr_s(self, # 3 nqp::atpos($task,1), nqp::atpos($task,2), nqp::decont(%attrinit.AT-KEY(nqp::atpos($task,3))) ) ) ) ) ), nqp::if( nqp::iseq_i($code,400), nqp::unless( # 400 nqp::p6attrinited( nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) ) ), nqp::if( nqp::istype(nqp::atpos($task,3),Block), nqp::stmts( (my \attr := nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) )), (attr = nqp::atpos($task,3)(self,attr)) ), nqp::getattr(self,nqp::atpos($task,1),nqp::atpos($task,2)) = nqp::atpos($task,3) ) ), nqp::if( nqp::iseq_i($code,401), nqp::if( # 401 nqp::iseq_i(my int $int = nqp::getattr_i(self, nqp::atpos($task,1), nqp::atpos($task,2) ), 0), nqp::bindattr_i(self, nqp::atpos($task,1), nqp::atpos($task,2), nqp::if( nqp::istype(nqp::atpos($task,3),Block), (nqp::atpos($task,3)(self,$int)), nqp::atpos($task,3) ) ) ), nqp::if( nqp::iseq_i($code,402), nqp::if( # 402 nqp::iseq_n(my num $num = nqp::getattr_n(self, nqp::atpos($task,1), nqp::atpos($task,2) ), 0e0), nqp::bindattr_n(self, nqp::atpos($task,1), nqp::atpos($task,2), nqp::if( nqp::istype(nqp::atpos($task,3),Block), (nqp::atpos($task,3)(self,$num)), nqp::atpos($task,3) ) ) ), nqp::if( nqp::iseq_i($code,403), nqp::if( # 403 nqp::isnull_s(my str $str = nqp::getattr_s(self, nqp::atpos($task,1), nqp::atpos($task,2) )), nqp::bindattr_s(self, nqp::atpos($task,1), nqp::atpos($task,2), nqp::if( nqp::istype(nqp::atpos($task,3),Block), (nqp::atpos($task,3)(self,$str)), nqp::atpos($task,3) ) ) ), nqp::if( nqp::iseq_i($code,410), nqp::if( # 410 nqp::iseq_i(my int $uint = nqp::getattr_i(self, nqp::atpos($task,1), nqp::atpos($task,2) ), 0), nqp::bindattr_u(self, nqp::atpos($task,1), nqp::atpos($task,2), nqp::if( nqp::istype(nqp::atpos($task,3),Block), (nqp::atpos($task,3)(self,$uint)), nqp::atpos($task,3) ) ) ), nqp::if( nqp::iseq_i($code,800), nqp::unless( # 800 nqp::p6attrinited( nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) ) ), X::Attribute::Required.new( name => nqp::atpos($task,2), why => nqp::atpos($task,3) ).throw ), nqp::if( nqp::iseq_i($code,900), nqp::bindattr(self, # 900 nqp::atpos($task,1), nqp::atpos($task,2), (nqp::atpos($task,3)()) ), nqp::if( nqp::iseq_i($code,1000), # Force vivification, for the sake of meta-object # mix-ins at compile time ending up with correctly # shared containers. nqp::stmts( # 1000 nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) ), nqp::while( # 1000's flock together nqp::islt_i(++$i,$count) && nqp::islist($task := nqp::atpos($bp,$i)) && nqp::iseq_i(nqp::atpos($task,0),1000), nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) ) ), --$i, ), nqp::if( nqp::iseq_i($code,1100), nqp::if( # 1100 nqp::existskey($init,nqp::atpos($task,3)), (nqp::getattr(self, nqp::atpos($task,1),nqp::atpos($task,2)) = %attrinit.AT-KEY(nqp::atpos($task,3))), nqp::bindattr(self, nqp::atpos($task,1),nqp::atpos($task,2), nqp::list ) ), nqp::if( nqp::iseq_i($code,1200), nqp::if( # 1200 nqp::existskey($init,nqp::atpos($task,3)), (nqp::getattr(self, nqp::atpos($task,1),nqp::atpos($task,2)) = %attrinit.AT-KEY(nqp::atpos($task,3))), nqp::bindattr(self, nqp::atpos($task,1),nqp::atpos($task,2), nqp::hash ) ), nqp::if( nqp::iseq_i($code,1300), nqp::if( # 1300 nqp::existskey($init,nqp::atpos($task,3)), nqp::bindattr(self, nqp::atpos($task,1),nqp::atpos($task,2), nqp::if( nqp::elems($task) == 5, nqp::p6bindassert( %attrinit.AT-KEY(nqp::atpos($task,3)), nqp::atpos($task,4)), %attrinit.AT-KEY(nqp::atpos($task,3)) ) ) ), nqp::if( nqp::iseq_i($code,1400), nqp::unless( # 1400 nqp::p6attrinited( nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) ) ), nqp::bindattr(self, nqp::atpos($task,1),nqp::atpos($task,2), nqp::if( nqp::istype( nqp::atpos($task,3),Block), nqp::if( nqp::elems($task) == 5, nqp::p6bindassert( nqp::atpos($task,3)(self, nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) )), nqp::atpos($task,4) ), nqp::atpos($task,3)(self, nqp::getattr(self, nqp::atpos($task,1), nqp::atpos($task,2) )), ), nqp::atpos($task,3) ) ) ), die('Invalid ' ~ self.^name ~ ".BUILD_LEAST_DERIVED plan: $code"), ))))))))))))), nqp::if( # 0 nqp::existskey($init,nqp::atpos($task,3)), (nqp::getattr(self,nqp::atpos($task,1),nqp::atpos($task,2)) = %attrinit.AT-KEY(nqp::atpos($task,3))), ) ) ) ); self } proto method Numeric(|) {*} multi method Numeric(Mu:U \v:) { warn "Use of uninitialized value of type {self.^name} in numeric context"; 0 } proto method Real(|) {*} multi method Real(Mu:U \v:) { warn "Use of uninitialized value of type {self.^name} in numeric context"; 0 } proto method Int(|) {*} multi method Int(Mu:U \v:) { warn "Use of uninitialized value of type {self.^name} in numeric context"; 0 } proto method Str(|) {*} multi method Str(Mu:U \v:) { my $name = (defined($*VAR_NAME) ?? $*VAR_NAME !! try v.VAR.name) // ''; $name ~= ' ' if $name ne ''; warn "Use of uninitialized value {$name}of type {self.^name} in string" ~ " context.\nMethods .^name, .raku, .gist, or .say can be" ~ " used to stringify it to something meaningful."; '' } multi method Str(Mu:D:) { nqp::eqaddr(self,IterationEnd) ?? "IterationEnd" !! self.^name ~ '<' ~ nqp::tostr_I(nqp::objectid(self)) ~ '>' } proto method Stringy(|) {*} multi method Stringy(Mu:U \v:) { my $*VAR_NAME = try v.VAR.name; self.Str } multi method Stringy(Mu:D $:) { self.Str } method item(Mu \item:) is raw { item } proto method say(|) {*} proto method put(|) {*} proto method note(|) {*} proto method print(|) {*} # Handle the typical "foo.say" multi method say() { my $method := self.^find_method("print"); if nqp::not_i(nqp::istype($method,Mu)) # an NQP routine || nqp::eqaddr($method.package,Mu) { # no own print method, use $*OUT $_ := $*OUT; .print(nqp::concat(self.gist,.nl-out)) } # has its own .print, let it handle the empty case else { self.print(self.nl-out) } } # Fallback for classes that act as $*OUT / $*ERR, but which do not have # a .say method themselves. multi method say(\x) { self.print: nqp::concat(x.gist,self.nl-out) } multi method say(|) { my \args := nqp::p6argvmarray; nqp::shift(args); # lose self my $parts := Rakudo::Internals.GistList2list_s(args); nqp::push_s($parts,self.nl-out); self.print: nqp::join("",$parts) } # Handle the typical "foo.put" multi method put() { my $method := self.^find_method("print"); if nqp::not_i(nqp::istype($method,Mu)) # an NQP routine || nqp::eqaddr($method.package,Mu) { # no own print method, use $*OUT $_ := $*OUT; .print(nqp::concat(self.Str,.nl-out)) } # has its own .print, let it handle the empty case else { self.print(self.nl-out) } } # Fallback for classes that act as $*OUT / $*ERR, but which do not have # a .put method themselves. multi method put(\x) { self.print: nqp::concat(x.Str,self.nl-out) } multi method put(|) { my \args := nqp::p6argvmarray; nqp::shift(args); # lose self my $parts := Rakudo::Internals.StrList2list_s(args); nqp::push_s($parts,self.nl-out); self.print: nqp::join("",$parts) } # Handle the typical "foo.note" multi method note() { my $method := self.^find_method("print"); if nqp::not_i(nqp::istype($method,Mu)) # an NQP routine || nqp::eqaddr($method.package,Mu) { # no own print method, use $*ERR $_ := $*ERR; .print(nqp::concat(self.gist,.nl-out)) } # has its own .print, let it handle the empty case else { self.print(self.nl-out) } } # Handle the typical "foo.print" multi method print() { $*OUT.print: self.Str } method gistseen(Mu:D \SELF: $id, $gist, *%named) { if nqp::not_i(nqp::isnull(nqp::getlexdyn('$*gistseen'))) { my \sems := $*gistseen; my str $WHICH = nqp::unbox_s(self.WHICH); if nqp::existskey(sems,$WHICH) && nqp::atkey(sems,$WHICH) { nqp::bindkey(sems,$WHICH,2); "{$id}_{nqp::objectid(SELF)}"; } else { nqp::bindkey(sems,$WHICH,1); my $result := $gist(|%named); my int $value = nqp::atkey(sems,$WHICH); nqp::deletekey(sems,$WHICH); $value == 2 ?? "(\\{$id}_{nqp::objectid(SELF)} = $result)" !! $result } } else { my $*gistseen := nqp::hash("TOP",1); SELF.gistseen($id,$gist,|%named) } } proto method gist(|) {*} multi method gist(Mu:U:) { '(' ~ self.^shortname ~ ')' } multi method gist(Mu:D:) { self.raku } method rakuseen(Mu:D \SELF: $id, $raku, *%named) { my $sigil = nqp::iseq_s($id, 'Array') ?? '@' !! nqp::iseq_s($id, 'Hash') ?? '%' !! '\\'; if nqp::not_i(nqp::isnull(nqp::getlexdyn('$*rakuseen'))) { my \sems := $*rakuseen; my str $WHICH = nqp::unbox_s(self.WHICH); if nqp::existskey(sems,$WHICH) && nqp::atkey(sems,$WHICH) { nqp::bindkey(sems,$WHICH,2); $sigil x nqp::isne_s($sigil, '\\') ~ "{$id}_{nqp::objectid(SELF)}"; } else { nqp::bindkey(sems,$WHICH,1); my $result := $raku(|%named); my int $value = nqp::atkey(sems,$WHICH); nqp::deletekey(sems,$WHICH); $value == 2 ?? nqp::iseq_s($sigil, '\\') ?? "(my {$sigil}{$id}_{nqp::objectid(SELF)} = $result)" !! "((my {$sigil}{$id}_{nqp::objectid(SELF)}) = $result)" !! $result } } else { my $*rakuseen := nqp::hash("TOP",1); SELF.rakuseen($id,$raku,|%named) } } proto method raku(|) {*} multi method raku(Mu:U:) { nqp::eqaddr(self.^find_method("perl").package,Mu) ?? self.^name !! self.perl } multi method raku(Mu:D:) { nqp::eqaddr(self,IterationEnd) ?? "IterationEnd" !! nqp::iscont(self) # a Proxy object would have a conted `self` ?? nqp::decont(self).raku !! nqp::eqaddr((my $proto := self.^find_method("perl")).package,Mu) && $proto.dispatchees == 1 ?? self!default-raku !! self.perl # class has dedicated old-style .perl } method !default-raku() { self.rakuseen: self.^name, { if self.^attributes.map( { nqp::concat( nqp::substr(.Str,2), nqp::concat(' => ',.get_value(self).raku) ) if .is_built; } ).join(', ') -> $attributes { self.^name ~ '.new(' ~ $attributes ~ ')' } else { self.^name ~ '.new' } } } proto method DUMP(|) {*} # is implementation-detail multi method DUMP(Mu:U:) { self.raku } multi method DUMP(Mu:D: :$indent-step = 4, :%ctx?) { return DUMP(self, :$indent-step) unless %ctx; my Mu $attrs := nqp::list(); for self.^attributes.flat -> $attr { my str $name = $attr.name; my str $acc_name = nqp::substr($name, 2, nqp::chars($name) - 2); my str $build_name = $attr.has_accessor ?? $acc_name !! $name; my Mu $value; if $attr.has_accessor { $value := self."$acc_name"(); } elsif nqp::can($attr, 'get_value') { $value := $attr.get_value(self); } elsif nqp::can($attr, 'package') { my Mu $package := $attr.package; $value := do given nqp::p6box_i(nqp::objprimspec($attr.type)) { when 0 { nqp::getattr( self,$package,$name) } when 1 { nqp::p6box_i(nqp::getattr_i(self,$package,$name)) } when 2 { nqp::p6box_n(nqp::getattr_n(self,$package,$name)) } when 3 { nqp::p6box_s(nqp::getattr_s(self,$package,$name)) } }; } else { next; } nqp::push($attrs, $build_name); nqp::push($attrs, $value); } self.DUMP-OBJECT-ATTRS($attrs, :$indent-step, :%ctx); } method DUMP-PIECES( @pieces: $before, $after = ')', :$indent = @pieces > 1, :$indent-step ) { # is implementation-detail $indent ?? $before ~ "\n" ~ @pieces.join(",\n").indent($indent-step) ~ "\n" ~ $after !! $before ~ @pieces.join(', ') ~ $after; } method DUMP-OBJECT-ATTRS( |args (*@args, :$indent-step, :%ctx, :$flags?) ) { # is implementation-detail my Mu $attrs := nqp::clone(nqp::captureposarg(nqp::usecapture(), 1)); my str $where = nqp::base_I(nqp::where(self), 16); my str $before = ($flags if defined $flags) ~ self.^name ~ '<' ~ %ctx{$where} ~ '>('; my @pieces; while $attrs { my str $name = nqp::shift($attrs); my Mu $value := nqp::shift($attrs); @pieces.push: ':' ~ $name ~ '(' ~ DUMP($value, :$indent-step, :%ctx) ~ ')'; } @pieces.DUMP-PIECES($before, :$indent-step); } proto method isa(|) {*} multi method isa(Mu \SELF: Mu $type --> Bool:D) { nqp::hllbool(SELF.^isa($type.WHAT)) } multi method isa(Mu \SELF: Str:D $name --> Bool:D) { return True if .^name eq $name for SELF.^mro; False } method does(Mu \SELF: Mu $type) { nqp::hllbool(nqp::istype(SELF, $type.WHAT)) } method can(Mu \SELF: $name) { SELF.^can($name) } proto method clone (|) {*} multi method clone(Mu:U: *%twiddles) { %twiddles and die 'Cannot set attribute values when cloning a type object'; self } multi method clone(Mu:D: *%twiddles) { my $cloned := nqp::clone(self); if %twiddles.elems { for self.^attributes.flat -> $attr { my $name := $attr.name; my $package := $attr.package; nqp::bindattr($cloned, $package, $name, nqp::clone_nd(nqp::getattr($cloned, $package, $name)) ) unless nqp::objprimspec($attr.type); my $acc_name := substr($name,2); nqp::getattr($cloned, $package, $name) = nqp::decont(%twiddles{$acc_name}) if $attr.has_accessor && %twiddles.EXISTS-KEY($acc_name); } } else { for self.^attributes.flat -> $attr { unless nqp::objprimspec($attr.type) { my $name := $attr.name; my $package := $attr.package; my $attr_val := nqp::getattr($cloned, $package, $name); nqp::bindattr($cloned, $package, $name, nqp::clone_nd($attr_val)) if nqp::iscont($attr_val); } } } $cloned } method Capture() { my $attrs := nqp::hash; for self.^attributes.flat -> $attr { if $attr.has_accessor { my str $name = substr($attr.name,2); nqp::bindkey($attrs,$name,self."$name"()) unless nqp::existskey($attrs,$name); } } my $capture := nqp::create(Capture); nqp::bindattr($capture,Capture,'%!hash',$attrs) if nqp::elems($attrs); $capture } # Various of the following dispatch methods are not called in situations # where the compiler can rewrite them into a cheaper form. # XXX TODO: Handle positional case. method dispatch:(Mu \SELF: $var, |c) is raw { # We put a `return` here to make sure we do the right thing if $var # happens to be &fail. return $var(SELF, |c) } method dispatch:<::>(Mu \SELF: $name, Mu $type, |c) is raw { my $meth; my $ctx := nqp::ctxcaller(nqp::ctx()); if nqp::istype(self, $type) { my $sym-found := 0; my $caller-type; repeat { my $pad := nqp::ctxlexpad($ctx); for <$?CONCRETIZATION $?CLASS> { if nqp::existskey($pad, $_) { $caller-type := nqp::atkey($pad, $_); $sym-found := 1; last; } } $ctx := nqp::ctxouterskipthunks($ctx); } while $ctx && !$sym-found; $meth = $caller-type.^find_method_qualified($type, $name) if $sym-found && nqp::istype($caller-type, $type); $meth = self.^find_method_qualified($type, $name) unless $meth; } unless nqp::defined($meth) { X::Method::InvalidQualifier.new( method => $name, invocant => SELF, qualifier-type => $type, ).throw; } $meth(SELF, |c) } method dispatch:(Mu \SELF: \name, Mu \type, |c) is raw { my $meth := type.^find_private_method(name); $meth ?? $meth(SELF, |c) !! X::Method::NotFound.new( invocant => SELF, method => name, typename => type.^name, :private, :in-class-call(nqp::eqaddr(nqp::what(SELF), nqp::getlexcaller('$?CLASS'))), :containerized(nqp::iscont(SELF)), ).throw; } method dispatch:<.=>(\mutate: Str() $name, |c) is raw { $/ := nqp::getlexcaller('$/'); mutate = mutate."$name"(|c) } method dispatch:<.?>(Mu \SELF: Str() $name, |c) is raw { nqp::can(SELF,$name) ?? SELF."$name"(|c) !! Nil } method !batch-call(Mu \SELF: \name, Capture:D \c, :$throw = False, :$reverse = False, :$roles = False) { my @mro := SELF.^mro(concretizations => $roles); my $results := nqp::create(IterationBuffer); my int $mro_high = $reverse ?? 0 !! @mro.elems - 1; my int $i = @mro.elems; while nqp::isge_i(--$i, 0) { my int $idx = nqp::abs_i($mro_high - $i); my Mu \type-obj = @mro[$idx]; my $meth = (type-obj.^method_table){name} unless type-obj.HOW.archetypes.composable; $meth = (type-obj.^submethod_table){name} if !$meth; nqp::push($results,$meth(SELF, |c)) if $meth; } if $throw && $results.elems == 0 { X::Method::NotFound.new( invocant => SELF, method => name, typename => SELF.^name, :containerized(nqp::iscont(SELF)), ).throw; } $results.List } method dispatch:<.+>(Mu \SELF: \name, |c) { SELF!batch-call(name, c, :throw); } method dispatch:<.*>(Mu \SELF: \name, |c) { SELF!batch-call(name, c) } method dispatch:(Mu \SELF: $nodality, Str $meth-name, |c) { nqp::if( nqp::if( nqp::istype($nodality,Str), nqp::if( $nodality, nqp::can(List,$nodality) && nqp::can(List.can($nodality ).AT-POS(0),'nodal'), nqp::can(List,$meth-name) && nqp::can(List.can($meth-name).AT-POS(0),'nodal')), nqp::can($nodality, 'nodal')), nqp::if( c, HYPER( sub (\obj) is nodal { obj."$meth-name"(|c) }, SELF ), HYPER( sub (\obj) is nodal { obj."$meth-name"() }, SELF )), nqp::if( c, HYPER( -> \obj { obj."$meth-name"(|c) }, SELF ), HYPER( -> \obj { obj."$meth-name"( ) }, SELF ))) } proto method WALK(|) {*} # is implementation-detail multi method WALK(:$name!, :$canonical, :$ascendant, :$descendant, :$preorder, :$breadth, :$super, :$omit, :$include, :$roles, :$submethods = True, :$methods = True --> WalkList) { # First, build list of classes in the order we'll need them. my sub maybe-with-roles(Mu \typeobj) { flat typeobj.^parents(:local), ($roles ?? typeobj.^roles(:local, :transitive, :mro) !! ()) } my @classes; if $super { @classes = maybe-with-roles(self) } elsif $breadth { my @search_list = self.WHAT; while @search_list { append @classes, @search_list; my @new_search_list; for @search_list -> $current { for maybe-with-roles($current) -> $next { unless @new_search_list.grep({ $^c.WHAT =:= $next.WHAT }) { push @new_search_list, $next; } } } @search_list = @new_search_list; } } elsif $ascendant | $preorder { sub build_ascendent(Mu $class) { unless @classes.grep({ $^c.WHAT =:= $class.WHAT }) { push @classes, $class; for maybe-with-roles($class) { build_ascendent($^parent); } } } build_ascendent(self.WHAT); } elsif $descendant { sub build_descendent(Mu $class) { unless @classes.grep({ $^c.WHAT =:= $class.WHAT }) { for maybe-with-roles($class) { build_descendent($^parent); } push @classes, $class; } } build_descendent(self.WHAT); } else { # Canonical, the default (just whatever the meta-class says) with us # on the start. @classes = self.^mro(concretizations => $roles); } # Now we have classes, build method list. my @methods; for @classes -> $class { if (!defined($include) || $include.ACCEPTS($class)) && (!defined($omit) || !$omit.ACCEPTS($class)) { if $methods && !$class.HOW.archetypes.composable { @methods.push: $_ with $class.^method_table{$name} } if $submethods && nqp::can($class.HOW, 'submethod_table') { @methods.push: $_ with $class.^submethod_table{$name} } } } WalkList.new(|@methods).set_invocant(self) } multi method WALK(Str:D $name, *%n --> WalkList ) { samewith(:$name, |%n) } } proto sub defined(Mu, *%) is pure {*} multi sub defined(Mu \x) { x.defined } proto sub infix:<~~>(Mu, Mu, *%) {*} multi sub infix:<~~>(Mu \topic, Mu \matcher) { matcher.ACCEPTS(topic).Bool; } proto sub infix:(Mu, Mu, *%) {*} multi sub infix:(Mu \topic, Mu \matcher) { matcher.ACCEPTS(topic).not; } proto sub infix:<=:=>(Mu $?, Mu $?, *%) is pure {*} multi sub infix:<=:=>($?) { Bool::True } multi sub infix:<=:=>(Mu \a, Mu \b) { nqp::hllbool(nqp::eqaddr(a, b)); } proto sub infix:(Mu $?, Mu $?, *%) is pure {*} multi sub infix:(Mu:U $, Any $ --> False) { } multi sub infix:(Any $, Mu:U $ --> False) { } multi sub infix:(Mu:U $, Mu:U $ --> True) { } multi sub infix:($?) { Bool::True } # Last ditch snapshot semantics. We shouldn't come here too often, so # please do not change this to be faster but wronger. (Instead, add # specialized multis for datatypes that can be tested piecemeal.) multi sub infix:(Any:U \a, Any:U \b) { nqp::hllbool(nqp::eqaddr(nqp::decont(a),nqp::decont(b))) } multi sub infix:(Any:D \a, Any:U \b --> False) { } multi sub infix:(Any:U \a, Any:D \b --> False) { } multi sub infix:(Any:D \a, Any:D \b) { nqp::hllbool( nqp::eqaddr(nqp::decont(a),nqp::decont(b)) || (nqp::eqaddr(a.WHAT,b.WHAT) && nqp::iseq_s(a.raku,b.raku)) ) } sub DUMP(|args (*@args, :$indent-step = 4, :%ctx?)) { # is implementation-detail my Mu $capture := nqp::usecapture(); my Mu $topic := nqp::captureposarg($capture, 0); return "\x25b6" ~ DUMP(nqp::decont($topic), :$indent-step, :%ctx) if nqp::iscont($topic); return '(null)' if nqp::isnull($topic); my str $type = $topic.^name; my str $where = nqp::base_I(nqp::where($topic), 16); if %ctx{$where} -> $obj_num { nqp::istype($topic, Bool) ?? $topic.DUMP(:$indent-step, :%ctx) !! nqp::isconcrete($topic) ?? '=' ~ $type ~ '<' ~ $obj_num ~ '>' !! nqp::can($topic, 'DUMP') ?? $topic.DUMP(:$indent-step, :%ctx) !! $type; } else { my int $obj_num = %ctx.elems + 1; %ctx{$where} = $obj_num; if nqp::islist($topic) { my str $id = $type ~ '<' ~ $obj_num ~ '>'; my @pieces; $topic := nqp::clone($topic); while $topic { my Mu $x := nqp::shift($topic); @pieces.push: DUMP($x, :$indent-step, :%ctx); } @pieces.DUMP-PIECES($id ~ '(', :$indent-step); } elsif nqp::ishash($topic) { my str $id = $type ~ '<' ~ $obj_num ~ '>'; my @pieces; { CATCH { default { @pieces.push: '...' } } for $topic.pairs { @pieces.push: $_.key ~ ' => ' ~ DUMP($_.value, :$indent-step, :%ctx); } } @pieces.DUMP-PIECES($id ~ '(', :$indent-step); } elsif nqp::can($topic, 'DUMP') { $topic.DUMP(:$indent-step, :%ctx); } else { given nqp::p6box_i(nqp::captureposprimspec($capture, 0)) { when 0 { $type ~ '<' ~ $obj_num ~ '>(...)' } when 1 { nqp::captureposarg_i($capture, 0).DUMP(:$indent-step, :%ctx) } when 2 { nqp::captureposarg_n($capture, 0).DUMP(:$indent-step, :%ctx) } when 3 { nqp::captureposarg_s($capture, 0).DUMP(:$indent-step, :%ctx) } } } } } # These must collapse Junctions proto sub so(Mu, *%) {*} multi sub so(Bool:U --> False) { } multi sub so(Bool:D \x) { x } multi sub so(Mu \x) { nqp::hllbool(nqp::istrue(x)) } proto sub not(Mu, *%) {*} multi sub not(Bool:U --> True) { } multi sub not(Mu \x) { nqp::hllbool(nqp::isfalse(x)) } #line 1 SETTING::src/core.c/Stringy.rakumod my role Stringy { } multi sub infix:(Stringy:D \a, Stringy:D \b --> Bool:D) { nqp::hllbool( nqp::eqaddr(nqp::decont(a),nqp::decont(b)) || (nqp::eqaddr(a.WHAT,b.WHAT) && nqp::iseq_i(a cmp b,0)) ) } proto sub prefix:<~>($, *%) is pure {*} multi sub prefix:<~>(\a) { a.Stringy } multi sub prefix:<~>(int $a) { nqp::p6box_s($a) } multi sub prefix:<~>(num $a) { nqp::p6box_s($a) } proto sub infix:<~>(|) is pure {*} multi sub infix:<~>(--> '') { } multi sub infix:<~>($x --> Str:D) { $x.Stringy } proto sub infix:($?, $?, *%) is pure {*} multi sub infix:() { "infix:".no-zero-arg } multi sub infix:(\x) { x.Stringy } multi sub infix:($s, Num:D $n) { $n == Inf ?? NYI('Cat object') !! $s.Stringy x $n.Int; } multi sub infix:(\s, Any:D $n) { s.Stringy x $n.Int } multi sub infix:(\s, Any:U $n) { s.Stringy x $n.Numeric.Int } proto sub infix:($?, $?, *%) is pure {*} multi sub infix:( --> Bool::True) { } multi sub infix:(Any --> Bool::True) { } multi sub infix:(\a, \b) { a.Stringy eq b.Stringy } proto sub infix:(Mu $?, Mu $?, *%) is pure {*} multi sub infix:( --> Bool::True) { } multi sub infix:(Any --> Bool::True) { } multi sub infix:(Mu \a, Mu \b) { not a eq b } multi sub infix:(\a, \b) { a.Stringy ne b.Stringy } proto sub infix:($?, $?, *%) is pure {*} multi sub infix:( --> Bool::True) { } multi sub infix:(Any --> Bool::True) { } multi sub infix:(\a, \b) { a.Stringy lt b.Stringy } proto sub infix:($?, $?, *%) is pure {*} multi sub infix:( --> Bool::True) { } multi sub infix:(Any --> Bool::True) { } multi sub infix:(\a, \b) { a.Stringy le b.Stringy } proto sub infix:($?, $?, *%) is pure {*} multi sub infix:( --> Bool::True) { } multi sub infix:(Any --> Bool::True) { } multi sub infix:(\a, \b) { a.Stringy gt b.Stringy } proto sub infix:($?, $?, *%) is pure {*} multi sub infix:( --> Bool::True) { } multi sub infix:(Any --> Bool::True) { } multi sub infix:(\a, \b) { a.Stringy ge b.Stringy } proto sub infix:<~|>($?, $?, *%) is pure {*} multi sub infix:<~|>() { '' } multi sub infix:<~|>(\a ) { a.Stringy } multi sub infix:<~|>(\a, \b) { a.Stringy ~| b.Stringy } proto sub infix:<~^>($?, $?, *%) is pure {*} multi sub infix:<~^>() { '' } multi sub infix:<~^>(\a ) { a.Stringy } multi sub infix:<~^>(\a, \b) { a.Stringy ~^ b.Stringy } proto sub infix:<~&>($?, $?, *%) is pure {*} multi sub infix:<~&>() { "infix:<~&>".no-zero-arg } multi sub infix:<~&>(\a ) { a.Stringy } multi sub infix:<~&>(\a, \b) { a.Stringy ~& b.Stringy } proto sub prefix:<~^>($, *%) is pure {*} multi sub prefix:<~^>(\a) { ~^ a.Stringy } #line 1 SETTING::src/core.c/Any.rakumod my class Pair { ... } my class Range { ... } my class Seq { ... } my class X::Adverb { ... } my class X::Bind { ... } my class X::Bind::Slice { ... } my class X::Bind::ZenSlice { ... } my class X::Item { ... } my class X::Match::Bool { ... } my class X::Pairup::OddNumber { ... } my class X::Subscript::Negative { ... } my role Numeric { ... } my class Any { # declared in BOOTSTRAP # my class Any is Mu multi method ACCEPTS(Any:D: Mu:U --> False) { } multi method ACCEPTS(Any:D: Mu:D \topic) { # XXX: &[===] works with Any, not Mu! self === topic } proto method EXISTS-KEY(|) is nodal {*} multi method EXISTS-KEY(Any:U: $ --> False) { } multi method EXISTS-KEY(Any:D: $ --> False) { } proto method DELETE-KEY(|) is nodal {*} multi method DELETE-KEY(Any:U: $ --> Nil) { } multi method DELETE-KEY(Any:D: $) { ('Can not remove values from a ' ~ self.^name).Failure } proto method DELETE-POS(|) is nodal {*} multi method DELETE-POS(Any:U: $pos --> Nil) { } multi method DELETE-POS(Any:D: $pos) { ('Can not remove elements from a ' ~ self.^name).Failure } multi method DELETE-POS(Any:D: \one, \two) is raw { self.AT-POS(one).DELETE-POS(two) } multi method DELETE-POS(Any:D: \one, \two, \three) is raw { self.AT-POS(one).AT-POS(two).DELETE-POS(three) } multi method DELETE-POS(Any:D: **@indices) { my $final := @indices.pop; Rakudo::Internals.WALK-AT-POS(self,@indices).DELETE-POS($final) } method cache() { self.list } proto method list(|) is nodal {*} multi method list(Any:U:) { infix:<,>(self) } multi method list(Any:D \SELF:) { infix:<,>(SELF) } proto method fmt(|) {*} proto method flat(|) is nodal {*} multi method flat() { self.list.flat } proto method eager(|) is nodal {*} multi method eager() { self.list.eager } proto method serial(|) is nodal {*} multi method serial() { self } # derived from .list proto method List(|) is nodal {*} multi method List() { self.list } proto method Slip(|) is nodal {*} multi method Slip() { self.list.Slip } proto method Array(|) is nodal {*} multi method Array() { self.list.Array } proto method Seq(|) is nodal {*} multi method Seq() { Seq.new(self.iterator) } proto method hash(|) is nodal {*} multi method hash(Any:U:) { my % = () } multi method hash(Any:D:) { my % = self } # derived from .hash proto method Hash(|) is nodal {*} multi method Hash() { self.hash.Hash } proto method Map(|) is nodal {*} multi method Map() { Map.new(self) } proto method elems(|) is nodal {*} multi method elems(Any:U: --> 1) { } multi method elems(Any:D:) { self.list.elems } proto method end(|) is nodal {*} multi method end(Any:U: --> 0) { } multi method end(Any:D:) { self.list.end } proto method keys(|) is nodal {*} multi method keys(Enumeration:) is default { self.enums.keys } multi method keys(Bool:) { self.enums.keys } multi method keys(Any:U:) { () } multi method keys(Any:D:) { self.list.keys } proto method kv(|) is nodal {*} multi method kv(Enumeration:) is default { self.enums.kv } multi method kv(Bool:) { self.enums.kv } multi method kv(Any:U:) { () } multi method kv(Any:D:) { self.list.kv } proto method values(|) is nodal {*} multi method values(Enumeration:) is default { self.enums.values } multi method values(Bool:) { self.enums.values } multi method values(Any:U:) { () } multi method values(Any:D:) { self.list } proto method pairs(|) is nodal {*} multi method pairs(Enumeration:) is default { self.enums.pairs } multi method pairs(Bool:) { self.enums.pairs } multi method pairs(Any:U:) { () } multi method pairs(Any:D:) { self.list.pairs } proto method antipairs(|) is nodal {*} multi method antipairs(Enumeration:) is default { self.enums.antipairs } multi method antipairs(Bool:) { self.enums.antipairs } multi method antipairs(Any:U:) { () } multi method antipairs(Any:D:) { self.list.antipairs } proto method invert(|) is nodal {*} multi method invert(Enumeration:) is default { self.enums.invert } multi method invert(Bool:) { self.enums.invert } multi method invert(Any:U:) { () } multi method invert(Any:D:) { self.list.invert } proto method splice(|) is nodal {*} proto method pick(|) is nodal {*} multi method pick() { self.list.pick } multi method pick($n) { self.list.pick($n) } multi method pick(HyperWhatever) is default { Seq.new: Rakudo::Iterator.Reiterate: { self.pick(Whatever).iterator } } proto method roll(|) is nodal {*} multi method roll() { self.list.roll } multi method roll($n) { self.list.roll($n) } multi method iterator(Any:) { self.list.iterator } method match(Any:U: |) { self.Str; nqp::getlexcaller('$/') = Nil } proto method classify(|) is nodal {*} multi method classify() { die "Must specify something to classify with, a Callable, Hash or List"; } multi method classify(Whatever, :&as) { Hash.^parameterize(Any,Mu).new.classify-list( { $_ }, self, :&as ) } multi method classify($test, :$into!, :&as) { ( $into // $into.new ).classify-list($test, self, :&as) } multi method classify($test, :&as) { Hash.^parameterize(Any,Mu).new.classify-list($test, self, :&as ) } proto method categorize(|) is nodal {*} multi method categorize() { die "Must specify something to categorize with, a Callable, Hash or List"; } multi method categorize(Whatever, :&as) { Hash.^parameterize(Any,Mu).new.categorize-list( { $_ }, self.list, :&as ) } multi method categorize($test, :$into!, :&as) { ( $into // $into.new ).categorize-list( $test, self.list, :&as ) } multi method categorize($test, :&as) { Hash.^parameterize(Any,Mu).new.categorize-list($test, self.list, :&as) } method reverse() is nodal { self.list.reverse } method combinations(|c) is nodal { self.list.combinations(|c) } method permutations(|c) is nodal { self.list.permutations(|c) } method join($separator = '') is nodal { self.list.join($separator) } proto method tree(|) is nodal {*} multi method tree(Any:U:) { self } multi method tree(Any:D:) { nqp::istype(self, Iterable) ?? self.map({ .tree }).item !! self } multi method tree(Any:D: Whatever ) { self.tree } multi method tree(Any:D: Int(Cool) $count) { nqp::istype(self, Iterable) && $count > 0 ?? self.map({ .tree($count - 1) }).item !! self } multi method tree(Any:D: @ [&first, *@rest]) { self.tree(&first, |@rest); } multi method tree(Any:D: &first, *@rest) { nqp::istype(self, Iterable) ?? @rest ?? first(self.map({ .tree(|@rest) })) !! first(self) !! self } # auto-vivifying proto method push(|) is nodal {*} multi method push(Any:U \SELF: |values) { SELF = nqp::istype(SELF,Positional) ?? SELF.new !! Array.new; SELF.push(|values); } proto method append(|) is nodal {*} multi method append(Any:U \SELF: |values) { SELF = nqp::istype(SELF,Positional) ?? SELF.new !! Array.new; SELF.append(|values); } proto method unshift(|) is nodal {*} multi method unshift(Any:U \SELF: |values) { SELF = Array.new; SELF.unshift(|values); } proto method prepend(|) is nodal {*} multi method prepend(Any:U \SELF: |values) { SELF = Array.new; SELF.prepend(|values); } proto method EXISTS-POS(|) is nodal {*} multi method EXISTS-POS(Any:U: Any:D $ --> False) { } multi method EXISTS-POS(Any:U: Any:U $pos) { die "Cannot use '{$pos.^name}' as an index"; } multi method EXISTS-POS(Any:D: Int:D \pos --> Bool:D) { pos == 0; } multi method EXISTS-POS(Any:D: Num:D \pos --> Bool:D) { X::Item.new(aggregate => self, index => pos).throw if nqp::isnanorinf(pos); self.AT-POS(nqp::unbox_i(pos.Int)); pos == 0; } multi method EXISTS-POS(Any:D: Any:D \pos --> Bool:D) { pos.Int == 0; } multi method EXISTS-POS(Any:D: Any:U \pos) { die "Cannot use '{pos.^name}' as an index"; } multi method EXISTS-POS(Any:D: \one, \two --> Bool:D) is raw { nqp::istype((my $one := self.AT-POS(one)),Failure) ?? False !! $one.EXISTS-POS(two) } multi method EXISTS-POS(Any:D: \one, \two,\three --> Bool:D) is raw { nqp::istype((my $one := self.AT-POS(one)),Failure) || nqp::istype((my $two := $one.AT-POS(two)),Failure) ?? False !! $two.EXISTS-POS(three) } multi method EXISTS-POS(Any:D: **@indices --> Bool:D) { my $final := @indices.pop; # also reifies my $target := self; my $indices := nqp::getattr(@indices,List,'$!reified'); my int $elems = nqp::elems($indices); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::istype( ($target := $target.AT-POS(nqp::atpos($indices,$i))), Failure ), (return False) ) ); $target.EXISTS-POS($final) } proto method AT-POS(|) is nodal {*} multi method AT-POS(Any:U \SELF: Int:D \pos) is raw { nqp::iscont(SELF) ?? nqp::p6scalarfromcertaindesc( ContainerDescriptor::VivifyArray.new(SELF, pos) ) !! pos ?? X::OutOfRange.new( :what($*INDEX // 'Index'), :got(pos), :range<0..0> ).Failure !! SELF } multi method AT-POS(Any:U: Num:D \pos) is raw { nqp::isnanorinf(pos) ?? X::Item.new(aggregate => self, index => pos).Failure !! self.AT-POS(nqp::unbox_i(pos.Int)) } multi method AT-POS(Any:U: Any:D \pos) is raw { self.AT-POS(nqp::unbox_i(pos.Int)); } multi method AT-POS(Any:D: Int:D \pos) is raw { pos ?? X::OutOfRange.new( :what($*INDEX // 'Index'), :got(pos), :range<0..0> ).Failure !! self } multi method AT-POS(Any:D: Num:D \pos) is raw { nqp::isnanorinf(pos) ?? X::Item.new(aggregate => self, index => pos).Failure !! self.AT-POS(nqp::unbox_i(pos.Int)) } multi method AT-POS(Any:D: Any:D \pos) is raw { self.AT-POS(nqp::unbox_i(pos.Int)); } multi method AT-POS(Any: Any:U \pos) is raw { die "Cannot use '{pos.^name}' as an index"; } multi method AT-POS(Any:D: \one, \two) is raw { self.AT-POS(one).AT-POS(two) } multi method AT-POS(Any:D: \one, \two, \three) is raw { self.AT-POS(one).AT-POS(two).AT-POS(three) } multi method AT-POS(Any:D: **@indices) is raw { my $final := @indices.pop; Rakudo::Internals.WALK-AT-POS(self,@indices).AT-POS($final) } proto method ZEN-KEY(|) {*} multi method ZEN-KEY(*%unexpected) { %unexpected ?? X::Adverb.new( :what('{} slice'), :source(try { self.VAR.name } // self.WHAT.raku), :unexpected(%unexpected.keys) ).Failure !! self } proto method ASSIGN-POS(|) is nodal {*} multi method ASSIGN-POS(Any:U \SELF: \pos, Mu \assignee) is raw { SELF.AT-POS(pos) = assignee; # defer < 0 check } multi method ASSIGN-POS(Any:D \SELF: Int:D \pos, Mu \assignee) is raw { SELF.AT-POS(pos) = assignee; # defer < 0 check } multi method ASSIGN-POS(Any:D \SELF: Num:D \pos, Mu \assignee) is raw { nqp::isnanorinf(pos) ?? X::Item.new(aggregate => SELF, index => pos).Failure !! SELF.AT-POS(nqp::unbox_i(pos.Int)) = assignee; # defer < 0 check } multi method ASSIGN-POS(Any:D \SELF: Any:D \pos, Mu \assignee) is raw { SELF.AT-POS(nqp::unbox_i(pos.Int)) = assignee; # defer < 0 check } multi method ASSIGN-POS(Any:D: Any:U \pos, Mu \assignee) { die "Cannot use '{pos.^name}' as an index"; } multi method ASSIGN-POS(Any:D \SELF: \one, \two, Mu \assignee) is raw { SELF.AT-POS(one).ASSIGN-POS(two, assignee) } multi method ASSIGN-POS(Any:D \SELF: \one, \two, \three, Mu \assignee) is raw { SELF.AT-POS(one).AT-POS(two).ASSIGN-POS(three, assignee) } multi method ASSIGN-POS(Any:D \SELF: **@indices) is raw { my \value := @indices.pop; my $final := @indices.pop; Rakudo::Internals.WALK-AT-POS(SELF,@indices).ASSIGN-POS($final,value) } proto method BIND-POS(|) {*} multi method BIND-POS(Any:D: **@indices is raw) is raw { # looks like Array.pop doesn't really return a bindable container # my \value := @indices.pop; # my $final := @indices.pop; # Rakudo::Internals.WALK-AT-POS(self,@indices).BIND-POS($final,value) my int $elems = @indices.elems; # reifies my \value := @indices.AT-POS(--$elems); my $final := @indices.AT-POS(--$elems); my $target := self; my int $i = -1; $target := $target.AT-POS(@indices.AT-POS($i)) while nqp::islt_i(++$i,$elems); X::Bind.new.throw if nqp::eqaddr($target,self); $target.BIND-POS($final, value) } method all() is nodal { Junction.new("all", self) } method any() is nodal { Junction.new("any", self) } method one() is nodal { Junction.new("one", self) } method none() is nodal { Junction.new("none",self) } # internals proto method AT-KEY(|) is nodal {*} multi method AT-KEY(Any:D: $key) is raw { (self ~~ Associative ?? "Associative indexing implementation missing from type {self.WHAT.raku}" !! "Type {self.WHAT.raku} does not support associative indexing." ).Failure } multi method AT-KEY(Any:U \SELF: \key) is raw { nqp::p6scalarfromcertaindesc(ContainerDescriptor::VivifyHash.new(SELF, key)) } proto method BIND-KEY(|) is nodal {*} multi method BIND-KEY(Any:D: \k, \v) is raw { X::Bind.new(target => self.^name).throw } multi method BIND-KEY(Any:U \SELF: $key, $BIND ) is raw { SELF = Hash.new; SELF.BIND-KEY($key, $BIND); $BIND } proto method ASSIGN-KEY(|) is nodal {*} multi method ASSIGN-KEY(\SELF: \key, Mu \assignee) is raw { SELF.AT-KEY(key) = assignee; } # XXX GLR review these method FLATTENABLE_LIST() is nodal { my $list := self.list; nqp::findmethod($list, 'FLATTENABLE_LIST')($list); } method FLATTENABLE_HASH() is nodal { nqp::hash() } proto method Set(|) is nodal {*} multi method Set(Any: --> Set:D) { Set.new-from-pairs([self.list]) } proto method SetHash(|) is nodal {*} multi method SetHash(Any: --> SetHash:D) { SetHash.new-from-pairs([self.list]) } proto method Bag(|) is nodal {*} multi method Bag(Any: --> Bag:D) { Bag.new-from-pairs([self.list]) } proto method BagHash(|) is nodal {*} multi method BagHash(Any: --> BagHash:D) { BagHash.new-from-pairs([self.list]) } proto method Mix(|) is nodal {*} multi method Mix(Any: --> Mix:D) { Mix.new-from-pairs([self.list]) } proto method MixHash(|) is nodal {*} multi method MixHash(Any: --> MixHash:D) { MixHash.new-from-pairs([self.list]) } # XXX GLR does this really need to force a list? proto method Supply(|) is nodal {*} multi method Supply() { self.list.Supply } method nl-out(--> Str:D) { "\n" } method print-nl() { self.print(self.nl-out) } method lazy-if($flag) { self } # no-op on non-Iterables } Metamodel::ClassHOW.exclude_parent(Any); # builtin ops proto sub infix:<===>($?, $?, *%) is pure {*} multi sub infix:<===>($? --> True) { } multi sub infix:<===>(\a, \b --> Bool:D) { nqp::hllbool( nqp::eqaddr(nqp::decont(a),nqp::decont(b)) || (nqp::eqaddr(a.WHAT,b.WHAT) && nqp::iseq_s(nqp::unbox_s(a.WHICH), nqp::unbox_s(b.WHICH))) ) } # U+2A76 THREE CONSECUTIVE EQUALS SIGNS my constant &infix:<⩶> = &infix:<===>; proto sub prefix:<++>(Mu, *%) {*} multi sub prefix:<++>(Mu:D $a is rw) { $a = $a.succ } multi sub prefix:<++>(Mu:U $a is rw) { $a = 1 } proto sub prefix:<-->(Mu, *%) {*} multi sub prefix:<-->(Mu:D $a is rw) { $a = $a.pred } multi sub prefix:<-->(Mu:U $a is rw) { $a = -1 } proto sub postfix:<++>(Mu, *%) {*} multi sub postfix:<++>(Mu:D $a is rw) { my $b = $a; $a = $a.succ; $b } multi sub postfix:<++>(Mu:U $a is rw) { $a = 1; 0 } proto sub postfix:<-->(Mu, *%) {*} multi sub postfix:<-->(Mu:D $a is rw) { my $b = $a; $a = $a.pred; $b } multi sub postfix:<-->(Mu:U $a is rw) { $a = -1; 0 } proto sub pick($, |) {*} multi sub pick($n, +values) { values.pick($n) } proto sub roll($, |) {*} multi sub roll($n, +values) { values.roll($n) } proto sub keys($, *%) {*} multi sub keys($x) { $x.keys } proto sub values($, *%) {*} multi sub values($x) { $x.values } proto sub pairs($, *%) {*} multi sub pairs($x) { $x.pairs } proto sub kv($, *%) {*} multi sub kv($x) { $x.kv } proto sub elems($, *%) is nodal {*} multi sub elems($a) { $a.elems } proto sub end($, *%) {*} multi sub end($a) { $a.end } proto sub sum(|) {*} multi sub sum() { 0 } multi sub sum(\SELF) { SELF.sum } multi sub sum(+SELF) { SELF.sum } proto sub classify($, |) {*} multi sub classify($test, +items, :$into!, *%named ) { ( $into // $into.new).classify-list($test, items, |%named) } multi sub classify($test, +items, *%named ) { Hash.^parameterize(Any,Mu).new.classify-list($test, items, |%named); } proto sub categorize($, |) {*} multi sub categorize($test, +items, :$into!, *%named ) { ( $into // $into.new).categorize-list($test, items, |%named) } multi sub categorize($test, +items, *%named ) { Hash.^parameterize(Any,Mu).new.categorize-list($test, items, |%named) } proto sub item(|) is pure {*} multi sub item(\x) { my $ = x } multi sub item(|c) { my $ = c.list } multi sub item(Mu $a) { $a } sub dd(|c) { # is implementation-detail # handler for BOOTxxxArrays sub BOOTArray(Mu \array) { my \buffer := nqp::create(IterationBuffer); my \clone := nqp::clone(array); my str $name = array.^name; if $name eq 'BOOTIntArray' { nqp::while( clone, nqp::push(buffer,nqp::shift_i(clone)) ); } elsif $name eq 'BOOTStrArray' { nqp::while( clone, nqp::push(buffer,nqp::shift_s(clone)) ); } elsif $name eq 'BOOTNumArray' { nqp::while( clone, nqp::push(buffer,nqp::shift_n(clone)) ); } else { nqp::while( clone, nqp::push(buffer,nqp::shift(clone)) ); } $name ~ buffer.List.raku } # handler for BOOTContext sub BOOTContext(Mu \context) { my $hash := nqp::hash; my \iterator := nqp::iterator(context); nqp::while( iterator, nqp::bindkey( $hash, nqp::iterkey_s(nqp::shift(iterator)), nqp::iterval(iterator) ) ); context.^name ~ '(' ~ nqp::substr(nqp::hllize($hash).raku.chop,1) ~ ')' } # handler for BOOTThread sub BOOTThread(Mu \thread) { "VM thread object for thread #{ nqp::threadid(thread) } with { nqp::threadlockcount(thread) } locks" } my Mu $args := nqp::p6argvmarray(); if nqp::elems($args) { while $args { my $var := nqp::shift($args); if nqp::istype($var,RakuAST::Node) { note $var.DEPARSE.chomp; } else { my $name := !nqp::istype($var.VAR,Failure) && try $var.VAR.name; $name := '' if $name && ($name eq 'element' | '%'); my @parts = $var.WHAT.^name.split("::"); my $type := @parts.pop; if @parts { $type := $type.chop if $type.contains(/ \W $ /); } my $what := nqp::can($var,'raku') ?? $var.raku !! nqp::can($var,'perl') ?? $var.perl !! $var.^name.starts-with('BOOT') ?? $var.^name.ends-with('Array') ?? BOOTArray($var) !! $var.^name.ends-with('Context') ?? BOOTContext($var) !! $var.^name.ends-with('Thread') ?? BOOTThread($var) !! "($var.^name() without .raku or .perl method)" !! "($var.^name() without .raku or .perl method)"; note $name ?? "$type $name = $what" !! $what; } } } elsif c.hash -> %named { note .raku for %named.sort: { .key } } else { # tell where we are note .name ?? "{lc .^name} {.name}{.signature.gist}" !! "{lc .^name} {.signature.gist}" with callframe(1).code; } return } #line 1 SETTING::src/core.c/Attribute.rakumod my class Attribute { # declared in BOOTSTRAP # class Attribute is Any # has str $!name; # has int $!rw; # has int $!is_built; # has int $!is_bound; # has int $!has_accessor; # has Mu $!type; # has Mu $!container_descriptor; # has Mu $!auto_viv_container; # has Mu $!build_closure; # has Mu $!package; # has int $!inlined; # has Mu $!dimensions; # has int $!positional_delegate; # has int $!associative_delegate; # has Mu $!why; # has $!required; # has Mu $!container_initializer; # has Attribute $!original; # has int $!composed; method compose(Mu $package, :$compiler_services) { return if $!composed; my $dcpkg := nqp::decont($package); nqp::bindattr(self, Attribute, '$!package', $dcpkg); # Generate accessor method, if we're meant to have one. if self.has_accessor { my str $name = nqp::unbox_s(self.name); my $meth_name := nqp::substr($name, 2); unless nqp::existskey($package.^method_table, $meth_name) || nqp::existskey($package.^submethod_table, $meth_name) || (nqp::can($package.HOW, 'has_multi_candidate') && $package.^has_multi_candidate($meth_name)) { my $meth; my int $attr_type = nqp::objprimspec($!type); if nqp::can(self,"DEPRECATED") && self.DEPRECATED -> $alternative { my $what = "Method $meth_name (from $package.^name())"; if self.rw { $meth := nqp::iseq_i($attr_type, 0) ?? method (Mu:D \fles:) is raw { Rakudo::Deprecations.DEPRECATED($alternative,:$what); nqp::getattr(nqp::decont(fles), $dcpkg, $name) } !! nqp::iseq_i($attr_type, 1) ?? method (Mu:D \fles:) is raw { Rakudo::Deprecations.DEPRECATED($alternative,:$what); nqp::getattrref_i(nqp::decont(fles), $dcpkg, $name) } !! nqp::iseq_i($attr_type, 10) ?? method (Mu:D \fles:) is raw { Rakudo::Deprecations.DEPRECATED($alternative,:$what); nqp::getattrref_u(nqp::decont(fles), $dcpkg, $name) } !! nqp::iseq_i($attr_type, 2) ?? method (Mu:D \fles:) is raw { Rakudo::Deprecations.DEPRECATED($alternative,:$what); nqp::getattrref_n(nqp::decont(fles), $dcpkg, $name) } !! method (Mu:D \fles:) is raw { Rakudo::Deprecations.DEPRECATED($alternative,:$what); nqp::getattrref_s(nqp::decont(fles), $dcpkg, $name) } $meth.set_name($meth_name); } else { # DEPRECATED ro accessor $meth := nqp::iseq_i($attr_type, 0) ?? method (Mu:D \fles:) { Rakudo::Deprecations.DEPRECATED($alternative,:$what); nqp::getattr(nqp::decont(fles), $dcpkg, $name) } !! nqp::iseq_i($attr_type, 1) ?? method (Mu:D \fles:) { Rakudo::Deprecations.DEPRECATED($alternative,:$what); nqp::p6box_i( nqp::getattr_i(nqp::decont(fles), $dcpkg, $name) ); } !! nqp::iseq_i($attr_type, 10) ?? method (Mu:D \fles:) { Rakudo::Deprecations.DEPRECATED($alternative,:$what); nqp::p6box_i( nqp::getattr_u(nqp::decont(fles), $dcpkg, $name) ); } !! nqp::iseq_i($attr_type, 2) ?? method (Mu:D \fles:) { Rakudo::Deprecations.DEPRECATED($alternative,:$what); nqp::p6box_n( nqp::getattr_n(nqp::decont(fles), $dcpkg, $name) ); } !! method (Mu:D \fles:) { Rakudo::Deprecations.DEPRECATED($alternative,:$what); nqp::p6box_s( nqp::getattr_s(nqp::decont(fles), $dcpkg, $name) ); } $meth.set_name($meth_name); } } # Get the compiler to generate us an accessor when possible. elsif $compiler_services.DEFINITE { $meth := $compiler_services.generate_accessor($meth_name, $dcpkg, $name, $!type, self.rw ?? 1 !! 0); } # No compiler services available, so do it as a closure. elsif self.rw { $meth := nqp::iseq_i($attr_type, 0) ?? method (Mu:D \fles:) is raw { nqp::getattr(nqp::decont(fles), $dcpkg, $name) } !! nqp::iseq_i($attr_type, 1) ?? method (Mu:D \fles:) is raw { nqp::getattrref_i(nqp::decont(fles), $dcpkg, $name) } !! nqp::iseq_i($attr_type, 10) ?? method (Mu:D \fles:) is raw { nqp::getattrref_u(nqp::decont(fles), $dcpkg, $name) } !! nqp::iseq_i($attr_type, 2) ?? method (Mu:D \fles:) is raw { nqp::getattrref_n(nqp::decont(fles), $dcpkg, $name) } !! method (Mu:D \fles:) is raw { nqp::getattrref_s(nqp::decont(fles), $dcpkg, $name) } $meth.set_name($meth_name); } else { # ro accessor $meth := nqp::iseq_i($attr_type, 0) ?? method (Mu:D \fles:) { nqp::getattr(nqp::decont(fles), $dcpkg, $name) } !! nqp::iseq_i($attr_type, 1) ?? method (Mu:D \fles:) { nqp::p6box_i( nqp::getattr_i(nqp::decont(fles), $dcpkg, $name) ); } !! nqp::iseq_i($attr_type, 10) ?? method (Mu:D \fles:) { nqp::p6box_i( nqp::getattr_u(nqp::decont(fles), $dcpkg, $name) ); } !! nqp::iseq_i($attr_type, 2) ?? method (Mu:D \fles:) { nqp::p6box_n( nqp::getattr_n(nqp::decont(fles), $dcpkg, $name) ); } !! method (Mu:D \fles:) { nqp::p6box_s( nqp::getattr_s(nqp::decont(fles), $dcpkg, $name) ); } $meth.set_name($meth_name); } $package.^add_method($meth_name, $meth); } } nqp::bindattr_i(self, Attribute, '$!composed', 1); # Apply any handles trait we may have. self.apply_handles($package); } method apply_handles(Mu $pkg) { # None by default. } method get_value(Mu $obj) is raw { (my int $t = nqp::objprimspec($!type)) ?? nqp::iseq_i($t,1) ?? nqp::getattr_i(nqp::decont($obj),$!package,$!name) !! nqp::iseq_i($t,10) ?? nqp::getattr_u(nqp::decont($obj),$!package,$!name) !! nqp::iseq_i($t,2) ?? nqp::getattr_n(nqp::decont($obj),$!package,$!name) !! nqp::getattr_s(nqp::decont($obj),$!package,$!name) # assume t=3 !! nqp::getattr(nqp::decont($obj),$!package,$!name) } method set_value(Mu $obj, Mu \value) is raw { (my int $t = nqp::objprimspec($!type)) ?? nqp::iseq_i($t,1) ?? nqp::bindattr_i(nqp::decont($obj),$!package,$!name,value) !! nqp::iseq_i($t,10) ?? nqp::bindattr_u(nqp::decont($obj),$!package,$!name,value) !! nqp::iseq_i($t,2) ?? nqp::bindattr_n(nqp::decont($obj),$!package,$!name,value) !! nqp::bindattr_s(nqp::decont($obj),$!package,$!name,value) # t=3 !! nqp::bindattr(nqp::decont($obj),$!package,$!name,value) } method container() is raw { nqp::ifnull($!auto_viv_container,Nil) } method readonly() { !self.rw } method package() { $!package } method inlined() { $!inlined } method dimensions() { $!dimensions } # turn list_i into List multi method Str(Attribute:D:) { self.name } multi method gist(Attribute:D:) { self.type.^name ~ " " ~ self.name } method WHY() { if nqp::isnull($!why) { nextsame } else { $!why.set_docee(self); $!why } } method set_why($why) { $!why := $why; } } # does trait multi sub trait_mod:(Attribute:D $a, Mu:U $role) { if $role.HOW.archetypes.composable() { nqp::getattr($a,Attribute,'$!auto_viv_container').VAR does $role; } elsif $role.HOW.archetypes.composalizable() { nqp::getattr($a,Attribute,'$!auto_viv_container').VAR does $role.HOW.composalize($role); } else { X::Composition::NotComposable.new( target-name => 'an attribute', composer => $role, ).throw; } } multi sub trait_mod:(Attribute:D $a, :$built!) { if nqp::istype($built,Bool) { nqp::bindattr_i($a,Attribute,'$!is_built',+$built); } elsif nqp::istype($built,Pair) { if $built.key eq 'bind' { nqp::bindattr_i($a,Attribute,'$!is_built',1); nqp::bindattr_i($a,Attribute,'$!is_bound',+$built.value); } else { die "Don't know how to handle 'is built($built.raku())' trait"; } } else { die "Don't know how to handle 'is built($built.raku())' trait"; } } #line 1 SETTING::src/core.c/Iterator.rakumod # 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) } } #line 1 SETTING::src/core.c/Metamodel/Primitives.rakumod my class Metamodel::Primitives { method create_type(Mu $how, $repr = 'P6opaque', :$mixin = False) { my \type = $mixin ?? nqp::newmixintype($how, $repr.Str) !! nqp::newtype($how, $repr.Str); nqp::settypehll(type, 'Raku') } method set_package(Mu $type, $package) { nqp::setwho(nqp::decont($type), nqp::decont($package)); $type } method install_method_cache(Mu $type, %cache, :$authoritative = True) { $type } method configure_type_checking(Mu $type, @cache, :$authoritative = True, :$call_accepts = False) { my Mu $cache := nqp::list(); for @cache { nqp::push($cache, nqp::decont($_)); } nqp::settypecache($type, $cache); nqp::settypecheckmode($type, ($authoritative ?? 0 !! 1) + ($call_accepts ?? 2 !! 0)); $type } method configure_destroy(Mu $type, $destroy) { nqp::settypefinalize($type, $destroy ?? 1 !! 0); $type } method compose_type(Mu $type, $configuration) { multi sub to_vm_types(@array) { my Mu $list := nqp::list(); for @array { nqp::push($list, to_vm_types($_)); } $list } multi sub to_vm_types(%hash) { my Mu $hash := nqp::hash(); for %hash.kv -> $k, $v { nqp::bindkey($hash, $k, to_vm_types($v)); } $hash } multi sub to_vm_types($other) { nqp::decont($other) } nqp::composetype(nqp::decont($type), to_vm_types($configuration)); $type } method rebless(Mu $obj, Mu $type) { nqp::rebless($obj, $type) } method is_type(Mu \obj, Mu \type) { nqp::hllbool(nqp::istype(obj, type)) } method set_parameterizer(Mu \obj, ¶meterizer --> Nil) { my $wrapper := -> |c { parameterizer(|c) } nqp::setparameterizer(obj, nqp::getattr(nqp::decont($wrapper), Code, '$!do')) } method parameterize_type(Mu \obj, +parameters --> Mu) { my Mu $parameters := nqp::list(); nqp::push($parameters, $_) for parameters; nqp::parameterizetype(obj, $parameters) } method type_parameterized(Mu \obj --> Mu) { nqp::typeparameterized(obj) } method type_parameters(Mu \obj --> List:D) { nqp::hllize(nqp::typeparameters(obj)) } method type_parameter_at(Mu \obj, Int:D $idx --> Mu) is raw { nqp::typeparameterat(obj, nqp::decont_i($idx)) } } #line 1 SETTING::src/core.c/Rakudo/SlippyIterator.rakumod # A SlippyIterator is one that comes with some infrastructure for handling # flattening a received Slip into its own stream of values. Please note # that the $!slipper attribute *must* be set to nqp::null upon object creation. my role Rakudo::SlippyIterator does Iterator { has Mu $!slipper; # iterator of the Slip we're iterating, null if none proto method start-slip(|) {*} multi method start-slip(Slip:U $slip) { $slip } multi method start-slip(Slip:D $slip) { nqp::if( nqp::eqaddr($slip,Empty), IterationEnd, # we know there's nothing nqp::stmts( nqp::if( nqp::eqaddr( (my $result := ($!slipper := $slip.iterator).pull-one), IterationEnd ), ($!slipper := nqp::null) # we've determined there's nothing ), $result ) ) } method slip-one() { nqp::if( nqp::eqaddr((my $result := $!slipper.pull-one),IterationEnd), ($!slipper := nqp::null) ); $result } # Helper method for pushing the rest of the slipper into the target method push-rest(\target --> Nil) { $!slipper.push-all(target); $!slipper := nqp::null; } # Helper method for sinking the rest of the slipper method sink-rest( --> Nil) { $!slipper.sink-all; $!slipper := nqp::null; } # Helper method to handle control exception payloads. Returns # IterationEnd if there was no payload, otherwise the value that # was obtained. Handles Slips. method control-payload() is raw { nqp::if( nqp::isnull(my $value := nqp::getpayload(nqp::exception)), IterationEnd, nqp::if( nqp::istype($value,Slip), self.start-slip($value), $value ) ) } # Process the payload of a control exception and push it to the # target while following Slip semantics. method push-control-payload(\target --> Nil) { nqp::unless( nqp::isnull(my $value := nqp::getpayload(nqp::exception)), nqp::if( nqp::istype($value,Slip), self.slip-all($value,target), target.push($value) ) ) } proto method slip-all(|) {*} multi method slip-all(Slip:U $slip, \target) { target.push($slip) } multi method slip-all(Slip:D $slip, \target) { nqp::unless( nqp::eqaddr($slip,Empty), $slip.iterator.push-all(target) ) } method is-deterministic(--> False) { } } #line 1 SETTING::src/core.c/Rakudo/Internals.rakumod my class DateTime { ... } my role IO { ... } my class IO::Handle { ... } my class IO::Path { ... } my class Rakudo::Metaops { ... } my class List::Reifier { ... } my class Proc { ... } my class Proc::Async { ... } my class X::Assignment::ToShaped { ... } my class X::IllegalDimensionInShape { ... } my class X::IllegalOnFixedDimensionArray { ... } my class X::Localizer::NoContainer { ... } my class X::Str::Sprintf::Directives::BadType { ... } my class X::Str::Sprintf::Directives::Count { ... } my class X::Str::Sprintf::Directives::Unsupported { ... } my class X::TypeCheck { ... } # Marker symbol for 6.c-mode regex boolification. my class Rakudo::Internals::RegexBoolification6cMarker { } my class Rakudo::Internals { method SLICE_HUH(\object, @nogo, %d, %adv) { @nogo.unshift('delete') # recover any :delete if necessary if @nogo && @nogo[0] ne 'delete' && %adv.EXISTS-KEY('delete'); for -> $valid { # check all valid params if nqp::existskey(%d,nqp::unbox_s($valid)) { nqp::deletekey(%d,nqp::unbox_s($valid)); @nogo.push($valid); } } X::Adverb.new( :what, :source(try { object.VAR.name } // object.^name), :unexpected(%d.keys), :nogo(@nogo), ).Failure } method Array-with-one-elem(Mu \type, Mu \value) { my \array := (nqp::eqaddr(type,Mu) ?? Array !! Array[type]).new; nqp::p6bindattrinvres(array,List,'$!reified', nqp::stmts( nqp::bindpos( (my \reified := nqp::create(IterationBuffer)), 0, nqp::p6scalarwithvalue( nqp::getattr(array,Array,'$!descriptor'), nqp::decont(value) ) ), reified ) ) } # for use in nqp::splice my $empty := nqp::list; our class CompilerServices { has Mu $!compiler; has Mu $!current-match; method generate_accessor(str $name, Mu \package_type, str $attr_name, Mu \type, int $rw) { $!compiler.generate_accessor( $!current-match, $name, package_type, $attr_name, type, $rw); } method generate_buildplan_executor(Mu \obj, Mu \buildplan) { $!compiler.generate_buildplan_executor( $!current-match, obj, buildplan) } } # Marker symbol for lexicals that we have lowered away. class LoweredAwayLexical { method dynamic() { False } } method RANGE-AS-ints ($range, $exception) { # Convert a Range to min/max values that can fit into an `int` # Treats values smaller than int.Range.min as int.Range.min # Treats values larger than int.Range.max as int.Range.max # Throws $exception for non-Numeric ranges or ranges with any NaN endpoints # If $exception is a Str, calls `die $exception` my $min := $range.min; my $max := $range.max; nqp::unless( nqp::istype($min, Numeric) && nqp::isfalse($min.isNaN) && nqp::istype($max, Numeric) && nqp::isfalse($max.isNaN), nqp::if(nqp::istype($exception, Str), die($exception), $exception.throw)); # Get rid of Infs $min := Int($min + $range.excludes-min) // -2**63; $max := Int($max - $range.excludes-max) // 2**63-1; # we have isbig_I, but it tells whether the value is above max int32 value nqp::if( nqp::islt_I(nqp::decont($min), -2**63), $min = -2**63); nqp::if( nqp::isgt_I(nqp::decont($max), 2**63-1), $max = 2**63-1); ($min, $max); } method GistList2list_s(List:D \list) { my \values := nqp::getattr(list,List,'$!reified'); my \parts := nqp::list_s; nqp::while( nqp::elems(values), nqp::push_s(parts,nqp::shift(values).gist) ); parts } method StrList2list_s(List:D \list) { my \values := nqp::getattr(list,List,'$!reified'); my \parts := nqp::list_s; nqp::while( nqp::elems(values), nqp::push_s(parts,nqp::shift(values).Str) ); parts } method SET_LEADING_DOCS($obj, $docs) { my $current_why := $obj.WHY; if $current_why { my $end := nqp::elems($*POD_BLOCKS) - 1; my $i := $end; while $i >= 0 { if $docs === nqp::atpos($*POD_BLOCKS, $i) { nqp::splice($*POD_BLOCKS, $empty, $i, 1); last; } $i := $i - 1; } $current_why._add_leading(~$docs); } else { $obj.set_why($docs); } } method SET_TRAILING_DOCS($obj, $docs) { my $current_why := $obj.WHY; if $current_why { $current_why._add_trailing(~$docs); } else { $obj.set_why($docs); $*POD_BLOCKS.push($docs); } } sub new-type-in-who-key(Str:D $name, Mu \who, Str:D $key) { my $type := Metamodel::PackageHOW.new_type(:$name); $type.^compose; who.BIND-KEY($key, $type); $type } my $export-symbol-lock := Lock.new; method EXPORT_SYMBOL(Str:D $name, @tags, Mu \sym) { $export-symbol-lock.protect: { my @export_packages = $*EXPORT; for $*R ?? $*R.packages.map({$_.meta-object}) !! nqp::hllize(@*PACKAGES).list { my $who := .WHO; @export_packages.append: $who.EXISTS-KEY('EXPORT') ?? $who.AT-KEY('EXPORT') !! new-type-in-who-key('EXPORT', $who, 'EXPORT') } for @export_packages -> $package { my $who := $package.WHO; for @tags -> $tag { my $install-in := $who.EXISTS-KEY($tag) ?? $who.AT-KEY($tag) !! new-type-in-who-key($name, $who, $tag); my $install-in-who := $install-in.WHO; X::Export::NameClash.new(:symbol($name)).throw if $install-in-who.EXISTS-KEY($name) && nqp::not_i( nqp::eqaddr($install-in-who.AT-KEY($name),sym) ); $install-in-who.BIND-KEY($name, sym); } } 0 } } # Helper method for prefix:/prefix:, which really do the same # thing apart from where they store data. Takes the IterationBuffer in # which to save data, the container to be inspected, and the type of op # for any error messaging. method TEMP-LET(\restore, \cont, str $localizer) is raw { my int $i = nqp::elems(restore); nqp::while( nqp::isgt_i($i,0), nqp::if( nqp::eqaddr(nqp::atpos(restore,($i = nqp::sub_i($i,2))),cont), (return-rw cont) ) ); nqp::if( nqp::istype(cont,Failure), cont.exception.throw, nqp::stmts( nqp::push(restore,cont), nqp::if( nqp::iscont(cont), nqp::push(restore,nqp::decont(cont)), nqp::if( nqp::can(cont,'TEMP-LET-LOCALIZE'), nqp::push(restore,cont.TEMP-LET-LOCALIZE), nqp::stmts( nqp::pop(restore), # lose the erroneously pushed value X::Localizer::NoContainer.new(:$localizer).throw ) ) ) ) ); cont } # fast whitespace trim: str to trim, str to store trimmed str method !TRIM(\string, \trimmed --> Nil) { my int $pos = nqp::chars(string) - 1; my int $left = nqp::findnotcclass(nqp::const::CCLASS_WHITESPACE, string, 0, $pos + 1); $pos = $pos - 1 while nqp::isge_i($pos, $left) && nqp::iscclass(nqp::const::CCLASS_WHITESPACE, string, $pos); trimmed = nqp::islt_i($pos, $left) ?? '' !! nqp::substr(string, $left, $pos + 1 - $left); Nil } # fast key:value split: Str to split, str to store key, str to store value method KEY_COLON_VALUE(Str $command, \key, \value --> Nil) { my str $str = nqp::unbox_s($command); my int $index = nqp::index($str,':'); if nqp::isgt_i($index,0) { self!TRIM(nqp::substr($str,0,$index),key); self!TRIM(nqp::substr($str,$index + 1,nqp::chars($str) - $index),value); } elsif nqp::islt_i($index,0) { self!TRIM($str,key); value = ''; } else { key = ''; self!TRIM(nqp::substr($str,1,nqp::chars($str) - 1),value); } Nil } # key space value split: Str to split, str to store key, str to store value method KEY_SPACE_VALUE(Str $command, \key, \value --> Nil) { my str $str = nqp::unbox_s($command); my int $index = nqp::index($str,' '); if nqp::isgt_i($index,0) { key = nqp::substr($str,0,$index); value = nqp::substr($str,$index + 1,nqp::chars($str) - $index); } elsif nqp::islt_i($index,0) { key = $str; value = ''; } else { key = ''; value = nqp::substr($str,1,nqp::chars($str) - 1); } Nil } # Fast mapping for identicals ### If updating encodings, also update src/core.c/Encoding/Registry.rakumod my constant $encodings = nqp::hash( # utf8 'utf8', 'utf8', 'utf-8', 'utf8', # utf8-c8 'utf8-c8', 'utf8-c8', 'utf8c8', 'utf8-c8', 'utf-8-c8', 'utf8-c8', # utf16 'utf16', 'utf16', 'utf-16', 'utf16', # utf16le 'utf16le', 'utf16le', 'utf-16le', 'utf16le', 'utf16-le', 'utf16le', 'utf-16-le', 'utf16le', # utf16be 'utf16be', 'utf16be', 'utf-16be', 'utf16be', 'utf16-be', 'utf16be', 'utf-16-be', 'utf16be', # ascii 'ascii', 'ascii', # iso-8859-1 according to http://de.wikipedia.org/wiki/ISO-8859-1 'iso-8859-1', 'iso-8859-1', 'iso_8859-1:1987', 'iso-8859-1', 'iso_8859-1', 'iso-8859-1', 'iso-ir-100', 'iso-8859-1', 'latin1', 'iso-8859-1', 'latin-1', 'iso-8859-1', 'csisolatin1', 'iso-8859-1', 'l1', 'iso-8859-1', 'ibm819', 'iso-8859-1', 'cp819', 'iso-8859-1', # windows-1251 'windows-1251', 'windows-1251', 'windows1251', 'windows-1251', # windows-1252 'windows-1252', 'windows-1252', 'windows1252', 'windows-1252', # ShiftJIS 'windows-932', 'windows-932', 'windows932', 'windows-932', # GB2312 'gb2312', 'gb2312', # GB18030 'gb18030', 'gb18030', ); method NORMALIZE_ENCODING(\encoding) { nqp::if( nqp::isconcrete(encoding), nqp::ifnull( nqp::atkey($encodings,encoding), nqp::ifnull( nqp::atkey($encodings,nqp::lc(encoding)), nqp::lc(encoding) ) ), 'utf8' ) } # 1 if all elements of given type, otherwise 0 method ALL_TYPE(\values,\type) { nqp::if( (my int $elems = values.elems), # reifies nqp::stmts( (my $values := nqp::getattr(values,List,'$!reified')), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::istype(nqp::atpos($values,$i),type), nqp::null ), nqp::iseq_i($i,$elems) ) ) } # 1 if all elems defined && type, otherwise 0 method ALL_DEFINED_TYPE(\values,\type) { nqp::if( (my int $elems = values.elems), # reifies nqp::stmts( (my $values := nqp::getattr(values,List,'$!reified')), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::istype(nqp::atpos($values,$i),type) && nqp::defined(nqp::atpos($values,$i)), nqp::null ), nqp::iseq_i($i,$elems) ) ) } # 1 if any element of defined && type, otherwise 0 method ANY_DEFINED_TYPE(\values,\type) { nqp::if( (my int $elems = values.elems), # reifies nqp::stmts( (my $values := nqp::getattr(values,List,'$!reified')), (my int $i = -1), nqp::until( nqp::iseq_i(++$i,$elems) || (nqp::istype(nqp::atpos($values,$i),type) && nqp::defined(nqp::atpos($values,$i))), nqp::null ), nqp::isne_i($i,$elems) ) ) } method TRANSPOSE(str $string, str $original, str $final) { nqp::join($final,nqp::split($original,$string)) } method TRANSPOSE-ONE(Str:D $string, Str:D $original, Str:D $final) { nqp::iseq_i((my int $index = nqp::index($string, $original)), -1) ?? $string !! nqp::concat( nqp::substr($string,0,$index), nqp::concat( $final, nqp::substr($string,nqp::add_i($index,nqp::chars($original))) ) ) } my constant \SHAPE-STORAGE-ROOT := do { my Mu $root := nqp::newtype(nqp::knowhow(), 'Uninstantiable'); nqp::setdebugtypename($root, 'MultiDimArray root'); Metamodel::Primitives.set_parameterizer($root, -> $, $key { # We "steal" the meta-object for the multi-dim storage. my $dims := $key.elems.pred; my $meta := $key.AT-POS(0); my $type := $key.AT-POS(1); my $dim_type := nqp::newtype($meta, 'MultiDimArray'); nqp::composetype($dim_type, nqp::hash('array', nqp::hash('dimensions', $dims, 'type', $type))); nqp::settypehll($dim_type, 'Raku'); # Make sure the debug name and various caches are propagated # for native arrays (where this type is exposed to userspace). if nqp::objprimspec($type) { nqp::setdebugtypename($dim_type, $meta.name($dim_type)); $meta.publish_method_cache($dim_type); $meta.publish_type_cache($dim_type); } $dim_type }); nqp::settypehll($root, 'Raku'); $root } method SHAPED-ARRAY-STORAGE(\spec, Mu \meta-obj, Mu \type) { my $types := nqp::list(meta-obj); # meta + type of each dimension my $dims := nqp::list_i; # elems per dimension nqp::if( nqp::istype(spec,List), nqp::stmts( # potentially more than 1 dim (my $spec := nqp::getattr(nqp::decont(spec),List,'$!reified')), (my int $elems = nqp::elems($spec)), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::istype((my $dim := nqp::atpos($spec,$i)),Whatever), NYI('Jagged array shapes').throw, nqp::if( nqp::istype(($dim := nqp::decont($dim.Int)),Failure), $dim.throw, nqp::if( nqp::isbig_I($dim) || nqp::isle_i($dim,0), X::IllegalDimensionInShape.new(:$dim).throw, nqp::stmts( nqp::push($types,type), nqp::push_i($dims,$dim) ) ) ) ) ) ), nqp::stmts( # only 1 dim nqp::push($types,type), nqp::push_i($dims,spec.Int) ) ); nqp::setdimensions( nqp::create(nqp::parameterizetype(SHAPE-STORAGE-ROOT,$types)), $dims ) } our role ImplementationDetail { method new(|) { die self.gist } method gist(--> Str:D) { "The '{self.^name}' class is a Rakudo-specific implementation detail and has no serviceable parts inside" } method Str( --> Str:D) { self.gist } method raku(--> Str:D) { self.gist } } our role ShapedArrayCommon { method !illegal($operation) { X::IllegalOnFixedDimensionArray.new(:$operation).throw } proto method pop(::?CLASS:D: |) { self!illegal("pop") } proto method shift(::?CLASS:D: |) { self!illegal("shift") } proto method splice(::?CLASS:D: |) { self!illegal("splice") } proto method push(|c) is nodal { self.DEFINITE ?? self!illegal("push") !! self.Any::push(|c) } proto method append(|c) is nodal { self.DEFINITE ?? self!illegal("append") !! self.Any::append(|c) } proto method unshift(|c) is nodal { self.DEFINITE ?? self!illegal("unshift") !! self.Any::unshift(|c) } proto method prepend(|c) is nodal { self.DEFINITE ?? self!illegal("prepend") !! self.Any::prepend(|c) } proto method STORE(::?CLASS:D: |) {*} multi method STORE(::?CLASS:D: Slip:D \slip) { nqp::eqaddr(slip,Empty) ?? X::AdHoc.new( payload => "Cannot Empty a shaped array as its size is fixed").throw !! self.STORE(slip.List) } # illegal unless overridden for 1dimmed case method reverse(::?CLASS:D: |) { self!illegal("reverse") } method rotate(::?CLASS:D: |) { self!illegal("rotate") } multi method values(::?CLASS:D:) { Seq.new(self.iterator) } multi method keys(::?CLASS:D:) { Seq.new(Rakudo::Iterator.ShapeIndex(self.shape)) } multi method invert(::?CLASS:D:) { Seq.new(Rakudo::Iterator.Invert(self.pairs.iterator)) } # These work on the flat view method roll(|c) { self.flat.roll(|c) } method pick(|c) { self.flat.pick(|c) } method permutations(|c) { self.flat.permutations(|c) } method combinations(|c) { self.flat.combinations(|c) } method join(|c) { self.flat.join(|c) } method sort(|c) { self.flat.sort(|c) } multi method gist(::?CLASS:D:) { self.gistseen('Array', { self!gist(nqp::create(Array),self.shape) }) } method !gist(@path, @dims) { if @dims.elems == 1 { '[' ~ (^@dims[0]).map( -> $elem { given ++$ { when 11 { '...' } when 12 { last } default { self.AT-POS(|@path, $elem).gist } } } ).join(' ') ~ ']'; } else { my @nextdims = @dims[1..^@dims.elems]; '[' ~ (^@dims[0]).map( -> $elem { given ++$ { when 11 { '...' } when 12 { last } default { self!gist((flat @path, $elem), @nextdims) } } } ).join("\n" ~ (' ' x @path.elems + 1)) ~ ']'; } } multi method raku(::?CLASS:D \SELF:) { SELF.rakuseen('Array', { self.^name ~ '.new(:shape' ~ nqp::decont(self.shape).raku ~ ', ' ~ self!perl(nqp::create(Array), self.shape) ~ ')' ~ (nqp::iscont(SELF) ?? '.item' !! '') }) } method !perl(@path, @dims) { if @dims.elems == 1 { '[' ~ (^@dims[0]).map({ nqp::decont(self.AT-POS(|@path, $_)).raku }).join(', ') ~ ',' x (@dims[0] == 1 && nqp::istype(self.AT-POS(|@path, 0), Iterable)) ~ ']' } else { my @nextdims = @dims[1..^@dims.elems]; '[' x (@path.elems > 0) ~ (^@dims[0]).map({ self!perl((flat @path, $_), @nextdims) }).join(', ') ~ ',' x (@dims[0] == 1) ~ ']' x (@path.elems > 0) } } multi method Slip() { Slip.from-iterator(self.iterator) } proto method AT-POS(|) is raw {*} multi method AT-POS(::?CLASS:U: |c) is raw { self.Any::AT-POS(|c) } multi method AT-POS(::?CLASS:D:) is raw { die "Must specify at least one index with {self.^name}.AT-POS" } proto method ASSIGN-POS(|) {*} multi method ASSIGN-POS(::?CLASS:U: |c) { self.Any::ASSIGN-POS(|c) } multi method ASSIGN-POS(::?CLASS:D:) { die "Must specify at least one index and a value with {self.^name}.ASSIGN-POS" } multi method ASSIGN-POS(::?CLASS:D: $) { die "Must specify at least one index and a value with {self.^name}.ASSIGN-POS" } proto method EXISTS-POS(|) {*} multi method EXISTS-POS(::?CLASS:U: |c) { self.Any::EXISTS-POS(|c) } multi method EXISTS-POS(::?CLASS:D:) { die "Must specify at least one index with {self.^name}.EXISTS-POS" } } our class SupplySequencer { has &!on-data-ready; has &!on-completed; has &!on-error; has $!buffer; has int $!buffer-start-seq; has int $!done-target; has int $!bust; has $!lock; submethod BUILD( :&!on-data-ready!, :&!on-completed!, :&!on-error! --> Nil) { $!buffer := nqp::list(); $!buffer-start-seq = 0; $!done-target = -1; $!bust = 0; $!lock := Lock::Async.new; } method process(Mu \seq, Mu \data, Mu \err) { $!lock.protect: { if err { &!on-error(err); $!bust = 1; } elsif nqp::isconcrete(data) { my int $insert-pos = seq - $!buffer-start-seq; nqp::bindpos($!buffer, $insert-pos, data); self!emit-events(); } else { $!done-target = seq; self!emit-events(); } } } method !emit-events() { unless $!bust { until nqp::elems($!buffer) == 0 || nqp::isnull(nqp::atpos($!buffer, 0)) { &!on-data-ready(nqp::shift($!buffer)); $!buffer-start-seq = $!buffer-start-seq + 1; } if $!buffer-start-seq == $!done-target { &!on-completed(); } } } } my int $sprintfHandlerInitialized = 0; method initialize-sprintf-handler(--> Nil) { class SprintfHandler { method mine($x) { nqp::reprname($x) eq "P6opaque"; } proto method int(|) {*} multi method int(Mu:D \n) { n.Int } multi method int(Mu:U \n) { n.Numeric.Int } proto method float(|) {*} multi method float(Numeric:D \n) { n } multi method float(Mu \n) { n.Numeric } } unless $sprintfHandlerInitialized { nqp::sprintfaddargumenthandler(SprintfHandler.new); $sprintfHandlerInitialized = 1; } } method MAYBE-STRING(Mu \thing, Str:D :$method = 'gist' --> Str:D) { my Mu \decont = nqp::decont(thing); nqp::can(decont, nqp::decont_s($method)) ?? decont."$method"() !! nqp::can(decont.HOW, 'name') ?? decont.^name !! '?' } method SHORT-STRING(Mu \thing, Str:D :$method = 'gist' --> Str:D) { my str $str = nqp::unbox_s(self.MAYBE-STRING: thing, :$method); nqp::isnull_s($str) ?? "" !! nqp::isgt_i(nqp::chars($str),23) ?? nqp::concat(nqp::substr($str, 0, 20), '...') !! $str } my $IS-WIN = do { my str $os = Rakudo::Internals.TRANSPOSE(nqp::lc( nqp::atkey(nqp::backendconfig,'osname') )," ",""); nqp::hllbool( nqp::iseq_s($os,'mswin32') || nqp::iseq_s($os,'mingw') || nqp::iseq_s($os,'msys') || nqp::iseq_s($os,'cygwin') ) } method IS-WIN() { $IS-WIN } method NUMERIC-ENV-KEY(\key) { %*ENV.EXISTS-KEY(key) ?? %*ENV.AT-KEY(key) ?? +%*ENV.AT-KEY(key) !! 0 !! Nil } method error-rcgye() { # red clear green yellow eject self.NUMERIC-ENV-KEY("RAKUDO_ERROR_COLOR") // !self.IS-WIN ?? ("\e[31m", "\e[0m", "\e[32m", "\e[33m", "\x[23CF]") !! ("", "", "", "", ""); } my num $init-time-num = nqp::div_n(nqp::time,1000000000e0); method INITTIME() is raw { $init-time-num } my $init-thread := nqp::currentthread(); method INITTHREAD() { $init-thread } # easy access to compile options my Mu $compiling-options := nqp::ifnull( # cannot be lazy nqp::atkey(nqp::getlexdyn('%*COMPILING'),'%?OPTIONS'), nqp::hash ); my $LL-EXCEPTION := my $PROFILE := my $OPTIMIZE := my $STAGESTATS := my $INCLUDE := my $E := nqp::null; # running with --ll-exception method LL-EXCEPTION() { nqp::ifnull( $LL-EXCEPTION, $LL-EXCEPTION := nqp::if( nqp::existskey($compiling-options,'ll-exception'), '--ll-exception', Empty ) ) } # running with --profile method PROFILE() { nqp::ifnull( $PROFILE, $PROFILE := nqp::if( nqp::existskey($compiling-options,'profile'), '--profile', Empty ) ) } # running with --optimize=X method OPTIMIZE() { nqp::ifnull( $OPTIMIZE, $OPTIMIZE := nqp::if( nqp::existskey($compiling-options,'optimize'), nqp::concat('--optimize=',nqp::atkey($compiling-options,'optimize')), Empty ) ) } # running with --stagestats method STAGESTATS() { nqp::ifnull( $STAGESTATS, $STAGESTATS := nqp::if( nqp::existskey($compiling-options,'stagestats'), '--stagestats', Empty ) ) } # whatever specified with -I method INCLUDE() { nqp::ifnull( $INCLUDE, $INCLUDE := nqp::p6bindattrinvres( nqp::create(List),List,'$!reified', nqp::if( nqp::existskey($compiling-options,'I'), nqp::stmts( (my $I := nqp::atkey($compiling-options,'I')), nqp::if(nqp::islist($I),$I,nqp::list($I)) ), nqp::list ) ) ) } # the program to execute, either a script or -e, code method PROGRAM() { nqp::ifnull( $E, $E := nqp::if( nqp::existskey($compiling-options,'e'), ('-e', nqp::atkey($compiling-options,'e')), $*PROGRAM-NAME ) ) } method PRECOMP-EXT(--> "moarvm") { } method PRECOMP-TARGET(--> "mbc") { } method TARGET() { "--target=" ~ Rakudo::Internals.PRECOMP-TARGET } # Keep track of the differences between TAI and UTC for internal use. # The "BEGIN" and "END" comments are for tools/add-leap-second.raku. # # Some handy tables: # http://tf.nist.gov/pubs/bulletin/leapsecond.htm # http://hpiers.obspm.fr/eop-pc/earthor/utc/TAI-UTC_tab.html my int constant $initial-offset = 10; # TAI - UTC at the Unix epoch (1970-01-01T00:00:00Z). my constant $dates = nqp::list_s( #BEGIN leap-second-dates '1972-06-30', '1972-12-31', '1973-12-31', '1974-12-31', '1975-12-31', '1976-12-31', '1977-12-31', '1978-12-31', '1979-12-31', '1981-06-30', '1982-06-30', '1983-06-30', '1985-06-30', '1987-12-31', '1989-12-31', '1990-12-31', '1992-06-30', '1993-06-30', '1994-06-30', '1995-12-31', '1997-06-30', '1998-12-31', '2005-12-31', '2008-12-31', '2012-06-30', '2015-06-30', '2016-12-31', #END leap-second-dates ); my int constant $elems = nqp::elems($dates); my constant $daycounts = nqp::list_i( #BEGIN leap-second-daycount 41498, 41682, 42047, 42412, 42777, 43143, 43508, 43873, 44238, 44785, 45150, 45515, 46246, 47160, 47891, 48256, 48803, 49168, 49533, 50082, 50629, 51178, 53735, 54831, 56108, 57203, 57753, #END leap-second-daycount ); method daycount-leapseconds(int $daycount) { my int $i = nqp::elems($daycounts); nqp::while( nqp::isge_i(($i = nqp::sub_i($i,1)),0) && nqp::islt_i($daycount,nqp::atpos_i($daycounts,$i)), nqp::null ); nqp::isge_i($i,0) && nqp::iseq_i($daycount,nqp::atpos_i($daycounts,$i)) } # our %leap-seconds = # @leap-second-dates Z=> $initial-offset + 1 .. *; # So for any date $d in @leap-second-dates, $d 23:59:00 UTC # is the leap second that made (or will make) UTC # %leap-seconds{$d} seconds behind TAI. # Ambiguous POSIX times. my constant $posixes = nqp::list_i( #BEGIN leap-second-posix 78796800, 94694400, 126230400, 157766400, 189302400, 220924800, 252460800, 283996800, 315532800, 362793600, 394329600, 425865600, 489024000, 567993600, 631152000, 662688000, 709948800, 741484800, 773020800, 820454400, 867715200, 915148800, 1136073600, 1230768000, 1341100800, 1435708800, 1483228800, #END leap-second-posix ); method is-leap-second-date(\date) { nqp::hllbool( nqp::stmts( (my str $date = date), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::isgt_s($date,nqp::atpos_s($dates,$i)), nqp::null ), nqp::islt_i($i,$elems) && nqp::iseq_s($date,nqp::atpos_s($dates,$i)) ) ) } method tai-from-posix(\posix, int $prefer-leap-second) { my int $p = posix.floor; my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems) && nqp::isgt_i($p,nqp::atpos_i($posixes,$i)), nqp::null ); posix + nqp::add_i( nqp::add_i($initial-offset,$i), nqp::islt_i($i,$elems) && nqp::not_i($prefer-leap-second) && nqp::iseq_i($p,nqp::atpos_i($posixes,$i)) ) } # take TAI, return epoch method epoch-from-tai(\tai) { my int $tai = tai.floor; my int $t = $tai - $initial-offset; my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems) && nqp::islt_i(nqp::atpos_i($posixes,$i),nqp::sub_i($t,$i)), nqp::null ); nqp::sub_i($tai,nqp::add_i($initial-offset,$i)) } # take TAI, return posix method posix-from-tai(\tai) { my int $t = tai.floor - $initial-offset; my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems) && nqp::islt_i(nqp::atpos_i($posixes,$i),nqp::sub_i($t,$i)), nqp::null ); tai - nqp::add_i($initial-offset,$i) } # take TAI, return epoch and if in leap-second method posix-and-leap-from-tai(\tai) { my int $t = tai.floor - $initial-offset; my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems) && nqp::islt_i(nqp::atpos_i($posixes,$i),nqp::sub_i($t,$i)), nqp::null ); (tai - nqp::add_i($initial-offset,$i), nqp::hllbool( nqp::islt_i($i,$elems) && nqp::iseq_i(nqp::atpos_i($posixes,$i),nqp::sub_i($t,$i)) ) ) } my $initializers; #nqp::print("running mainline\n"); #method INITIALIZERS() { $initializers } method REGISTER-DYNAMIC( str $name, &code, str $version = '6.c', :$override --> Nil) { #my $id := nqp::p6box_i(nqp::threadid(nqp::currentthread)); #nqp::say("$id: Registering $name"); $initializers := nqp::hash unless $initializers; my str $with = nqp::concat($version,nqp::concat("\0",$name)); nqp::if( $override, nqp::stmts( nqp::bindkey($initializers,$with,&code), nqp::bindkey($initializers,$name,&code) ), nqp::stmts( nqp::if( nqp::existskey($initializers,$with), (die "Already have initializer for '$name' ('$version')"), nqp::bindkey($initializers,$with,&code) ), nqp::unless( # first come, first kept nqp::existskey($initializers,$name), nqp::bindkey($initializers,$name,&code) ) ) ) } my $dynamics-not-found := nqp::hash; sub dynamic-not-found(str $key, str $name) { #nqp::say("failed: $name"); nqp::ifnull( nqp::atkey($dynamics-not-found,$key), nqp::bindkey($dynamics-not-found,$key, X::Dynamic::NotFound.new(:$name).Failure ) ) } my $DYNAMIC-INITIALIZATION-LOCK := Lock.new; method INITIALIZE-DYNAMIC(str $name, @deprecation?) is raw { my str $key = nqp::replace($name,1,1,''); $DYNAMIC-INITIALIZATION-LOCK.protect: { #my $id := nqp::p6box_i(nqp::threadid(nqp::currentthread)); #nqp::say("$id: Initializing $name"); PROCESS::.EXISTS-KEY($key) # beat another thread us to it? ?? PROCESS::.AT-KEY($key) # yes, so just return that !! nqp::isnull( my $code := nqp::ifnull( nqp::atkey( $initializers, nqp::concat( nqp::getcomp('Raku').language_version, nqp::concat("\0",$name) ) ), nqp::atkey($initializers,$name) ) ) ?? dynamic-not-found($key, $name) !! do { Rakudo::Deprecations.DEPRECATED(@deprecation[1], '6.' ~ @deprecation[0], :what($name), :file(@deprecation[2]), :line(@deprecation[3])) if @deprecation; $code() } } } method EXPAND-LITERAL-RANGE(Str:D \x,$list) { my str $s = nqp::unbox_s(x); my int $chars = nqp::chars($s); my Mu $result := nqp::list(); my int $start = 1; my int $found = nqp::index($s,'..',$start); # found and not at the end without trail while nqp::isne_i($found,-1) && nqp::isne_i($found,$chars-2) { if $found - $start -> $unsplit { nqp::splice( $result, nqp::split("",nqp::substr($s,$start - 1,$unsplit)), nqp::elems($result), 0 ) } # add the range excluding last (may be begin point next range) my int $from = nqp::ordat($s,$found - 1) - 1; my int $to = nqp::ordat($s,$found + 2); nqp::push($result,nqp::chr($from)) while nqp::islt_i($from = $from + 1,$to); # look for next range $found = nqp::index($s,'..',$start = $found + 3); } # add final bits nqp::splice( $result, nqp::split("",nqp::substr($s,$start - 1)), nqp::elems($result), 0 ) if nqp::isle_i($start,$chars); $list ?? $result !! nqp::join("",$result) } my int $VERBATIM-EXCEPTION = 0; method VERBATIM-EXCEPTION($set?) { my int $value = $VERBATIM-EXCEPTION; $VERBATIM-EXCEPTION = $set if defined($set); $value } method MAKE-ABSOLUTE-PATH(Str:D $path, Str:D $abspath) { if $path.ord == 47 { # 4x faster substr($path,0,1) eq "/" $path } elsif $path.substr-eq(":",1) { # assume C: something if $path.substr-eq("/",2) { # assume C:/ like prefix $path } elsif !$abspath.starts-with(substr($path,0,2)) { die "Can not set relative dir from different roots"; } else { $abspath ~ substr($path,2) } } else { # assume relative path $abspath ~ $path; } } method MAKE-BASENAME(Str:D \abspath) { my str $abspath = nqp::unbox_s(abspath); my int $offset = nqp::rindex($abspath,'/'); nqp::iseq_i($offset,-1) ?? abspath !! nqp::p6box_s(nqp::substr($abspath,$offset + 1)); } my constant $clean-parts-nul = nqp::hash( '..', 1, '.', 1, '', 1 ); method MAKE-CLEAN-PARTS(Str:D \abspath) { my str $abspath = nqp::unbox_s(abspath); my $parts := nqp::split('/',$abspath); # handle //unc/ on win if nqp::iseq_s(nqp::atpos($parts,1),'') # // && nqp::iseq_s(nqp::atpos($parts,0),'') { # and no C: like stuff my str $front = nqp::join('/',nqp::list( # collapse to '//unc/' nqp::atpos($parts,0), nqp::atpos($parts,1), nqp::atpos($parts,2), )); nqp::splice($parts,nqp::list($front),0,3); # and replace } # front part cleanup nqp::splice($parts,$empty,1,1) while nqp::existskey($clean-parts-nul,nqp::atpos($parts,1)); # recursive ".." and "." handling sub updirs($index is copy) { # the end if $index == 1 { nqp::splice($parts,$empty,1,1); 1 } # something to check elsif nqp::atpos($parts,$index - 1) -> $part { if nqp::iseq_i(nqp::ord($part),46) { # substr($part,0,1) eq '.' if nqp::iseq_s($part,'..') { updirs($index - 1); } elsif nqp::iseq_s($part,'.') { nqp::splice($parts,$empty,$index,1); updirs($index - 1); } else { nqp::splice($parts,$empty,--$index,2); $index; } } else { nqp::splice($parts,$empty,--$index,2); $index; } } # nul, just ignore else { nqp::splice($parts,$empty,$index,1); updirs($index); } } # back part cleanup my int $checks = nqp::elems($parts) - 1; while nqp::isgt_i($checks,1) { if nqp::atpos($parts,$checks) -> $part { nqp::iseq_s($part,'..') ?? ($checks = updirs($checks)) !! nqp::iseq_s($part,'.') ?? nqp::splice($parts,$empty,$checks--,1) !! --$checks; } else { nqp::splice($parts,$empty,$checks--,1); } } # need / at the end nqp::push($parts,""); $parts } method REMOVE-ROOT(Str:D \root, Str:D \path) { my str $root = nqp::unbox_s(root); my str $path = nqp::unbox_s(path); nqp::eqat($path,$root,0) ?? nqp::p6box_s(nqp::substr($path,nqp::chars($root))) !! path; } my class DirRecurse does Iterator { has str $!abspath; has $!handle; has $!dir; has $!file, has str $!dir-sep; has $!todo; has $!seen; method !SET-SELF(\abspath,$!dir,$!file) { $!abspath = abspath; $!handle := nqp::opendir($!abspath); $!dir-sep = $*SPEC.dir-sep; $!todo := nqp::list_s; $!seen := nqp::hash($!abspath,1); $!abspath = nqp::concat($!abspath,$!dir-sep); self } method new(\abspath,\dir,\file) { nqp::stat(abspath,nqp::const::STAT_EXISTS) && nqp::stat(abspath,nqp::const::STAT_ISDIR) ?? nqp::create(self)!SET-SELF(abspath,dir,file) !! Rakudo::Iterator.Empty } method !next() { nqp::while( !$!handle || nqp::isnull_s(my str $elem = nqp::nextfiledir($!handle)) || nqp::iseq_i(nqp::chars($elem),0), nqp::stmts( nqp::if( nqp::defined($!handle), nqp::stmts( nqp::closedir($!handle), ($!handle := Any), ) ), nqp::if( nqp::elems($!todo), nqp::stmts( ($!abspath = nqp::pop_s($!todo)), nqp::handle( ($!handle := nqp::opendir($!abspath)), 'CATCH', 0 ), ($!abspath = nqp::concat($!abspath,$!dir-sep)) ), return '' ) ) ); $elem } method pull-one() { nqp::while( nqp::chars(my str $entry = self!next), nqp::stmts( (my str $path = nqp::concat($!abspath,$entry)), (my $stat := nqp::dispatch('boot-syscall', 'file-stat', nqp::decont_s($path), 0)), nqp::if( nqp::dispatch('boot-syscall', 'stat-flags', $stat, nqp::const::STAT_ISREG) && $!file.ACCEPTS($entry), (return $path), nqp::if( $!dir.ACCEPTS($entry) && nqp::dispatch('boot-syscall', 'stat-flags', $stat, nqp::const::STAT_ISDIR), nqp::stmts( nqp::if( nqp::fileislink($path), $path = IO::Path.new( $path,:CWD($!abspath)).resolve.absolute ), nqp::unless( nqp::existskey($!seen,$path), nqp::stmts( nqp::bindkey($!seen,$path,1), nqp::push_s($!todo,$path) ) ) ) ) ) ) ); IterationEnd } } method DIR-RECURSE( \abspath, Mu :$dir = -> str $elem { nqp::not_i(nqp::eqat($elem,'.',0)) }, Mu :$file = True ) { Seq.new(DirRecurse.new(abspath,$dir,$file)) } method FILETEST-E(str \abspath) is raw { nqp::stat(abspath,nqp::const::STAT_EXISTS) } method FILETEST-LE(str \abspath) is raw { nqp::lstat(abspath,nqp::const::STAT_EXISTS) } method FILETEST-D(str \abspath) is raw { nqp::stat(abspath,nqp::const::STAT_ISDIR); } method FILETEST-F(str \abspath) is raw { nqp::stat(abspath,nqp::const::STAT_ISREG); } method FILETEST-S(str \abspath) is raw { nqp::stat(abspath,nqp::const::STAT_FILESIZE) } method FILETEST-ES(str \abspath) is raw { nqp::stat(abspath,nqp::const::STAT_EXISTS) && nqp::stat(abspath,nqp::const::STAT_FILESIZE) } method FILETEST-L(str \abspath) is raw { nqp::fileislink(abspath) } method FILETEST-R(str \abspath) is raw { nqp::filereadable(abspath) } method FILETEST-W(str \abspath) is raw { nqp::filewritable(abspath) } method FILETEST-RW(str \abspath) is raw { nqp::filereadable(abspath) && nqp::filewritable(abspath) } method FILETEST-X(str \abspath) is raw { nqp::fileexecutable(abspath) } method FILETEST-RWX(str \abspath) is raw { nqp::filereadable(abspath) && nqp::filewritable(abspath) && nqp::fileexecutable(abspath) } method FILETEST-Z(str \abspath) is raw { nqp::iseq_i(nqp::stat(abspath,nqp::const::STAT_FILESIZE),0) } method FILETEST-CREATED(str \abspath) is raw { nqp::stat_time(abspath,nqp::const::STAT_CREATETIME) } method FILETEST-MODIFIED(str \abspath) is raw { nqp::stat_time(abspath,nqp::const::STAT_MODIFYTIME) } method FILETEST-ACCESSED(str \abspath) is raw { nqp::stat_time(abspath,nqp::const::STAT_ACCESSTIME) } method FILETEST-CHANGED(str \abspath) is raw { nqp::stat_time(abspath,nqp::const::STAT_CHANGETIME) } method FILETEST-MODE(str \abspath) is raw { nqp::bitand_i(nqp::stat(abspath,nqp::const::STAT_PLATFORM_MODE),0o7777) } method HANDLE-NQP-SPRINTF-ERRORS(Mu \exception, str $format) { my $vmex := nqp::getattr(nqp::decont(exception), Exception, '$!ex'); my \payload := nqp::getpayload($vmex); if nqp::elems(payload) == 1 { if nqp::existskey(payload, 'BAD_TYPE_FOR_DIRECTIVE') { X::Str::Sprintf::Directives::BadType.new: type => nqp::atkey(nqp::atkey(payload, 'BAD_TYPE_FOR_DIRECTIVE'), 'TYPE'), directive => nqp::atkey(nqp::atkey(payload, 'BAD_TYPE_FOR_DIRECTIVE'), 'DIRECTIVE'), value => nqp::atkey(nqp::atkey(payload, 'BAD_TYPE_FOR_DIRECTIVE'), 'VALUE'), format => $format, } elsif nqp::existskey(payload, 'BAD_DIRECTIVE') { X::Str::Sprintf::Directives::Unsupported.new: directive => nqp::atkey(nqp::atkey(payload, 'BAD_DIRECTIVE'), 'DIRECTIVE'), sequence => nqp::atkey(nqp::atkey(payload, 'BAD_DIRECTIVE'), 'SEQUENCE'), } elsif nqp::existskey(payload, 'DIRECTIVES_COUNT') { X::Str::Sprintf::Directives::Count.new: args-have => nqp::atkey(nqp::atkey(payload, 'DIRECTIVES_COUNT'), 'ARGS_HAVE'), args-used => nqp::atkey(nqp::atkey(payload, 'DIRECTIVES_COUNT'), 'ARGS_USED'), format => $format, } else { exception } } else { exception } } #- start of generated part of succ/pred --------------------------------------- #- Generated on 2016-08-10T14:19:20+02:00 by tools/build/makeMAGIC_INC_DEC.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE # normal increment magic chars & incremented char at same index my $succ-nlook = '012345678ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨαβγδεζηθικλμνξοπρστυφχψאבגדהוזחטיךכלםמןנסעףפץצקרשАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮабвгдежзийклмнопрстуфхцчшщъыьэю٠١٢٣٤٥٦٧٨۰۱۲۳۴۵۶۷۸߀߁߂߃߄߅߆߇߈०१२३४५६७८০১২৩৪৫৬৭৮੦੧੨੩੪੫੬੭੮૦૧૨૩૪૫૬૭૮୦୧୨୩୪୫୬୭୮௦௧௨௩௪௫௬௭௮౦౧౨౩౪౫౬౭౮೦೧೨೩೪೫೬೭೮൦൧൨൩൪൫൬൭൮෦෧෨෩෪෫෬෭෮๐๑๒๓๔๕๖๗๘໐໑໒໓໔໕໖໗໘༠༡༢༣༤༥༦༧༨၀၁၂၃၄၅၆၇၈႐႑႒႓႔႕႖႗႘០១២៣៤៥៦៧៨᠐᠑᠒᠓᠔᠕᠖᠗᠘᥆᥇᥈᥉᥊᥋᥌᥍᥎᧐᧑᧒᧓᧔᧕᧖᧗᧘᪀᪁᪂᪃᪄᪅᪆᪇᪈᪐᪑᪒᪓᪔᪕᪖᪗᪘᭐᭑᭒᭓᭔᭕᭖᭗᭘᮰᮱᮲᮳᮴᮵᮶᮷᮸᱀᱁᱂᱃᱄᱅᱆᱇᱈᱐᱑᱒᱓᱔᱕᱖᱗᱘⁰ⁱ⁲⁳⁴⁵⁶⁷⁸₀₁₂₃₄₅₆₇₈ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴▁▂▃▄▅▆▇⚀⚁⚂⚃⚄❶❷❸❹❺❻❼❽❾꘠꘡꘢꘣꘤꘥꘦꘧꘨꣐꣑꣒꣓꣔꣕꣖꣗꣘꣠꣡꣢꣣꣤꣥꣦꣧꣨꤀꤁꤂꤃꤄꤅꤆꤇꤈꧐꧑꧒꧓꧔꧕꧖꧗꧘꧰꧱꧲꧳꧴꧵꧶꧷꧸꩐꩑꩒꩓꩔꩕꩖꩗꩘꯰꯱꯲꯳꯴꯵꯶꯷꯸012345678🍺🐪'; my $succ-nchrs = '123456789BCDEFGHIJKLMNOPQRSTUVWXYZbcdefghijklmnopqrstuvwxyzΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩβγδεζηθικλμνξοπρστυφχψωבגדהוזחטיךכלםמןנסעףפץצקרשתБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯбвгдежзийклмнопрстуфхцчшщъыьэюя١٢٣٤٥٦٧٨٩۱۲۳۴۵۶۷۸۹߁߂߃߄߅߆߇߈߉१२३४५६७८९১২৩৪৫৬৭৮৯੧੨੩੪੫੬੭੮੯૧૨૩૪૫૬૭૮૯୧୨୩୪୫୬୭୮୯௧௨௩௪௫௬௭௮௯౧౨౩౪౫౬౭౮౯೧೨೩೪೫೬೭೮೯൧൨൩൪൫൬൭൮൯෧෨෩෪෫෬෭෮෯๑๒๓๔๕๖๗๘๙໑໒໓໔໕໖໗໘໙༡༢༣༤༥༦༧༨༩၁၂၃၄၅၆၇၈၉႑႒႓႔႕႖႗႘႙១២៣៤៥៦៧៨៩᠑᠒᠓᠔᠕᠖᠗᠘᠙᥇᥈᥉᥊᥋᥌᥍᥎᥏᧑᧒᧓᧔᧕᧖᧗᧘᧙᪁᪂᪃᪄᪅᪆᪇᪈᪉᪑᪒᪓᪔᪕᪖᪗᪘᪙᭑᭒᭓᭔᭕᭖᭗᭘᭙᮱᮲᮳᮴᮵᮶᮷᮸᮹᱁᱂᱃᱄᱅᱆᱇᱈᱉᱑᱒᱓᱔᱕᱖᱗᱘᱙ⁱ⁲⁳⁴⁵⁶⁷⁸⁹₁₂₃₄₅₆₇₈₉ⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻ②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵▂▃▄▅▆▇█⚁⚂⚃⚄⚅❷❸❹❺❻❼❽❾❿꘡꘢꘣꘤꘥꘦꘧꘨꘩꣑꣒꣓꣔꣕꣖꣗꣘꣙꣡꣢꣣꣤꣥꣦꣧꣨꣩꤁꤂꤃꤄꤅꤆꤇꤈꤉꧑꧒꧓꧔꧕꧖꧗꧘꧙꧱꧲꧳꧴꧵꧶꧷꧸꧹꩑꩒꩓꩔꩕꩖꩗꩘꩙꯱꯲꯳꯴꯵꯶꯷꯸꯹123456789🍻🐫'; # magic increment chars at boundary & incremented char at same index my $succ-blook = '9ZzΩωתЯя٩۹߉९৯੯૯୯௯౯೯൯෯๙໙༩၉႙៩᠙᥏᧙᪉᪙᭙᮹᱉᱙⁹₉Ⅻⅻ⑳⒇⒵█⚅❿꘩꣙꣩꤉꧙꧹꩙꯹9🍻🐫'; my $succ-bchrs = '10AAaaΑΑααאאААаа١٠۱۰߁߀१०১০੧੦૧૦୧୦௧௦౧౦೧೦൧൦෧෦๑๐໑໐༡༠၁၀႑႐១០᠑᠐᥇᥆᧑᧐᪁᪀᪑᪐᭑᭐᮱᮰᱁᱀᱐᱐ⁱ⁰₁₀ⅠⅠⅰⅰ①①⑴⑴⒜⒜▁▁⚀⚀❶❶꘡꘠꣐꣐꣠꣠꤁꤀꧑꧐꧱꧰꩑꩐꯱꯰10🍻🍺🐫🐪'; # normal decrement magic chars & incremented char at same index my $pred-nlook = '123456789BCDEFGHIJKLMNOPQRSTUVWXYZbcdefghijklmnopqrstuvwxyzΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩβγδεζηθικλμνξοπρστυφχψωבגדהוזחטיךכלםמןנסעףפץצקרשתБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯбвгдежзийклмнопрстуфхцчшщъыьэюя١٢٣٤٥٦٧٨٩۱۲۳۴۵۶۷۸۹߁߂߃߄߅߆߇߈߉१२३४५६७८९১২৩৪৫৬৭৮৯੧੨੩੪੫੬੭੮੯૧૨૩૪૫૬૭૮૯୧୨୩୪୫୬୭୮୯௧௨௩௪௫௬௭௮௯౧౨౩౪౫౬౭౮౯೧೨೩೪೫೬೭೮೯൧൨൩൪൫൬൭൮൯෧෨෩෪෫෬෭෮෯๑๒๓๔๕๖๗๘๙໑໒໓໔໕໖໗໘໙༡༢༣༤༥༦༧༨༩၁၂၃၄၅၆၇၈၉႑႒႓႔႕႖႗႘႙១២៣៤៥៦៧៨៩᠑᠒᠓᠔᠕᠖᠗᠘᠙᥇᥈᥉᥊᥋᥌᥍᥎᥏᧑᧒᧓᧔᧕᧖᧗᧘᧙᪁᪂᪃᪄᪅᪆᪇᪈᪉᪑᪒᪓᪔᪕᪖᪗᪘᪙᭑᭒᭓᭔᭕᭖᭗᭘᭙᮱᮲᮳᮴᮵᮶᮷᮸᮹᱁᱂᱃᱄᱅᱆᱇᱈᱉᱑᱒᱓᱔᱕᱖᱗᱘᱙ⁱ⁲⁳⁴⁵⁶⁷⁸⁹₁₂₃₄₅₆₇₈₉ⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻ②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵▂▃▄▅▆▇█⚁⚂⚃⚄⚅❷❸❹❺❻❼❽❾❿꘡꘢꘣꘤꘥꘦꘧꘨꘩꣑꣒꣓꣔꣕꣖꣗꣘꣙꣡꣢꣣꣤꣥꣦꣧꣨꣩꤁꤂꤃꤄꤅꤆꤇꤈꤉꧑꧒꧓꧔꧕꧖꧗꧘꧙꧱꧲꧳꧴꧵꧶꧷꧸꧹꩑꩒꩓꩔꩕꩖꩗꩘꩙꯱꯲꯳꯴꯵꯶꯷꯸꯹123456789🍻🐫'; my $pred-nchrs = '012345678ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨαβγδεζηθικλμνξοπρστυφχψאבגדהוזחטיךכלםמןנסעףפץצקרשАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮабвгдежзийклмнопрстуфхцчшщъыьэю٠١٢٣٤٥٦٧٨۰۱۲۳۴۵۶۷۸߀߁߂߃߄߅߆߇߈०१२३४५६७८০১২৩৪৫৬৭৮੦੧੨੩੪੫੬੭੮૦૧૨૩૪૫૬૭૮୦୧୨୩୪୫୬୭୮௦௧௨௩௪௫௬௭௮౦౧౨౩౪౫౬౭౮೦೧೨೩೪೫೬೭೮൦൧൨൩൪൫൬൭൮෦෧෨෩෪෫෬෭෮๐๑๒๓๔๕๖๗๘໐໑໒໓໔໕໖໗໘༠༡༢༣༤༥༦༧༨၀၁၂၃၄၅၆၇၈႐႑႒႓႔႕႖႗႘០១២៣៤៥៦៧៨᠐᠑᠒᠓᠔᠕᠖᠗᠘᥆᥇᥈᥉᥊᥋᥌᥍᥎᧐᧑᧒᧓᧔᧕᧖᧗᧘᪀᪁᪂᪃᪄᪅᪆᪇᪈᪐᪑᪒᪓᪔᪕᪖᪗᪘᭐᭑᭒᭓᭔᭕᭖᭗᭘᮰᮱᮲᮳᮴᮵᮶᮷᮸᱀᱁᱂᱃᱄᱅᱆᱇᱈᱐᱑᱒᱓᱔᱕᱖᱗᱘⁰ⁱ⁲⁳⁴⁵⁶⁷⁸₀₁₂₃₄₅₆₇₈ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴▁▂▃▄▅▆▇⚀⚁⚂⚃⚄❶❷❸❹❺❻❼❽❾꘠꘡꘢꘣꘤꘥꘦꘧꘨꣐꣑꣒꣓꣔꣕꣖꣗꣘꣠꣡꣢꣣꣤꣥꣦꣧꣨꤀꤁꤂꤃꤄꤅꤆꤇꤈꧐꧑꧒꧓꧔꧕꧖꧗꧘꧰꧱꧲꧳꧴꧵꧶꧷꧸꩐꩑꩒꩓꩔꩕꩖꩗꩘꯰꯱꯲꯳꯴꯵꯶꯷꯸012345678🍺🐪'; # magic decrement chars at boundary & incremented char at same index my $pred-blook = '0AaΑαאАа٠۰߀०০੦૦୦௦౦೦൦෦๐໐༠၀႐០᠐᥆᧐᪀᪐᭐᮰᱀᱐⁰₀Ⅰⅰ①⑴⒜▁⚀❶꘠꣐꣠꤀꧐꧰꩐꯰0🍺🐪'; my $pred-bchrs = '9ZzΩωתЯя٩۹߉९৯੯૯୯௯౯೯൯෯๙໙༩၉႙៩᠙᥏᧙᪉᪙᭙᮹᱉᱙⁹₉Ⅻⅻ⑳⒇⒵█⚅❿꘩꣙꣩꤉꧙꧹꩙꯹9🍻🐫'; #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of succ/pred ----------------------------------------- # number of chars that should be considered for magic .succ/.pred method POSSIBLE-MAGIC-CHARS(str \string) { # only look at stuff before the last period my int $i = nqp::index(string,"."); nqp::iseq_i($i,-1) ?? nqp::chars(string) !! $i } # return -1 if string cannot support .succ, else index of last char method CAN-SUCC-INDEX(str \string, int \chars) { my int $i = chars; Nil while nqp::isge_i(--$i,0) && nqp::iseq_i(nqp::index($succ-nlook,nqp::substr(string,$i,1)),-1) && nqp::iseq_i(nqp::index($succ-blook,nqp::substr(string,$i,1)),-1); $i } # next logical string frontend, hopefully inlinable (pos >= 0) method SUCC(str \string, int \pos) { my int $at = nqp::index($succ-nlook,nqp::substr(string,pos,1)); nqp::iseq_i($at,-1) ?? SUCC-NOT-SO-SIMPLE(string,pos) !! nqp::replace(string,pos,1,nqp::substr($succ-nchrs,$at,1)) } # slow path for next logical string sub SUCC-NOT-SO-SIMPLE(str \string, int \pos) { # nothing magical going on my int $at = nqp::index($succ-blook,nqp::substr(string,pos,1)); if nqp::iseq_i($at,-1) { string } # we have a boundary else { # initial change my int $i = pos; my str $str = nqp::replace(string,$i,1, nqp::substr($succ-bchrs,nqp::add_i($at,$at),2)); # until we run out of chars to check while nqp::isge_i(--$i,0) { # not an easy magical $at = nqp::index($succ-nlook,nqp::substr($str,$i,1)); if nqp::iseq_i($at,-1) { # done if not a boundary magical either $at = nqp::index($succ-blook,nqp::substr($str,$i,1)); return $str if nqp::iseq_i($at,-1); # eat first of last magical, and continue $str = nqp::replace($str,$i,2, nqp::substr($succ-bchrs,nqp::add_i($at,$at),2)); } # normal magical, eat first of last magical, and we're done else { return nqp::replace($str,$i,2, nqp::substr($succ-nchrs,$at,1)); } } $str } } # previous logical string frontend, hopefully inlinable method PRED(str \string, int \pos) { my int $at = nqp::index($pred-nlook,nqp::substr(string,pos,1)); nqp::iseq_i($at,-1) ?? PRED-NOT-SO-SIMPLE(string,pos) !! nqp::replace(string,pos,1,nqp::substr($pred-nchrs,$at,1)) } # slow path for previous logical string sub PRED-NOT-SO-SIMPLE(str \string, int \pos) { # nothing magical going on my int $at = nqp::index($pred-blook,nqp::substr(string,pos,1)); if nqp::iseq_i($at,-1) { string } # we have a boundary else { # initial change my int $i = pos; my str $str = nqp::replace(string,$i,1, nqp::substr($pred-bchrs,$at,1)); # until we run out of chars to check while nqp::isge_i(--$i,0) { # not an easy magical $at = nqp::index($pred-nlook,nqp::substr($str,$i,1)); if nqp::iseq_i($at,-1) { # not a boundary magical either $at = nqp::index($pred-blook,nqp::substr($str,$i,1)); nqp::iseq_i($at,-1) ?? fail('Decrement out of range') !! ($str = nqp::replace($str,$i,1, nqp::substr($pred-bchrs,$at,1))) } # normal magical, update, and we're done else { return nqp::replace($str,$i,1, nqp::substr($pred-nchrs,$at,1)) } } 'Decrement out of range'.Failure } } method WALK-AT-POS(\target,\indices) is raw { my $target := target; my $indices := nqp::getattr(indices,List,'$!reified'); my int $elems = nqp::elems($indices); my int $i = -1; $target := $target.AT-POS(nqp::atpos($indices,$i)) while nqp::islt_i(++$i,$elems); $target } method INFIX_COMMA_SLIP_HELPER(\reified, \future) { my $list := nqp::p6bindattrinvres(nqp::create(List),List,'$!reified',reified); nqp::bindattr($list,List,'$!todo', my $todo:= nqp::create(List::Reifier) ); nqp::bindattr($todo,List::Reifier,'$!reified',reified); nqp::bindattr($todo,List::Reifier,'$!future', nqp::getattr(future,List,'$!reified') ); nqp::bindattr($todo,List::Reifier,'$!reification-target',reified); $list } my $METAOP_ASSIGN := nqp::null; # lazy storage for core METAOP_ASSIGN ops method METAOP_ASSIGN(\op) { my \op-is := nqp::ifnull( nqp::atkey( # is it a core op? nqp::ifnull($METAOP_ASSIGN,INSTALL-CORE-METAOPS()), nqp::objectid(op) ), -> Mu \a, Mu \b { a = op.( ( a.DEFINITE ?? a !! op.() ), b) } ); op-is.set_name(op.name ~ ' + {assigning}'); # checked for in Hyper.new op-is } # Method for lazily installing fast versions of METAOP_ASSIGN ops for # core infix ops. Since the compilation of &[op] happens at build time # of the setting, we're sure we're referring to the core ops and not one # that has been locally installed. Called by METAOP_ASSIGN. Please add # any other core ops that seem to be necessary. sub INSTALL-CORE-METAOPS() { my $metaop_assign := nqp::create(Rakudo::Internals::IterationSet); for ( &[+], -> Mu \a, Mu \b { a = a.DEFINITE ?? a + b !! +b }, &[%], -> Mu \a, Mu \b { a = a.DEFINITE ?? a % b !! "infix:<%>".no-zero-arg }, &[-], -> Mu \a, Mu \b { a = a.DEFINITE ?? a - b !! -b }, &[*], -> Mu \a, Mu \b { a = a.DEFINITE ?? a * b !! +b }, &[~], -> Mu \a, Mu \b { a = a.DEFINITE ?? a ~ b !! ~b }, ) -> \op, \metaop { metaop.set_name(op.name ~ ' + {assigning}'); nqp::bindkey($metaop_assign, nqp::objectid(op), metaop); } $METAOP_ASSIGN := $metaop_assign; } # Return a nqp list iterator from an IterationSet proto method IterationSet2keys(|) {*} multi method IterationSet2keys(IterationSet:U) { nqp::list_s } multi method IterationSet2keys(IterationSet:D \iterationset) { my $iter := nqp::iterator(iterationset); my $keys := nqp::list_s; nqp::while( $iter, nqp::push_s($keys,nqp::iterkey_s(nqp::shift($iter))) ); $keys } # Return an Inline::Perl5 interpreter if possible my $P5; method PERL5() { $P5 //= do { { CATCH { #X::Eval::NoSuchLang.new(:$lang).throw; .note; } my $compunit := $*REPO.need( CompUnit::DependencySpecification.new( :short-name ) ); GLOBAL.WHO.merge-symbols($compunit.handle.globalish-package); } ::("Inline::Perl5").default_perl5 } } my %vm-sigs; method VM-SIGNALS() { %vm-sigs ?? %vm-sigs !! %vm-sigs = nqp::getsignals } # Re-run current process with a given environment variable and value, # given as a named variable, e.g. .RERUN-WITH(MVM_SPESH_INLINE_LOG => 1). # If that environment variable is *not* set, it will run the current # executor with the originally given parameters *and* the environment # variable set and return the associated Proc::Async object. Once the # process is done, it will do an exit with the status result of the # child process. If that environment variable is set, it will return # Nil (and do nothing), allowing that process to run its normal course. method RERUN-WITH() { my str $name = nqp::iterkey_s( nqp::shift(nqp::iterator(nqp::getattr(%_,Map,'$!storage'))) ); return Nil if %*ENV.EXISTS-KEY($name); %*ENV{$name} := %_{$name}; Proc::Async.new( $*EXECUTABLE.absolute, Rakudo::Internals.LL-EXCEPTION, Rakudo::Internals.PROFILE, Rakudo::Internals.INCLUDE.map({ ("-I", $_).Slip }), Rakudo::Internals.PROGRAM, |@*ARGS ) } # NEXT-ID must generate a unique never repeating integer ID. We don't use atomicint here first, because it is not # available yet; second, because Int provides virtually unlimited pool of IDs. # Start with 1024 to preserve lower values for compiler-specific needs. For example, $*STACK-ID of the program # mainline is always 0. my Int:D $next-id = 1024; method NEXT-ID(--> Int:D) { cas $next-id, { nqp::add_I(nqp::decont($_), 1, Int) } } method DEPARSE(Mu \node) { nqp::gethllsym('Raku', 'DEPARSE').new.deparse(node) } } # expose the number of bits a native int has my constant $?BITS = nqp::isgt_i(nqp::add_i(2147483648, 1), 0) ?? 64 !! 32; { # setting up END phaser handling my int $the-end-is-done; my $the-end-locker = Lock.new; # END handling, returns trueish if END handling already done/in progress nqp::bindcurhllsym('&THE_END', { unless $the-end-is-done { $the-end-locker.protect: { unless $the-end-is-done { my $end := nqp::getcurhllsym('@END_PHASERS'); my @exceptions; while nqp::elems($end) { # run all END blocks quietly { my $result := nqp::shift($end)(); nqp::isfalse(nqp::isnull($result)) && nqp::can($result, 'sink') && $result.sink; CATCH { default { @exceptions.push($_) } } } } # close all open files IO::Handle.^find_private_method( 'close-all-open-handles' )(IO::Handle); if @exceptions { note "Some exceptions were thrown in END blocks:"; note " $_.^name(): $_.message()\n$_.backtrace.Str.indent(4)" for @exceptions; } nqp::not_i(($the-end-is-done = 1)); # we're really done now } } } } ); } # we need this to run *after* the mainline of Rakudo::Internals has run PROCESS::<$EXIT> = 0; PROCESS::<$EXCEPTION> = Exception; Rakudo::Internals.REGISTER-DYNAMIC: '&*EXIT', { PROCESS::<&EXIT> := sub exit($status) { state $exit = $status; # first call to exit sets value $*EXIT = $exit; nqp::getcurhllsym('&THE_END')() ?? $exit !! nqp::exit(nqp::unbox_i($exit.Int)) } } proto sub exit($?, *%) {*} multi sub exit() { &*EXIT(0) } multi sub exit(Int(Any) $status) { &*EXIT($status) } Metamodel::Configuration.set_utility_class(Rakudo::Internals); #line 1 SETTING::src/core.c/Rakudo/Iterator.rakumod # necessary stubs my class X::Range::CannotIterate { ... } # 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 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 method !SET-SELF(\path, Mu \tester) { $!path := path; $!prefix = path.prefix-for-dir; $!CWD := path.CWD.IO; $!tester := tester; $!dirhandle := nqp::opendir(path.absolute); self } method new(\path, Mu \tester) { nqp::create(self)!SET-SELF(path, tester) } method pull-one() { my str $entry; my $*CWD := $!CWD; nqp::while( ($entry = nqp::nextfiledir($!dirhandle)), 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) { my $path := $!path; my str $prefix = $!prefix; my $tester := $!tester; my $dirhandle := $!dirhandle; my str $entry; my $*CWD := $!CWD; nqp::while( ($entry = nqp::nextfiledir($dirhandle)), 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; 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,'..') }, ) } } 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 $!source; has Iterator $!nested; method new(\source) { nqp::p6bindattrinvres(nqp::create(self),self,'$!source',source) } method pull-one() is raw { nqp::if( $!nested, nqp::if( nqp::eqaddr((my \nested := $!nested.pull-one),IterationEnd), nqp::stmts( ($!nested := Iterator), self.pull-one ), nested ), nqp::if( nqp::iscont(my \got := $!source.pull-one), got, nqp::if( nqp::istype(got,Iterable), nqp::stmts( ($!nested := Flat.new(got.iterator)), self.pull-one ), got ) ) ) } method push-all(\target --> IterationEnd) { nqp::if( $!nested, nqp::stmts( $!nested.push-all(target), ($!nested := Iterator) ) ); nqp::until( nqp::eqaddr((my \got := $!source.pull-one), IterationEnd), nqp::if( nqp::iscont(got), target.push(got), nqp::if( nqp::istype(got,Iterable), Flat.new(got.iterator).push-all(target), target.push(got) ) ) ); } method is-lazy() { $!source.is-lazy } method is-deterministic(--> Bool:D) { $!source.is-deterministic } method is-monotonically-increasing(--> Bool:D) { $!source.is-monotonically-increasing } } method Flat(\iterator) { Flat.new(iterator) } # 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(®ex, \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(®ex, \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 ?? nqp::eqaddr((my \pulled := $!iterator.pull-one),IterationEnd) ?? IterationEnd !! pulled !! 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) { NGrams.new($string, $size, $limit, $step, $partial) } # 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 } } method Permutations($n, int $b) { $n > nqp::if(nqp::iseq_i($?BITS,32),13,20) # must be HLL comparison ?? die "Cowardly refusing to permutate more than { $?BITS == 32 ?? 13 !! 20 } elements, tried $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) } # 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::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) ) } } #line 1 SETTING::src/core.c/Rakudo/QuantHash.rakumod my role Real { ... } my class X::TypeCheck::Binding { ... } my class Rakudo::QuantHash { # a Pair with the value 0 my $p0 := nqp::p6bindattrinvres(nqp::create(Pair),Pair,'$!value',0); # Specialized role for .kv methods on QuantHashes: copied methods # from Quanty because of visibility issues wrt to $!elems and $!iter :-( our role Quanty-kv does Iterator { has $!elems; has $!iter; has $!on; method !SET-SELF(\quanthash) { nqp::if( ($!elems := quanthash.RAW-HASH) && nqp::elems($!elems), nqp::stmts( ($!iter := nqp::iterator($!elems)), self ), Rakudo::Iterator.Empty # nothing to iterate ) } method new(\quanthash) { nqp::create(self)!SET-SELF(quanthash) } method skip-one() { nqp::if( $!on, nqp::not_i($!on = 0), nqp::if( $!iter, nqp::stmts( nqp::shift($!iter), ($!on = 1) ) ) ) } method sink-all(--> IterationEnd) { $!iter := nqp::null } method is-deterministic(--> False) { } } our role Pairs does Iterator { has $!elems; has $!picked; method !SET-SELF(\elems,\count) { $!elems := elems; $!picked := Rakudo::QuantHash.PICK-N(elems, count); self } method new(Mu \elems, \count) { (my $todo := Rakudo::QuantHash.TODO(count)) && elems && nqp::elems(elems) ?? nqp::create(self)!SET-SELF(elems, $todo) !! Rakudo::Iterator.Empty } method is-deterministic(--> False) { } } # Return the iterator state of a randomly selected entry in a # given IterationSet method ROLL(Mu \elems) { my int $i = nqp::add_i(nqp::rand_n(nqp::elems(elems)),1); my $iter := nqp::iterator(elems); nqp::while( nqp::shift($iter) && --$i, nqp::null ); $iter } # Return a list_s of N keys of the given IterationSet in random order. method PICK-N(Mu \elems, \count) { my int $elems = nqp::elems(elems); my int $count = count > $elems ?? $elems !! count; my $keys := nqp::setelems(nqp::list_s,$elems); my $iter := nqp::iterator(elems); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_s($keys,$i,nqp::iterkey_s(nqp::shift($iter))) ); my $picked := nqp::setelems(nqp::list_s,$count); $i = -1; nqp::while( nqp::islt_i(++$i,$count), nqp::stmts( nqp::bindpos_s($picked,$i, nqp::atpos_s($keys,(my int $pick = $elems.rand.floor)) ), nqp::bindpos_s($keys,$pick,nqp::atpos_s($keys,--$elems)) ) ); $picked } # Return number of items to be done if > 0, or 0 if < 1, or throw if NaN method TODO(\count) is raw { count < 1 ?? 0 !! count == Inf ?? count !! nqp::istype((my $todo := count.Int),Failure) ?? $todo.throw !! $todo } # Return an nqp::list_s of all keys of a QuantHash method RAW-KEYS(\quanthash) is raw { nqp::if( (my $elems := quanthash.RAW-HASH) && (my $iter := nqp::iterator($elems)), nqp::stmts( (my $keys := # presize result back to 0 so we can push_s nqp::setelems(nqp::setelems(nqp::list_s,nqp::elems($elems)),0)), nqp::while( $iter, nqp::push_s($keys,nqp::iterkey_s(nqp::shift($iter))) ), $keys ), nqp::list_s ) } # Return an nqp::list_s of all values of a QuantHash, mapped to a str method RAW-VALUES-MAP(\quanthash, &mapper) is raw { nqp::if( (my $elems := quanthash.RAW-HASH) && (my $iter := nqp::iterator($elems)), nqp::stmts( (my $values := # presize result back to 0 so we can push_s nqp::setelems(nqp::setelems(nqp::list_s,nqp::elems($elems)),0)), nqp::while( $iter, nqp::push_s($values,mapper(nqp::iterval(nqp::shift($iter)))) ), $values ), nqp::list_s ) } # Return an nqp::list_s of all keys in a Baggy with the weight # joined with a null-byte inbetween. method BAGGY-RAW-KEY-VALUES(\baggy) is raw { nqp::if( (my $elems := baggy.RAW-HASH) && (my $iter := nqp::iterator($elems)), nqp::stmts( (my $list := # presize result back to 0 so we can push_s nqp::setelems(nqp::setelems(nqp::list_s,nqp::elems($elems)),0)), nqp::while( $iter, nqp::stmts( nqp::shift($iter), nqp::push_s( $list, nqp::concat( nqp::iterkey_s($iter), nqp::concat( '\0', nqp::getattr(nqp::iterval($iter),Pair,'$!value').Str ) ) ) ) ), $list ), nqp::list_s ) } # Create intersection of 2 Baggies, default to given type (Bag|Mix) method INTERSECT-BAGGIES(\a,\b,\type) { my $object := nqp::create( nqp::istype(type,Mix) ?? a.WHAT.Mixy !! a.WHAT.Baggy ); nqp::if( (my $araw := a.RAW-HASH) && nqp::elems($araw) && (my $braw := b.RAW-HASH) && nqp::elems($braw), nqp::stmts( # both have elems nqp::if( nqp::islt_i(nqp::elems($araw),nqp::elems($braw)), nqp::stmts( # $a smallest, iterate over it (my $iter := nqp::iterator($araw)), (my $base := $braw) ), nqp::stmts( # $b smallest, iterate over that ($iter := nqp::iterator($braw)), ($base := $araw) ) ), (my $elems := nqp::create(Rakudo::Internals::IterationSet)), nqp::while( $iter, nqp::if( # bind if in both nqp::existskey($base,nqp::iterkey_s(nqp::shift($iter))), nqp::bindkey( $elems, nqp::iterkey_s($iter), nqp::if( nqp::getattr( nqp::decont(nqp::iterval($iter)), Pair, '$!value' ) < nqp::getattr( # must be HLL comparison nqp::atkey($base,nqp::iterkey_s($iter)), Pair, '$!value' ), nqp::iterval($iter), nqp::atkey($base,nqp::iterkey_s($iter)) ) ) ) ), $object.SET-SELF($elems) ), $object # one/neither has elems ) } # create a deep clone of the given IterSet with baggy method BAGGY-CLONE(\raw) { my $elems := nqp::clone(raw); my $iter := nqp::iterator($elems); nqp::while( $iter, nqp::bindkey( $elems, nqp::iterkey_s(nqp::shift($iter)), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', nqp::getattr(nqp::iterval($iter),Pair,'$!value') ) ) ); $elems } #--- Set/SetHash related methods # Create an IterationSet with baggy semantics from IterationSet with # Setty semantics. method SET-BAGGIFY(\raw) { my $elems := nqp::clone(raw); my $iter := nqp::iterator($elems); nqp::while( $iter, nqp::bindkey( $elems, nqp::iterkey_s(nqp::shift($iter)), Pair.new(nqp::decont(nqp::iterval($iter)),1) ) ); $elems } # bind the given value to the given IterationSet, check for given type method BIND-TO-TYPED-SET(\elems, Mu \value, Mu \type --> Nil) { nqp::istype(value,type) ?? nqp::bindkey(elems,value.WHICH,value) !! X::TypeCheck::Binding.new( got => value, expected => type ).throw } # add to given IterationSet with setty semantics the values of iterator method ADD-ITERATOR-TO-SET(\elems,Mu \iterator, Mu \type) { nqp::until( nqp::eqaddr( (my \pulled := nqp::decont(iterator.pull-one)), IterationEnd ), self.BIND-TO-TYPED-SET(elems, pulled, type) ); elems } # Add to IterationSet with setty semantics the values of the given # iterator while checking for Pairs (only include if value is trueish) method ADD-PAIRS-TO-SET(\elems,Mu \iterator, Mu \type) { nqp::until( nqp::eqaddr( (my \pulled := nqp::decont(iterator.pull-one)), IterationEnd ), nqp::if( nqp::istype(pulled,Pair), nqp::if( nqp::getattr(pulled,Pair,'$!value'), self.BIND-TO-TYPED-SET( elems, nqp::getattr(pulled,Pair,'$!key'), type ) ), self.BIND-TO-TYPED-SET(elems, pulled, type) ) ); elems } # Add to given IterationSet with setty semantics the values of the two # given iterators where the first iterator supplies objects, and the # second supplies values (only include if value is trueish). method ADD-OBJECTS-VALUES-TO-SET(\elems,Mu \objects, Mu \bools) is raw { nqp::until( nqp::eqaddr((my \object := objects.pull-one),IterationEnd), nqp::if( bools.pull-one, nqp::bindkey(elems,object.WHICH,nqp::decont(object)) ) ); elems } # Add to given IterationSet with setty semantics the keys of given Map method ADD-MAP-TO-SET(\elems, \map) { nqp::if( (my $iter := nqp::iterator(nqp::getattr(nqp::decont(map),Map,'$!storage'))), nqp::if( nqp::istype(map,Hash::Object), nqp::while( # object hash $iter, nqp::if( nqp::getattr( nqp::decont(nqp::iterval(nqp::shift($iter))), Pair, '$!value' ), nqp::bindkey( elems, nqp::iterkey_s($iter), nqp::getattr(nqp::iterval($iter),Pair,'$!key') ) ) ), nqp::while( # normal Map $iter, nqp::if( nqp::iterval(nqp::shift($iter)), nqp::bindkey( elems,nqp::iterkey_s($iter).WHICH,nqp::iterkey_s($iter)) ) ) ) ); elems } # coerce a Map to an IterationSet with setty semantics method COERCE-MAP-TO-SET(\map) { # Once object hashes have IterationSets, we could optimize the # object hash case by cloning the object hash, rather than creating # an empty IterationSet. Until then, this is just a wrapper. Rakudo::QuantHash.ADD-MAP-TO-SET( nqp::create(Rakudo::Internals::IterationSet), map ) } # remove set elements from set, stop when the result is the empty Set method SUB-SET-FROM-SET(\aelems, \belems) { my $elems := nqp::clone(aelems); # both have elems my $iter := nqp::iterator(belems); nqp::while( $iter && nqp::elems($elems), nqp::deletekey($elems,nqp::iterkey_s(nqp::shift($iter))) ); $elems } # remove hash elements from set, stop if the result is the empty Set method SUB-MAP-FROM-SET(\aelems, \map) { my $elems := nqp::clone(aelems); nqp::if( (my $iter := nqp::iterator(nqp::getattr(nqp::decont(map),Map,'$!storage'))), nqp::if( nqp::istype(map,Hash::Object), nqp::while( # object hash $iter && nqp::elems($elems), nqp::if( nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value'), nqp::deletekey($elems,nqp::iterkey_s($iter)) ) ), nqp::while( # normal Map $iter && nqp::elems($elems), nqp::if( nqp::iterval(nqp::shift($iter)), nqp::deletekey($elems,nqp::iterkey_s($iter).WHICH) ) ) ) ); $elems } # remove iterator elements from set using Pair semantics, stops pulling # from the iterator as soon as the result is the empty set. method SUB-PAIRS-FROM-SET(\elems, \iterator) { my $elems := nqp::clone(elems); nqp::until( nqp::eqaddr( # end of iterator? (my $pulled := nqp::decont(iterator.pull-one)), IterationEnd ) || nqp::not_i(nqp::elems($elems)), # nothing left to remove? nqp::if( nqp::istype($pulled,Pair), nqp::if( # must check for thruthiness nqp::getattr($pulled,Pair,'$!value'), nqp::deletekey($elems,nqp::getattr($pulled,Pair,'$!key').WHICH) ), nqp::deletekey($elems,$pulled.WHICH) # attempt to remove ) ); $elems } #--- Bag/BagHash related methods # Calculate total of value of a Bag(Hash). Takes a (possibly # uninitialized) IterationSet in Bag format. method BAG-TOTAL(Mu \elems) { nqp::if( elems && nqp::elems(elems), nqp::stmts( (my Int $total := 0), (my $iter := nqp::iterator(elems)), nqp::while( $iter, $total := nqp::add_I( $total, nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value'), Int ) ), $total ), 0 ) } # Return random iterator item from a given Bag(Hash). Takes an # initialized IterationSet with at least 1 element in Bag format, # and the total value of values in the Bag. method BAG-ROLL(\elems, \total) { my Int $rand := total.rand.Int; my Int $seen := 0; my $iter := nqp::iterator(elems); nqp::while( $iter && nqp::isle_I( ($seen := nqp::add_I( $seen, nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value'), Int )), $rand ), nqp::null ); $iter } # Return random object from a given BagHash. Takes an initialized # IterationSet with at least 1 element in Bag format, and the total # value of values in the Bag. Decrements the count of the iterator # found, completely removes it when going to 0. method BAG-GRAB(\elems, \total) { my $iter := Rakudo::QuantHash.BAG-ROLL(elems,total); nqp::if( (my $value := nqp::getattr(nqp::iterval($iter),Pair,'$!value')) == 1, nqp::stmts( # going to 0, so remove (my $object := nqp::getattr(nqp::iterval($iter),Pair,'$!key')), nqp::deletekey(elems,nqp::iterkey_s($iter)), $object ), nqp::stmts( nqp::bindattr( nqp::iterval($iter), Pair, '$!value', $value - 1 ), nqp::getattr(nqp::iterval($iter),Pair,'$!key') ) ) } method BAGGY-CLONE-RAW(Mu \baggy) { nqp::if( baggy && nqp::elems(baggy), nqp::stmts( # something to coerce (my $elems := nqp::clone(baggy)), (my $iter := nqp::iterator($elems)), nqp::while( $iter, nqp::bindkey( $elems, nqp::iterkey_s(nqp::shift($iter)), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', nqp::getattr(nqp::iterval($iter),Pair,'$!value') ) ) ), $elems ), baggy ) } method ADD-BAG-TO-BAG(\elems,Mu \bag) { nqp::if( bag && nqp::elems(bag), nqp::stmts( (my $iter := nqp::iterator(bag)), nqp::while( $iter, nqp::if( nqp::existskey(elems,nqp::iterkey_s(nqp::shift($iter))), nqp::stmts( (my $pair := nqp::atkey(elems,nqp::iterkey_s($iter))), nqp::bindattr($pair,Pair,'$!value', nqp::getattr($pair,Pair,'$!value') + nqp::getattr(nqp::iterval($iter),Pair,'$!value') ) ), nqp::bindkey(elems,nqp::iterkey_s($iter), nqp::clone(nqp::iterval($iter)) ) ) ) ) ); elems } # bind the given which/object/value to the given IterationSet, # check object for given type method BIND-TO-TYPED-BAG( \elems, Mu \which, Mu \object, Int:D \value, Mu \type --> Nil) { nqp::istype(object,type) ?? nqp::bindkey(elems,which,Pair.new(object,value)) !! X::TypeCheck::Binding.new( got => object, expected => type ).throw } method ADD-ITERATOR-TO-BAG(\elems, Mu \iterator, Mu \type) { nqp::until( nqp::eqaddr( (my \pulled := nqp::decont(iterator.pull-one)), IterationEnd ), nqp::stmts( (my $pair := nqp::ifnull( nqp::atkey(elems,(my \which := pulled.WHICH)), nqp::if( nqp::istype(pulled,type), nqp::bindkey(elems,which,Pair.new(pulled,0)), X::TypeCheck::Binding.new( got => pulled, expected => type ).throw ) )), nqp::bindattr($pair,Pair,'$!value', nqp::add_i(nqp::getattr($pair,Pair,'$!value'),1) ) ) ); elems } method SUB-ITERATOR-FROM-BAG(\elems, Mu \iterator) { nqp::until( nqp::eqaddr( (my \pulled := nqp::decont(iterator.pull-one)), IterationEnd ), nqp::if( nqp::existskey(elems,(my \which := pulled.WHICH)), nqp::stmts( (my \pair := nqp::atkey(elems,which)), nqp::if( nqp::isgt_i((my \freq := nqp::getattr(pair,Pair,'$!value')),1), nqp::bindattr(pair,Pair,'$!value',nqp::sub_i(freq,1)), nqp::deletekey(elems,which) ) ) ) ); elems } # Add to given IterationSet with baggy semantics the keys of given Map method ADD-MAP-TO-BAG(\elems, \map) { nqp::if( (my $iter := nqp::iterator(nqp::getattr(nqp::decont(map),Map,'$!storage'))), nqp::if( nqp::istype(map,Hash::Object), nqp::while( # object hash $iter, nqp::if( nqp::istype( (my $value := nqp::getattr( nqp::iterval(nqp::shift($iter)),Pair,'$!value' ).Int), Int ), nqp::if( # a valid Int $value > 0, nqp::if( # and a positive one at that nqp::existskey(elems,nqp::iterkey_s($iter)), nqp::stmts( # seen before, add value (my $pair := nqp::atkey(elems,nqp::iterkey_s($iter))), nqp::bindattr( $pair, Pair, '$!value', nqp::getattr($pair,Pair,'$!value') + $value ) ), nqp::bindkey( # new, create new Pair elems, nqp::iterkey_s($iter), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', $value ) ) ) ), $value.throw # huh? let the world know ) ), nqp::while( # ordinary Map $iter, nqp::if( nqp::istype( ($value := nqp::iterval(nqp::shift($iter)).Int), Int ), nqp::if( # a valid Int $value > 0, nqp::if( # and a positive one at that nqp::existskey( elems, (my $which := nqp::iterkey_s($iter).WHICH) ), nqp::stmts( # seen before, add value ($pair := nqp::atkey(elems,$which)), nqp::bindattr( $pair, Pair, '$!value', nqp::getattr($pair,Pair,'$!value') + $value ) ), nqp::bindkey( # new, create new Pair elems, $which, Pair.new(nqp::iterkey_s($iter),$value) ) ) ), $value.throw # huh? let the world know ) ) ) ); elems } # Coerce the given Map to an IterationSet with baggy semantics. method COERCE-MAP-TO-BAG(\map) { nqp::if( (my $iter := nqp::iterator(nqp::getattr(nqp::decont(map),Map,'$!storage'))), nqp::if( # something to coerce nqp::istype(map,Hash::Object), nqp::stmts( # object hash # once object hashes have IterationSets inside them, we can # make this an nqp::clone for more performance, which would # pre-populate the IterationSet with the right keys off the # bat. (my $elems := nqp::create(Rakudo::Internals::IterationSet)), nqp::while( $iter, nqp::if( nqp::istype( (my $value := nqp::getattr( nqp::iterval(nqp::shift($iter)),Pair,'$!value' ).Int), Int ), nqp::if( # a valid Int $value > 0, nqp::bindkey( # and a positive one at that $elems, nqp::iterkey_s($iter), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', $value ) ) ), $value.throw # huh? let the world know ) ), $elems ), nqp::stmts( # ordinary Map ($elems := nqp::create(Rakudo::Internals::IterationSet)), nqp::while( $iter, nqp::if( nqp::istype( ($value := nqp::iterval(nqp::shift($iter)).Int), Int ), nqp::if( # a valid Int $value > 0, nqp::bindkey( # and a positive one at that $elems, nqp::iterkey_s($iter).WHICH, Pair.new(nqp::iterkey_s($iter),$value) ) ), $value.throw # huh? let the world know ) ), $elems ) ), nqp::create(Rakudo::Internals::IterationSet) # nothing to coerce ) } # Add to given IterationSet with baggy semantics the values of the given # iterator while checking for Pairs with numeric values. method ADD-PAIRS-TO-BAG(\elems, Mu \iterator, Mu \type) { nqp::until( nqp::eqaddr( (my $pulled := nqp::decont(iterator.pull-one)), IterationEnd ), nqp::if( nqp::istype($pulled,Pair), nqp::if( # we have a Pair nqp::istype( (my $value := nqp::decont(nqp::getattr($pulled,Pair,'$!value')).Int), Int ), nqp::if( # is a (coerced) Int $value > 0, nqp::if( # and a positive one at that nqp::existskey( elems, (my $which := nqp::getattr($pulled,Pair,'$!key').WHICH) ), nqp::stmts( # seen before, add value (my $pair := nqp::atkey(elems,$which)), nqp::bindattr( $pair, Pair, '$!value', nqp::getattr($pair,Pair,'$!value') + $value ) ), self.BIND-TO-TYPED-BAG( # new, create new Pair elems, $which, nqp::getattr($pulled,Pair,'$!key'), $value, type ) ) ), $value.throw # value cannot be made Int, so throw ), nqp::if( # not a Pair ($pair := nqp::atkey(elems,($which := $pulled.WHICH))), nqp::bindattr( # seen before, so increment $pair, Pair, '$!value', nqp::getattr($pair,Pair,'$!value') + 1 ), self.BIND-TO-TYPED-BAG( # new, create new Pair elems, $which, $pulled, 1, type ) ) ) ); elems # we're done, return what we got so far } # Add to given IterationSet with baggy semantics the values of the two # given iterators where the first iterator supplies objects, and the # second supplies values. method ADD-OBJECTS-VALUES-TO-BAG( \elems, Mu \objects, Mu \values, Mu \type ) is raw { nqp::until( nqp::eqaddr((my \object := objects.pull-one),IterationEnd), nqp::if( (my \value := values.pull-one.Int) > 0, self.BIND-TO-TYPED-BAG( # new, create new Pair elems, object.WHICH, object, value, type ) ) ); elems } # Take the given IterationSet with baggy semantics, and add the other # IterationSet with setty semantics to it. Return the given IterationSet. method ADD-SET-TO-BAG(\elems, Mu \set) { nqp::if( set && nqp::elems(set), nqp::stmts( (my \iter := nqp::iterator(set)), nqp::while( iter, nqp::if( nqp::existskey(elems,nqp::iterkey_s(nqp::shift(iter))), nqp::stmts( (my \pair := nqp::atkey(elems,nqp::iterkey_s(iter))), nqp::bindattr(pair,Pair,'$!value', nqp::getattr(pair,Pair,'$!value') + 1 ) ), nqp::bindkey(elems,nqp::iterkey_s(iter), Pair.new(nqp::iterval(iter), 1) ) ) ) ) ); elems } method MULTIPLY-BAG-TO-BAG(\elems,Mu \bag) { my $iter := nqp::iterator(elems); nqp::if( bag && nqp::elems(bag), nqp::while( $iter, nqp::if( nqp::existskey(bag,nqp::iterkey_s(nqp::shift($iter))), nqp::stmts( (my $pair := nqp::iterval($iter)), nqp::bindattr($pair,Pair,'$!value', nqp::mul_i( nqp::getattr($pair,Pair,'$!value'), nqp::getattr( nqp::atkey(bag,nqp::iterkey_s($iter)), Pair, '$!value' ) ) ) ), nqp::deletekey(elems,nqp::iterkey_s($iter)) ) ), nqp::while( # nothing to match against, so reset $iter, nqp::deletekey(elems,nqp::iterkey_s(nqp::shift($iter))) ) ); elems } method MULTIPLY-SET-TO-BAG(\elems,Mu \set) { my $iter := nqp::iterator(elems); nqp::if( set && nqp::elems(set), nqp::while( $iter, nqp::unless( nqp::existskey(set,nqp::iterkey_s(nqp::shift($iter))), nqp::deletekey(elems,nqp::iterkey_s($iter)) ) ), nqp::while( # nothing to match against, so reset $iter, nqp::deletekey(elems,nqp::iterkey_s(nqp::shift($iter))) ) ); elems } # set difference Baggy IterSet from Bag IterSet, both assumed to have elems method SUB-BAGGY-FROM-BAG(\aelems, \belems) { my $elems := nqp::create(Rakudo::Internals::IterationSet); my $iter := nqp::iterator(aelems); nqp::while( $iter, nqp::if( (my $value := nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value') - nqp::getattr( nqp::ifnull(nqp::atkey(belems,nqp::iterkey_s($iter)),$p0), Pair, '$!value' ) ) > 0, nqp::bindkey( $elems, nqp::iterkey_s($iter), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)),Pair,'$!value',$value ) ) ) ); $elems } # set difference Setty IterSet from Bag IterSet, both assumed to have elems method SUB-SETTY-FROM-BAG(\aelems, \belems) { my $elems := nqp::create(Rakudo::Internals::IterationSet); my $iter := nqp::iterator(aelems); nqp::while( $iter, nqp::if( (my $value := nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value') - nqp::existskey(belems,nqp::iterkey_s($iter)) ) > 0, nqp::bindkey( $elems, nqp::iterkey_s($iter), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)),Pair,'$!value',$value ) ) ) ); $elems } # set difference of a Baggy and a QuantHash method DIFFERENCE-BAGGY-QUANTHASH(\a, \b) { nqp::if( (my $araw := a.RAW-HASH) && nqp::elems($araw), nqp::if( (my $braw := b.RAW-HASH) && nqp::elems($braw), nqp::create(a.WHAT).SET-SELF( nqp::if( nqp::istype(b,Setty), self.SUB-SETTY-FROM-BAG($araw, $braw), self.SUB-BAGGY-FROM-BAG($araw, $braw) ) ), a ), nqp::if( nqp::istype(b,Failure), b.throw, a ) ) } #--- Mix/MixHash related methods # Calculate total of values of a Mix(Hash). Takes a (possibly # uninitialized) IterationSet in Mix format. method MIX-TOTAL(Mu \elems) { nqp::if( elems && nqp::elems(elems), nqp::stmts( (my $total := 0), (my $iter := nqp::iterator(elems)), nqp::while( $iter, $total := $total + nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value') ), $total ), 0 ) } # Calculate total of positive value of a Mix(Hash). Takes a # (possibly uninitialized) IterationSet in Mix format. method MIX-TOTAL-POSITIVE(Mu \elems) { nqp::if( elems && nqp::elems(elems), nqp::stmts( (my $total := 0), (my $iter := nqp::iterator(elems)), nqp::while( $iter, nqp::if( 0 < (my $value := nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value')), ($total := $total + $value) ) ), $total ), 0 ) } # Return random iterator item from a given Mix(Hash). Takes an # initialized IterationSet with at least 1 element in Mix format, # and the total value of values in the Mix. method MIX-ROLL(\elems, \total) { my $rand := total.rand; my Real $seen := 0; my $iter := nqp::iterator(elems); nqp::while( $iter && ( 0 > (my $value := # negative values ignored nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value')) || $rand > ($seen := $seen + $value) # positive values add up ), nqp::null ); $iter } # Given an IterationSet in baggy/mixy format considered to contain the # final result, add the other IterationSet using Mix semantics and return # the first IterationSet. method ADD-MIX-TO-MIX(\elems, Mu \mix) { nqp::if( mix && nqp::elems(mix), nqp::stmts( (my $iter := nqp::iterator(mix)), nqp::while( $iter, nqp::if( nqp::isnull((my $pair := nqp::atkey(elems,nqp::iterkey_s(nqp::shift($iter))) )), nqp::bindkey( # doesn't exist on left, create elems, nqp::iterkey_s($iter), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', nqp::getattr(nqp::iterval($iter),Pair,'$!value') ) ), nqp::if( # exists on left, update (my $value := nqp::getattr($pair,Pair,'$!value') + nqp::getattr(nqp::iterval($iter),Pair,'$!value')), nqp::bindattr($pair,Pair,'$!value',$value), # valid for Mix nqp::deletekey(elems,nqp::iterkey_s($iter)) # bye bye ) ) ) ) ); elems } # Add to given IterationSet with mixy semantics the keys of given Map method ADD-MAP-TO-MIX(\elems, \map) { nqp::if( (my $iter := nqp::iterator(nqp::getattr(nqp::decont(map),Map,'$!storage'))), nqp::if( nqp::istype(map,Hash::Object), nqp::while( # object hash $iter, nqp::if( nqp::istype( (my $value := nqp::getattr( nqp::iterval(nqp::shift($iter)),Pair,'$!value' ).Real), Real ), nqp::if( # a valid Real $value, nqp::if( # and not 0 nqp::existskey(elems,nqp::iterkey_s($iter)), nqp::if( # seen before: add value, remove if sum 0 ($value := nqp::getattr( (my $pair := nqp::atkey(elems,nqp::iterkey_s($iter))), Pair, '$!value' ) + $value), nqp::bindattr($pair,Pair,'$!value',$value), # okidoki nqp::deletekey(elems,nqp::iterkey_s($iter)) # alas, bye ), nqp::bindkey( # new, create new Pair elems, nqp::iterkey_s($iter), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', nqp::getattr(nqp::iterval($iter),Pair,'$!value') ) ) ) ), $value.throw # huh? let the world know ) ), nqp::while( # normal Map $iter, nqp::if( nqp::istype( ($value := nqp::iterval(nqp::shift($iter)).Real), Real ), nqp::if( # a valid Real $value, nqp::if( # and not 0 nqp::existskey( elems, (my $which := nqp::iterkey_s($iter).WHICH) ), nqp::if( # seen before: add value, remove if sum 0 ($value := nqp::getattr( ($pair := nqp::atkey(elems,$which)), Pair, '$!value' ) + $value), nqp::bindattr($pair,Pair,'$!value',$value), # okidoki nqp::deletekey(elems,$which) # alas, bye ), nqp::bindkey( # new, create new Pair elems, $which, Pair.new(nqp::iterkey_s($iter),$value) ) ) ), $value.throw # huh? let the world know ) ) ) ); elems } # bind the given which/object/value to the given IterationSet, # check object for given type method BIND-TO-TYPED-MIX( \elems, Mu \which, Mu \object, Real:D \value, Mu \type --> Nil) { nqp::istype(object,type) ?? nqp::bindkey(elems,which,Pair.new(object,value)) !! X::TypeCheck::Binding.new( got => object, expected => type ).throw } # Add to given IterationSet with mixy semantics the values of the given # iterator while checking for Pairs with numeric values. method ADD-PAIRS-TO-MIX(\elems, Mu \iterator, Mu \type) is raw { nqp::until( nqp::eqaddr( (my $pulled := nqp::decont(iterator.pull-one)), IterationEnd ), nqp::if( nqp::istype($pulled,Pair), nqp::if( # got a Pair (my $value := nqp::decont(nqp::getattr($pulled,Pair,'$!value'))), nqp::if( # non-zero value nqp::istype($value,Num) && nqp::isnanorinf($value), X::OutOfRange.new( # NaN or -Inf or Inf, we're done what => 'Value', got => $value, range => '-Inf^..^Inf' ).throw, nqp::stmts( # apparently valid nqp::unless( nqp::istype(($value := $value.Real),Real), $value.throw # not a Real value, so throw Failure ), nqp::if( # valid Real value nqp::existskey( elems, (my $which := nqp::getattr($pulled,Pair,'$!key').WHICH) ), nqp::if( # seen before, add value ($value := nqp::getattr( (my $pair := nqp::atkey(elems,$which)), Pair, '$!value' ) + $value), nqp::bindattr($pair,Pair,'$!value',$value), # non-zero nqp::deletekey(elems,$which) # zero ), self.BIND-TO-TYPED-MIX( # new, create new Pair elems, $which, nqp::getattr($pulled,Pair,'$!key'), $value,type ) ) ) ) ), nqp::if( # not a Pair ($pair := nqp::atkey(elems,($which := $pulled.WHICH))), nqp::bindattr( # seen before, so increment $pair, Pair, '$!value', nqp::getattr($pair,Pair,'$!value') + 1 ), self.BIND-TO-TYPED-MIX( # new, create new Pair elems, $which, $pulled, 1, type ) ) ) ); elems # we're done, return what we got so far } # Add to given IterationSet with mixy semantics the values of the two # given iterators where the first iterator supplies objects, and the # second supplies values. method ADD-OBJECTS-VALUES-TO-MIX( \elems, Mu \objects, Mu \values, Mu \type ) is raw { nqp::until( nqp::eqaddr((my \object := objects.pull-one),IterationEnd), nqp::if( nqp::istype((my \value := values.pull-one),Num) && nqp::isnanorinf(value), X::OutOfRange.new( # NaN or -Inf or Inf, we're done what => 'Value', got => value, range => '-Inf^..^Inf' ).throw, nqp::if( nqp::istype(nqp::bind(value,value.Real),Real), nqp::if( value, self.BIND-TO-TYPED-MIX( elems, object.WHICH, object, value, type ) ), value.throw ) ) ); elems } # Take the given IterationSet with mixy semantics, and add the other # IterationSet with setty semantics to it. Return the given IterationSet. method ADD-SET-TO-MIX(\elems,Mu \set) { nqp::if( set && nqp::elems(set), nqp::stmts( (my $iter := nqp::iterator(set)), nqp::while( $iter, nqp::if( nqp::existskey(elems,nqp::iterkey_s(nqp::shift($iter))), nqp::if( (my $value := nqp::getattr( (my $pair := nqp::atkey(elems,nqp::iterkey_s($iter))), Pair, '$!value' ) + 1), nqp::bindattr($pair,Pair,'$!value',$value), # still valid nqp::deletekey(elems,nqp::iterkey_s($iter)) # not, byebye ), nqp::bindkey(elems,nqp::iterkey_s($iter), # new key Pair.new(nqp::iterval($iter), 1) ) ) ) ) ); elems } # Coerce the given Map to an IterationSet with mixy semantics. method COERCE-MAP-TO-MIX(\map) { nqp::if( (my $iter := nqp::iterator(nqp::getattr(nqp::decont(map),Map,'$!storage'))), nqp::if( # something to coerce nqp::istype(map,Hash::Object), nqp::stmts( # object hash # once object hashes have IterationSets inside them, we can # make this an nqp::clone for more performance, which would # pre-populate the IterationSet with the right keys off the # bat. (my $elems := nqp::create(Rakudo::Internals::IterationSet)), nqp::while( $iter, nqp::if( nqp::istype( (my $value := nqp::getattr( nqp::iterval(nqp::shift($iter)),Pair,'$!value' ).Real), Real ), nqp::if( # a valid Real $value, nqp::bindkey( # and not 0 $elems, nqp::iterkey_s($iter), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', $value ) ) ), $value.throw # huh? let the world know ) ), $elems ), nqp::stmts( # ordinary Map ($elems := nqp::create(Rakudo::Internals::IterationSet)), nqp::while( $iter, nqp::if( nqp::istype( ($value := nqp::iterval(nqp::shift($iter)).Real), Real ), nqp::if( # a valid Real $value, nqp::bindkey( # and not 0 $elems, nqp::iterkey_s($iter).WHICH, Pair.new(nqp::iterkey_s($iter),$value) ) ), $value.throw # huh? let the world know ) ), $elems ) ), nqp::create(Rakudo::Internals::IterationSet) # nothing to coerce ) } method MULTIPLY-MIX-TO-MIX(\elems,Mu \mix --> Nil) { my $iter := nqp::iterator(elems); nqp::if( mix && nqp::elems(mix), nqp::while( $iter, nqp::if( nqp::existskey(mix,nqp::iterkey_s(nqp::shift($iter))), nqp::stmts( (my $pair := nqp::iterval($iter)), nqp::bindattr($pair,Pair,'$!value', nqp::getattr($pair,Pair,'$!value') * nqp::getattr( nqp::atkey(mix,nqp::iterkey_s($iter)), Pair, '$!value' ) ) ), nqp::deletekey(elems,nqp::iterkey_s($iter)) ) ), nqp::while( # nothing to match against, so reset $iter, nqp::deletekey(elems,nqp::iterkey_s(nqp::shift($iter))) ) ); } method MIX-CLONE-ALL-POSITIVE(\elems) { my $iter := nqp::iterator(my $clone := nqp::clone(elems)); nqp::while( $iter, nqp::stmts( nqp::shift($iter), nqp::bindkey( $clone, nqp::iterkey_s($iter), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', abs(nqp::getattr(nqp::iterval($iter),Pair,'$!value')) ) ) ) ); $clone } method MIX-ALL-POSITIVE(\elems) { my $iter := nqp::iterator(elems); nqp::while( $iter, nqp::unless( nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value') > 0, return False ) ); True } method MIX-ALL-NEGATIVE(\elems) { my $iter := nqp::iterator(elems); nqp::while( $iter, nqp::unless( nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value') < 0, return False ) ); True } method MIX-IS-EQUAL(\a,\b) { nqp::unless( nqp::eqaddr(nqp::decont(a),nqp::decont(b)), nqp::stmts( # A and B not same object (my \araw := a.RAW-HASH), (my \braw := b.RAW-HASH), nqp::if( araw && braw, nqp::if( # A and B both allocated nqp::isne_i(nqp::elems(araw),nqp::elems(braw)), (return False), # different number of elements nqp::stmts( # same number of elements (my \iter := nqp::iterator(araw)), nqp::while( # number of elems in B >= A iter, nqp::unless( nqp::getattr(nqp::iterval(nqp::shift(iter)),Pair,'$!value') == # value in A should equal to B nqp::getattr( nqp::ifnull(nqp::atkey(braw,nqp::iterkey_s(iter)),$p0), Pair, '$!value' ), return False # not same weight ) ) ) ), nqp::if( # A and B not both allocated (araw && nqp::elems(araw)) || (braw && nqp::elems(braw)), return False # allocated side contains elements ) ) ) ); True } method MIX-IS-SUBSET($a,$b) { nqp::if( nqp::eqaddr(nqp::decont($a),nqp::decont($b)), True, # X is always a subset of itself nqp::if( (my $araw := $a.RAW-HASH) && (my $iter := nqp::iterator($araw)), nqp::if( # elems in A (my $braw := $b.RAW-HASH) && nqp::elems($braw), nqp::stmts( # elems in A and B nqp::while( # check all values in A with B $iter, nqp::unless( nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value') <= # value in A should be less or equal than B nqp::getattr( nqp::ifnull(nqp::atkey($braw,nqp::iterkey_s($iter)),$p0), Pair, '$!value' ), return False ) ), ($iter := nqp::iterator($braw)), nqp::while( # check all values in B with A $iter, nqp::unless( nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value') >= # value in B should be more or equal than A nqp::getattr( nqp::ifnull(nqp::atkey($araw,nqp::iterkey_s($iter)),$p0), Pair, '$!value' ), return False ) ), True # all checks worked out, so ok ), # nothing in B, all elems in A should be < 0 Rakudo::QuantHash.MIX-ALL-NEGATIVE($araw) ), nqp::if( ($braw := $b.RAW-HASH) && nqp::elems($braw), # nothing in A, all elems in B should be >= 0 Rakudo::QuantHash.MIX-ALL-POSITIVE($braw), True # nothing in A nor B ) ) ) } # Return whether first Baggy is a proper subset of the second Baggy, # using Mixy semantics method MIX-IS-PROPER-SUBSET($a,$b) { nqp::if( nqp::eqaddr(nqp::decont($a),nqp::decont($b)), False, # X is never a true subset of itself nqp::if( (my $araw := $a.RAW-HASH) && (my $iter := nqp::iterator($araw)), nqp::if( # elems in A (my $braw := $b.RAW-HASH) && nqp::elems($braw), nqp::stmts( # elems in A and B (my int $less), nqp::while( # check all values in A with B $iter, nqp::if( (my $left := nqp::getattr( nqp::iterval(nqp::shift($iter)), Pair, '$!value' )) > # value in A should be <= than B (my $right := nqp::getattr( nqp::ifnull(nqp::atkey($braw,nqp::iterkey_s($iter)),$p0), Pair, '$!value' )), (return False), # too many on left, we're done nqp::unless($less,$less = $left < $right) ) ), ($iter := nqp::iterator($braw)), nqp::while( # check all values in B with A $iter, nqp::if( ($left := nqp::getattr( nqp::ifnull( nqp::atkey($araw,nqp::iterkey_s(nqp::shift($iter))), $p0 ), Pair, '$!value' )) > # value in A should be <= than B ($right := nqp::getattr( nqp::iterval($iter),Pair,'$!value' )), (return False), nqp::unless($less,$less = $left < $right) ) ), nqp::hllbool($less) # all checks worked out so far ), # nothing in B, all elems in A should be < 0 Rakudo::QuantHash.MIX-ALL-NEGATIVE($araw) ), nqp::if( # nothing in A ($braw := $b.RAW-HASH) && nqp::elems($braw), # something in B, all elems in B should be > 0 Rakudo::QuantHash.MIX-ALL-POSITIVE($braw), False # nothing in A nor B ) ) ) } # set difference QuantHash IterSet from Mix IterSet, both assumed to have # elems. 3rd parameter is 1 for Setty, 0 for Baggy semantics method SUB-QUANTHASH-FROM-MIX(\aelems, \belems, \issetty) { my $elems := nqp::create(Rakudo::Internals::IterationSet); my $iter := nqp::iterator(belems); nqp::while( # subtract all righthand keys $iter, nqp::bindkey( $elems, nqp::iterkey_s(nqp::shift($iter)), nqp::if( issetty, Pair.new( nqp::iterval($iter), nqp::getattr( nqp::ifnull(nqp::atkey(aelems,nqp::iterkey_s($iter)),$p0), Pair, '$!value' ) - 1 ), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', nqp::getattr( nqp::ifnull(nqp::atkey(aelems,nqp::iterkey_s($iter)),$p0), Pair, '$!value' ) - nqp::getattr(nqp::iterval($iter),Pair,'$!value') ) ) ) ), ($iter := nqp::iterator(aelems)), nqp::while( # vivify all untouched lefthand keys $iter, nqp::if( nqp::existskey($elems,nqp::iterkey_s(nqp::shift($iter))), nqp::unless( # was touched nqp::getattr( nqp::atkey($elems,nqp::iterkey_s($iter)), Pair, '$!value' ), nqp::deletekey($elems,nqp::iterkey_s($iter)) # but no value ), nqp::bindkey( # not touched, add it $elems, nqp::iterkey_s($iter), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', nqp::getattr(nqp::iterval($iter),Pair,'$!value') ) ) ) ); $elems } # set difference of a Mixy and a QuantHash method DIFFERENCE-MIXY-QUANTHASH(\a, \b) { nqp::if( (my $araw := a.RAW-HASH) && nqp::elems($araw), nqp::if( (my $braw := b.RAW-HASH) && nqp::elems($braw), nqp::create(a.WHAT).SET-SELF( self.SUB-QUANTHASH-FROM-MIX($araw, $braw, nqp::istype(b,Setty)), ), a ), nqp::if( nqp::istype(b,Failure), b.throw, nqp::if( ($braw := b.RAW-HASH) && nqp::elems($braw), nqp::stmts( (my $elems := nqp::clone($braw)), (my $iter := nqp::iterator($braw)), nqp::while( $iter, nqp::bindkey( # clone with negated value $elems, nqp::iterkey_s(nqp::shift($iter)), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', - nqp::getattr(nqp::iterval($iter),Pair,'$!value') ) ) ), nqp::create(a.WHAT).SET-SELF($elems) ), a ) ) ) } } #line 1 SETTING::src/core.c/HyperConfiguration.rakumod my class X::Invalid::Value { ... } # Configuration for hyper/race, controlling how we parallelize (number of # items at a time, and number of threads). my class HyperConfiguration { has int $.batch; has Int $.degree; submethod TWEAK(:$method) { X::Invalid::Value.new(:$method,:name,:value($!batch)).throw if $!batch <= 0; X::Invalid::Value.new(:$method,:name,:value($!degree)).throw if $!degree <= 0; } } #line 1 SETTING::src/core.c/Iterable.rakumod # Iterable is done by anything that we should be able to get an iterator # from. Things that are Iterable will flatten in flattening contexts, so a # default implementation of .flat is provided by this role. As itemization is # what defeats flattening, this role also provides a default .item method. # Additionally, as .lazy and .eager are about iterator behavior, they are # provided by this role. Overriding those is not likely to be needed, and # discouraged to maintain predictable semantics. Finally, both .hyper() and # .race() are methods to enter the hyper and race paradigm and implemented # here, so they can use any Iterable as a source. my class HyperSeq { ... } my class RaceSeq { ... } my class Rakudo::Internals::HyperIteratorBatcher { ... } my class Kernel { ... } my role Iterable { method iterator() { ... } method item() { nqp::p6bindattrinvres(nqp::create(Scalar), Scalar, '$!value', self) } method flat(Iterable:D:) { Seq.new(Rakudo::Iterator.Flat(self.iterator)) } method lazy-if($flag) { $flag ?? self.lazy !! self } method lazy() { # Return a Seq with an iterator wrapping this Iterable, claiming to # be lazy, and implicitly preventing working ahead (by hiding any # push-at-least-n of the source iterator). Seq.new(Rakudo::Iterator.Lazy(self)) } method hyper( Int(Cool) :$batch, Int(Cool) :$degree, ) { HyperSeq.new: configuration => HyperConfiguration.new( :batch($batch // 64), :degree($degree // Kernel.cpu-cores-but-one), :method ), work-stage-head => Rakudo::Internals::HyperIteratorBatcher.new(:$.iterator) } method race( Int(Cool) :$batch, Int(Cool) :$degree, ) { RaceSeq.new: configuration => HyperConfiguration.new( :batch($batch // 64), :degree($degree // Kernel.cpu-cores-but-one), :method ), work-stage-head => Rakudo::Internals::HyperIteratorBatcher.new(:$.iterator) } method !MIXIFY(\type) { (my \iterator := self.flat.iterator).is-lazy ?? type.fail-iterator-cannot-be-lazy('coerce') !! nqp::elems(my \elems := Rakudo::QuantHash.ADD-PAIRS-TO-MIX( nqp::create(Rakudo::Internals::IterationSet),iterator,Mu )) ?? nqp::create(type).SET-SELF(elems) !! nqp::eqaddr(type,Mix) ?? mix() !! nqp::create(type) } multi method Mix(Iterable:D:) { self!MIXIFY(Mix) } multi method MixHash(Iterable:D:) { self!MIXIFY(MixHash) } method !BAGGIFY(\type) { (my \iterator := self.flat.iterator).is-lazy ?? type.fail-iterator-cannot-be-lazy('coerce') !! nqp::elems(my \elems := Rakudo::QuantHash.ADD-PAIRS-TO-BAG( nqp::create(Rakudo::Internals::IterationSet),iterator,Mu )) ?? nqp::create(type).SET-SELF(elems) !! nqp::eqaddr(type,Bag) ?? bag() !! nqp::create(type) } multi method Bag(Iterable:D:) { self!BAGGIFY(Bag) } multi method BagHash(Iterable:D:) { self!BAGGIFY(BagHash) } method !SETIFY(\type) { (my \iterator := self.flat.iterator).is-lazy ?? type.fail-iterator-cannot-be-lazy('coerce') !! nqp::elems(my $elems := Rakudo::QuantHash.ADD-PAIRS-TO-SET( nqp::create(Rakudo::Internals::IterationSet),iterator,Mu )) ?? nqp::create(type).SET-SELF($elems) !! nqp::eqaddr(type,Set) ?? set() !! nqp::create(type) } multi method Set(Iterable:D:) { self!SETIFY(Set) } multi method SetHash(Iterable:D:) { self!SETIFY(SetHash) } } multi sub infix:(Iterable:D \a, Iterable:D \b) { nqp::hllbool( nqp::unless( nqp::eqaddr(nqp::decont(a),nqp::decont(b)), nqp::if( # not same object nqp::eqaddr(a.WHAT,b.WHAT), nqp::if( # same type nqp::iseq_i( nqp::istrue(my \ial := (my \ia := a.iterator).is-lazy), nqp::istrue( (my \ib := b.iterator).is-lazy) ), nqp::if( ial, Any.throw-iterator-cannot-be-lazy('eqv',''), nqp::stmts( nqp::if( nqp::istype(ia,PredictiveIterator) && nqp::istype(ib,PredictiveIterator) && nqp::isne_i(ia.count-only,ib.count-only), (return False) ), nqp::until( nqp::stmts( (my \pa := ia.pull-one), (my \pb := ib.pull-one), nqp::eqaddr(pa,IterationEnd) || nqp::eqaddr(pb,IterationEnd) || nqp::isfalse(pa eqv pb) ), nqp::null ), nqp::eqaddr(pa,pb) # both IterationEnd = success! ) ) ) ) ) ) } #line 1 SETTING::src/core.c/Any-iterable-methods.rakumod my class X::Cannot::Empty { ... } my class X::Cannot::Lazy { ... } my class X::Cannot::Map { ... } my class Rakudo::Sorting { ... } # Now that Iterable is defined, we add extra methods into Any for the list # operations. (They can't go into Any right away since we need Attribute to # define the various roles, and Attribute inherits from Any. We will do a # re-compose of Attribute to make sure it gets the list methods at the end # of this file. Note the general pattern for these list-y methods is that # they check if they have an Iterable already, and if not obtain one to # work on by doing a .list coercion. use MONKEY-TYPING; augment class Any { # Because the first occurrence of "method chrs" is in the intarray # role, we need to create the proto earlier in the setting. That's # why it is not in unicodey. proto method chrs(*%) is pure {*} # A helper method for throwing an exception because of a lazy iterator, # to help reduce bytecode size in hot code paths, making it more likely # that the (conditional) caller of this method, can be inlined. method throw-iterator-cannot-be-lazy( str $action, str $what = self.^name ) is hidden-from-backtrace is implementation-detail { X::Cannot::Lazy.new(:$action, :$what).throw } # A helper method for creating a failure because of a lazy iterator, to # to help reduce bytecode size in hot code paths, making it more likely # that the (conditional) caller of this method, can be inlined. method fail-iterator-cannot-be-lazy( str $action, str $what = self.^name ) is hidden-from-backtrace is implementation-detail { X::Cannot::Lazy.new(:$action, :$what).Failure } # A helper method for throwing an exception because of an array being # empty, to help reduce bytecode size in hot code paths, making it more # likely that the (conditional) caller of this method, can be inlined. method throw-cannot-be-empty( str $action, str $what = self.^name ) is hidden-from-backtrace is implementation-detail { X::Cannot::Empty.new(:$action, :$what).throw } # A helper method for creating a failure because of an array being empty # to help reduce bytecode size in hot code paths, making it more likely # that the (conditional) caller of this method, can be inlined. method fail-cannot-be-empty( str $action, str $what = self.^name ) is hidden-from-backtrace is implementation-detail { X::Cannot::Empty.new(:$action, :$what).Failure } my class 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::unless( # a 'last' in the block nqp::eqaddr(($value := self.control-payload),IterationEnd), ($!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 $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 $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 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 $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 $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 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); 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); 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 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 $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 $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 IterateMoreWithPhasers does Rakudo::SlippyIterator { has &!block; has $!source; has $!count; has $!label; has $!value-buffer; has $!did-init; has $!did-iterate; has $!NEXT; has $!CAN_FIRE_PHASERS; method new(&block, 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 int $redo = 1; my $result; if !$!did-init && nqp::can(&!block, 'fire_phasers') { $!did-init = 1; $!CAN_FIRE_PHASERS = 1; $!NEXT = &!block.has-phaser('NEXT'); nqp::p6setfirstflag(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 { 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 } } proto method map(|) is nodal {*} multi method map(Hash:D \hash) { X::Cannot::Map.new( what => self.^name, using => "a {hash.^name}", suggestion => "Did you mean to add a stub (\{ ... \}) or did you mean to .classify?" ).throw; } multi method map(Iterable:D \iterable) { X::Cannot::Map.new( what => self.^name, using => "a {iterable.^name}", suggestion => "Did a * (Whatever) get absorbed by a comma, range, series, or list repetition? Consider using a block if any of these are necessary for your mapping code." ).throw; } multi method map(|c) { X::Cannot::Map.new( what => self.^name, using => "'{c.raku.substr(2).chop}'", suggestion => "Did a * (Whatever) get absorbed by a list?" ).throw; } # We want map to be fast, so we go to some effort to build special # case iterators that can ignore various interesting cases. multi method map(\SELF: &code;; :$label, :$item) { my $count := &code.count; my $source := $item ?? Rakudo::Iterator.OneValue(SELF) !! SELF.iterator; Seq.new: &code.?has-loop-phasers ?? $count < 2 || $count == Inf ?? IterateOneWithPhasers.new(&code, $source, $label) !! IterateMoreWithPhasers.new(&code, $source, $count, $label) !! $count < 2 || $count == Inf ?? IterateOneWithoutPhasers.new(&code, $source, $label) !! $count == 2 ?? IterateTwoWithoutPhasers.new(&code, $source, $label) !! IterateMoreWithoutPhasers.new(&code, $source, $count, $label) } proto method flatmap (|) is nodal {*} multi method flatmap(&block, :$label) { self.map(&block, :$label).flat } my class Grep-K does Iterator { has Mu $!iter; has Mu $!test; has int $!index; method !SET-SELF(\list,Mu \test) { $!iter = list.iterator; $!test := test; $!index = -1; self } method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) } method pull-one() is raw { nqp::until( nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd) || $!test($_), ++$!index ); nqp::eqaddr($_,IterationEnd) ?? IterationEnd !! nqp::p6box_i(++$!index) } method push-all(\target --> IterationEnd) { my $iter := $!iter; # lexicals faster than attrs my $test := $!test; my int $i = $!index; nqp::until( nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd), nqp::stmts( ++$i, nqp::if( $!test($_), target.push(nqp::p6box_i($i)) ) ) ); $!index = $i; } method is-deterministic(--> Bool:D) { $!iter.is-deterministic } method is-monotonically-increasing(--> Bool:D) { $!iter.is-monotonically-increasing } } method !grep-k(Callable:D $test) { Seq.new(Grep-K.new(self,$test)) } my class Grep-KV does Iterator { has Mu $!iter; has Mu $!test; has int $!index; has Mu $!value; method !SET-SELF(\list,Mu \test) { $!iter = list.iterator; $!test := test; $!index = -1; self } method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) } method pull-one() is raw { nqp::if( nqp::isconcrete($!value), nqp::stmts( ($_ := $!value), ($!value := nqp::null), $_ ), nqp::stmts( nqp::until( nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd) || $!test($_), ++$!index ), nqp::if( nqp::eqaddr($_,IterationEnd), IterationEnd, nqp::stmts( ($!value := $_), nqp::p6box_i(++$!index) ) ) ) ) } method push-all(\target --> IterationEnd) { nqp::until( nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd), nqp::stmts( ++$!index, nqp::if( $!test($_), nqp::stmts( # doesn't sink target.push(nqp::p6box_i($!index)); target.push($_); ) ) ) ); } method is-deterministic(--> Bool:D) { $!iter.is-deterministic } method is-monotonically-increasing(--> Bool:D) { $!iter.is-monotonically-increasing } } method !grep-kv(Callable:D $test) { Seq.new(Grep-KV.new(self,$test)) } my class Grep-P does Iterator { has Mu $!iter; has Mu $!test; has int $!index; method !SET-SELF(\list,Mu \test) { $!iter = list.iterator; $!test := test; $!index = -1; self } method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) } method pull-one() is raw { nqp::until( nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd) || $!test($_), ++$!index ); nqp::eqaddr($_,IterationEnd) ?? IterationEnd !! Pair.new(++$!index,$_) } method push-all(\target --> IterationEnd) { my $iter := $!iter; # lexicals are faster than attrs my $test := $!test; my int $i = $!index; nqp::until( nqp::eqaddr(($_ := $iter.pull-one),IterationEnd), nqp::stmts( ++$i, nqp::if( $test($_), target.push(Pair.new($i,$_)) ) ) ); $!index = $i; } method is-deterministic(--> Bool:D) { $!iter.is-deterministic } method is-monotonically-increasing(--> Bool:D) { $!iter.is-monotonically-increasing } } method !grep-p(Callable:D $test) { Seq.new(Grep-P.new(self,$test)) } my role Grepper does Iterator { has Mu $!iter; has Mu $!test; method !SET-SELF(\list,Mu \test) { $!iter = list.iterator; $!test := test; self } method new(\list,Mu \test) { nqp::create(self)!SET-SELF(list,test) } method is-lazy() { $!iter.is-lazy } method is-deterministic(--> Bool:D) { $!iter.is-deterministic } method is-monotonically-increasing(--> Bool:D) { $!iter.is-monotonically-increasing } } method !grep-callable(Callable:D $test) { sub judge(Mu $result, Mu $value) is raw { nqp::if( nqp::istype($result,Regex), $result.ACCEPTS($value), nqp::if( nqp::istype($result,Junction), $result.Bool, $result ) ) } my $count := $test.count; Seq.new: $count < 2 || $count == Inf ?? IterateOneWithoutPhasers.new( -> \a { nqp::stmts( # don't sink the result nqp::handle( (my $result := $test(a)), 'NEXT', nqp::if( judge( nqp::ifnull(nqp::getpayload(nqp::exception),False), a ), THROW(nqp::const::CONTROL_NEXT, a), nqp::throwextype(nqp::const::CONTROL_NEXT) ), 'LAST', nqp::if( judge( nqp::ifnull(nqp::getpayload(nqp::exception),False), a ), THROW(nqp::const::CONTROL_LAST, a), nqp::throwextype(nqp::const::CONTROL_LAST) ), ), nqp::if(judge($result, a),a,Empty) ) }, self.iterator, Any ) !! $count == 2 ?? IterateTwoWithoutPhasers.new( -> \a, \b { my \params := (a, b); nqp::handle( (my $result := $test(a, b)), 'NEXT', nqp::if( judge( nqp::ifnull(nqp::getpayload(nqp::exception),False), params ), THROW(nqp::const::CONTROL_NEXT, params), nqp::throwextype(nqp::const::CONTROL_NEXT) ), 'LAST', nqp::if( judge( nqp::ifnull(nqp::getpayload(nqp::exception),False), params ), THROW(nqp::const::CONTROL_LAST, params), nqp::throwextype(nqp::const::CONTROL_LAST) ) ); judge($result, params) ?? params !! Empty }, self.iterator, Any ) !! IterateMoreWithPhasers.new( -> |c { my \params := c.list; nqp::handle( (my $result := $test(|c)), 'NEXT', nqp::if( judge( nqp::ifnull(nqp::getpayload(nqp::exception),False), params ), THROW(nqp::const::CONTROL_NEXT, params), nqp::throwextype(nqp::const::CONTROL_NEXT) ), 'LAST', nqp::if( judge( nqp::ifnull(nqp::getpayload(nqp::exception),False), params ), THROW(nqp::const::CONTROL_LAST, params), nqp::throwextype(nqp::const::CONTROL_LAST) ) ); judge($result, params) ?? params !! Empty }, self.iterator, $count, Any ) } # Create a braid and fail cursor that we can use with all the normal, # "boring", regex matches that are on the Regex type. This saves them # being created every single time. my $cursor := Match.'!cursor_init'(''); my $braid := $cursor.braid; my $fail_cursor := $cursor.'!cursor_start_cur'(); my class Grep-Regex does Grepper { method pull-one() is raw { nqp::until( nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd) || nqp::isge_i( nqp::getattr_i( $!test.(Match.'!cursor_init'( .Str, :c(0), :$braid, :$fail_cursor )), Match, '$!pos' ), 0 ), nqp::null ); $_ } method push-all(\target --> IterationEnd) { nqp::until( nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd), nqp::if( # doesn't sink nqp::isge_i( nqp::getattr_i( $!test.(Match.'!cursor_init'( .Str, :c(0), :$braid, :$fail_cursor )), Match, '$!pos' ), 0 ), target.push($_) ) ); } } method !grep-regex(Mu $test) { Seq.new(Grep-Regex.new(self,$test)) } my class Grep-Accepts does Grepper { method pull-one() is raw { nqp::until( nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd) || $!test.ACCEPTS($_), nqp::null ); $_ } method push-all(\target --> IterationEnd) { nqp::until( nqp::eqaddr(($_ := $!iter.pull-one),IterationEnd), nqp::if( # doesn't sink $!test.ACCEPTS($_), target.push($_) ) ); } } method !grep-accepts(Mu $test) { Seq.new(Grep-Accepts.new(self,$test)) } method !first-result(\index,\value,$what,%a) is raw { nqp::stmts( (my $storage := nqp::getattr(%a,Map,'$!storage')), nqp::if( nqp::elems($storage), # some adverb nqp::if( nqp::iseq_i(nqp::elems($storage),1), # one adverb nqp::if( nqp::atkey($storage,"k"), # :k nqp::p6box_i(index), nqp::if( nqp::atkey($storage,"p"), # :p Pair.new(index,value), nqp::if( nqp::atkey($storage,"v"), # :v value, nqp::if( nqp::atkey($storage,"kv"), # :kv (index,value), nqp::stmts( # no truthy or different (my str $key = nqp::iterkey_s(nqp::shift(nqp::iterator($storage)))), nqp::if( (nqp::iseq_s($key,"k") # :!k || :!p || :!kv || nqp::iseq_s($key,"p") || nqp::iseq_s($key,"kv")), value, nqp::if( nqp::iseq_s($key,"v"), # :!v "Specified a negated :v adverb".Failure, X::Adverb.new( # :foo ?? :$what, :source(try { self.VAR.name } // self.WHAT.raku), :unexpected(%a.keys) ).Failure ) ) ) ) ) ) ), X::Adverb.new( # multiple adverbs ?? :$what, :source(try { self.VAR.name } // self.WHAT.raku), :nogo(%a.keys.grep: /k|v|p/), :unexpected(%a.keys.grep: { !.match(/k|v|p/) } ) ).Failure ), value # no adverb ) ) } proto method grep(|) is nodal {*} multi method grep(Bool:D $t) { X::Match::Bool.new( type => '.grep').throw } multi method grep(Mu $t) { my $storage := nqp::getattr(%_,Map,'$!storage'); if nqp::iseq_i(nqp::elems($storage),0) { nqp::istype($t,Regex:D) ?? self!grep-regex: $t !! nqp::istype($t,Callable:D) ?? self!grep-callable: $t !! self!grep-accepts: $t } elsif nqp::iseq_i(nqp::elems($storage),1) { if nqp::atkey($storage,"k") { nqp::istype($t,Regex:D) ?? self!grep-k: { $t.ACCEPTS($_) } !! nqp::istype($t,Callable:D) ?? self!grep-k: self!wrap-callable-for-grep($t) !! self!grep-k: { $t.ACCEPTS($_) } } elsif nqp::atkey($storage,"kv") { nqp::istype($t,Regex:D) ?? self!grep-kv: { $t.ACCEPTS($_) } !! nqp::istype($t,Callable:D) ?? self!grep-kv: self!wrap-callable-for-grep($t) !! self!grep-kv: { $t.ACCEPTS($_) } } elsif nqp::atkey($storage,"p") { nqp::istype($t,Regex:D) ?? self!grep-p: { $t.ACCEPTS($_) } !! nqp::istype($t,Callable:D) ?? self!grep-p: self!wrap-callable-for-grep($t) !! self!grep-p: { $t.ACCEPTS($_) } } elsif nqp::atkey($storage,"v") { nqp::istype($t,Regex:D) ?? self!grep-regex: $t !! nqp::istype($t,Callable:D) ?? self!grep-callable: self!wrap-callable-for-grep($t) !! self!grep-accepts: $t } else { my str $key = nqp::iterkey_s(nqp::shift(nqp::iterator($storage))); if nqp::iseq_s($key,"k") || nqp::iseq_s($key,"kv") || nqp::iseq_s($key,"p") { nqp::istype($t,Regex:D) ?? self!grep-regex: $t !! nqp::istype($t,Callable:D) ?? self!grep-callable: self!wrap-callable-for-grep($t) !! self!grep-accepts: $t } else { nqp::iseq_s($key,"k") ?? die "Specified a negated :v adverb" !! X::Adverb.new( :what, :source(try { self.VAR.name } // self.WHAT.raku), :unexpected($key) ).throw } } } else { X::Adverb.new( :what, :source(try { self.VAR.name } // self.WHAT.raku), :nogo(%_.keys.grep: /k|v|kv|p/), :unexpected(%_.keys.grep: { !.match(/k|v|kv|p/) } ) ).throw } } method !wrap-callable-for-grep($test) { ({ nqp::istype((my \result := $test($_)),Regex) ?? result.ACCEPTS($_) !! nqp::istype(result,Junction) ?? result.Bool !! result }) } proto method first(|) is nodal {*} multi method first(Bool:D $t) { X::Match::Bool.new( type => '.first' ).Failure } # need to handle Regex differently, since it is also Callable multi method first(Regex:D $test, :$end, *%a) is raw { $end ?? self!first-regex-end($test,%a) !! self!first-regex($test,%a) } multi method first(Callable:D $test, :$end, *%a) is raw { $end ?? self!first-callable-end($test, %a) !! self!first-callable($test, %a) } multi method first(Mu $test, :$end, *%a) is raw { $end ?? self!first-accepts-end($test,%a) !! self!first-accepts($test,%a) } multi method first(:$end, *%a) is raw { nqp::elems(nqp::getattr(%a,Map,'$!storage')) ?? $end ?? self!first-accepts-end(True,%a) !! self!first-accepts(True,%a) !! $end ?? self.tail !! self.head } method !first-callable(Callable:D $test, %a) is raw { my $iter := self.iterator; my int $index; nqp::until( (nqp::eqaddr(($_ := $iter.pull-one),IterationEnd) || $test($_)), ++$index ); nqp::eqaddr($_,IterationEnd) ?? Nil !! self!first-result($index,$_,'first',%a) } method !first-callable-end(Callable:D $test, %a) is raw { my $elems := self.elems; nqp::if( ($elems && nqp::not_i($elems == Inf)), nqp::stmts( (my int $index = $elems), nqp::while( nqp::isge_i(--$index,0), nqp::if( $test(self.AT-POS($index)), return self!first-result( $index,self.AT-POS($index),'first :end',%a) ) ), Nil ), Nil ) } method !first-regex(Mu $test, %a) is raw { my $iter := self.iterator; my int $index; nqp::until( nqp::eqaddr(($_ := $iter.pull-one),IterationEnd) || nqp::isge_i( nqp::getattr_i( $test.(Match.'!cursor_init'( .Str, :c(0), :$braid, :$fail_cursor )), Match, '$!pos' ), 0 ), ++$index ); nqp::eqaddr($_,IterationEnd) ?? Nil !! self!first-result($index,$_,'first',%a) } method !first-regex-end(Mu $test, %a) is raw { my $elems = self.elems; nqp::if( ($elems && nqp::not_i($elems == Inf)), nqp::stmts( (my int $index = $elems), nqp::while( nqp::isge_i(--$index,0), nqp::if( nqp::isge_i( nqp::getattr_i( $test.(Match.'!cursor_init'( self.AT-POS($index).Str, :c(0), :$braid, :$fail_cursor )), Match, '$!pos' ), 0 ), return self!first-result( $index,self.AT-POS($index),'first :end',%a) ) ), Nil ), Nil ) } method !first-accepts(Mu $test, %a) is raw { my $iter := self.iterator; my int $index; nqp::until( (nqp::eqaddr(($_ := $iter.pull-one),IterationEnd) || $test.ACCEPTS($_)), ++$index ); nqp::eqaddr($_,IterationEnd) ?? Nil !! self!first-result($index,$_,'first',%a) } method !first-accepts-end(Mu $test, %a) is raw { my $elems = self.elems; nqp::if( ($elems && nqp::not_i($elems == Inf)), nqp::stmts( (my int $index = $elems), nqp::while( nqp::isge_i(--$index,0), nqp::if( $test.ACCEPTS(self.AT-POS($index)), return self!first-result( $index,self.AT-POS($index),'first :end',%a) ) ), Nil ), Nil ) } # Returns an iterator only if there is at least one value that # can be produced. If the iterator is lazy, throws an error # with the action that is specified as the first argument. # Assigns the produced value in the variable that must be # specified as the second argument. method iterator-and-first( $action, $first is rw ) is implementation-detail { nqp::if( self.is-lazy, X::Cannot::Lazy.new(:$action).throw, nqp::stmts( (my $iterator := self.iterator), nqp::until( nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), nqp::if( nqp::isconcrete($pulled), nqp::stmts( ($first = $pulled), (return $iterator) ) ) ), Mu ) ) } proto method sum(*%) is nodal {*} multi method sum(Any:D:) { nqp::if( (my \iterator := self.iterator).is-lazy, self.fail-iterator-cannot-be-lazy('.sum'), nqp::stmts( (my $sum := 0), nqp::until( nqp::eqaddr((my \pulled := iterator.pull-one),IterationEnd), ($sum := $sum + pulled) ), $sum ) ) } proto method sort(|) is nodal {*} multi method sort() { my $iterator := self.iterator; $iterator.is-monotonically-increasing ?? Seq.new($iterator) !! nqp::eqaddr( $iterator.push-until-lazy( my \buf := nqp::create(IterationBuffer)), IterationEnd ) ?? Seq.new( Rakudo::Iterator.ReifiedListMonotonicallyIncreasing( Rakudo::Sorting.MERGESORT-REIFIED-LIST(buf.List) ) ) !! X::Cannot::Lazy.new(:action).throw } multi method sort(&by) { nqp::unless( nqp::eqaddr( self.iterator.push-until-lazy( my \buf := nqp::create(IterationBuffer)), IterationEnd ), X::Cannot::Lazy.new(:action).throw ); Seq.new( nqp::eqaddr(&by,&infix:) ?? Rakudo::Iterator.ReifiedListMonotonicallyIncreasing( Rakudo::Sorting.MERGESORT-REIFIED-LIST(buf.List) ) !! Rakudo::Iterator.ReifiedList(&by.count < 2 ?? Rakudo::Sorting.MERGESORT-REIFIED-LIST-AS( buf.List,&by) !! Rakudo::Sorting.MERGESORT-REIFIED-LIST-WITH(buf.List,&by) ) ) } proto method collate(|) {*} multi method collate() { self.sort(&[coll]) } proto method reduce(|) is nodal {*} multi method reduce(Any:U: & --> Nil) { } multi method reduce(Any:D: &with) { (&with.reducer)(&with)(self) } proto method produce(|) is nodal {*} multi method produce(Any:U: & --> Nil) { } multi method produce(Any:D: &with) { (&with.reducer)(&with, 1)(self) } proto method slice(|) is nodal { * } multi method slice(Any:D: *@indices --> Seq:D) { self.Seq.slice(@indices) } proto method unique(|) is nodal {*} my class Unique does Iterator { has $!iter; has $!seen; method !SET-SELF(\list) { $!iter := list.iterator; $!seen := nqp::hash; self } method new(\list) { nqp::create(self)!SET-SELF(list) } method pull-one() is raw { nqp::until( nqp::eqaddr((my \pulled := $!iter.pull-one),IterationEnd) || (nqp::not_i(nqp::existskey( $!seen, (my \needle := pulled.WHICH) )) && nqp::bindkey($!seen,needle,1)), nqp::null ); pulled } method push-all(\target --> IterationEnd) { nqp::until( nqp::eqaddr((my \pulled := $!iter.pull-one),IterationEnd), nqp::unless( nqp::existskey($!seen,(my \needle := pulled.WHICH)), nqp::stmts( nqp::bindkey($!seen,needle,1), target.push(pulled) ) ) ) } method is-lazy() { $!iter.is-lazy } method is-deterministic(--> Bool:D) { $!iter.is-deterministic } method is-monotonically-increasing(--> Bool:D) { $!iter.is-monotonically-increasing } method sink-all(--> IterationEnd) { $!iter.sink-all } } multi method unique() { Seq.new(Unique.new(self)) } multi method unique( :&as!, :&with! ) { nqp::eqaddr(&with,&[===]) # use optimized version ?? self.unique(:&as) !! Seq.new( Rakudo::Iterator.UniqueRepeatedAsWith(self.iterator,&as,&with,1) ) } my class Unique-As does Iterator { has Mu $!iter; has &!as; has $!seen; method !SET-SELF(\list, &!as) { $!iter = list.iterator; $!seen := nqp::hash(); self } method new(\list, &as) { nqp::create(self)!SET-SELF(list, &as) } method pull-one() is raw { nqp::until( nqp::eqaddr((my \value := $!iter.pull-one),IterationEnd), nqp::unless( nqp::existskey($!seen,my \needle := &!as(value).WHICH), nqp::stmts( nqp::bindkey($!seen,needle,1), return-rw value ) ) ); IterationEnd } method push-all(\target --> IterationEnd) { nqp::until( nqp::eqaddr((my \value := $!iter.pull-one),IterationEnd), nqp::unless( nqp::existskey($!seen,my \needle := &!as(value).WHICH), nqp::stmts( # doesn't sink nqp::bindkey($!seen,needle,1), target.push(value) ) ) ) } method is-deterministic(--> Bool:D) { $!iter.is-deterministic } method is-monotonically-increasing(--> Bool:D) { $!iter.is-monotonically-increasing } } multi method unique( :&as! ) { Seq.new(Unique-As.new(self,&as)) } multi method unique( :&with! ) { nqp::eqaddr(&with,&[===]) # use optimized version ?? self.unique !! Seq.new(Rakudo::Iterator.UniqueRepeatedWith(self.iterator,&with,1)) } proto method repeated(|) is nodal {*} my class Repeated does Iterator { has Mu $!iter; has $!seen; method !SET-SELF(\list) { $!iter = list.iterator; $!seen := nqp::hash(); self } method new(\list) { nqp::create(self)!SET-SELF(list) } method pull-one() is raw { my Mu $value; my str $needle; nqp::until( nqp::eqaddr(($value := $!iter.pull-one),IterationEnd), nqp::existskey($!seen,$needle = nqp::unbox_s($value.WHICH)) ?? return-rw $value !! nqp::bindkey($!seen, $needle, 1) ); IterationEnd } method push-all(\target --> IterationEnd) { my Mu $value; my str $needle; nqp::until( # doesn't sink nqp::eqaddr(($value := $!iter.pull-one),IterationEnd), nqp::existskey($!seen,$needle = nqp::unbox_s($value.WHICH)) ?? target.push($value) !! nqp::bindkey($!seen, $needle, 1) ); } method is-lazy() { $!iter.is-lazy } method is-deterministic(--> Bool:D) { $!iter.is-deterministic } method is-monotonically-increasing(--> Bool:D) { $!iter.is-monotonically-increasing } } multi method repeated() { Seq.new(Repeated.new(self)) } multi method repeated( :&as!, :&with! ) { nqp::eqaddr(&with,&[===]) # use optimized version ?? self.repeated(:&as) !! Seq.new( Rakudo::Iterator.UniqueRepeatedAsWith(self.iterator,&as,&with,0) ) } my class Repeated-As does Iterator { has Mu $!iter; has &!as; has $!seen; method !SET-SELF(\list, &!as) { $!iter = list.iterator; $!seen := nqp::hash(); self } method new(\list, &as) { nqp::create(self)!SET-SELF(list, &as) } method pull-one() is raw { my Mu $value; my str $needle; nqp::until( nqp::eqaddr(($value := $!iter.pull-one),IterationEnd), nqp::existskey($!seen,$needle = nqp::unbox_s(&!as($value).WHICH)) ?? return-rw $value !! nqp::bindkey($!seen, $needle, 1) ); IterationEnd } method push-all(\target --> IterationEnd) { my Mu $value; my str $needle; nqp::until( # doesn't sink nqp::eqaddr(($value := $!iter.pull-one),IterationEnd), nqp::existskey($!seen,$needle = nqp::unbox_s(&!as($value).WHICH)) ?? target.push($value) !! nqp::bindkey($!seen, $needle, 1) ); } method is-lazy() { $!iter.is-lazy } method is-deterministic(--> Bool:D) { $!iter.is-deterministic } method is-monotonically-increasing(--> Bool:D) { $!iter.is-monotonically-increasing } } multi method repeated( :&as! ) { Seq.new(Repeated-As.new(self,&as)) } multi method repeated( :&with! ) { nqp::eqaddr(&with,&[===]) # use optimized version ?? self.repeated !! Seq.new(Rakudo::Iterator.UniqueRepeatedWith(self.iterator,&with,0)) } proto method squish(|) is nodal {*} my class Squish-As does Iterator { has Mu $!iter; has &!as; has &!with; has $!last_as; has int $!first; method !SET-SELF($!iter, &!as, &!with) { $!first = 1; self } method new(\iter, \as, \with) { nqp::create(self)!SET-SELF(iter, as, with) } method pull-one() is raw { nqp::if( nqp::eqaddr((my $pulled := $!iter.pull-one),IterationEnd), IterationEnd, nqp::stmts( (my $which := &!as($pulled)), nqp::if( $!first, ($!first = 0), nqp::until( nqp::isfalse(&!with($!last_as,$which)) || nqp::eqaddr( ($pulled := $!iter.pull-one), IterationEnd ), nqp::stmts( ($!last_as := $which), ($which := &!as($pulled)) ) ) ), ($!last_as := $which), $pulled ) ) } method push-all(\target --> IterationEnd) { my Mu $value := $!iter.pull-one; unless nqp::eqaddr($value,IterationEnd) { my $which; my $last_as := $!last_as; nqp::if( $!first, nqp::stmts( # doesn't sink (target.push($value)), ($which := &!as($value)), ($last_as := $which), ($value := $!iter.pull-one) ) ); nqp::until( nqp::eqaddr($value,IterationEnd), nqp::stmts( nqp::unless( # doesn't sink &!with($last_as,$which := &!as($value)), target.push($value) ), ($last_as := $which), ($value := $!iter.pull-one) ) ); } } method is-lazy() { $!iter.is-lazy } method is-deterministic(--> Bool:D) { $!iter.is-deterministic } method is-monotonically-increasing(--> Bool:D) { $!iter.is-monotonically-increasing } } multi method squish( :&as!, :&with = &[===] ) { Seq.new(Squish-As.new(self.iterator, &as, &with)) } my class Squish-With does Iterator { has Mu $!iter; has &!with; has Mu $!last; has int $!first; method !SET-SELF($!iter, &!with) { $!first = 1; self } method new(\iter, \with) { nqp::create(self)!SET-SELF(iter, with) } method pull-one() is raw { nqp::if( nqp::eqaddr((my $pulled := $!iter.pull-one),IterationEnd), IterationEnd, nqp::stmts( nqp::if( $!first, ($!first = 0), nqp::stmts( (my $old := $pulled), nqp::until( nqp::isfalse(&!with($!last,$pulled)) || nqp::eqaddr( ($pulled := $!iter.pull-one), IterationEnd ), nqp::stmts( ($!last := $old), ($old := $pulled) ) ) ) ), ($!last := $pulled) ) ) } method push-all(\target --> IterationEnd) { my Mu $value := $!iter.pull-one; unless nqp::eqaddr($value,IterationEnd) { my $last_val = $!last; nqp::if( $!first, nqp::stmts( # doesn't sink (target.push($value)), ($last_val := $value), ($value := $!iter.pull-one) ) ); nqp::until( nqp::eqaddr($value,IterationEnd), nqp::stmts( nqp::unless( # doesn't sink &!with($last_val, $value), target.push($value) ), ($last_val := $value), ($value := $!iter.pull-one) ) ); } } method is-lazy() { $!iter.is-lazy } method is-deterministic(--> Bool:D) { $!iter.is-deterministic } method is-monotonically-increasing(--> Bool:D) { $!iter.is-monotonically-increasing } } multi method squish( :&with = &[===] ) { Seq.new(Squish-With.new(self.iterator,&with)) } proto method pairup(|) is nodal {*} multi method pairup(Any:U:) { ().Seq } multi method pairup(Any:D:) { my \iter := self.iterator; gather { nqp::until( nqp::eqaddr((my $pulled := iter.pull-one),IterationEnd), nqp::if( nqp::istype($pulled,Pair), (take nqp::p6bindattrinvres( nqp::clone($pulled), Pair, '$!value', nqp::clone(nqp::decont(nqp::getattr($pulled,Pair,'$!value'))) )), nqp::if( nqp::istype($pulled,Map) && nqp::not_i(nqp::iscont($pulled)), (take Slip.from-iterator($pulled.iterator)), nqp::if( nqp::eqaddr((my $value := iter.pull-one),IterationEnd), X::Pairup::OddNumber.new.throw, take Pair.new($pulled,$value) ) ) ) ) } } proto method toggle(|) {*} multi method toggle(Any:D: Callable:D \condition, :$off!) { Seq.new( $off ?? Rakudo::Iterator.Until(self.iterator, condition) !! Rakudo::Iterator.While(self.iterator, condition) ) } multi method toggle(Any:D: Callable:D \condition) { Seq.new(Rakudo::Iterator.While(self.iterator, condition)) } multi method toggle(Any:D: *@conditions, :$off) { Seq.new( Rakudo::Iterator.Toggle(self.iterator, @conditions.iterator, !$off) ) } proto method head(|) {*} multi method head(Any:U: |c) { (self,).head(|c) } multi method head(Any:D:) is raw { nqp::eqaddr((my $pulled := self.iterator.pull-one),IterationEnd) ?? Nil !! $pulled } multi method head(Any:D: Callable:D $w) { Seq.new( Rakudo::Iterator.AllButLastNValues(self.iterator,-($w(0).Int)) ) } multi method head(Any:D: $n) { Seq.new(Rakudo::Iterator.NextNValues(self.iterator,$n)) } proto method tail(|) {*} multi method tail(Any:U: |c) { (self,).tail(|c) } multi method tail(Any:D:) is raw { nqp::eqaddr((my $pulled := Rakudo::Iterator.LastValue(self.iterator,'tail')), IterationEnd ) ?? Nil !! $pulled } multi method tail(Any:D: $n) { Seq.new( nqp::if( nqp::istype($n,Callable), nqp::stmts( (my $iterator := self.iterator), nqp::if( nqp::isgt_i((my $skip := -($n(0).Int)),0), nqp::if( $iterator.skip-at-least($skip), $iterator, Rakudo::Iterator.Empty), $iterator)), Rakudo::Iterator.LastNValues(self.iterator,$n,'tail') ) ) } proto method skip(|) {*} multi method skip() { my $iter := self.iterator; Seq.new( $iter.skip-one ?? $iter !! Rakudo::Iterator.Empty ) } multi method skip(Whatever) { Seq.new(Rakudo::Iterator.Empty) } multi method skip(Callable:D $w) { nqp::isgt_i((my $tail := -($w(0).Int)),0) ?? self.tail($tail) !! Seq.new(Rakudo::Iterator.Empty) } multi method skip(Int() $n) { my $iter := self.iterator; Seq.new( $iter.skip-at-least($n) ?? $iter !! Rakudo::Iterator.Empty ) } proto method batch(|) is nodal {*} multi method batch(Any:D: Int:D :$elems!) { Seq.new(Rakudo::Iterator.Batch(self.iterator,$elems,1)) } multi method batch(Any:D: Int:D $batch) { Seq.new(Rakudo::Iterator.Batch(self.iterator,$batch,1)) } proto method rotor(|) is nodal {*} multi method rotor(Any:D: Int:D $batch, :$partial) { Seq.new(Rakudo::Iterator.Batch(self.iterator,$batch,$partial)) } multi method rotor(Any:D: +@cycle, :$partial) { Seq.new(Rakudo::Iterator.Rotor(self.iterator,@cycle,$partial)) } proto method are(|) {*} multi method are(Any:U:) { self } multi method are(Any:D:) { my $iterator := self.iterator; nqp::if( nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), Nil, # nothing to check nqp::stmts( (my $type := $pulled.WHAT), # initial type nqp::until( nqp::eqaddr(($pulled := $iterator.pull-one),IterationEnd) || nqp::not_i(nqp::eqaddr($pulled.WHAT,$type)), nqp::null ), nqp::if( nqp::eqaddr($pulled,IterationEnd), $type, # all the same self!slow-infer($iterator, $type, $pulled) # find out what ) ) ) } method !slow-infer($iterator, Mu $type is copy, Mu $pulled is copy) { # set up types to check my $mro := nqp::clone(nqp::getattr($type.^mro(:roles),List,'$!reified')); nqp::repeat_until( nqp::eqaddr(($pulled := $iterator.pull-one),IterationEnd) || nqp::eqaddr($type,Mu), nqp::until( nqp::istype($pulled,nqp::atpos($mro,0)), nqp::stmts( # not the same base type nqp::shift($mro), ($type := nqp::atpos($mro,0)), # assume next type for now ) ) ); $type } proto method nodemap(|) is nodal {*} multi method nodemap(Associative:D: &op) { self.new.STORE: self.keys, self.values.nodemap(&op), :INITIALIZE } multi method nodemap(&op) { my $source := self.iterator; return X::Cannot::Lazy.new(:action).Failure if $source.is-lazy; my \buffer := nqp::create(IterationBuffer); my $value := $source.pull-one; nqp::until( nqp::eqaddr($value,IterationEnd), nqp::handle( nqp::stmts( nqp::push(buffer,op($value)), ($value := $source.pull-one) ), 'NEXT', nqp::stmts( nqp::unless( nqp::isnull($value := nqp::getpayload(nqp::exception)), nqp::push(buffer,$value) ), ($value := $source.pull-one) ), 'REDO', nqp::null, 'LAST', nqp::stmts( nqp::unless( nqp::isnull($value := nqp::getpayload(nqp::exception)), nqp::push(buffer,$value) ), ($value := IterationEnd) ) ), :nohandler ); buffer.List } proto method deepmap(|) is nodal {*} multi method deepmap(Associative:D: &op) { self.new.STORE: self.keys, self.values.deepmap(&op), :INITIALIZE } multi method deepmap(&op) { my $source := self.iterator; my \buffer := nqp::create(IterationBuffer); my $pulled := $source.pull-one; sub deep(\value) is raw { my $ = value.deepmap(&op) } nqp::until( nqp::eqaddr($pulled,IterationEnd), nqp::handle( nqp::stmts( (my $value := nqp::if( nqp::istype($pulled,Iterable) && $pulled.DEFINITE, deep($pulled), op($pulled) )), nqp::if( nqp::istype($value,Slip), $value.iterator.push-all(buffer), nqp::push(buffer,$value) ), ($pulled := $source.pull-one) ), 'NEXT', nqp::stmts( nqp::unless( nqp::isnull($value := nqp::getpayload(nqp::exception)), nqp::if( nqp::istype($value,Slip), $value.iterator.push-all(buffer), nqp::push(buffer,$value) ), ), ($pulled := $source.pull-one) ), 'REDO', nqp::null, 'LAST', nqp::stmts( nqp::unless( nqp::isnull($value := nqp::getpayload(nqp::exception)), nqp::if( nqp::istype($value,Slip), $value.iterator.push-all(buffer), nqp::push(buffer,$value) ), ), ($pulled := IterationEnd) ) ), :nohandler ); nqp::p6bindattrinvres( nqp::if(nqp::istype(self,List),self,List).new, # keep subtypes of List List,'$!reified',buffer ) } proto method duckmap(|) is nodal {*} multi method duckmap(Associative:D: &op) { self.new.STORE: self.keys, self.values.duckmap(&op) } multi method duckmap(&op) { my $source := self.iterator; my \buffer := nqp::create(IterationBuffer); my $pulled := $source.pull-one; sub duck() is raw { CATCH { return nqp::istype($pulled,Iterable:D) ?? (my $ = $pulled.duckmap(&op)) !! $pulled } op($pulled) } sub process(Mu \value --> Nil) { nqp::istype(value,Slip) ?? value.iterator.push-all(buffer) !! nqp::push(buffer,value) } nqp::until( nqp::eqaddr($pulled,IterationEnd), nqp::handle( nqp::stmts( process(duck), ($pulled := $source.pull-one) ), 'NEXT', nqp::stmts( nqp::unless( nqp::isnull(my $value := nqp::getpayload(nqp::exception)), process($value) ), ($pulled := $source.pull-one) ), 'REDO', nqp::null, 'LAST', nqp::stmts( nqp::unless( nqp::isnull($value := nqp::getpayload(nqp::exception)), process($value) ), ($pulled := IterationEnd) ) ), :nohandler ); nqp::p6bindattrinvres( nqp::if(nqp::istype(self,List),self,List).new, # keep subtypes of List List,'$!reified',buffer ) } } BEGIN Attribute.^compose; proto sub infix:(|) is pure {*} multi sub infix:(Mu:D \a, Mu:U) { a } multi sub infix:(Mu:U, Mu:D \b) { b } multi sub infix:(Mu:D \a, Mu:D \b) { (a cmp b) ≥ 0 ?? b !! a } multi sub infix:(Int:D $a, Int:D $b) { nqp::isgt_i(nqp::cmp_I($a,$b),0) ?? $b !! $a } multi sub infix:(int $a, int $b) { nqp::isgt_i(nqp::cmp_i($a,$b),0) ?? $b !! $a } multi sub infix:(Num:D $a, Num:D $b) { nqp::isgt_i(nqp::cmp_n($a,$b),0) ?? $b !! $a } multi sub infix:(num $a, num $b) { nqp::isgt_i(nqp::cmp_n($a,$b),0) ?? $b !! $a } multi sub infix:(+args is raw) { args.min } proto sub min(|) is pure {*} multi sub min(+args, :&by!, *%_) { args.min(&by, |%_) } multi sub min(+args, *%_) { args.min(|%_) } proto sub infix:(|) is pure {*} multi sub infix:(Mu:D \a, Mu:U) { a } multi sub infix:(Mu:U, Mu:D \b) { b } multi sub infix:(Mu:D \a, Mu:D \b) { (a cmp b) ≤ 0 ?? b !! a } multi sub infix:(Int:D $a, Int:D $b) { nqp::islt_i(nqp::cmp_I($a,$b),0) ?? $b !! $a } multi sub infix:(int $a, int $b) { nqp::islt_i(nqp::cmp_i($a,$b),0) ?? $b !! $a } multi sub infix:(Num:D $a, Num:D $b) { nqp::islt_i(nqp::cmp_n($a,$b),0) ?? $b !! $a } multi sub infix:(num $a, num $b) { nqp::islt_i(nqp::cmp_n($a,$b),0) ?? $b !! $a } multi sub infix:(+args) { args.max } proto sub max(|) is pure {*} multi sub max(+args, :&by!, *%_) { args.max(&by, |%_) } multi sub max(+args, *%_) { args.max(|%_) } proto sub infix:(|) is pure {*} multi sub infix:(+args) { args.minmax } proto sub minmax(|) is pure {*} multi sub minmax(+args, :&by!) { args.minmax(&by) } multi sub minmax(+args) { args.minmax } proto sub map($, |) {*} multi sub map(&code, +values) { my $laze = values.is-lazy; values.map(&code).lazy-if($laze) } proto sub grep(Mu, |) {*} multi sub grep(Mu $test, +values, *%a) { my $laze := values.is-lazy; values.grep($test,|%a).lazy-if($laze) } multi sub grep(Bool:D $t, |) { X::Match::Bool.new(:type).throw } proto sub head(Mu, |) {*} multi sub head($head, +values) { values.head($head) } proto sub tail(Mu, |) {*} multi sub tail($tail, +values) { values.tail($tail) } proto sub skip(Mu, |) {*} multi sub skip($skip, +values) { values.skip($skip) } proto sub first(Mu, |) {*} multi sub first(Bool:D $t, |) { X::Match::Bool.new(:type).Failure } multi sub first(Mu $test, +values, *%a) { values.first($test,|%a) } proto sub join($?, |) {*} multi sub join($sep = '', *@values) { @values.join($sep) } proto sub reduce ($, |) {*} multi sub reduce (&with, +list) { list.reduce(&with) } proto sub produce ($, |) {*} multi sub produce (&with, +list) { list.produce(&with) } proto sub unique(|) {*} multi sub unique(+values, |c) { my $laze = values.is-lazy; values.unique(|c).lazy-if($laze) } proto sub squish(|) {*} multi sub squish(+values, |c) { my $laze = values.is-lazy; values.squish(|c).lazy-if($laze) } proto sub repeated(|) {*} multi sub repeated(+values, |c) { my $laze = values.is-lazy; values.repeated(|c).lazy-if($laze) } proto sub sort(|) {*} multi sub sort(&by, @values, *%_) { @values.sort(&by, |%_) } multi sub sort(&by, +values, *%_) { values.sort(&by, |%_) } multi sub sort(@values, *%_) { @values.sort(|%_) } multi sub sort(+values, *%_) { values.sort(|%_) } multi sub sort(*%_) { die "Must specify something to sort" } proto sub nodemap($, $, *%) {*} multi sub nodemap(&op, \obj) { obj.nodemap(&op) } proto sub deepmap($, $, *%) {*} multi sub deepmap(&op, \obj) { obj.deepmap(&op) } proto sub duckmap($, $, *%) {*} multi sub duckmap(&op, \obj) { obj.duckmap(&op) } #line 1 SETTING::src/core.c/SLICE.rakumod #=============================================================================== # # This file has been generated by tools/build/makeSLICE.raku # on 2022-02-07T04:04:47.552579Z. # # Please do *NOT* make changes to this file, as they will be lost # whenever this file is generated again. # #=============================================================================== # internal 1 element hash access with adverbs sub SLICE_ONE_HASH(\SELF,Mu $one,$key,$value,%adv) is implementation-detail { my Mu $d := nqp::clone(nqp::getattr(%adv,Map,'$!storage')); nqp::bindkey($d,nqp::unbox_s($key),nqp::decont($value)); sub HANDLED($key) { nqp::if( nqp::existskey($d,nqp::unbox_s($key)), nqp::stmts( (my $value := nqp::atkey($d,$key)), nqp::deletekey($d,$key), $value ), Nil ) } my @nogo; my \result = do { if HANDLED('delete') { # :delete:* if nqp::elems($d) == 0 { # :delete SELF.DELETE-KEY($one); } elsif nqp::existskey($d,'exists') { # :delete:exists(0|1):* my $exists := HANDLED('exists'); my $wasthere := SELF.EXISTS-KEY($one); SELF.DELETE-KEY($one); if nqp::elems($d) == 0 { # :delete:exists(0|1) !( $wasthere ?^ $exists ) } elsif nqp::existskey($d,'kv') { # :delete:exists(0|1):kv(0|1) my $kv := HANDLED('kv'); if nqp::elems($d) == 0 { !$kv || $wasthere ?? ( $one, !( $wasthere ?^ $exists ) ) !! (); } else { @nogo = ; } } elsif nqp::existskey($d,'p') { # :delete:exists(0|1):p(0|1) my $p := HANDLED('p'); if nqp::elems($d) == 0 { !$p || $wasthere ?? Pair.new($one, !($wasthere ?^ $exists) ) !! (); } else { @nogo = ; } } else { @nogo = ; } } elsif nqp::existskey($d,'kv') { # :delete:kv(0|1) my $kv := HANDLED('kv'); if nqp::elems($d) == 0 { !$kv || SELF.EXISTS-KEY($one) ?? ( $one, SELF.DELETE-KEY($one) ) !! (); } else { @nogo = ; } } elsif nqp::existskey($d,'p') { # :delete:p(0|1) my $p := HANDLED('p'); if nqp::elems($d) == 0 { !$p || SELF.EXISTS-KEY($one) ?? Pair.new($one, SELF.DELETE-KEY($one)) !! (); } else { @nogo = ; } } elsif nqp::existskey($d,'k') { # :delete:k(0|1) my $k := HANDLED('k'); if nqp::elems($d) == 0 { !$k || SELF.EXISTS-KEY($one) ?? do { SELF.DELETE-KEY($one); $one } !! (); } else { @nogo = ; } } elsif nqp::existskey($d,'v') { # :delete:v(0|1) my $v := HANDLED('v'); if nqp::elems($d) == 0 { !$v || SELF.EXISTS-KEY($one) ?? SELF.DELETE-KEY($one) !! (); } else { @nogo = ; } } else { @nogo = ; } } elsif nqp::existskey($d,'exists') { # :!delete?:exists(0|1):* my $exists := HANDLED('exists'); my $wasthere = SELF.EXISTS-KEY($one); if nqp::elems($d) == 0 { # :!delete?:exists(0|1) !( $wasthere ?^ $exists ) } elsif nqp::existskey($d,'kv') { # :!delete?:exists(0|1):kv(0|1) my $kv := HANDLED('kv'); if nqp::elems($d) == 0 { !$kv || $wasthere ?? ( $one, !( $wasthere ?^ $exists ) ) !! (); } else { @nogo = ; } } elsif nqp::existskey($d,'p') { # :!delete?:exists(0|1):p(0|1) my $p := HANDLED('p'); if nqp::elems($d) == 0 { !$p || $wasthere ?? Pair.new($one, !( $wasthere ?^ $exists )) !! (); } else { @nogo = ; } } else { @nogo = ; } } elsif nqp::existskey($d,'kv') { # :!delete?:kv(0|1):* my $kv := HANDLED('kv'); if nqp::elems($d) == 0 { # :!delete?:kv(0|1) !$kv || SELF.EXISTS-KEY($one) ?? ($one, SELF.AT-KEY($one)) !! (); } else { @nogo = ; } } elsif nqp::existskey($d,'p') { # :!delete?:p(0|1):* my $p := HANDLED('p'); if nqp::elems($d) == 0 { # :!delete?:p(0|1) !$p || SELF.EXISTS-KEY($one) ?? Pair.new($one, SELF.AT-KEY($one)) !! (); } else { @nogo =

; } } elsif nqp::existskey($d,'k') { # :!delete?:k(0|1):* my $k := HANDLED('k'); if nqp::elems($d) == 0 { # :!delete?:k(0|1) !$k || SELF.EXISTS-KEY($one) ?? $one !! (); } else { @nogo = ; } } elsif nqp::existskey($d,'v') { # :!delete?:v(0|1):* my $v := HANDLED('v'); # :!delete?:v(0|1) if nqp::elems($d) == 0 { !$v || SELF.EXISTS-KEY($one) ?? SELF.AT-KEY($one) !! (); } else { @nogo = ; } } elsif nqp::elems($d) == 0 { # :!delete SELF.AT-KEY($one); } } @nogo || nqp::elems($d) ?? Rakudo::Internals.SLICE_HUH( SELF, @nogo, $d, %adv ) !! result; } #SLICE_ONE_HASH # internal >1 element hash access with adverbs sub SLICE_MORE_HASH(\SELF,$more,$key,$value,%adv) is implementation-detail { my Mu $d := nqp::clone(nqp::getattr(%adv,Map,'$!storage')); nqp::bindkey($d,nqp::unbox_s($key),nqp::decont($value)); sub HANDLED($key) { nqp::if( nqp::existskey($d,nqp::unbox_s($key)), nqp::stmts( (my $value := nqp::atkey($d,$key)), nqp::deletekey($d,$key), $value ), Nil ) } my @nogo; my \result = do { if HANDLED('delete') { # :delete:* if nqp::elems($d) == 0 { # :delete $more.cache.flatmap( { SELF.DELETE-KEY($_) } ).eager.list; } elsif nqp::existskey($d,'exists') { # :delete:exists(0|1):* my $exists := HANDLED('exists'); my $wasthere; # no need to initialize every iteration of map if nqp::elems($d) == 0 { # :delete:exists(0|1) $more.cache.flatmap( { SELF.DELETE-KEY($_) if $wasthere = SELF.EXISTS-KEY($_); !( $wasthere ?^ $exists ); } ).eager.list; } elsif nqp::existskey($d,'kv') { # :delete:exists(0|1):kv(0|1):* my $kv := HANDLED('kv'); if nqp::elems($d) == 0 { # :delete:exists(0|1):kv(0|1) $more.cache.flatmap( { SELF.DELETE-KEY($_) if $wasthere = SELF.EXISTS-KEY($_); next unless !$kv || $wasthere; ($_, !( $wasthere ?^ $exists )); } ).flat.eager.list; } else { @nogo = ; } } elsif nqp::existskey($d,'p') { # :delete:exists(0|1):p(0|1):* my $p := HANDLED('p'); if nqp::elems($d) == 0 { # :delete:exists(0|1):p(0|1) $more.cache.flatmap( { SELF.DELETE-KEY($_) if $wasthere = SELF.EXISTS-KEY($_); next unless !$p || $wasthere; Pair.new($_,!($wasthere ?^ $exists)); } ).eager.list; } else { @nogo = ; } } else { @nogo = ; } } elsif nqp::existskey($d,'kv') { # :delete:kv(0|1):* my $kv := HANDLED('kv'); if nqp::elems($d) == 0 { # :delete:kv(0|1) $kv ?? $more.cache.flatmap( { next unless SELF.EXISTS-KEY($_); ( $_, SELF.DELETE-KEY($_) ); } ).flat.eager.list !! $more.cache.flatmap( { ( $_, SELF.DELETE-KEY($_) ) } ).flat.eager.list; } else { @nogo = ; } } elsif nqp::existskey($d,'p') { # :delete:p(0|1):* my $p := HANDLED('p'); if nqp::elems($d) == 0 { # :delete:p(0|1) $p ?? $more.cache.flatmap( { next unless SELF.EXISTS-KEY($_); Pair.new($_, SELF.DELETE-KEY($_)); } ).eager.list !! $more.cache.flatmap( { Pair.new($_, SELF.DELETE-KEY($_)) } ).eager.list; } else { @nogo = ; } } elsif nqp::existskey($d,'k') { # :delete:k(0|1):* my $k := HANDLED('k'); if nqp::elems($d) == 0 { # :delete:k(0|1) $k ?? $more.cache.flatmap( { nqp::if( SELF.EXISTS-KEY($_), nqp::stmts( SELF.DELETE-KEY($_), $_ ), next ) } ).eager.list !! $more.cache.flatmap( { SELF.DELETE-KEY($_); $_ } ).eager.list; } else { @nogo = ; } } elsif nqp::existskey($d,'v') { # :delete:v(0|1):* my $v := HANDLED('v'); if nqp::elems($d) == 0 { # :delete:v(0|1) $v ?? $more.cache.flatmap( { next unless SELF.EXISTS-KEY($_); SELF.DELETE-KEY($_); } ).eager.list !! $more.cache.flatmap( { SELF.DELETE-KEY($_) } ).eager.list; } else { @nogo = ; } } else { @nogo = ; } } elsif nqp::existskey($d,'exists') { # :!delete?:exists(0|1):* my $exists := HANDLED('exists'); if nqp::elems($d) == 0 { # :!delete?:exists(0|1) $more.cache.flatmap({ !( SELF.EXISTS-KEY($_) ?^ $exists ) }).eager.list; } elsif nqp::existskey($d,'kv') { # :!delete?:exists(0|1):kv(0|1):* my $kv := HANDLED('kv'); if nqp::elems($d) == 0 { # :!delete?:exists(0|1):kv(0|1) $kv ?? $more.cache.flatmap( { next unless SELF.EXISTS-KEY($_); ( $_, $exists ); } ).flat.eager.list !! $more.cache.flatmap( { ( $_, !( SELF.EXISTS-KEY($_) ?^ $exists ) ) } ).flat.eager.list; } else { @nogo = ; } } elsif nqp::existskey($d,'p') { # :!delete?:exists(0|1):p(0|1):* my $p := HANDLED('p'); if nqp::elems($d) == 0 { # :!delete?:exists(0|1):p(0|1) $p ?? $more.cache.flatmap( { next unless SELF.EXISTS-KEY($_); Pair.new( $_, $exists ); } ).eager.list !! $more.cache.flatmap( { Pair.new( $_, !( SELF.EXISTS-KEY($_) ?^ $exists ) ) } ).eager.list; } else { @nogo = ; } } else { @nogo = ; } } elsif nqp::existskey($d,'kv') { # :!delete?:kv(0|1):* my $kv := HANDLED('kv'); if nqp::elems($d) == 0 { # :!delete?:kv(0|1) $kv ?? $more.cache.flatmap( { next unless SELF.EXISTS-KEY($_); $_, SELF.AT-KEY($_); } ).flat.eager.list !! $more.cache.flatmap( { $_, SELF.AT-KEY($_) } ).flat.eager.list; } else { @nogo = ; } } elsif nqp::existskey($d,'p') { # :!delete?:p(0|1):* my $p := HANDLED('p'); if nqp::elems($d) == 0 { # :!delete?:p(0|1) $p ?? $more.cache.flatmap( { next unless SELF.EXISTS-KEY($_); Pair.new($_, SELF.AT-KEY($_)); } ).eager.list !! $more.cache.flatmap( { Pair.new( $_, SELF.AT-KEY($_) ) } ).eager.list; } else { @nogo =

} } elsif nqp::existskey($d,'k') { # :!delete?:k(0|1):* my $k := HANDLED('k'); if nqp::elems($d) == 0 { # :!delete?:k(0|1) $k ?? $more.cache.flatmap( { next unless SELF.EXISTS-KEY($_); $_; } ).eager.list !! $more.cache.flat.eager.list; } else { @nogo = ; } } elsif nqp::existskey($d,'v') { # :!delete?:v(0|1):* my $v := HANDLED('v'); if nqp::elems($d) == 0 { # :!delete?:v(0|1) $v ?? $more.cache.flatmap( { next unless SELF.EXISTS-KEY($_); SELF.AT-KEY($_); } ).eager.list !! $more.cache.flatmap( { SELF.AT-KEY($_) } ).eager.list; } else { @nogo = ; } } elsif nqp::elems($d) == 0 { # :!delete $more.cache.flatmap( { SELF.AT-KEY($_) } ).eager.list; } } @nogo || nqp::elems($d) ?? Rakudo::Internals.SLICE_HUH( SELF, @nogo, $d, %adv ) !! result; } #SLICE_MORE_HASH #line 1 SETTING::src/core.c/Whatever.rakumod my class X::Cannot::Capture { ... } my class X::Cannot::New { ... } my class Whatever { multi method ACCEPTS(Whatever:D: Mu --> True) { } multi method raku(Whatever:D: --> '*') { } multi method Str(Whatever:D: --> '*') { } method Capture() { X::Cannot::Capture.new( :what(self) ).throw } } my class HyperWhatever { multi method new(HyperWhatever:) { X::Cannot::New.new(class => self).throw } multi method ACCEPTS(HyperWhatever:D: $ --> True) { } multi method raku(HyperWhatever:D:) { '**' } method Capture() { X::Cannot::Capture.new( :what(self) ).throw } } sub HYPERWHATEVER (&c) { # is implementation-detail sub (*@_) { map &c, @_ } } #line 1 SETTING::src/core.c/hash_slice.rakumod # all sub postcircumfix {} candidates here please proto sub postcircumfix:<{ }>(Mu $, Mu $?, Mu $?, *%) is nodal {*} # %h multi sub postcircumfix:<{ }>( \SELF, Mu \key ) is raw { SELF.AT-KEY(key); } multi sub postcircumfix:<{ }>(\SELF, Mu \key, Mu \ASSIGN) is raw { SELF.ASSIGN-KEY(key, ASSIGN); } multi sub postcircumfix:<{ }>(\SELF, Mu \key, Mu :$BIND! is raw) is raw { SELF.BIND-KEY(key, $BIND); } multi sub postcircumfix:<{ }>( \SELF, Mu \key, Bool() :$delete! ) is raw { $delete ?? SELF.DELETE-KEY(key) !! SELF.AT-KEY(key) } multi sub postcircumfix:<{ }>( \SELF, Mu \key, Bool() :$delete!, *%other ) is raw { SLICE_ONE_HASH( SELF, key, 'delete', $delete, %other ) } multi sub postcircumfix:<{ }>( \SELF, Mu \key, Bool() :$exists! ) is raw { $exists ?? SELF.EXISTS-KEY(key) !! !SELF.EXISTS-KEY(key) } multi sub postcircumfix:<{ }>( \SELF, Mu \key, Bool() :$exists!, *%other ) is raw { SLICE_ONE_HASH( SELF, key, 'exists', $exists, %other ) } multi sub postcircumfix:<{ }>( \SELF, Mu \key, Bool() :$kv!, *%other ) is raw { $kv && nqp::not_i(nqp::elems(nqp::getattr(%other,Map,'$!storage'))) ?? (SELF.EXISTS-KEY(key) ?? (key,SELF.AT-KEY(key)) !! ()) !! SLICE_ONE_HASH( SELF, key, 'kv', $kv, %other ); } multi sub postcircumfix:<{ }>( \SELF, Mu \key, Bool() :$p!, *%other ) is raw { $p && nqp::not_i(nqp::elems(nqp::getattr(%other,Map,'$!storage'))) ?? (SELF.EXISTS-KEY(key) ?? Pair.new(key,SELF.AT-KEY(key)) !! ()) !! SLICE_ONE_HASH( SELF, key, 'p', $p, %other ); } multi sub postcircumfix:<{ }>( \SELF, Mu \key, Bool() :$k!, *%other ) is raw { $k && nqp::not_i(nqp::elems(nqp::getattr(%other,Map,'$!storage'))) ?? (SELF.EXISTS-KEY(key) ?? key !! ()) !! SLICE_ONE_HASH( SELF, key, 'k', $k, %other ); } multi sub postcircumfix:<{ }>( \SELF, Mu \key, Bool() :$v!, *%other ) is raw { $v && nqp::not_i(nqp::elems(nqp::getattr(%other,Map,'$!storage'))) ?? (SELF.EXISTS-KEY(key) ?? nqp::decont(SELF.AT-KEY(key)) !! ()) !! SLICE_ONE_HASH( SELF, key, 'v', $v, %other ); } # %h multi sub postcircumfix:<{ }>( \SELF, Iterable \key ) is raw { nqp::iscont(key) ?? SELF.AT-KEY(key) !! nqp::iscont(SELF) && nqp::not_i(nqp::isconcrete(SELF)) ?? key.flatmap({ SELF{$_} }).eager.list !! nqp::p6bindattrinvres(nqp::create(List),List,'$!reified', nqp::stmts( Rakudo::Iterator.AssociativeIterableKeys(SELF,key) .push-all(my \buffer := nqp::create(IterationBuffer)), buffer ) ) } multi sub postcircumfix:<{ }>(\SELF, Iterable \keys, Mu \values) is raw { return SELF.ASSIGN-KEY(keys, values) if nqp::iscont(keys); my $result := nqp::create(IterationBuffer); my $todo := nqp::create(IterationBuffer); my $keys := keys.iterator; my $values := Rakudo::Iterator.TailWith(values.iterator, Nil); nqp::until( nqp::eqaddr((my \key := $keys.pull-one),IterationEnd), nqp::stmts( nqp::push($todo,key), nqp::push($todo,$values.pull-one) ) ); nqp::while( nqp::elems($todo), nqp::push($result,SELF.ASSIGN-KEY(nqp::shift($todo),nqp::shift($todo))) ); $result.List } multi sub postcircumfix:<{ }>(\SELF, Iterable \key, :$BIND! is raw) is raw { return SELF.BIND-KEY(key, $BIND) if nqp::iscont(key); my $result := nqp::create(IterationBuffer); my $keys := key.iterator; my $binds := $BIND.iterator; nqp::until( nqp::eqaddr((my $bind := $binds.pull-one),IterationEnd) || nqp::eqaddr((my $key := $keys.pull-one),IterationEnd), nqp::push($result, SELF.BIND-KEY($key, $bind)) ); # fill up if ran out of values to bind? nqp::until( nqp::eqaddr(($key := $keys.pull-one),IterationEnd), nqp::push($result,SELF.ASSIGN-KEY($key,Nil)) ) if nqp::eqaddr($bind,IterationEnd); $result.List } multi sub postcircumfix:<{ }>(\SELF,Iterable \key, Bool() :$delete!,*%other) is raw { nqp::iscont(key) ?? SLICE_ONE_HASH( SELF, key, 'delete', $delete, %other ) !! SLICE_MORE_HASH( SELF, key, 'delete', $delete, %other ) } multi sub postcircumfix:<{ }>(\SELF,Iterable \key, Bool() :$exists!,*%other) is raw { nqp::iscont(key) ?? SLICE_ONE_HASH( SELF, key, 'exists', $exists, %other ) !! SLICE_MORE_HASH( SELF, key, 'exists', $exists, %other ) } multi sub postcircumfix:<{ }>(\SELF, Iterable \key, Bool() :$kv!, *%other) is raw { nqp::iscont(key) ?? SLICE_ONE_HASH( SELF, key, 'kv', $kv, %other ) !! SLICE_MORE_HASH( SELF, key, 'kv', $kv, %other ) } multi sub postcircumfix:<{ }>(\SELF, Iterable \key, Bool() :$p!, *%other) is raw { nqp::iscont(key) ?? SLICE_ONE_HASH( SELF, key, 'p', $p, %other ) !! SLICE_MORE_HASH( SELF, key, 'p', $p, %other ) } multi sub postcircumfix:<{ }>(\SELF, Iterable \key, Bool() :$k!, *%other) is raw { nqp::iscont(key) ?? SLICE_ONE_HASH( SELF, key, 'k', $k, %other ) !! SLICE_MORE_HASH( SELF, key, 'k', $k, %other ) } multi sub postcircumfix:<{ }>(\SELF, Iterable \key, Bool() :$v!, *%other) is raw { nqp::iscont(key) ?? SLICE_ONE_HASH( SELF, key, 'v', $v, %other ) !! SLICE_MORE_HASH( SELF, key, 'v', $v, %other ) } # %h{*} multi sub postcircumfix:<{ }>( \SELF, Whatever ) is raw { SELF{SELF.keys.list}; } multi sub postcircumfix:<{ }>(\SELF, Whatever, Mu \ASSIGN) is raw { die "Cannot assign to *, as the order of keys is non-deterministic"; } multi sub postcircumfix:<{ }>(\SELF, Whatever, :$BIND!) is raw { X::Bind::Slice.new(type => SELF.WHAT).throw; } multi sub postcircumfix:<{ }>(\SELF, Whatever, Bool() :$delete!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'delete', $delete, %other ); } multi sub postcircumfix:<{ }>(\SELF, Whatever, Bool() :$exists!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'exists', $exists, %other ); } multi sub postcircumfix:<{ }>(\SELF, Whatever, Bool() :$kv!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'kv', $kv, %other ); } multi sub postcircumfix:<{ }>(\SELF, Whatever, Bool() :$p!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'p', $p, %other ); } multi sub postcircumfix:<{ }>(\SELF, Whatever, Bool() :$k!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'k', $k, %other ); } multi sub postcircumfix:<{ }>(\SELF, Whatever, Bool() :$p!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'p', $p, %other ); } multi sub postcircumfix:<{ }>(\SELF, Whatever, Bool() :$v!, *%other) is raw { nqp::elems(nqp::getattr(%other,Map,'$!storage')) ?? SLICE_MORE_HASH( SELF, SELF.keys.list, 'v', $v, %other ) !! SELF{SELF.keys.list}; } # %h{} multi sub postcircumfix:<{ }>(\SELF, :$BIND!) is raw { X::Bind::ZenSlice.new(type => SELF.WHAT).throw; } multi sub postcircumfix:<{ }>(\SELF, Bool() :$delete!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'delete', $delete, %other ); } multi sub postcircumfix:<{ }>(\SELF, Bool() :$exists!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'exists', $exists, %other ); } multi sub postcircumfix:<{ }>(\SELF, Bool() :$kv!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'kv', $kv, %other ); } multi sub postcircumfix:<{ }>(\SELF, Bool() :$p!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'p', $p, %other ); } multi sub postcircumfix:<{ }>(\SELF, Bool() :$k!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'k', $k, %other ); } multi sub postcircumfix:<{ }>(\SELF, Bool() :$p!, *%other) is raw { SLICE_MORE_HASH( SELF, SELF.keys.list, 'p', $p, %other ); } multi sub postcircumfix:<{ }>(\SELF, Bool() :$v!, *%other) is raw { nqp::elems(nqp::getattr(%other,Map,'$!storage')) ?? SLICE_MORE_HASH( SELF, SELF.keys.list, 'v', $v, %other ) !! SELF{SELF.keys.list}; } multi sub postcircumfix:<{ }>(Mu \SELF, *%other ) is raw { nqp::elems(nqp::getattr(%other,Map,'$!storage')) ?? SELF.ZEN-KEY(|%other) !! nqp::decont(SELF) } #line 1 SETTING::src/core.c/hash_multislice.rakumod # all sub postcircumfix {;} candidates here please proto sub postcircumfix:<{; }>($, $, *%) is nodal {*} multi sub postcircumfix:<{; }>(\SELF, @indices) { my \target = nqp::create(IterationBuffer); my int $dims = @indices.elems; # reifies my $indices := nqp::getattr(@indices,List,'$!reified'); sub MD-HASH-SLICE-ONE-POSITION(\SELF, \idx, int $dim --> Nil) { my int $next-dim = $dim + 1; if nqp::istype(idx, Iterable) && nqp::not_i(nqp::iscont(idx)) { MD-HASH-SLICE-ONE-POSITION(SELF, $_, $dim) for idx; } elsif $next-dim < $dims { if nqp::istype(idx,Whatever) { MD-HASH-SLICE-ONE-POSITION(SELF.AT-KEY($_), nqp::atpos($indices,$next-dim), $next-dim) for SELF.keys; } else { MD-HASH-SLICE-ONE-POSITION(SELF.AT-KEY(idx), nqp::atpos($indices,$next-dim), $next-dim); } } # $next-dim == $dims elsif nqp::istype(idx,Whatever) { nqp::push(target, SELF.AT-KEY($_)) for SELF.keys; } else { nqp::push(target, SELF.AT-KEY(idx)); } } MD-HASH-SLICE-ONE-POSITION(SELF, nqp::atpos($indices,0), 0); target.List } multi sub postcircumfix:<{; }>(\SELF, @indices, :$exists!) { sub recurse-at-key(\SELF, \indices) { my \idx := indices[0]; my \exists := SELF.EXISTS-KEY(idx); nqp::if( nqp::istype(idx, Iterable), idx.map({ |recurse-at-key(SELF, ($_, |indices.skip.cache)) }).List, nqp::if( nqp::iseq_I(indices.elems, 1), exists, nqp::if( exists, recurse-at-key(SELF{idx}, indices.skip.cache), nqp::stmts( (my \times := indices.map({ .elems }).reduce(&[*])), nqp::if( nqp::iseq_I(times, 1), False, (False xx times).List ) ).head ) ) ); } recurse-at-key(SELF, @indices) } #line 1 SETTING::src/core.c/Scalar.rakumod my class Scalar { # declared in BOOTSTRAP # class Scalar is Any # has Mu $!descriptor; # has Mu $!value; method new(|) { X::Cannot::New.new(class => self.WHAT).throw } multi method WHICH(Scalar:D \SELF: --> ObjAt:D) { nqp::box_s( nqp::concat( nqp::concat(nqp::unbox_s(SELF.^name), '|'), nqp::tostr_I(nqp::objectid(SELF)) ), ObjAt ) } method name() { my $d := $!descriptor; nqp::isnull($d) ?? Nil !! $d.name() } proto method of() {*} multi method of(Scalar:U:) { Mu } multi method of(Scalar:D:) { nqp::isnull($!descriptor) ?? Mu !! $!descriptor.of } method default() { my $d := $!descriptor; nqp::isnull($d) ?? Any !! $d.default; } method dynamic() { my $d := $!descriptor; nqp::isnull($d) ?? False !! nqp::hllbool($d.dynamic); } } # Also compose native reference classes declared in BOOTSTRAP. my class IntLexRef { } my class UIntLexRef { } my class NumLexRef { } my class StrLexRef { } my class IntAttrRef { } my class UIntAttrRef { } my class NumAttrRef { } my class StrAttrRef { } my class IntPosRef { } my class UIntPosRef { } my class NumPosRef { } my class StrPosRef { } #line 1 SETTING::src/core.c/Code.rakumod my class Code { # declared in BOOTSTRAP # class Code is Any does Callable # has Code $!do; # Low level code object # has Signature $!signature; # Signature object # has @!compstuff; # Place for the compiler to hang stuff multi method ACCEPTS(Code:D $self: Mu $topic is raw) { nqp::getattr($!signature,Signature,'$!count') ?? $self($topic) !! $self() } method is-implementation-detail(--> False) { } method precedence(Code:D: --> "") { } method associative(Code:D: --> "") { } method thunky(Code:D: --> "") { } method iffy(Code:D: --> 0 ) { } # runtime lookup because of bootstrap issues method reducer() { ::('&METAOP_REDUCE_LEFT') } method bytecode-size() { nqp::dispatch('boot-syscall', 'code-bytecode-size', $!do) } proto method POSITIONS(|) {*} # is implementation-detail method arity(Code:D:) { nqp::getattr_i($!signature,Signature,'$!arity') } method count(Code:D:) { nqp::getattr($!signature,Signature,'$!count') } method signature(Code:D:) { $!signature } method cando(Capture:D $c) { $!signature.ACCEPTS($c) ?? (self,) !! () } proto method prec(|) {*} multi method prec() { my % } multi method prec(Str:D $) { '' } multi method Str(Code:D:) { warn( self.WHAT.raku ~ " object coerced to string (please use .gist or .raku to do that)"); self.name } method outer(Code:D:) { nqp::ifnull(nqp::getcodeobj(nqp::p6staticouter($!do)), Mu) } # returns an identifier for this code object # that is the same even for cloned closures method static_id(Code:D:) { nqp::p6box_i(nqp::where(nqp::getstaticcode($!do))); } multi method new(Code:) { X::Cannot::New.new(class => self).throw } method file(Code:D:) { nqp::getcodelocation($!do); } method line(Code:D:) { nqp::getcodelocation($!do); } method assuming(Code:D $self: |primers) { my $sig = nqp::getattr(nqp::decont($self), Code, '$!signature'); # A ::() that does not throw. Also does not need to deal # with chunks or sigils. my sub soft_indirect_name_lookup($name) { my @subtypes = ($name ~~ /^ (.*?) [ \( (.*) \) ]? $/).list; for @subtypes -> $subtype { my @parts = $subtype.split('::'); my Mu $thing := ::.EXISTS-KEY(@parts[0]); return False unless $thing; $thing := ::.AT-KEY(@parts.shift); for @parts { return False unless $thing.WHO.EXISTS-KEY($_); $thing := $thing.WHO{$_}; } } True; } # sub strip-parm # This is mostly a stripped-down version of Parameter.raku, removing # where clauses, turning "= { ... }" from defaults into just # "?", removing type captures, subsignatures, and undeclared types # (e.g. types set to or parameterized by captured types.) my sub strip_parm (Parameter:D $parm, :$make_optional = False) { my $type := $parm.type; my $type_coercive := $type.^archetypes.coercive; my @types = $type_coercive ?? ($type.^target_type.^name, $type.^constraint_type.^name) !! $type.^name; my @raku_names; my $raku; my $rest = ''; my $sigil = $parm.sigil; for @types -> $type_name is copy { my $out_name = $type_name; my $elide_agg_cont = so ($sigil eqv '@' or $sigil eqv '%' or $type_name ~~ /^^ Callable >> /); $out_name = '' if $elide_agg_cont; unless $type_name eq "Any" { my int $FIRST = 1; # broken FIRST workaround while $type_name ~~ / (.*?) \[ (.*?) \] $$/ { # FIRST { # seems broken in setting if $FIRST { # broken FIRST workaround $out_name = $elide_agg_cont ?? ~$1 !! ~$/; $FIRST = 0; } $type_name = ~$1; unless soft_indirect_name_lookup(~$0) { $out_name = ''; last }; } $out_name = '' unless soft_indirect_name_lookup($type_name); } @raku_names.push: $out_name; } $raku = @raku_names[0]; if $type_coercive { $raku ~= "(" ~ @raku_names[1] ~ ")"; } $raku ~= $parm.modifier if $raku ne ''; my $name = $parm.name; if !$name and $parm.raw { $name = '$'; } elsif !$name or !$name.starts-with($sigil) { $name = $sigil ~ $parm.twigil ~ ($name // ''); } if $parm.slurpy { $name = '*' ~ $name; } elsif $parm.named { my @names := $parm.named_names; $name = ':' ~ $_ ~ '(' ~ $name ~ ')'for @names; $name ~= '!' unless ($parm.optional or $make_optional); $name ~= '?' if ($make_optional); } elsif $parm.optional or $parm.default { $name ~= '?'; } if $parm.rw { $rest ~= ' is rw'; } elsif $parm.copy { $rest ~= ' is copy'; } if $parm.raw { $rest ~= ' is raw' unless $name.starts-with('\\'); } if $name or $rest { $raku ~= ($raku ?? ' ' !! '') ~ $name; } $raku ~ $rest; } # If we have only one parameter and it is a capture with a # subsignature, we might as will jump down into it. while +$sig.params == 1 and $sig.params[0].capture and $sig.params[0].sub_signature { $sig = $sig.params[0].sub_signature; } my @plist; # Positionals in the returned closure's signature my @clist; # The positional args used to call the original code my @tlist; # Positional params to verify binding primers against my @alist; # Primers as positional arguments after processing # Find a name safe to use across slurpies, captures and sigilless my $safename = '_'; $safename ~= '_' while $sig.params.first: { $_.name.defined and $_.name eq $safename and ($_.slurpy or $_.sigil eq '\\' or $_.sigil eq '|') }; my $capwrap = $safename ~ '_'; $capwrap ~= '_' while $sig.params.first: { $_.name.defined and $_.name eq $capwrap and ($_.slurpy or $_.sigil eq '\\' or $_.sigil eq '|') }; # Look for slurpies and captures my $slurp_p = $sig.params.first: {.slurpy and .sigil eq '@'}; my $slurp_n = $sig.params.first: {.slurpy and .sigil eq '%'}; $slurp_p //= (); $slurp_n //= (); # This gets sticky. A bare capture will take anything # you throw at it. A capture with a subsignature, not always. # Both will raise Signature.count to Inf, unfortunately, # and neither counts towards Signature.arity. That might # eventually change as it is LTA. # # We have no real use for any captures defined in the original # signature, but if there is one, we must emulate its slurpylike # effects. We cannot tell if it actually has slurpylike # effects without looking at subsignatures, recursively, # but really Signature should be able to tell us that. # # Until then, we will add slurpy behaviors, assuming we # do not already have them, if we see a capture. my $need_cap = ($sig.count == Inf and not ($slurp_p and $slurp_n)); if $need_cap { $need_cap = False; for $sig.params.grep({.capture}) { $need_cap = True; last; } } # For now this is how we fabricate parameters. my &safeparms = EVAL sprintf('sub (|%s) { }', $safename); if ($need_cap) { $slurp_p ||= &safeparms.signature.params[0]; $slurp_n ||= &safeparms.signature.params[0]; } # Normal Positionals my Int $idx = -1; for $sig.params.grep({.positional}) -> $parm { ++$idx; unless $idx < primers.list.elems { @plist.push($parm); @clist.push($capwrap ~ '[' ~ @plist.end ~ ']'); next; } given primers.list[$idx] { when Whatever { @plist.push($parm); @clist.push($capwrap ~ '[' ~ @plist.end ~ ']'); } when Nil { @alist.push($parm.type); @clist.push($parm.type.^name); @tlist.push($parm); } default { @alist.push($_); @clist.push("primers.list[$idx]"); @tlist.push($parm); } } } my $widx = @plist.end; @tlist.push($slurp_p) if $slurp_p; @plist.push($slurp_p) if $slurp_p and not $slurp_p.capture; ++$idx; my $cidx = 0; # Even if we prime above the arity, do it anyway, for errors. while $idx < primers.list.elems { given primers.list[$idx] { when Whatever { @clist.push($capwrap ~ '[' ~ ++$widx ~ ']'); } when Nil { my $t = "Any"; if $slurp_p { unless $slurp_p.capture { $t = $slurp_p.type.of.^name } } @alist.push($t); @clist.push($t); } default { @alist.push($_); @clist.push("primers.list[$idx]"); } } ++$idx; } if $slurp_p { @clist.push('|' ~ $capwrap ~ '[' ~ ++$widx ~ '..*-1]' ); # If it is a true slurpy we already pushed it to $plist $slurp_p = () unless $slurp_p.capture; } # Normal Nameds. # I noted this: # raku -e 'sub a (*%A, :$a?, *%B) { %A.say; %B.say }; a(:a(1));' # {:a(1)}<> # {}<> # I am going to treat that as a feature and preserve the behavior. # So we will care for ordering of the named parameters in the # user-facing signature as well, for introspection purposes. my %ahash = primers.hash; my @phash = $sig.params.grep: {.named}; my @thash = $sig.params.grep: { .named and ( .slurpy or any(%ahash.keys) eq any(.named_names.list) ) } @phash .= map: { my @names = .named_names.list; my $p = strip_parm($_); if not .optional and any(%ahash.keys) eq any(@names) { # Make mandatory parameters optional once they have # been supplied at least once. $p = strip_parm($_, :make_optional); } $p; } if ($slurp_n and $slurp_n.capture and !($slurp_n === $slurp_p)) { @phash.push(strip_parm($slurp_n)); } my $error = False; EVAL(sprintf('anon sub trybind (%s) { }(|@alist, |%%ahash);', (flat @tlist.map(&strip_parm), @thash.map(&strip_parm)).join(", ")) ); my $f; my $primed_sig = (flat @plist.map(&strip_parm), @phash, ($slurp_p ?? strip_parm($slurp_p) !! ())).join(", "); $primed_sig ~= ' --> ' ~ $sig.returns.^name; $f = EVAL sprintf( '{ my $res = (my proto __PRIMED_ANON (%s) { {*} }); my multi __PRIMED_ANON (|%s(%s)) { my %%chash := %s.hash; $self(%s%s |{ %%ahash, %%chash }); # |{} workaround https://github.com/Raku/old-issue-tracker/issues/2157 }; $res }()', $primed_sig, $capwrap, $primed_sig, $capwrap, (flat @clist).join(", "), (@clist ?? ',' !! '') ); $error ~~ Exception ?? $f but $error.Failure !! $f; } } #line 1 SETTING::src/core.c/WhateverCode.rakumod my class WhateverCode is Code { has Str $!original-expression; # helper method for array slicing multi method POSITIONS(WhateverCode:D: Failure:D \failure) { failure } multi method POSITIONS(WhateverCode:D $self: \list) { nqp::isconcrete(list) ?? nqp::iseq_i( (my \count := nqp::getattr( nqp::getattr($self,Code,'$!signature'), Signature, '$!count' )), 1 ) ?? $self(list.elems) !! $self(|(list.elems xx count)) !! $self(0) } multi method ACCEPTS(WhateverCode:D: \value) is raw { nqp::call(nqp::getattr(self,Code,'$!do'),value) } method has-phasers(--> False) { } method has-loop-phasers(--> False) { } multi method raku(WhateverCode:D:) { $!original-expression // "WhateverCode.new" } } #line 1 SETTING::src/core.c/Block.rakumod my class Block { # declared in BOOTSTRAP # class Block is Code # has Mu $!phasers; # has Mu $!why; proto method of() {*} multi method of(Block:U:) { Mu } multi method of(Block:D:) { nqp::getattr(self,Code,'$!signature').returns } method returns(Block:D:) { nqp::getattr(self,Code,'$!signature').returns } # These methods cannot be private methods as private method dispatch is # not working yet this early in the setting. method unshift-phaser(str $name, &block --> Nil) is implementation-detail { nqp::unshift( nqp::ifnull( nqp::atkey($!phasers,$name), nqp::bindkey($!phasers,$name,nqp::create(IterationBuffer)) ), &block ) } method push-phaser(str $name, &block --> Nil) is implementation-detail { nqp::push( nqp::ifnull( nqp::atkey($!phasers,$name), nqp::bindkey($!phasers,$name,nqp::create(IterationBuffer)) ), &block ) } method fatalize() is implementation-detail { self.add_phaser: 'POST', -> $_ { nqp::istype($_,Failure) ?? .throw !! True } } method add_phaser(str $name, &block --> Nil) { # $!phasers is either a Block (which indicates the fast path for # handling an only LEAVE phaser), or a hash (indicating one or more # other phasers) or not concrete (no phasers at all). # adding another phaser after a lone LEAVE phaser? if nqp::isconcrete($!phasers) && nqp::not_i(nqp::ishash($!phasers)) { my &leave := $!phasers; $!phasers := nqp::hash; self.unshift-phaser('!LEAVE-ORDER', &leave); self.unshift-phaser('LEAVE', &leave); } # NOTE: nqp::iseq_s is needed as it is too early in the setting to # have eq work on native strings if nqp::iseq_s($name,'LEAVE') { if nqp::isconcrete($!phasers) { self.unshift-phaser('!LEAVE-ORDER', &block); # slow leaving self.unshift-phaser($name, &block); # introspection } else { $!phasers := █ # fast path for an only LEAVE phaser } } else { $!phasers := nqp::hash unless nqp::isconcrete($!phasers); if nqp::iseq_s($name,'KEEP') || nqp::iseq_s($name,'UNDO') { nqp::unshift( nqp::ifnull( nqp::atkey($!phasers,'!LEAVE-ORDER'), nqp::bindkey( $!phasers,'!LEAVE-ORDER',nqp::create(IterationBuffer)) ), nqp::list($name,&block) ); self.unshift-phaser($name, &block); } else { nqp::iseq_s($name,'NEXT') || nqp::iseq_s($name,'POST') ?? self.unshift-phaser($name, &block) !! self.push-phaser($name, &block); } } } # Return a Callable to run any phasers for the given name on this # Block. Returns Nil if there are no phasers, the only phaser if # there only is one, or a Callable that will call all of the phasers. method callable_for_phaser(str $name) { nqp::ishash($!phasers) && (my \blocks := nqp::atkey($!phasers,$name)) ?? nqp::iseq_i(nqp::elems(blocks),1) ?? nqp::atpos(blocks,0) !! { my int $i = -1; nqp::while( ++$i < nqp::elems(blocks), nqp::atpos(blocks,$i)(), :nohandler ); } !! nqp::isconcrete($!phasers) && nqp::iseq_s($name,'LEAVE') ?? $!phasers # lone LEAVE phaser !! Nil } method fire_if_phasers(str $name --> Nil) { if nqp::isconcrete($!phasers) { if nqp::ishash($!phasers) && nqp::atkey($!phasers,$name) -> \blocks { my int $i = -1; nqp::while( ++$i < nqp::elems(blocks), nqp::atpos(blocks,$i)(), :nohandler ); } elsif nqp::iseq_s($name,'LEAVE') { $!phasers(); # lone LEAVE phaser } } } method fire_phasers(str $name --> Nil) { if nqp::ishash($!phasers) { if nqp::atkey($!phasers,$name) -> \blocks { my int $i = -1; nqp::while( ++$i < nqp::elems(blocks), nqp::atpos(blocks,$i)(), :nohandler ); } } elsif nqp::isconcrete($!phasers) && nqp::iseq_s($name,'LEAVE') { $!phasers(); } } method has-phasers() { nqp::hllbool(nqp::isconcrete($!phasers)) } method has-loop-phasers() { nqp::hllbool( nqp::ishash($!phasers) && ( nqp::existskey($!phasers,'NEXT') || nqp::existskey($!phasers,'LAST') || nqp::existskey($!phasers,'FIRST') ) ) } method has-phaser(str $name) { nqp::hllbool( (nqp::ishash($!phasers) && nqp::existskey($!phasers,$name)) || (nqp::iseq_s($name,'LEAVE') && nqp::isconcrete($!phasers)) ) } method phasers(str $name) { nqp::ishash($!phasers) ?? nqp::existskey($!phasers,$name) ?? nqp::atkey($!phasers,$name).List !! () !! nqp::iseq_s($name,'LEAVE') && nqp::isconcrete($!phasers) ?? ($!phasers,) !! () } multi method raku(Block:D:) { "-> {self.signature.raku.substr(2,*-1)} \{ #`({self.WHICH}) ... \}" } method WHY() { if nqp::isnull($!why) { nextsame unless $*COMPILING_CORE_SETTING; } else { $!why.set_docee(self); $!why } } method set_why($why --> Nil) { $!why := $why; } # helper method for array slicing multi method POSITIONS(Block:D: Failure:D \failure) { failure } multi method POSITIONS(Block:D $self: \list) { nqp::isconcrete(list) ?? (nqp::istype( (my \count := nqp::getattr( nqp::getattr($self,Code,'$!signature'),Signature,'$!count' )), Num ) && nqp::isnanorinf(count) ) || nqp::iseq_i(count,1) ?? $self(list.elems) !! $self(|(list.elems xx count)) !! $self(0) } } #line 1 SETTING::src/core.c/Variable.rakumod # for our tantrums my class X::Comp::NYI { ... }; my class X::Comp::Trait::Unknown { ... }; my class X::Comp::Trait::NotOnNative { ... }; my class X::Comp::Trait::Scope { ... }; # Variable traits come here, not in traits.rakumod, since we declare Variable # in the setting rather than BOOTSTRAP. my class Variable { has str $.name; has str $.scope; has $.var is rw; has $.block; has $.slash; has $.implicit-lexical-usage is rw; # make throwing easier submethod throw ( $ex_type, |c ) { $*W ?? $*W.throw( self.slash, $ex_type, |c ) !! ::($ex_type).new(|c).throw } submethod willdo(&block) { my str $name = self.name; -> { my $ctx := nqp::ctxcaller(nqp::ctx); nqp::until( nqp::existskey($ctx,$name), $ctx := nqp::ctxcaller($ctx) ); block(nqp::atkey($ctx,$name)) } } submethod native(Mu $what) { my $name := $what.raku; $name.starts-with('array') || $name eq 'Mu' ?? $name !! $name.ends-with('LexRef') ?? $name.substr(0,3).lc !! ''; } } # "is" traits multi sub trait_mod:(Variable:D $v, |c ) { $v.throw( 'X::Comp::Trait::Unknown', type => 'is', subtype => c.hash.keys[0], declaring => ' variable', expected => , ); } multi sub trait_mod:(Variable:D $v, Mu :$default!) { my $var := $v.var; my $what := $var.VAR.WHAT; my $descriptor; { CATCH { my $native = $v.native($what); $native ?? nqp::istype($default,Whatever) ?? $v.throw('X::Comp::NYI', :feature("is default(*) on native $native")) !! $v.throw( 'X::Comp::Trait::NotOnNative', :type, :subtype, :native($native eq 'Mu' ?? ''!! $native )) # yuck !! $v.throw('X::Comp::NYI', :feature("is default on shaped $what.raku()")) } $descriptor := nqp::getattr($var, $what.^mixin_base, '$!descriptor'); } my $of := $descriptor.of; $v.throw( 'X::Parameter::Default::TypeCheck', :expected($var.WHAT), :what, :got(nqp::eqaddr($default,Nil) ?? 'Nil' !! $default) ) unless nqp::istype($default, $of) or nqp::eqaddr($default,Nil) or nqp::eqaddr($of,Mu); $descriptor.set_default(nqp::decont($default)); # make sure we start with the default if a scalar $var = $default if nqp::istype($what, Scalar); } multi sub trait_mod:(Variable:D $v, :$dynamic!) { my $var := $v.var; my $what := $var.VAR.WHAT; { CATCH { my $native = $v.native($what); $native ?? $v.throw( 'X::Comp::Trait::NotOnNative', :type, :subtype, :native($native eq 'Mu' ?? ''!! $native )) # yuck !! $v.throw('X::Comp::NYI', :feature("is dynamic on shaped $what.raku()")) } nqp::getattr($var,$what.^mixin_base,'$!descriptor').set_dynamic($dynamic); } } multi sub trait_mod:(Variable:D $v, :$export!) { if $v.scope ne 'our' { $v.throw( 'X::Comp::Trait::Scope', type => 'is', subtype => 'export', declaring => 'variable', scope => $v.scope, supported => ['our'], ); } my $var := $v.var; my @tags = flat 'ALL', (nqp::istype($export,Pair) ?? $export.key() !! nqp::istype($export,Positional) ?? @($export)>>.key !! 'DEFAULT'); Rakudo::Internals.EXPORT_SYMBOL($var.VAR.name, @tags, $var); } # does trait multi sub trait_mod:(Variable:D $v, Mu:U $role) { if $role.HOW.archetypes.composable() { $v.var.VAR does $role; } elsif $role.HOW.archetypes.composalizable() { $v.var.VAR does $role.HOW.composalize($role); } else { X::Composition::NotComposable.new( target-name => 'a variable', composer => $role, ).throw; } } # phaser traits multi sub trait_mod:(Variable:D $v, $block, |c ) { $v.throw( 'X::Comp::Trait::Unknown', type => 'will', subtype => c.hash.keys[0], declaring => ' variable', expected => ('begin check final init end', 'enter leave keep undo', 'first next last pre post', 'compose'), ); } multi sub trait_mod:(Variable:D $v, $block, :begin($)! ) { $block($v.var); # no need to delay execution } multi sub trait_mod:(Variable:D $v, $block, :check($)! ) { $*W.add_phaser($v.slash, 'CHECK', $block); } multi sub trait_mod:(Variable:D $v, $block, :final($)! ) { $v.throw( 'X::Comp::NYI', feature => "Variable trait 'will final {...}'", ); } multi sub trait_mod:(Variable:D $v, $block, :init($)! ) { $v.throw( 'X::Comp::NYI', feature => "Variable trait 'will init {...}'", ); } multi sub trait_mod:(Variable:D $v, $block, :end($)! ) { $*W.add_object($block); $*W.add_phaser($v.slash, 'END', $block); } multi sub trait_mod:(Variable:D $v, $block, :enter($)! ) { $v.block.add_phaser('ENTER', $v.willdo($block) ); $v.implicit-lexical-usage = True; } multi sub trait_mod:(Variable:D $v, $block, :leave($)! ) { $v.block.add_phaser('LEAVE', $v.willdo($block) ); $v.implicit-lexical-usage = True; } multi sub trait_mod:(Variable:D $v, $block, :keep($)! ) { $v.block.add_phaser('KEEP', $v.willdo($block)); $v.implicit-lexical-usage = True; } multi sub trait_mod:(Variable:D $v, $block, :undo($)! ) { $v.block.add_phaser('UNDO', $v.willdo($block)); $v.implicit-lexical-usage = True; } multi sub trait_mod:(Variable:D $v, $block, :first($)! ) { $v.block.add_phaser('FIRST', $v.willdo($block)); $v.implicit-lexical-usage = True; } multi sub trait_mod:(Variable:D $v, $block, :next($)! ) { $v.block.add_phaser('NEXT', $block); } multi sub trait_mod:(Variable:D $v, $block, :last($)! ) { $v.block.add_phaser('LAST', $block); } multi sub trait_mod:(Variable:D $v, $block, :pre($)! ) { $v.block.add_phaser('PRE', $v.willdo($block)); $v.implicit-lexical-usage = True; } multi sub trait_mod:(Variable:D $v, $block, :post($)! ) { $v.throw( 'X::Comp::NYI', feature => "Variable trait 'will post {...}'", ); } multi sub trait_mod:(Variable:D $v, $block, :compose($)! ) { $v.throw( 'X::Comp::NYI', feature => "Variable trait 'will compose {...}'", ); } #line 1 SETTING::src/core.c/Routine.rakumod my class X::Routine::Unwrap { ... } my role HardRoutine { method soft(--> False) { } } my role SoftRoutine { method soft(--> True) { } } my class Routine { # declared in BOOTSTRAP # class Routine is Block # has @!dispatchees; # has Mu $!dispatcher; # has int $!flags; # has Mu $!inline_info; # has Mu $!package; # has @!dispatch_order; # has Mu $!dispatch_cache; # has Mu $!op_props; method candidates(Bool :$local = True, Bool() :$with-proto) { $local ?? (self.is_dispatcher ?? nqp::hllize(@!dispatchees) !! (self,)) !! Seq.new(self.iterator(:candidates, :!local, :$with-proto)) } proto method cando(|) {*} multi method cando(Capture:D $c) { my $disp; if self.is_dispatcher { $disp := self; } else { $disp := nqp::create(self); nqp::bindattr($disp, Routine, '@!dispatchees', nqp::list(self)); } # Call this lexical sub to get rid of 'self' in the signature. sub checker(|) { nqp::hllize($disp.find_best_dispatchee(nqp::usecapture(), 1)) } checker(|$c); } multi method cando(|c) { self.cando(c) } method multi() { self.dispatcher.defined } multi method gist(Routine:D:) { (my $name := self.name) ?? "&$name" !! (self.^name ~~ m/^\w+/).lc ~ ' { }' } multi method raku(Routine:D:) { my $raku = ( self.^name ~~ m/^\w+/ ).lc; if self.is_dispatcher { $raku = "proto $raku"; } elsif self.multi { $raku = "multi $raku"; } if self.name() -> $n { $raku ~= " $n"; } my $sig := self.signature.raku; $raku ~= " $sig.substr(1)" unless $sig eq ':()'; $raku ~= self.onlystar ?? ' {*}' !! self.yada ?? ' { ... }' !! ' { #`(' ~ self.WHICH ~ ') ... }'; $raku } method soft(--> Nil) { } method is-wrapped(--> False) { } my role Wrapped { has Mu $!wrappers; has Routine $!wrapper-type; method WRAPPERS() { $!wrappers } method WRAPPER-TYPE() { $!wrapper-type } method ADD-WRAPPER(&wrapper --> Nil) { my $new-wrappers := nqp::isconcrete($!wrappers) ?? nqp::clone($!wrappers) !! IterationBuffer.new; nqp::unshift($new-wrappers, &wrapper); $!wrappers := $new-wrappers; } method REMOVE-WRAPPER(&wrapper --> Bool) { my $new-wrappers := IterationBuffer.new; my int $i = 0; my Bool $found := False; while $i < nqp::elems($!wrappers) { my $wrapper := nqp::atpos($!wrappers, $i); if nqp::eqaddr(&wrapper, $wrapper) { $found := True; } else { nqp::push($new-wrappers, $wrapper); } $i++; } $!wrappers := $new-wrappers if $found; $found } method is-wrapped(--> Bool) { nqp::elems($!wrappers) > 1 } } my class WrapHandle { has &!routine; has $!wrapper; method restore(--> Bool) { nqp::can(&!routine, 'REMOVE-WRAPPER') ?? &!routine.REMOVE-WRAPPER($!wrapper) !! False } } method wrap(&wrapper) { # We can't wrap a hardened routine (that is, one that's been # marked inlinable). if nqp::istype(self, HardRoutine) { die "Cannot wrap a HardRoutine, since it may have been inlined; " ~ "use the 'soft' pragma to avoid marking routines as hard."; } # Mix in the Wrapped role if needed and add the wrapper. unless nqp::istype(self, Wrapped) { my $orig := self.clone; self does Wrapped; nqp::bindattr(self, self.WHAT, '$!wrapper-type', self.WHAT); self.ADD-WRAPPER($orig); } self.ADD-WRAPPER(&wrapper); # Create and return a wrap handle my $handle := nqp::create(WrapHandle); nqp::bindattr($handle, WrapHandle, '&!routine', self); nqp::bindattr($handle, WrapHandle, '$!wrapper', &wrapper); $handle } method unwrap($handle) { $handle.can('restore') && $handle.restore() || X::Routine::Unwrap.new.throw } method package() { $!package } method leave(*@) { NYI("{self.^name}.leave()").throw; } my class CandidateIterator does Iterator { has $!routine; has Mu $!candidates; has int $!pos; has Mu $!backlog; has $!local; has $!with-proto; method !SET-SELF($routine, $!local, $!with-proto) { self!SET-FROM-CANDIDATE($routine); $!backlog := nqp::list(); self } method new(Routine:D $routine, $local, $with-proto) { nqp::create(self)!SET-SELF($routine, $local, $with-proto) } method !SET-FROM-CANDIDATE($routine) { $!routine := nqp::decont($routine); $!pos = 0; my $candidates; if nqp::istype($!routine, Routine) { if $!routine.?is-wrapped { $candidates := $!routine.WRAPPERS; } elsif $!routine.?is_dispatcher { $candidates := nqp::getattr($!routine, Routine, '@!dispatchees'); $!pos = -1 if $!with-proto; } } if nqp::defined($candidates) { $!candidates := $candidates; } else { $!candidates := nqp::list($!routine); } } method pull-one() { my $cand := Nil; while nqp::eqaddr($cand, Nil) { while $!pos >= nqp::elems($!candidates) { return IterationEnd unless nqp::elems($!backlog); my $state := nqp::pop($!backlog); $!candidates := nqp::atpos($state, 0); $!pos = nqp::atpos($state, 1); $!routine := nqp::atpos($state, 2); } my $pos = $!pos; ++$!pos; if $pos == -1 { $cand := $!routine; } else { $cand := nqp::atpos($!candidates, $pos); } if !$!local && ( $cand.?is-wrapped || ($pos > -1 && $cand.?is_dispatcher) ) { nqp::push($!backlog, nqp::list($!candidates, nqp::unbox_i($!pos), $!routine)); self!SET-FROM-CANDIDATE($cand); $cand := Nil; } } $cand } method is-lazy(--> True) {} } method iterator(Bool :$candidates, Bool() :$local, Bool() :$with-proto) { return self.Mu::iterator unless $candidates && (self.is_dispatcher || self.is-wrapped); CandidateIterator.new(self, $local, $with-proto) } method IS-SETTING-ONLY(:$U, :$D, :$with-proto --> Bool:D) is implementation-detail { for self.candidates(:!local, :$with-proto) -> &cand { if $U || $D { next unless nqp::istype(&cand, Method) || nqp::istype(&cand, Submethod); my $invocant-type := &cand.signature.params[0].type; my $is-definite := $invocant-type.^archetypes.definite && $invocant-type.^definite; next unless ($U && !$is-definite) || ($D && $is-definite); } return False unless &cand.file.starts-with: 'SETTING::'; } True } #------------------------------------------------------------------------------- # The REST of this file can be REMOVED **AFTER** the Raku grammar has # become the grammar to build the setting with. XXX method prec(|c --> Hash:D) { ($!op_props // OperatorProperties).prec(|c) } method !proto() { $!dispatcher // self } # Return the OperatorProperties of the proto of the invocant method op_props(Routine:D: --> OperatorProperties) is implementation-detail { nqp::getattr(self!proto,Routine,'$!op_props') // OperatorProperties } method precedence(Routine:D: --> Str:D) { self.op_props.precedence } method associative(Routine:D: --> Str:D) { self.op_props.associative } method thunky(Routine:D: --> Str:D) { self.op_props.thunky } method iffy(Routine:D: --> Bool:D) { self.op_props.iffy.Bool } method reducer(Routine:D: --> Callable:D) { self.op_props.reducer } # Set operator properties, usually called through trait_mods method equiv(Routine:D: &op --> Nil) { nqp::bindattr(self!proto,Routine,'$!op_props', &op.op_props.equiv(self.associative) ) } method tighter(Routine:D: &op --> Nil) { nqp::bindattr(self!proto,Routine,'$!op_props', &op.op_props.tighter(self.associative) ) } method looser(Routine:D: &op --> Nil) { nqp::bindattr(self!proto,Routine,'$!op_props', &op.op_props.looser(self.associative) ) } method assoc(Routine:D: Str:D $associative --> Nil) { nqp::bindattr(self!proto,Routine,'$!op_props', self.op_props.new(:$associative)) } # Internal helper method to set operator properties method set_op_props(Routine:D:) is implementation-detail { (my str $type, my str $name) = self.name.split(":",2); $name = nqp::eqat($name,'<<',0) ?? nqp::substr($name,2,nqp::chars($name) - 4) !! nqp::substr($name,1,nqp::chars($name) - 2); nqp::bindattr(self,Routine,'$!op_props', OperatorProperties."$type"($name)) } # Helper method to apply a trait by name and given operator target string # using information of target operator of the same category method apply-operator-trait(Routine:D: Str:D $trait, Str:D $target --> Nil ) is implementation-detail { my str $name = self.name; my int $index = nqp::index($name,':'); die "Operator given to 'is $trait' does not appear to be an operator" if $index < 0; my $fqn := '&' ~ nqp::substr($name,0,$index) ~ ($target.contains('<') || $target.contains('>') ?? ":«$target»" !! ":<$target>" ); nqp::istype((my $op := ::($fqn)),Failure) ?? $op.throw !! self."$trait"($op) } } multi sub trait_mod:(Routine:D $r, :&equiv! --> Nil) { $r.equiv(&equiv) } multi sub trait_mod:(Routine:D $r, Str:D :$equiv! --> Nil) { $r.apply-operator-trait('equiv', $equiv) } multi sub trait_mod:(Routine:D $r, :&tighter! --> Nil) { $r.tighter(&tighter) } multi sub trait_mod:(Routine:D $r, Str:D :$tighter!) { $r.apply-operator-trait('tighter', $tighter) } multi sub trait_mod:(Routine:D $r, :&looser! --> Nil) { $r.looser(&looser) } multi sub trait_mod:(Routine:D $r, Str:D :$looser!) { $r.apply-operator-trait('looser', $looser) } multi sub trait_mod:(Routine:D $r, :$assoc! --> Nil) { # --> Nil $r.assoc($assoc) } # old interface, should probably be marked DEPRECATED multi sub trait_mod:(Routine:D $r, :%prec! --> Nil) { nqp::bindattr($r,Routine,'$!op_props', OperatorProperties.new-compat(|%prec) ) } #line 1 SETTING::src/core.c/Sub.rakumod augment class Sub { # declared in BOOTSTRAP, composed in prologue # class Sub is Routine } #line 1 SETTING::src/core.c/Operator.rakumod #------------------------------------------------------------------------------- # A subroutine that is also an operator my class Operator { # declared in BOOTSTRAP # class Operator is Sub # has Mu $!properties; # Old style prec hash, should probably be DEPRECATED method prec(Operator:D: |c --> Hash:D) { self.properties.prec(|c) } # Return the OperatorProperties of the proto of the invocant method properties(Operator:D: --> OperatorProperties) is implementation-detail { $!properties // OperatorProperties } method precedence(Operator:D: --> Str:D) { $!properties.precedence } method associative(Operator:D: --> Str:D) { $!properties.associative } method thunky(Operator:D: --> Str:D) { $!properties.thunky } method iffy(Operator:D: --> Bool:D) { $!properties.iffy.Bool } method reducer(Operator:D: --> Callable:D) { ::($!properties.reducer) } # Set operator properties, usually called through trait_mods method equiv(Operator:D: &op --> Nil) { nqp::bindattr(self.proto,Operator,'$!properties', &op.properties.equiv(self.associative) ) } method tighter(Operator:D: &op --> Nil) { nqp::bindattr(self.proto,Operator,'$!properties', &op.properties.tighter(self.associative) ) } method looser(Operator:D: &op --> Nil) { nqp::bindattr(self.proto,Operator,'$!properties', &op.properties.looser(self.associative) ) } method assoc(Operator:D: Str:D $associative --> Nil) { nqp::bindattr(self.proto,Operator,'$!properties', self.properties.new(:$associative)) } proto method set-properties(|) {*} multi method set-properties(Operator:D:) { (my str $type, my str $name) = self.name.split(":",2); $name = nqp::eqat($name,'<<',0) ?? nqp::substr($name,2,nqp::chars($name) - 4) !! nqp::substr($name,1,nqp::chars($name) - 2); nqp::bindattr(self,Operator,'$!properties', OperatorProperties."$type"($name)) } multi method set-properties(Operator:D: OperatorProperties:D $properties) { nqp::bindattr(self,Operator,'$!properties',$properties) } # Helper method to apply a trait by name and given operator target string # using information of target operator of the same category method apply-operator-trait(Operator:D: Str:D $trait, Str:D $target --> Nil ) is implementation-detail { my str $name = self.name; my int $index = nqp::index($name,':'); die "Operator given to 'is $trait' does not appear to be an operator" if $index < 0; my $fqn := '&' ~ nqp::substr($name,0,$index) ~ ($target.contains('<') || $target.contains('>') ?? ":«$target»" !! ":<$target>" ); nqp::istype((my $op := ::($fqn)),Failure) ?? $op.throw !! self."$trait"($op) } } multi sub trait_mod:(Operator:D $o, :&equiv! --> Nil) { $o.equiv(&equiv) } multi sub trait_mod:(Operator:D $o, Str:D :$equiv! --> Nil) { $o.apply-operator-trait('equiv', $equiv) } multi sub trait_mod:(Operator:D $o, :&tighter! --> Nil) { $o.tighter(&tighter) } multi sub trait_mod:(Operator:D $o, Str:D :$tighter! --> Nil) { $o.apply-operator-trait('tighter', $tighter) } multi sub trait_mod:(Operator:D $o, :&looser! --> Nil) { $o.looser(&looser) } multi sub trait_mod:(Operator:D $o, Str:D :$looser! --> Nil) { $o.apply-operator-trait('looser', $looser) } multi sub trait_mod:(Operator:D $o, :$assoc! --> Nil) { $o.assoc($assoc) } #line 1 SETTING::src/core.c/Macro.rakumod my class Macro is Routine { } #line 1 SETTING::src/core.c/Method.rakumod augment class Method { # declared in BOOTSTRAP, composed in prologue # class Method is Routine multi method gist(Method:D:) { self.name } } #line 1 SETTING::src/core.c/Submethod.rakumod augment class Submethod { # declared in BOOTSTRAP, composed in prologue # class Submethod is Routine multi method gist(Submethod:D:) { self.name } } #line 1 SETTING::src/core.c/Junction.rakumod my class Junction { # declared in BOOTSTRAP # class Junction is Mu # has Mu $!eigenstates; # elements of Junction # has str $!type; # type of Junction # Both of these are also accessed directly inside optimizer when # optimizing param typechecks with where clauses method !SET-SELF(str $type,\values) { if nqp::iseq_s($type,"any") || nqp::iseq_s($type,"all") || nqp::iseq_s($type,"none") || nqp::iseq_s($type,"one") { my \iterator := values.iterator; my \buffer := nqp::create(IterationBuffer); nqp::until( nqp::eqaddr((my \pulled := iterator.pull-one),IterationEnd), nqp::push(buffer,nqp::decont(pulled)) ); $!eigenstates := buffer; $!type = $type; self } else { "Junction can only have 'any', 'all', 'none', 'one' type".Failure } } # Swap 2 Junctions in place if they need to be for an infix operation # on the two Junctions. Returns a truthy (0|1)value if the Junctions # were of the same type and can be merged. method INFIX-TWO(Junction:U: Junction:D \a, Junction:D \b ) is implementation-detail { nqp::if( nqp::iseq_s( (my \atype := nqp::getattr(nqp::decont(a),Junction,'$!type')), (my \btype := nqp::getattr(nqp::decont(b),Junction,'$!type')) ), nqp::isne_s(atype,"one"), # same nqp::if( # not same (nqp::iseq_s(btype,"all") || nqp::iseq_s(btype,"none")) && (nqp::iseq_s(atype,"any") || nqp::iseq_s(atype,"one")), nqp::stmts( # need to be swapped nqp::bindattr( (my \ajunc := nqp::clone(nqp::decont(b))), Junction, '$!eigenstates', nqp::getattr(nqp::decont(a),Junction,'$!eigenstates') ), nqp::bindattr( (my \bjunc := nqp::clone(nqp::decont(a))), Junction, '$!eigenstates', nqp::getattr(nqp::decont(b),Junction,'$!eigenstates') ), (a = ajunc), (b = bjunc), 0 # not same, now swapped ) ) ) } proto method new(|) {*} multi method new(Junction: \values, Str :$type!) { nqp::create(Junction)!SET-SELF($type,values) } multi method new(Junction: Str:D \type, \values) { nqp::create(Junction)!SET-SELF(type,values) } method !defined-any() { my \eigenstates := $!eigenstates; my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems(eigenstates)) && nqp::isfalse(nqp::atpos(eigenstates,$i).defined), nqp::null ); nqp::hllbool(nqp::islt_i($i,nqp::elems(eigenstates))) } method !defined-all() { my \eigenstates := $!eigenstates; my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems(eigenstates)) && nqp::atpos(eigenstates,$i).defined, nqp::null ); nqp::hllbool(nqp::iseq_i($i,nqp::elems(eigenstates))) } method !defined-none() { my \eigenstates := $!eigenstates; my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems(eigenstates)) && nqp::isfalse(nqp::atpos(eigenstates,$i).defined), nqp::null ); nqp::hllbool(nqp::iseq_i($i,nqp::elems(eigenstates))) } method !defined-one() { my \eigenstates := $!eigenstates; my int $i = -1; my int $seen; nqp::while( nqp::islt_i(++$i,nqp::elems(eigenstates)) && nqp::isfalse(nqp::atpos(eigenstates,$i).defined) && nqp::not_i($seen++), nqp::null ); nqp::hllbool(nqp::iseq_i($seen,1)) } multi method defined(Junction:D: --> Bool:D) { nqp::iseq_s($!type,'any') ?? self!defined-any !! nqp::iseq_s($!type,'all') ?? self!defined-all !! nqp::iseq_s($!type,'none') ?? self!defined-none !! self!defined-one # nqp::iseq_s($!type,'one') } multi method Bool(Junction:D:) { nqp::hllbool( nqp::stmts( (my int $elems = nqp::elems($!eigenstates)), (my int $i), nqp::if( nqp::iseq_s($!type,'any'), nqp::stmts( nqp::while( nqp::islt_i($i,$elems) && nqp::isfalse(nqp::atpos($!eigenstates,$i)), ++$i ), nqp::islt_i($i,$elems) ), nqp::if( nqp::iseq_s($!type,'all'), nqp::stmts( nqp::while( nqp::islt_i($i,$elems) && nqp::atpos($!eigenstates,$i), ++$i ), nqp::iseq_i($i,$elems) ), nqp::if( nqp::iseq_s($!type,'none'), nqp::stmts( nqp::while( nqp::islt_i($i,$elems) && nqp::isfalse(nqp::atpos($!eigenstates,$i)), ++$i ), nqp::iseq_i($i,$elems) ), nqp::stmts( # $!type eq 'one' (my int $seen = 0), --$i, # increment in condition nqp::while( nqp::islt_i(++$i,$elems) && nqp::isle_i($seen,1), nqp::if( nqp::atpos($!eigenstates,$i), ++$seen ) ), nqp::iseq_i($seen,1) ) ) ) ) ) ) } multi method WHICH(Junction:D: --> ValueObjAt:D) { nqp::if( nqp::defined($!WHICH), $!WHICH, ($!WHICH := nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT, Junction), 'Junction|', nqp::concat(nqp::unbox_s(self.^name), '|') ), nqp::sha1(self.gist) ), ValueObjAt ))); } multi method ACCEPTS(Junction:U: Junction:D --> True) { } multi method ACCEPTS(Junction:D \SELF: Junction:D \topic) { topic.BOOLIFY-ACCEPTS(self) } multi method ACCEPTS(Junction:D: Mu \topic) { nqp::hllbool( nqp::stmts( (my int $elems = nqp::elems($!eigenstates)), (my int $i), nqp::if( nqp::iseq_s($!type,'any'), nqp::stmts( nqp::while( nqp::islt_i($i,$elems) && nqp::isfalse(nqp::atpos($!eigenstates,$i).ACCEPTS(topic)), ++$i ), nqp::islt_i($i,$elems) ), nqp::if( nqp::iseq_s($!type,'all'), nqp::stmts( nqp::while( nqp::islt_i($i,$elems) && nqp::atpos($!eigenstates,$i).ACCEPTS(topic), ++$i ), nqp::iseq_i($i,$elems) ), nqp::if( nqp::iseq_s($!type,'none'), nqp::stmts( nqp::while( nqp::islt_i($i,$elems) && nqp::isfalse( nqp::atpos($!eigenstates,$i).ACCEPTS(topic) ), ++$i ), nqp::iseq_i($i,$elems) ), nqp::stmts( # $!type eq 'one' (my int $seen), --$i, # increment in condition nqp::while( nqp::islt_i(++$i,$elems) && nqp::isle_i($seen,1), nqp::if( nqp::atpos($!eigenstates,$i).ACCEPTS(topic), ++$seen ) ), nqp::iseq_i($seen,1) ) ) ) ) ) ) } multi method Str (Junction:D:) { self.THREAD: *.Str } multi method Int (Junction:D:) { self.THREAD: *.Int } multi method Numeric (Junction:D:) { self.THREAD: *.Numeric } multi method Real (Junction:D:) { self.THREAD: *.Real } multi method iterator(Junction:D:) { # If we're asked for an iterator, we should really give one rather than # auto-thread over the `iterator` method. Otherwise we get decidedly # confusing outcomes from things that do `.iterator` and then expect it # to follow the iterator API. list(self).iterator } multi method gist(Junction:D:) { my int $elems = nqp::elems($!eigenstates); my int $i = -1; my $gists := nqp::setelems(nqp::list_s,$elems); nqp::bindpos_s($gists,$i,nqp::atpos($!eigenstates,$i).gist) while nqp::islt_i(++$i,$elems); $!type ~ '(' ~ nqp::join(', ',$gists) ~ ')' } multi method raku(Junction:D:) { my int $elems = nqp::elems($!eigenstates); my int $i = -1; my $rakus := nqp::setelems(nqp::list_s,$elems); nqp::bindpos_s($rakus,$i,nqp::atpos($!eigenstates,$i).raku) while nqp::islt_i(++$i,$elems); $!type ~ '(' ~ nqp::join(', ',$rakus) ~ ')' } multi method put() { self.THREAD: *.put } method CALL-ME(|c) { my \storage := nqp::getattr(self, Junction, '$!eigenstates'); my int $elems = nqp::elems(storage); my \result := nqp::setelems(nqp::list, $elems); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos(result, $i, nqp::atpos(storage, $i)(|c)) ); nqp::p6bindattrinvres( nqp::clone(self), Junction, '$!eigenstates', result) } method sink(Junction:D: --> Nil) { my int $elems = nqp::elems($!eigenstates); my int $i = -1; nqp::atpos($!eigenstates,$i).sink while nqp::islt_i(++$i,$elems); } # Helper method for handling those cases where auto-threading doesn't cut # it. Call the given Callable with each of the Junction values, and return # a Junction with the results of the calls. method THREAD(&call) is implementation-detail { my \storage := nqp::getattr(self,Junction,'$!eigenstates'); my int $i = -1; my int $elems = nqp::elems(storage); my \result := nqp::setelems(nqp::list,$elems); nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos(result,$i,call(nqp::atpos(storage,$i))) ); nqp::p6bindattrinvres(nqp::clone(self),Junction,'$!eigenstates',result) } method AUTOTHREAD(&call, |args) is implementation-detail { my \positionals := nqp::getattr(nqp::decont(args),Capture,'@!list'); sub thread_junction(int $pos) { my \junction := nqp::decont(nqp::atpos(positionals, $pos)); my \storage := nqp::getattr(junction,Junction,'$!eigenstates'); my int $elems = nqp::elems(storage); my \result := nqp::setelems(nqp::list,$elems); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), # Next line is Officially Naughty, since captures are # meant to be immutable. But hey, it's our capture to # be naughty with... nqp::stmts( nqp::bindpos(positionals,$pos,nqp::atpos(storage,$i)), nqp::bindpos(result,$i,call(|args)) ) ); nqp::p6bindattrinvres( nqp::clone(junction),Junction,'$!eigenstates',result) } # Look for a junctional arg in the positionals. # we have to autothread the first all or none junction before # doing any one or any junctions. my int $first_any_one = -1; my int $elems = nqp::elems(positionals); my int $i = -1; my @params := &call.signature.params; while nqp::islt_i(++$i,$elems) { # Junctional positional argument? my Mu $arg := nqp::atpos(positionals, $i); if nqp::istype($arg, Junction) and ( # No auto-threading for Mu or Junction parameters necessary not nqp::istype(Junction, @params[$i].type) # Can't handle protos yet because auto-generated protos # will report Mu as parameter type or &call.?is_dispatcher ) { my str $type = nqp::getattr_s(nqp::decont($arg),Junction,'$!type'); nqp::iseq_s($type,'any') || nqp::iseq_s($type,'one') ?? $first_any_one == -1 ?? ($first_any_one = $i) !! Nil !! return thread_junction($i); } } return thread_junction($first_any_one) if $first_any_one >= 0; # Otherwise, look for one in the nameds. my \nameds := nqp::getattr(nqp::decont(args), Capture, '%!hash'); my \iter := nqp::iterator(nameds); while iter { if nqp::istype(nqp::iterval(nqp::shift(iter)),Junction) { my \junction := nqp::decont(nqp::iterval(iter)); my \storage := nqp::getattr(junction,Junction,'$!eigenstates'); my int $elems = nqp::elems(storage); my \result := nqp::setelems(nqp::list,$elems); my int $i = -1; while nqp::islt_i(++$i,$elems) { # also naughty, like above nqp::bindkey(nameds, nqp::iterkey_s(iter),nqp::atpos(storage,$i)); nqp::bindpos(result,$i,call(|args)); } my \threaded := nqp::clone(nqp::decont(junction)); nqp::bindattr(threaded,Junction,'$!eigenstates',result); return threaded; } } # If we get here, wasn't actually anything to autothread. call(|args); } # BOOLIFY-ACCEPTS is kind of a reverse to ACCEPTS combined with short-circuitting THREAD. The idea is to optimize # smartmatches where a junction is on LHS and RHS ACCEPTS would auto-thread over it. In this case, instead of doing # RHS.ACCEPTS(LHS).Bool, which is what smartmatches are all about, we can reverse the invocation by doing # LHS.BOOLIFY-ACCEPTS(RHS). This wold invoke RHS.ACCEPTS.Bool only on particular eigenstates of LHS. Then, as soon # as the outcome of the whole smartmatch is known, the remaining N eigenstates can be skipped, sparing at least N*2 # method invocations. Note that this can only be used with classes using the default ACCEPTS method from the core as # only with it we can guarantee the default handling of junctions. proto method BOOLIFY-ACCEPTS(|) is implementation-detail {*} multi method BOOLIFY-ACCEPTS(Junction:U, $negate?) { nqp::hllbool(nqp::isfalse($negate)) } multi method BOOLIFY-ACCEPTS(Mu \matcher, $negate?) { my $matches := nqp::stmts( (my int $elems = nqp::elems($!eigenstates)), (my int $i), nqp::if( nqp::iseq_s($!type,'any'), nqp::stmts( nqp::while( nqp::islt_i($i,$elems) && nqp::dispatch('raku-smartmatch', nqp::atpos($!eigenstates, $i), matcher, nqp::unbox_i(-1)), ++$i ), nqp::islt_i($i,$elems) ), nqp::if( nqp::iseq_s($!type,'all'), nqp::stmts( nqp::while( nqp::islt_i($i,$elems) && nqp::dispatch('raku-smartmatch', nqp::atpos($!eigenstates, $i), matcher, nqp::unbox_i(1)), ++$i ), nqp::iseq_i($i,$elems) ), nqp::if( nqp::iseq_s($!type,'none'), nqp::stmts( nqp::while( nqp::islt_i($i,$elems) && nqp::dispatch('raku-smartmatch', nqp::atpos($!eigenstates, $i), matcher, nqp::unbox_i(-1)), ++$i ), nqp::iseq_i($i,$elems) ), nqp::stmts( # $!type eq 'one' (my int $seen = 0), --$i, # increment in condition nqp::while( nqp::islt_i(++$i,$elems) && nqp::isle_i($seen,1), nqp::if( nqp::dispatch('raku-smartmatch', nqp::atpos($!eigenstates, $i), matcher, nqp::unbox_i(1)), ++$seen ) ), nqp::iseq_i($seen,1) ) ) ) ) ); nqp::hllbool(nqp::if($negate, nqp::not_i($matches), $matches)) } } proto sub any(|) is pure {*} #multi sub any(@values) { @values.any } # this breaks S02-literals/radix.t multi sub any(+values) { values.any } proto sub all(|) is pure {*} multi sub all(@values) { @values.all } multi sub all(+values) { values.all } proto sub one(|) is pure {*} multi sub one(@values) { @values.one } multi sub one(+values) { values.one } proto sub none(|) is pure {*} multi sub none(@values) { @values.none } multi sub none(+values) { values.none } proto sub infix:<|>(|) is pure {*} multi sub infix:<|>(+values) { values.any } proto sub infix:<&>(|) is pure {*} multi sub infix:<&>(+values) { values.all } proto sub infix:<^>(|) is pure {*} multi sub infix:<^>(+values) is pure { values.one } multi sub infix:<~>(Str:D $a, Junction:D $b) { nqp::if( $a, nqp::stmts( # something to concat with (my \storage := nqp::bindattr( (my \junction := nqp::clone($b)), Junction, '$!eigenstates', nqp::clone(nqp::getattr($b,Junction,'$!eigenstates')) )), (my int $elems = nqp::elems(storage)), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos(storage,$i, nqp::if( nqp::istype((my \value := nqp::atpos(storage,$i)),Junction), infix:<~>($a,value), nqp::concat($a,nqp::if(nqp::istype(value,Str),value,value.Str)) ) ) ), junction ), $b.Str # nothing to concat with ) } multi sub infix:<~>(Junction:D $a, Str:D $b) { nqp::if( $b, nqp::stmts( # something to concat with (my \storage := nqp::bindattr( (my \junction := nqp::clone($a)), Junction, '$!eigenstates', nqp::clone(nqp::getattr($a,Junction,'$!eigenstates')) )), (my int $elems = nqp::elems(storage)), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos(storage,$i, nqp::if( nqp::istype((my \value := nqp::atpos(storage,$i)),Junction), infix:<~>(value,$b), nqp::concat(nqp::if(nqp::istype(value,Str),value,value.Str),$b) ) ) ), junction ), $a.Str # nothing to concat with ) } multi sub infix:<~>(Junction:D \a, Junction:D \b) { my int $mergeable = Junction.INFIX-TWO(my $a = a, my $b = b); my \astor := nqp::getattr(nqp::decont($a),Junction,'$!eigenstates'); my \bstor := nqp::getattr(nqp::decont($b),Junction,'$!eigenstates'); my int $aelems = nqp::elems(astor); my int $belems = nqp::elems(bstor); my int $i = -1; my \seen := nqp::hash; my \outer := nqp::bindattr( # outer eigenstates (my \junction := nqp::clone(nqp::decont($a))), Junction, '$!eigenstates', nqp::if( $mergeable, nqp::list, nqp::setelems(nqp::list,$aelems) ) ); nqp::while( # outer loop nqp::islt_i(++$i,$aelems), nqp::stmts( (my \aval := nqp::if( nqp::istype(nqp::atpos(astor,$i),Str), nqp::atpos(astor,$i), nqp::atpos(astor,$i).Str )), (my int $j = -1), nqp::if( $mergeable, nqp::while( # merge eigenstates nqp::islt_i(++$j,$belems), nqp::unless( nqp::existskey( seen, (my \concat := nqp::concat( aval, nqp::if( nqp::istype(nqp::atpos(bstor,$j),Str), nqp::atpos(bstor,$j), nqp::atpos(bstor,$j).Str, ) )) ), nqp::bindkey( # new one, remember seen,nqp::push(outer,concat),1) ) ), nqp::stmts( # cannot merge eigenstates (my \inner := nqp::bindattr( nqp::bindpos(outer,$i,nqp::clone(nqp::decont($b))), Junction, '$!eigenstates', nqp::setelems(nqp::list,$belems) )), nqp::while( nqp::islt_i(++$j,$belems), nqp::bindpos( inner, $j, nqp::concat( aval, nqp::if( nqp::istype(nqp::atpos(bstor,$j),Str), nqp::atpos(bstor,$j), nqp::atpos(bstor,$j).Str, ) ) ) ) ) ) ) ); junction } nqp::p6setautothreader( -> |c { Junction.AUTOTHREAD(|c) } ); Mu.HOW.setup_junction_fallback(Junction, -> $name, |c { my \positionals := nqp::getattr(nqp::decont(c), Capture, '@!list'); my \junction := nqp::decont(nqp::atpos(positionals, 0)); my \storage := nqp::getattr(junction, Junction, '$!eigenstates'); my int $elems = nqp::elems(storage); my \result := nqp::setelems(nqp::list, $elems); my int $i = -1; nqp::shift(positionals); # remove Junction nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos(result, $i, nqp::atpos(storage, $i)."$name"(|c)) ); nqp::p6bindattrinvres( nqp::clone(junction), Junction, '$!eigenstates', result) } ); #line 1 SETTING::src/core.c/Cool.rakumod my class Cool { # declared in BOOTSTRAP # class Cool is Any proto method sqrt() {*} multi method sqrt(Cool:D:) { self.Numeric.sqrt } ## numeric methods method abs() { self.Numeric.abs } method conj() { self.Numeric.conj } method sign() { self.Real.sign } method rand() { self.Num.rand } method sin() { self.Numeric.sin } method asin() { self.Numeric.asin } method cos() { self.Numeric.cos } method acos() { self.Numeric.acos } method tan() { self.Numeric.tan } method atan() { self.Numeric.atan } method atan2($y = 1e0) { self.Numeric.atan2($y.Numeric) } method sec() { self.Numeric.sec } method asec() { self.Numeric.asec } method cosec() { self.Numeric.cosec } method acosec() { self.Numeric.acosec } method cotan() { self.Numeric.cotan } method acotan() { self.Numeric.acotan } method sinh() { self.Numeric.sinh } method asinh() { self.Numeric.asinh } method cosh() { self.Numeric.cosh } method acosh() { self.Numeric.acosh } method tanh() { self.Numeric.tanh } method atanh() { self.Numeric.atanh } method sech() { self.Numeric.sech } method asech() { self.Numeric.asech } method cosech() { self.Numeric.cosech } method acosech() { self.Numeric.acosech } method cotanh() { self.Numeric.cotanh } method acotanh() { self.Numeric.acotanh } method cis() { self.Numeric.cis } method is-prime(--> Bool:D) { self.Real.is-prime } proto method log(|) {*} multi method log(Cool:D: ) { self.Numeric.log } multi method log(Cool:D: $base) { self.Numeric.log($base.Numeric) } method Order(Cool:D:) { nqp::istype((my $value := self.Int),Failure) ?? $value !! ORDER($value) } proto method exp(|) {*} multi method exp(Cool:D: ) { self.Numeric.exp } multi method exp(Cool:D: $base) { self.Numeric.exp($base.Numeric) } proto method round(|) {*} multi method round() { self.Numeric.round() } multi method round($base) { self.Numeric.round($base) } method roots(Cool $n) { self.Numeric.roots($n) } method log2() { self.Numeric.log2 } method log10() { self.Numeric.log10 } method unpolar($n) { self.Numeric.unpolar($n.Numeric) } method floor() { self.Numeric.floor } method ceiling() { self.Numeric.ceiling } method truncate() { self.Numeric.truncate } ## string methods proto method chars(*%) {*} multi method chars(Cool:D: --> Int:D) { self.Str.chars } proto method codes(*%) {*} multi method codes(Cool:D: --> Int:D) { self.Str.codes } proto method encode($?, *%) {*} multi method encode(Cool:D: |c) { self.Str.encode(|c) } multi method fmt(Str(Cool) $format = '%s') { Rakudo::Internals.initialize-sprintf-handler; nqp::p6box_s( nqp::sprintf(nqp::unbox_s($format), nqp::list(self)) ) } proto method wordcase(*%) {*} multi method wordcase(Cool:D:) { self.Str.wordcase(|%_) } proto method trans(|) {*} multi method trans(Cool:D: |c) { self.Str.trans(|c) } proto method indent($, *%) {*} multi method indent(Cool:D: $steps) { self.Str.indent($steps) } proto method uc(*%) {*} multi method uc(Cool:D:) { self.Str.uc } proto method lc(*%) {*} multi method lc(Cool:D:) { self.Str.lc } proto method tc(*%) {*} multi method tc(Cool:D:) { self.Str.tc } proto method fc(*%) {*} multi method fc(Cool:D:) { self.Str.fc } proto method tclc(*%) {*} multi method tclc(Cool:D:) { self.Str.tclc } proto method flip(*%) {*} multi method flip(Cool:D:) { self.Str.flip } proto method chomp($?, *%) {*} multi method chomp(Cool:D:) { self.Str.chomp } multi method chomp(Cool:D: Cool:D $needle) { self.Str.chomp($needle.Str) } proto method chop(|) {*} multi method chop(Cool:D:) { self.Str.chop } multi method chop(Cool:D: Int() $n) { self.Str.chop($n) } proto method samecase($, *%) {*} multi method samecase(Cool:D: Cool:D $pattern) { self.Str.samecase($pattern) } proto method samemark($, *%) {*} multi method samemark(Cool:D: Cool:D $pattern) { self.Str.samemark($pattern) } proto method samespace($, *%) {*} multi method samespace(Cool:D: Cool:D $pattern) { self.Str.samespace($pattern) } proto method starts-with(|) {*} multi method starts-with(Cool:D: Cool:D $needle, :i(:$ignorecase)!, :m(:$ignoremark) --> Bool:D) { self.Str.starts-with($needle.Str, :$ignorecase, :$ignoremark) } multi method starts-with(Cool:D: Cool:D $needle, :m(:$ignoremark)! --> Bool:D) { self.Str.starts-with($needle.Str, :$ignoremark) } multi method starts-with(Cool:D: Cool:D $needle --> Bool:D) { self.Str.starts-with($needle.Str) } proto method ends-with(|) {*} multi method ends-with(Cool:D: Cool:D $suffix, :i(:$ignorecase)!, :m(:$ignoremark) --> Bool:D) { self.Str.ends-with($suffix.Str, :$ignorecase, :$ignoremark) } multi method ends-with(Cool:D: Cool:D $suffix, :m(:$ignoremark)! --> Bool:D) { self.Str.ends-with($suffix.Str, :$ignoremark) } multi method ends-with(Cool:D: Cool:D $suffix --> Bool:D) { self.Str.ends-with($suffix.Str) } proto method substr(|) {*} multi method substr(Cool:D:) { self.Str.substr } multi method substr(Cool:D: \from) { self.Str.substr(from) } multi method substr(Cool:D: \from, \chars) { self.Str.substr(from,chars) } proto method substr-rw(|) {*} multi method substr-rw(Cool:D \SELF:) is rw { (SELF = self.Str).substr-rw } multi method substr-rw(Cool:D \SELF: \from) is rw { (SELF = self.Str).substr-rw(from) } multi method substr-rw(Cool:D \SELF: \from, \want) is rw { (SELF = self.Str).substr-rw(from, want) } proto method substr-eq(|) {*} multi method substr-eq(Cool:D: Cool:D $needle, :i(:$ignorecase)!, :m(:$ignoremark) --> Bool:D) { self.Str.starts-with($needle.Str, :$ignorecase, :$ignoremark) } multi method substr-eq(Cool:D: Cool:D $needle, :m(:$ignoremark) --> Bool:D) { self.Str.starts-with($needle.Str, :$ignoremark) } multi method substr-eq(Cool:D: Cool:D $needle --> Bool:D) { self.Str.starts-with($needle.Str) } multi method substr-eq(Cool:D: Cool:D $needle, Cool:D $pos, :i(:$ignorecase)!, :m(:$ignoremark) --> Bool:D) { self.Str.substr-eq($needle.Str, $pos.Int, :$ignorecase, :$ignoremark) } multi method substr-eq(Cool:D: Cool:D $needle, Cool:D $pos, :m(:$ignoremark)! --> Bool:D) { self.Str.substr-eq($needle.Str, $pos.Int, :$ignoremark) } multi method substr-eq(Cool:D: Cool:D $needle, Cool:D $pos --> Bool:D) { self.Str.substr-eq($needle.Str, $pos.Int) } method !list-as-string($suggestion) is hidden-from-backtrace { warn "Calling '.{callframe(2).code.name}' on a {self.^name}, did you mean '$suggestion'?"; } proto method contains(|) {*} multi method contains(List:D: Cool:D \needle) { # Warn about newbie trap self!list-as-string('$item (elem) @list'); self.Str.contains: needle.Str, |%_ } multi method contains(Cool:D: Cool:D $needle, :i(:$ignorecase)!, :m(:$ignoremark) --> Bool:D) { self.Str.contains: $needle.Str, :$ignorecase, :$ignoremark } multi method contains(Cool:D: Cool:D $needle, :m(:$ignoremark)! --> Bool:D) { self.Str.contains: $needle.Str, :$ignoremark } multi method contains(Cool:D: Cool:D $needle --> Bool:D) { self.Str.contains: $needle.Str } multi method contains(Cool:D: Regex:D $needle --> Bool:D) { self.Str.contains: $needle } multi method contains(Cool:D: Cool:D $needle, Cool:D $pos, :i(:$ignorecase)!, :m(:$ignoremark) --> Bool:D) { self.Str.contains($needle.Str, $pos.Int, :$ignorecase, :$ignoremark) } multi method contains(Cool:D: Cool:D $needle, Cool:D $pos, :m(:$ignoremark)! --> Bool:D) { self.Str.contains($needle.Str, $pos.Int, :$ignoremark) } multi method contains(Cool:D: Cool:D $needle, Cool:D $pos --> Bool:D) { self.Str.contains($needle.Str, $pos.Int) } multi method contains(Cool:D: Regex:D $needle, Cool:D $pos --> Bool:D) { self.Str.contains($needle, $pos) } proto method indices(|) {*} multi method indices(List:D: Cool:D \needle) { # Warn about newbie trap self!list-as-string('.grep( ..., :k)'); self.Str.indices(needle.Str, |%_) } multi method indices(Cool:D: Cool:D $needle, :i(:$ignorecase)!, :m(:$ignoremark), :$overlap) { self.Str.indices($needle.Str, :$ignorecase, :$ignoremark, :$overlap) } multi method indices(Cool:D: Cool:D $needle, :m(:$ignoremark)!, :$overlap) { self.Str.indices($needle.Str, :$ignoremark, :$overlap) } multi method indices(Cool:D: Cool:D $needle, :$overlap) { self.Str.indices($needle.Str, :$overlap) } multi method indices(Cool:D: Cool:D $needle, Cool:D $pos, :i(:$ignorecase), :m(:$ignoremark), :$overlap) { self.Str.indices($needle.Str, $pos.Int, :$ignorecase, :$ignoremark, :$overlap) } multi method indices(Cool:D: Cool:D $needle, Cool:D $pos, :m(:$ignoremark)!, :$overlap) { self.Str.indices($needle.Str, $pos.Int, :$ignoremark, :$overlap) } multi method indices(Cool:D: Cool:D $needle, Cool:D $pos, :$overlap) { self.Str.indices($needle.Str, $pos.Int, :$overlap) } proto method index(|) {*} multi method index(List:D: Cool:D $needle) { # Warn about newbie trap self!list-as-string('.first( ..., :k)'); self.Str.index(nqp::istype($needle,List) ?? $needle !! $needle.Str,|%_) } multi method index(Cool:D: Cool:D $needle, :i(:$ignorecase)!, :m(:$ignoremark) --> Int:D) { self.Str.index( nqp::istype($needle,List) ?? $needle !! $needle.Str, :$ignorecase, :$ignoremark ) } multi method index(Cool:D: Cool:D $needle, :m(:$ignoremark)! --> Int:D) { self.Str.index( nqp::istype($needle,List) ?? $needle !! $needle.Str, :$ignoremark ) } multi method index(Cool:D: Cool:D $needle --> Int:D) { self.Str.index(nqp::istype($needle,List) ?? $needle !! $needle.Str) } multi method index(Cool:D: Cool:D $needle, Cool:D $pos, :i(:$ignorecase)!, :m(:$ignoremark) --> Int:D) { self.Str.index: $needle.Str, $pos.Int, :$ignorecase, :$ignoremark } multi method index(Cool:D: Cool:D $needle, Cool:D $pos, :m(:$ignoremark)! --> Int:D) { self.Str.index: $needle.Str, $pos.Int, :$ignoremark } multi method index(Cool:D: Cool:D $needle, Cool:D $pos --> Int:D) { self.Str.index: $needle.Str, $pos.Int } proto method rindex(|) {*} multi method rindex(List:D: Cool:D $needle) { # Warn about newbie trap self!list-as-string('.first( ..., :k, :end)'); self.Str.rindex(nqp::istype($needle,List) ?? $needle !! $needle.Str,|%_) } multi method rindex(Cool:D: Cool:D $needle --> Int:D) { self.Str.rindex: nqp::istype($needle,List) ?? $needle !! $needle.Str } multi method rindex(Cool:D: Cool:D $needle, Cool:D $pos --> Int:D) { self.Str.rindex: nqp::istype($needle,List) ?? $needle !! $needle.Str, $pos.Int } method split(Cool: |c) { self.Str.split(|c); } method match(Cool:D: |c) { $/ := nqp::getlexcaller('$/'); self.Str.match(|c) } proto method comb(|) {*} multi method comb(Cool:D: --> Seq:D) { self.Str.comb } multi method comb(Cool:D: Cool:D $size, $limit = * --> Seq:D) { self.Str.comb($size.Int, $limit) } multi method comb(Cool:D: Int:D $size, $limit = * --> Seq:D) { self.Str.comb($size, $limit) } multi method comb(Cool:D: Cool:D $pat --> Seq:D) { self.Str.comb($pat.Str) } multi method comb(Cool:D: Str:D $pat --> Seq:D) { self.Str.comb($pat) } multi method comb(Cool:D: Cool:D $pat, $limit --> Seq:D) { self.Str.comb($pat.Str, $limit) } multi method comb(Cool:D: Str:D $pat, $limit --> Seq:D) { self.Str.comb($pat, $limit) } multi method comb(Cool:D: Regex:D $pattern, :$match --> Seq:D) { self.Str.comb($pattern, :$match) } multi method comb(Cool:D: Regex:D $pattern, $limit, :$match --> Seq:D) { self.Str.comb($pattern, $limit, :$match) } proto method lines(|) {*} multi method lines(Cool:D:) { self.Str.lines } multi method lines(Cool:D: :$count! ) { self.Str.lines(:$count) } multi method lines(Cool:D: $limit ) { self.Str.lines($limit) } proto method words(|) {*} multi method words(Cool:D:) { self.Str.words } multi method words(Cool:D: $limit ) { self.Str.words($limit) } proto method subst(|) {*} multi method subst(Cool:D: $original, $replacement = "", *%options) { $/ := nqp::getlexcaller('$/'); self.Str.subst($original, $replacement, |%options); } # `$value-to-subst-mutate` will show up in errors when called on non-rw # container, so use more descriptive name instead of just `$self` method subst-mutate(Cool:D $value-to-subst-mutate is rw: |c) { $/ := nqp::getlexcaller('$/'); my $str = $value-to-subst-mutate.Str; my $match := $str.subst-mutate(|c); $value-to-subst-mutate = $str if $match; # only change if successful $match } proto method IO(|) {*} multi method IO(Cool:D:) { IO::Path.new(self) } multi method IO(Cool:U:) { IO::Path } method sprintf(*@args) { sprintf(self, @args) }; method printf (*@args) { printf(self, @args) }; proto method trim(*%) {*} multi method trim(Cool:D:) { self.Str.trim } proto method trim-leading(*%) {*} multi method trim-leading(Cool:D:) { self.Str.trim-leading } proto method trim-trailing(*%) {*} multi method trim-trailing(Cool:D:) { self.Str.trim-trailing } method EVAL(*%opts) { EVAL(self, context => CALLER::LEXICAL::, |%opts); } method Failure(Cool:D:) is hidden-from-backtrace { Failure.new(self) } multi method Real() { nqp::istype((my $numeric := self.Numeric),Failure) ?? $numeric !! $numeric.Real } multi method Int() { nqp::istype((my $numeric := self.Numeric),Failure) ?? $numeric !! $numeric.Int } proto method UInt(|) {*} multi method UInt() { nqp::istype((my $got := self.Int),Failure) ?? $got !! $got < 0 ?? X::OutOfRange.new( :what('Coercion to UInt'), :$got, :range<0..^Inf> ).Failure !! $got } method Num() { nqp::istype((my $numeric := self.Numeric),Failure) ?? $numeric !! $numeric.Num } method Rat() { nqp::istype((my $numeric := self.Numeric),Failure) ?? $numeric !! $numeric.Rat } method FatRat() { nqp::istype((my $numeric := self.Numeric),Failure) ?? $numeric !! $numeric.FatRat } method Complex() { nqp::istype((my $numeric := self.Numeric),Failure) ?? $numeric !! $numeric.Complex } method Version() { self.Str.Version } } Metamodel::ClassHOW.exclude_parent(Cool); proto sub chop($, $?, *%) {*} multi sub chop($s --> Str:D) { $s.chop } multi sub chop($s, Int() $n --> Str:D) { $s.chop($n) } proto sub chomp($, *%) {*} multi sub chomp($s --> Str:D) { $s.chomp } proto sub flip($, *%) {*} multi sub flip($s --> Str:D) { $s.flip } proto sub index($, $, $?, *%) {*} multi sub index($s, Cool:D $needle, :i(:$ignorecase), :m(:$ignoremark) --> Int:D) { $s.index($needle, :$ignorecase, :$ignoremark) } multi sub index($s, Cool:D $needle, Cool:D $pos, :i(:$ignorecase), :m(:$ignoremark) --> Int:D) { $s.index($needle, $pos, :$ignorecase, :$ignoremark) } proto sub rindex($, $, $?, *%) {*} multi sub rindex($s, Cool:D $needle --> Int:D) { $s.rindex($needle) } multi sub rindex($s, Cool:D $needle, Cool:D $pos --> Int:D) { $s.rindex($needle,$pos) } proto sub lc($, *%) {*} multi sub lc($s) { $s.lc } proto sub uc($, *%) {*} multi sub uc($s) { $s.uc } proto sub tc($, *%) {*} multi sub tc($s) { $s.tc } proto sub fc($, *%) {*} multi sub fc($s) { $s.fc } proto sub tclc($, *%) {*} multi sub tclc($s) { $s.tclc } proto sub indices($, $, $?, *%) {*} multi sub indices($s, Cool:D $needle, :i(:$ignorecase), :m(:$ignoremark), :$overlap) { $s.indices($needle, :$ignorecase, :$ignoremark, :$overlap) } multi sub indices($s, Cool:D $needle, Cool:D $pos, :i(:$ignorecase), :m(:$ignoremark), :$overlap) { $s.indices($needle, $pos, :$ignorecase, :$ignoremark, :$overlap) } proto sub comb($, $, $?, *%) {*} multi sub comb(Regex $matcher, $input, $limit = *, :$match) { $input.comb($matcher, $limit, :$match) } multi sub comb(Str $matcher, $input, $limit = *) { $input.comb($matcher, $limit) } multi sub comb(Int:D $size, $input, $limit = *) { $input.comb($size, $limit) } proto sub wordcase($, *%) is pure {*} multi sub wordcase($x) { $x.wordcase } proto sub sprintf($, |) {*} multi sub sprintf(Str(Cool) $format, *@args) { CATCH { when X::Cannot::Lazy { X::Cannot::Lazy.new(:action('(s)printf')).throw } default { Rakudo::Internals.HANDLE-NQP-SPRINTF-ERRORS($_, $format).throw } } Rakudo::Internals.initialize-sprintf-handler; nqp::p6box_s( nqp::sprintf( nqp::unbox_s($format), @args.elems ?? nqp::clone(nqp::getattr(@args,List,'$!reified')) !! nqp::create(IterationBuffer) ) ) } proto sub samecase($, $, *%) {*} multi sub samecase($s, Cool:D $pattern) { $s.samecase($pattern) } proto sub split($, $, |) {*} multi sub split($pat, $target, |c) { c ?? $target.split($pat, |c) !! $target.split($pat) } proto sub chars($, *%) is pure {*} multi sub chars(Str:D $x) { nqp::p6box_i(nqp::chars($x)) } #?js: NFG multi sub chars(str $x --> int) { nqp::chars($x) } #?js: NFG multi sub chars($x) { $x.chars } #line 1 SETTING::src/core.c/Enumeration.rakumod my class X::Enum::NoValue {...}; my class X::Constructor::BadType {...} # Method that we have on enumeration types. my role Enumeration { has $.key; has $.value; has int $!index; method new { X::Constructor::BadType.new(type => self.WHAT).throw } method enums() { self.^enum_values.Map } multi method kv(::?CLASS:D:) { ($!key, $!value) } method pair(::?CLASS:D:) { $!key => $!value } multi method gist(::?CLASS:D:) { $!key } multi method raku(::?CLASS:D:) { self.^name ~ '::' ~ $!key } multi method pick(::?CLASS:U:) { self.^enum_value_list.pick } multi method pick(::?CLASS:U: \n) { self.^enum_value_list.pick(n) } multi method pick(::?CLASS:D: *@pos) { self xx +?( @pos[0] // 1 ) } multi method roll(::?CLASS:U:) { self.^enum_value_list.roll } multi method roll(::?CLASS:U: \n) { self.^enum_value_list.roll(n) } multi method roll(::?CLASS:D: *@pos) { self xx +?( @pos[0] // 1 ) } multi method Numeric(::?CLASS:D:) { $!value.Numeric } multi method Int(::?CLASS:D:) { $!value.Int } multi method Real(::?CLASS:D:) { $!value.Real } multi method WHICH(::?CLASS:D: --> ValueObjAt:D) { nqp::box_s( nqp::concat(self.^name,nqp::concat("|",$!index)), ValueObjAt ) } multi method ACCEPTS(::?CLASS:D: ::?CLASS:D $v) { self === $v } method !FROM-VALUE(Mu \val) { my $res := Nil; my $dcval := nqp::decont(val); # If value is a mixin of enum try to pull out the mixed in value first if $dcval.^is_mixin { my $attr_name := '$!' ~ self.^name; if $dcval.^has_attribute($attr_name) { my $mixin_value := nqp::getattr($dcval, $dcval.WHAT, $attr_name); return $mixin_value if nqp::istype($mixin_value, ::?CLASS); } } if nqp::istype($dcval, ::?CLASS) { $res := $dcval; } elsif nqp::isconcrete($dcval) { $res := self.^enum_from_value($dcval); } $res // X::Enum::NoValue.new(:type(self.WHAT), :value($dcval)).Failure } proto method CALL-ME(Mu) {*} multi method CALL-ME(Mu \val) { self!FROM-VALUE(val) } proto method COERCE(Mu) {*} multi method COERCE(Mu \val) { self!FROM-VALUE(val) } method pred(::?CLASS:D:) { nqp::getattr_i(self,::?CLASS,'$!index') ?? nqp::atpos( nqp::getattr(self.^enum_value_list,List,'$!reified'), nqp::sub_i(nqp::getattr_i(self,::?CLASS,'$!index'),1) ) !! self } method succ(::?CLASS:D:) { my $values := nqp::getattr(self.^enum_value_list,List,'$!reified'); nqp::islt_i( nqp::getattr_i(self,::?CLASS,'$!index'), nqp::sub_i(nqp::elems($values),1) ) ?? nqp::atpos( $values, nqp::add_i(nqp::getattr_i(self,::?CLASS,'$!index'),1) ) !! self } } # Methods that we also have if the base type of an enumeration is # Numeric. my role NumericEnumeration { multi method Str(::?CLASS:D:) { self.key } } my role StringyEnumeration { multi method Str(::?CLASS:D:) { self.value } } my role NumericStringyEnumeration { multi method Str(::?CLASS:D:) { self.key } } sub ENUM_VALUES(*@args --> Map:D) is implementation-detail { my Mu $prev = -1; my $res := nqp::hash; nqp::istype($_,Pair) ?? nqp::bindkey($res, .key, nqp::decont($prev = .value)) !! nqp::bindkey($res, $_, nqp::decont($prev = $prev.succ)) for @args; nqp::p6bindattrinvres(nqp::create(Map),Map,'$!storage',$res) } Metamodel::EnumHOW.set_composalizer(-> $type, $name, @enum_values { my Mu $r := Metamodel::ParametricRoleHOW.new_type(:name($name)); $r.^add_attribute(Attribute.new( :name('$!' ~ $name), :type(nqp::decont($type)), :has_accessor(1), :package($r))); for @enum_values { my $key = $_.key; my $value = $_.value; my $meth = method () { self."$name"() == $value } $meth.set_name($key); $r.^add_method($key, $meth); } $r.^set_body_block( -> |c {nqp::list($r,nqp::hash('$?CLASS',c<$?CLASS>))}); $r.^compose; $r }); # We use this one because, for example, Int:D === Int:D, has an optimization # that simply unboxes the values. That's no good for us, since two different # Enumeration:Ds could have the same Int:D value. multi infix:<===> (Enumeration:D $a, Enumeration:D $b --> Bool:D) { nqp::hllbool(nqp::eqaddr($a,$b)) } #line 1 SETTING::src/core.c/Numeric.rakumod my role Rational[::NuT = Int, ::DeT = ::("NuT")] does Real { ... } my class X::Numeric::DivideByZero { ... } my role Numeric { multi method Numeric(Numeric:D:) { self } multi method Numeric(Numeric:U:) { self.Mu::Numeric; # issue a warning # We need to be specific about coercions to make `Numeric() == 0` working as specced. (self.^archetypes.coercive ?? self.^nominalize !! self).new } multi method ACCEPTS(Numeric:D: Any:D \a) { (try my \numeric = a.Numeric).defined ?? (self.isNaN && numeric.isNaN or numeric == self) !! False } proto method log(|) {*} multi method log(Numeric:D: Cool $base) { self.log / $base.Numeric.log } multi method log(Numeric:D: Numeric $base) { self.log / $base.log } method log2() { self.log / 2e0.log } method log10() { self.log / 10e0.log } proto method exp(|) {*} multi method exp(Numeric:D: $base) { $base ** self; } method roots(Cool $n) { self.Complex.roots($n.Int) } method FatRat(Numeric:D:) { self.Rat.FatRat } multi method Bool(Numeric:D:) { self != 0 } multi method gist(Numeric:D:) { self.Str } multi method DUMP(Numeric:D:) { self.raku } method succ() { self + 1 } method pred() { self - 1 } } multi sub infix:(Numeric:D \a, Numeric:D \b --> Bool:D) { # Use === for Nums, to properly handle signed zeros and NaNs # For Rationals, properly handle NaN-y Rationals nqp::hllbool( nqp::eqaddr(nqp::decont(a),nqp::decont(b)) || nqp::eqaddr(a.WHAT,b.WHAT) && nqp::if( nqp::istype(a,Num), a === b, nqp::if( nqp::istype(a,Rational) && nqp::isfalse(a.denominator) && nqp::isfalse(b.denominator) && nqp::isfalse(a.numerator) && nqp::isfalse(b.numerator), True, # got two NaN Rationals a == b))) } ## arithmetic operators proto sub prefix:<+>($, *%) is pure {*} multi sub prefix:<+>(\a) { a.Numeric } proto sub prefix:<->($, *%) is pure {*} multi sub prefix:<->(\a) { -a.Numeric } # U+2212 MINUS SIGN my constant &prefix:<−> := &prefix:<->; proto sub abs($, *%) is pure {*} multi sub abs(\a) { abs a.Numeric } proto sub sign($, *%) is pure {*} multi sub sign(Numeric \x) { x.sign } multi sub sign(Cool \x) { x.Numeric.sign } proto sub log($, $?, *%) is pure {*} multi sub log(Numeric $x) { $x.log } multi sub log(Numeric $x, Numeric $base) { $x.log($base) } multi sub log(Cool $x) { $x.Numeric.log } multi sub log(Cool $x, Cool $base) { $x.Numeric.log($base.Numeric) } proto sub log2($, *%) is pure {*} multi sub log2(Numeric $x) { $x.log(2e0) } multi sub log2(Cool $x) { $x.Numeric.log(2e0) } proto sub log10($, *%) is pure {*} multi sub log10(Numeric $x) { $x.log(10e0) } multi sub log10(Cool $x) { $x.Numeric.log(10e0) } proto sub exp($, $?, *%) is pure {*} multi sub exp(Numeric $x) { $x.exp } multi sub exp(Numeric $x, Numeric $base) { $x.exp($base) } proto sub sin($, *%) is pure {*} multi sub sin(Numeric \x) { x.sin } multi sub sin(Cool \x) { x.Numeric.sin } proto sub asin($, *%) is pure {*} multi sub asin(Numeric \x) { x.asin } multi sub asin(Cool \x) { x.Numeric.asin } proto sub cos($, *%) is pure {*} multi sub cos(Numeric \x) { x.cos } multi sub cos(Cool \x) { x.Numeric.cos } proto sub acos($, *%) is pure {*} multi sub acos(Numeric \x) { x.acos } multi sub acos(Cool \x) { x.Numeric.acos } proto sub tan($, *%) is pure {*} multi sub tan(Numeric \x) { x.tan } multi sub tan(Cool \x) { x.Numeric.tan } proto sub atan($, *%) is pure {*} multi sub atan(Numeric \x) { x.atan } multi sub atan(Cool \x) { x.Numeric.atan } proto sub sec($, *%) is pure {*} multi sub sec(Numeric \x) { x.sec } multi sub sec(Cool \x) { x.Numeric.sec } proto sub asec($, *%) is pure {*} multi sub asec(Numeric \x) { x.asec } multi sub asec(Cool \x) { x.Numeric.asec } proto sub cosec($, *%) is pure {*} multi sub cosec(Numeric \x) { x.cosec } multi sub cosec(Cool \x) { x.Numeric.cosec } proto sub acosec($, *%) is pure {*} multi sub acosec(Numeric \x) { x.acosec } multi sub acosec(Cool \x) { x.Numeric.acosec } proto sub cotan($, *%) is pure {*} multi sub cotan(Numeric \x) { x.cotan } multi sub cotan(Cool \x) { x.Numeric.cotan } proto sub acotan($, *%) is pure {*} multi sub acotan(Numeric \x) { x.acotan } multi sub acotan(Cool \x) { x.Numeric.acotan } proto sub sinh($, *%) is pure {*} multi sub sinh(Numeric \x) { x.sinh } multi sub sinh(Cool \x) { x.Numeric.sinh } proto sub asinh($, *%) is pure {*} multi sub asinh(Numeric \x) { x.asinh } multi sub asinh(Cool \x) { x.Numeric.asinh } proto sub cosh($, *%) is pure {*} multi sub cosh(Numeric \x) { x.cosh } multi sub cosh(Cool \x) { x.Numeric.cosh } proto sub acosh($, *%) is pure {*} multi sub acosh(Numeric \x) { x.acosh } multi sub acosh(Cool \x) { x.Numeric.acosh } proto sub tanh($, *%) is pure {*} multi sub tanh(Numeric \x) { x.tanh } multi sub tanh(Cool \x) { x.Numeric.tanh } proto sub atanh($, *%) is pure {*} multi sub atanh(Numeric \x) { x.atanh } multi sub atanh(Cool \x) { x.Numeric.atanh } proto sub sech($, *%) is pure {*} multi sub sech(Numeric \x) { x.sech } multi sub sech(Cool \x) { x.Numeric.sech } proto sub asech($, *%) is pure {*} multi sub asech(Numeric \x) { x.asech } multi sub asech(Cool \x) { x.Numeric.asech } proto sub cosech($, *%) is pure {*} multi sub cosech(Numeric \x) { x.cosech } multi sub cosech(Cool \x) { x.Numeric.cosech } proto sub acosech($, *%) is pure {*} multi sub acosech(Numeric \x) { x.acosech } multi sub acosech(Cool \x) { x.Numeric.acosech } proto sub cotanh($, *%) is pure {*} multi sub cotanh(Numeric \x) { x.cotanh } multi sub cotanh(Cool \x) { x.Numeric.cotanh } proto sub acotanh($, *%) is pure {*} multi sub acotanh(Numeric \x) { x.acotanh } multi sub acotanh(Cool \x) { x.Numeric.acotanh } proto sub sqrt($, *%) is pure {*} multi sub sqrt(Numeric \x) { x.sqrt } multi sub sqrt(Cool \x) { x.Numeric.sqrt } proto sub roots($, $, *%) is pure {*} multi sub roots($x, Cool $n) { $x.Numeric.Complex.roots($n.Int) } multi sub roots($x, Numeric $n) { $x.Numeric.Complex.roots($n.Int) } proto sub floor($, *%) is pure {*} multi sub floor($a) { $a.Numeric.floor } multi sub floor(Numeric $a) { $a.floor } proto sub ceiling($, *%) is pure {*} multi sub ceiling($a) { $a.Numeric.ceiling } multi sub ceiling(Numeric $a) { $a.ceiling } proto sub round($, $?, *%) is pure {*} multi sub round(Numeric() $a) { $a.round } multi sub round(Numeric() $a, $scale) { $a.round($scale) } proto sub infix:<+>($?, $?, *%) is pure {*} multi sub infix:<+>($x = 0) { $x.Numeric } multi sub infix:<+>(\a, \b) { a.Numeric + b.Numeric } proto sub infix:<->($?, $?, *%) is pure {*} multi sub infix:<->($x = 0) { $x.Numeric } multi sub infix:<->(\a, \b) { a.Numeric - b.Numeric } # U+2212 MINUS SIGN my constant &infix:<−> := &infix:<->; proto sub infix:<*>($?, $?, *%) is pure {*} multi sub infix:<*>($x = 1) { $x.Numeric } multi sub infix:<*>(\a, \b) { a.Numeric * b.Numeric } # U+00D7 MULTIPLICATION SIGN my constant &infix:<×> = &infix:<*>; proto sub infix:($?, $?, *%) is pure {*} multi sub infix:() { "infix:".no-zero-arg } multi sub infix:($x) { $x.Numeric } multi sub infix:(\a, \b) { a.Numeric / b.Numeric } # U+00F7 DIVISION SIGN my constant &infix:<÷> = &infix:; proto sub infix:

($, $, *%) is pure {*} # rest of infix:
is in Int.rakumod proto sub infix:<%>($?, $?, *%) is pure {*} multi sub infix:<%>() { "infix:<%>".no-zero-arg } multi sub infix:<%>($x) { $x } multi sub infix:<%>(\a, \b) { a.Real % b.Real } proto sub infix:<%%>($?, $?, *%) is pure {*} multi sub infix:<%%>() { "infix:<%%>".no-zero-arg } multi sub infix:<%%>($) { Bool::True } multi sub infix:<%%>(\a, \b) { b ?? (a.Real % b.Real == 0) !! X::Numeric::DivideByZero.new( using => 'infix:<%%>', numerator => a ).Failure } proto sub infix:($?, $?, *%) is pure {*} multi sub infix:(\a, \b) { a.Int lcm b.Int } proto sub infix:($?, $?, *%) is pure {*} multi sub infix:() { 'infix:'.no-zero-arg } multi sub infix:(\a, \b) { a.Int gcd b.Int } proto sub infix:<**>($?, $?, *%) is pure {*} multi sub infix:<**>($x = 1) { $x.Numeric } multi sub infix:<**>(\a, \b) { a.Numeric ** b.Numeric } proto sub postfix:<ⁿ>($, $, *%) is pure {*} multi sub postfix:<ⁿ>(\a, \b) { a ** b } ## relational operators proto sub infix:<==>($?, $?, *%) is pure {*} multi sub infix:<==>($?) { Bool::True } multi sub infix:<==>(\a, \b) { a.Numeric == b.Numeric } # U+2A75 TWO CONSECUTIVE EQUALS SIGNS my constant &infix:<⩵> = &infix:<==>; proto sub infix:<=~=>($?, $?, *%) {*} # note, can't be pure due to dynvar multi sub infix:<=~=>($?) { Bool::True } multi sub infix:<=~=>(\a, \b, :$tolerance = $*TOLERANCE) { # If operands are non-0, scale the tolerance to the larger of the abs values. # We test b first since $value ≅ 0 is the usual idiom and falsifies faster. if b && a && $tolerance { abs(a - b) < (a.abs max b.abs) * $tolerance; } else { # interpret tolerance as absolute abs(a.Num - b.Num) < $tolerance; } } # U+2245 APPROXIMATELY EQUAL TO my constant &infix:<≅> = &infix:<=~=>; proto sub infix:(Mu $?, Mu $?, *%) is pure {*} multi sub infix:($?) { Bool::True } multi sub infix:(Mu \a, Mu \b) { not a == b } # U+2260 NOT EQUAL TO my constant &infix:<≠> := &infix:; proto sub infix:«<»($?, $?, *%) is pure {*} multi sub infix:«<»($?) { Bool::True } multi sub infix:«<»(\a, \b) { a.Real < b.Real } proto sub infix:«<=»($?, $?, *%) is pure {*} multi sub infix:«<=»($?) { Bool::True } multi sub infix:«<=»(\a, \b) { a.Real <= b.Real } # U+2264 LESS-THAN OR EQUAL TO my constant &infix:<≤> := &infix:«<=»; proto sub infix:«>»($?, $?, *%) is pure {*} multi sub infix:«>»($?) { Bool::True } multi sub infix:«>»(\a, \b) { a.Real > b.Real } proto sub infix:«>=»($?, $?, *%) is pure {*} multi sub infix:«>=»($?) { Bool::True } multi sub infix:«>=»(\a, \b) { a.Real >= b.Real } # U+2265 GREATER-THAN OR EQUAL TO my constant &infix:<≥> := &infix:«>=»; ## bitwise operators proto sub infix:<+&>($?, $?, *%) is pure {*} multi sub infix:<+&>() { +^0 } multi sub infix:<+&>($x) { $x } multi sub infix:<+&>($x, $y) { $x.Numeric.Int +& $y.Numeric.Int } proto sub infix:<+|>($?, $?, *%) is pure {*} multi sub infix:<+|>() { 0 } multi sub infix:<+|>($x) { $x } multi sub infix:<+|>($x, $y) { $x.Numeric.Int +| $y.Numeric.Int } proto sub infix:<+^>($?, $?, *%) is pure {*} multi sub infix:<+^>() { 0 } multi sub infix:<+^>($x) { $x } multi sub infix:<+^>($x, $y) { $x.Numeric.Int +^ $y.Numeric.Int } proto sub infix:«+<»($?, $?, *%) is pure {*} multi sub infix:«+<»() { "infix:«+<»".no-zero-arg } multi sub infix:«+<»($x) { $x } multi sub infix:«+<»($x,$y) { $x.Numeric.Int +< $y.Numeric.Int } proto sub infix:«+>»($?, $?, *%) is pure {*} multi sub infix:«+>»() { "infix:«+>»".no-zero-arg } multi sub infix:«+>»($x) { $x } multi sub infix:«+>»($x,$y) { $x.Numeric.Int +> $y.Numeric.Int } proto sub prefix:<+^>($, *%) is pure {*} multi sub prefix:<+^>($x) { +^ $x.Numeric.Int } #line 1 SETTING::src/core.c/Real.rakumod my class Complex { ... } my class X::Numeric::Uninitialized { ... } my role Real does Numeric { method Rat(Real:D: Real $epsilon = 1.0e-6) { self.Bridge.Rat($epsilon) } method abs() { self < 0 ?? -self !! self } method sign(Real:D:) { self > 0 ?? 1 !! self < 0 ?? -1 !! 0 } method conj(Real:D:) { self } method rand() { self.Bridge.rand } method sin() { self.Bridge.sin } method asin() { self.Bridge.asin } method cos() { self.Bridge.cos } method acos() { self.Bridge.acos } method tan() { self.Bridge.tan } method atan() { self.Bridge.atan } proto method atan2(|) {*} multi method atan2(Real $x = 1e0) { self.Bridge.atan2($x.Bridge) } multi method atan2(Cool $x = 1e0) { self.Bridge.atan2($x.Numeric.Bridge) } method sec() { self.Bridge.sec } method asec() { self.Bridge.asec } method cosec() { self.Bridge.cosec } method acosec() { self.Bridge.acosec } method cotan() { self.Bridge.cotan } method acotan() { self.Bridge.acotan } method sinh() { self.Bridge.sinh } method asinh() { self.Bridge.asinh } method cosh() { self.Bridge.cosh } method acosh() { self.Bridge.acosh } method tanh() { self.Bridge.tanh } method atanh() { self.Bridge.atanh } method sech() { self.Bridge.sech } method asech() { self.Bridge.asech } method cosech() { self.Bridge.cosech } method acosech() { self.Bridge.acosech } method cotanh() { self.Bridge.cotanh } method acotanh() { self.Bridge.acotanh } method floor() { self.Bridge.floor } method ceiling() { self.Bridge.ceiling } multi method sqrt(Real:D:) { self.Bridge.sqrt } proto method round(|) {*} multi method round(Real:D:) { (self + 1/2).floor; # Rat NYI here, so no .5 } multi method round(Real:D: Real() $scale) { (self / $scale + 1/2).floor * $scale; } method unpolar(Real $angle) { Complex.new(self * $angle.cos, self * $angle.sin); } method cis() { Complex.new(self.cos, self.sin); } method Complex() { Complex.new(self.Num, 0e0) } proto method log(|) {*} multi method log(Real:D: ) { self.Bridge.log } multi method log(Real:D: Real $base) { self.Bridge.log($base.Bridge) } proto method exp(|) {*} multi method exp(Real:D: ) { self.Bridge.exp } method truncate(Real:D:) { self < 0 ?? self.ceiling !! self.floor } method isNaN { Bool::False } method polymod(Real:D: +@mods) { my $more = self; my $lazy = @mods.is-lazy; fail X::OutOfRange.new( :what('invocant to polymod'), :got($more), :range<0..Inf> ) if $more < 0; gather { for @mods -> $mod { last if $lazy and not $more; Failure.new(X::Numeric::DivideByZero.new: using => 'polymod', numerator => $more ) unless $mod; take my $rem = $more % $mod; $more -= $rem; $more /= $mod; } take $more if ($lazy and $more) or not $lazy; } } method base(Int:D $base, $digits? is copy) { $digits = Nil if nqp::istype($digits, Whatever); fail X::OutOfRange.new( :what('digits argument to base'), :got($digits), :range<0..1073741824> ) if $digits.defined and $digits < 0; my $prec = $digits // 1e8.log($base.Num).Int; my Int $int_part = self.Int.self; # .self blows up Failures my $frac = abs(self - $int_part); my @frac_digits; my @conversion := <0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z>; for ^$prec { last unless $digits // $frac; $frac = $frac * $base; push @frac_digits, $frac.Int; $frac = $frac - $frac.Int; } if 2 * $frac >= 1 { if @frac_digits { for @frac_digits-1 ... 0 -> $x { last if ++@frac_digits[$x] < $base; @frac_digits[$x] = 0; ++$int_part if $x == 0 } } else { ++$int_part; } } my Str $r = $int_part.base($base); $r ~= '.' ~ @conversion[@frac_digits].join if @frac_digits; # if $int_part is 0, $int_part.base doesn't see the sign of self $int_part == 0 && self < 0 ?? '-' ~ $r !! $r; } multi method Real(Real:D:) { self } multi method Real(Real:U:) { self.Mu::Real; # issue a warning; self.new } method Bridge(Real: --> Num:D) { self.defined ?? self.Num !! (self.HOW.archetypes.coercive ?? self.Mu::Numeric.Num !! X::Numeric::Uninitialized.new(:type(self)).throw) } method Int(Real:D:) { self.Bridge.Int } method Num(Real:D:) { self.Bridge.Num } multi method Str(Real:D:) { self.Bridge.Str } } proto sub cis($, *%) {*} multi sub cis(Real $a) { $a.cis } multi sub infix:<+>( Real $a, Real $b) { $a.Bridge + $b.Bridge } multi sub infix:<->( Real $a, Real $b) { $a.Bridge - $b.Bridge } multi sub infix:<*>( Real $a, Real $b) { $a.Bridge * $b.Bridge } multi sub infix:( Real $a, Real $b) { $a.Bridge / $b.Bridge } multi sub infix:<%>( Real $a, Real $b) { $a.Bridge % $b.Bridge } multi sub infix:<**>(Real $a, Real $b) { $a.Bridge ** $b.Bridge } multi sub infix:<==>(Real $a, Real $b) { $a.Bridge == $b.Bridge } multi sub infix:«<»( Real $a, Real $b) { $a.Bridge < $b.Bridge } multi sub infix:«<=»(Real $a, Real $b) { $a.Bridge <= $b.Bridge } multi sub infix:«>»( Real $a, Real $b) { $a.Bridge > $b.Bridge } multi sub infix:«>=»(Real $a, Real $b) { $a.Bridge >= $b.Bridge } multi sub prefix:<->(Real:D $a) { -$a.Bridge } # NOTE: According to the spec, infix: is "Not coercive, # so fails on differing types." Thus no casts here. proto sub infix:($, $, *%) is pure {*} multi sub infix:(Real:D $a, Real:D $b) { $a - ($a div $b) * $b } multi sub abs(Real:D $a) { $a < 0 ?? -$a !! $a } proto sub truncate($, *%) {*} multi sub truncate(Real:D $x) { $x.truncate } multi sub truncate(Cool:D $x) { $x.Numeric.truncate } proto sub atan2($, $?, *%) {*} multi sub atan2(Real:D $a, Real:D $b = 1e0) { $a.Bridge.atan2($b.Bridge) } # should really be (Cool, Cool), and then (Cool, Real) and (Real, Cool) # candidates, but since Int both conforms to Cool and Real, we'd get lots # of ambiguous dispatches. So just go with (Any, Any) for now. multi sub atan2(\a, \b = 1e0) { a.Numeric.atan2(b.Numeric) } proto sub unpolar($, $, *%) {*} multi sub unpolar(Real $mag, Real $angle) { $mag.unpolar($angle) } #line 1 SETTING::src/core.c/Int.rakumod my class Rat { ... } my class X::Cannot::Capture { ... } my class X::Numeric::DivideByZero { ... } my class X::NYI::BigInt { ... } my class Int { ... } my subset UInt of Int where { nqp::not_i(nqp::isconcrete($_)) || nqp::isge_I(nqp::decont($_),0) } nqp::dispatch('boot-syscall', 'set-cur-hll-config-key', 'uint_box', UInt); my class Int does Real { # declared in BOOTSTRAP # class Int is Cool # has bigint $!value is box_target; multi method WHICH(Int:D: --> ValueObjAt:D) { nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Int), 'Int|', nqp::concat(nqp::unbox_s(self.^name), '|') ), nqp::tostr_I(self) ), ValueObjAt ) } multi method ACCEPTS(Int:D: Int:D $other, --> Bool:D) { nqp::hllbool(nqp::iseq_I(self, $other)) } proto method new(|) {*} multi method new(Any:U $type) { die "Cannot create an Int from a '$type.^name()' type object"; } multi method new(Any:D \value --> Int:D) { self.new: value.Int } multi method new(int \value --> Int:D) { # rebox the value, so we get rid of any potential mixins nqp::fromI_I(nqp::decont(value), self) } multi method new(Int:D \value = 0 --> Int:D) { # rebox the value, so we get rid of any potential mixins nqp::fromI_I(nqp::decont(value), self) } multi method raku(Int:D: --> Str:D) { self.Str; } multi method Bool(Int:D: --> Bool:D) { nqp::hllbool(nqp::bool_I(self)); } method Capture() { X::Cannot::Capture.new( :what(self) ).throw } method Int() { self } method sign(Int:D: --> Int:D) { nqp::isgt_I(self,0) || nqp::neg_i(nqp::islt_I(self,0)) } multi method Str(Int:D: --> Str:D) { nqp::p6box_s(nqp::tostr_I(self)) } multi method Str(Int:D: :$superscript! --> Str:D) { $_ := self.Str; $superscript ?? .trans('-0123456789' => '⁻⁰¹²³⁴⁵⁶⁷⁸⁹') !! $_ } multi method Str(Int:D: :$subscript! --> Str:D) { $_ := self.Str; $subscript ?? .trans('-0123456789' => '₋₀₁₂₃₄₅₆₇₈₉') !! $_ } method Num(Int:D: --> Num:D) { nqp::p6box_n(nqp::tonum_I(self)); } method Rat(Int:D: $? --> Rat:D) { nqp::p6bindattrinvres( nqp::p6bindattrinvres(nqp::create(Rat),Rat,'$!numerator',self), Rat,'$!denominator',1 ) } method FatRat(Int:D: $? --> FatRat:D) { nqp::p6bindattrinvres( nqp::p6bindattrinvres(nqp::create(FatRat),FatRat,'$!numerator',self), FatRat,'$!denominator',1 ) } method abs(Int:D: --> Int:D) { nqp::abs_I(self, Int) } method Bridge(Int: --> Num:D) { self.defined ?? nqp::p6box_n(nqp::tonum_I(self)) !! self.Real::Bridge } multi method sqrt(Int:D: --> Num:D) { nqp::p6box_n(nqp::sqrt_n(nqp::tonum_I(self))) } sub BASE_OUT_OF_RANGE(int $got) { X::OutOfRange.new( :what('base argument to base'), :$got, :range<2..36> ).Failure } sub DIGITS_OUT_OF_RANGE(int $got) { X::OutOfRange.new( :what('digits argument to base'), :$got, :range<2..36> ).Failure } proto method base(|) {*} multi method base(Int:D: Int:D $base --> Str:D) { 2 <= $base <= 36 ?? nqp::base_I(self,nqp::unbox_i($base)) !! BASE_OUT_OF_RANGE($base) } multi method base(Int:D: Int(Cool) $base, Whatever --> Str:D) { self.base($base) } multi method base(Int:D: Int(Cool) $base, Int:D $digits = 0 --> Str:D) { 2 <= $base <= 36 ?? $digits ?? $digits < 0 ?? DIGITS_OUT_OF_RANGE($digits) !! nqp::base_I(self,nqp::unbox_i($base)) ~ '.' ~ nqp::x('0',nqp::unbox_i($digits)) !! nqp::base_I(self,nqp::unbox_i($base)) !! BASE_OUT_OF_RANGE($base) } method !eggify($egg --> Str:D) { self.base(2).trans("01" => $egg) } multi method base(Int:D: "camel" --> Str:D) { self!eggify: "🐪🐫" } multi method base(Int:D: "beer" --> Str:D) { self!eggify: "🍺🍻" } # If self is Int, we assume mods are Ints also. (div fails otherwise.) # If do-not-want, user should cast invocant to proper domain. method polymod(Int:D: +@mods --> Seq:D) { fail X::OutOfRange.new( :what('invocant to polymod'), :got(self), :range<0..^Inf> ) if self < 0; gather { my $more = self; if @mods.is-lazy { for @mods -> $mod { $more ?? $mod ?? take $more mod $mod !! X::Numeric::DivideByZero.new( using => 'polymod', numerator => $more ).Failure !! last; $more = $more div $mod; } take $more if $more; } else { for @mods -> $mod { $mod ?? take $more mod $mod !! X::Numeric::DivideByZero.new( using => 'polymod', numerator => $more ).Failure; $more = $more div $mod; } take $more; } } } method expmod(Int:D: Int:D \base, Int:D \mod --> Int:D) { nqp::expmod_I(self, nqp::decont(base), nqp::decont(mod), Int); } method is-prime(--> Bool:D) { nqp::hllbool(nqp::isprime_I(self)) } method floor(Int:D:) { self } method ceiling(Int:D:) { self } proto method round(|) {*} multi method round(Int:D:) { self } multi method round(Int:D: Real(Cool) $scale --> Real:D) { (self / $scale + 1/2).floor * $scale } method lsb(Int:D: --> Int:D) { nqp::unless( self, # short-circuit `0`, as it doesn't have any bits set… Nil, # … and the algo we'll use requires at least one that is. nqp::stmts( (my int $lsb), (my $x := nqp::abs_I(self, Int)), nqp::while( # "fast-forward": shift off by whole all-zero-bit bytes nqp::isfalse(nqp::bitand_I($x, 0xFF, Int)), nqp::stmts( ($lsb += 8), ($x := nqp::bitshiftr_I($x, 8, Int)))), nqp::while( # our lsb is in the current byte; shift off zero bits nqp::isfalse(nqp::bitand_I($x, 0x01, Int)), nqp::stmts( ++$lsb, ($x := nqp::bitshiftr_I($x, 1, Int)))), $lsb)) # we shifted enough to get to the first set bit } method msb(Int:D: --> Int:D) { nqp::unless( self, Nil, nqp::if( nqp::iseq_I(self, -1), 0, nqp::stmts( (my int $msb), (my $x := self), nqp::islt_I($x, 0) # handle conversion of negatives && ($x := nqp::mul_I(-2, nqp::add_I($x, 1, Int), Int)), nqp::while( nqp::isgt_I($x, 0xFF), nqp::stmts( ($msb += 8), ($x := nqp::bitshiftr_I($x, 8, Int)))), nqp::isgt_I($x, 0x0F) && ($msb += 4) && ($x := nqp::bitshiftr_I($x, 4, Int)), nqp::bitand_I($x, 0x8, Int) && ($msb += 3) || nqp::bitand_I($x, 0x4, Int) && ($msb += 2) || nqp::bitand_I($x, 0x2, Int) && ($msb += 1), $msb))) } method narrow(Int:D:) { self } method Range(Int:U: --> Range:D) { given self { when int { $?BITS == 64 ?? int64.Range !! int32.Range } when uint { $?BITS == 64 ?? uint64.Range !! uint32.Range } when int64 { Range.new(-9223372036854775808, 9223372036854775807) } when int32 { Range.new( -2147483648, 2147483647 ) } when int16 { Range.new( -32768, 32767 ) } when int8 { Range.new( -128, 127 ) } # Bring back in a future Raku version, or just put on the type object #when int4 { Range.new( -8, 7 ) } #when int2 { Range.new( -2, 1 ) } #when int1 { Range.new( -1, 0 ) } when uint64 { Range.new( 0, 18446744073709551615 ) } when uint32 { Range.new( 0, 4294967295 ) } when uint16 { Range.new( 0, 65535 ) } when uint8 { Range.new( 0, 255 ) } when byte { Range.new( 0, 255 ) } # Bring back in a future Raku version, or just put on the type object #when uint4 { Range.new( 0, 15 ) } #when uint2 { Range.new( 0, 3 ) } #when uint1 { Range.new( 0, 1 ) } default { # some other kind of Int .^name eq 'UInt' ?? Range.new( 0, Inf, :excludes-max ) !! Range.new( -Inf, Inf, :excludes-min, :excludes-max ) } } } } multi sub prefix:<++>(Int:D $a is rw --> Int:D) { $a = nqp::add_I(nqp::decont($a), 1, Int); } multi sub prefix:<++>( int $a is rw --> int) { $a = nqp::add_i($a, 1) } multi sub prefix:<++>(uint $a is rw --> uint) { $a = nqp::add_i($a, 1) } multi sub prefix:<-->(Int:D $a is rw --> Int:D) { $a = nqp::sub_I(nqp::decont($a), 1, Int); } multi sub prefix:<-->( int $a is rw --> int) { $a = nqp::sub_i($a, 1) } multi sub prefix:<-->(uint $a is rw --> uint) { $a = nqp::sub_i($a, 1) } multi sub postfix:<++>(Int:D $a is rw --> Int:D) { my \b := nqp::decont($a); $a = nqp::add_I(b, 1, Int); b } multi sub postfix:<++>(int $a is rw --> int) { my int $b = $a; $a = nqp::add_i($b, 1); $b } multi sub postfix:<++>(uint $a is rw --> uint) { my uint $b = $a; $a = my uint $ = nqp::add_i($b, 1); $b } multi sub postfix:<-->(Int:D $a is rw --> Int:D) { my \b := nqp::decont($a); $a = nqp::sub_I(b, 1, Int); b } multi sub postfix:<-->(int $a is rw --> int) { my int $b = $a; $a = nqp::sub_i($b, 1); $b } multi sub postfix:<-->(uint $a is rw --> uint) { my int $b = $a; $a = nqp::sub_i($b, 1); $b } multi sub prefix:<->(Int:D \a --> Int:D) { nqp::neg_I(nqp::decont(a), Int) } multi sub prefix:<->( int $a --> int) { nqp::neg_i($a) } multi sub prefix:<->(uint $a --> int) { nqp::neg_i($a) } multi sub abs(Int:D \a --> Int:D) { nqp::abs_I(nqp::decont(a), Int) } multi sub abs( int $a --> int) { nqp::abs_i($a) } multi sub abs(uint $a --> uint) { $a } multi sub infix:<+>(Int:D $a, Int:D $b --> Int:D) { nqp::add_I($a,$b,Int) } multi sub infix:<+>( int $a, int $b --> int) { nqp::add_i($a,$b) } multi sub infix:<+>(uint $a, uint $b --> uint) { nqp::add_i($a,$b) } multi sub infix:<->(Int:D $a, Int:D $b --> Int:D) { nqp::sub_I($a,$b,Int) } multi sub infix:<->( int $a, int $b --> int) { nqp::sub_i($a,$b) } multi sub infix:<->(uint $a, uint $b --> uint) { nqp::sub_i($a,$b) } multi sub infix:<*>(Int:D $a, Int:D $b --> Int:D) { nqp::mul_I($a,$b,Int) } multi sub infix:<*>( int $a, int $b --> int) { nqp::mul_i($a,$b) } multi sub infix:<*>(uint $a, uint $b --> uint) { nqp::mul_i($a,$b) } multi sub infix:(Int:D $a, Int:D $b --> Bool:D) { nqp::hllbool( # need to check types as enums such as Bool wind up here nqp::eqaddr($a.WHAT,$b.WHAT) && nqp::iseq_I($a,$b) ) } multi sub infix:(int $a, int $b --> Bool:D) { nqp::hllbool(nqp::iseq_i($a,$b)) } multi sub infix:(uint $a, uint $b --> Bool:D) { nqp::hllbool(nqp::iseq_i($a,$b)) } multi sub infix:
(Int:D $a, Int:D $b --> Int:D) { $b ?? nqp::div_I($a,$b,Int) !! X::Numeric::DivideByZero.new(:using
, :numerator($a)).Failure } # relies on opcode or hardware to detect division by 0 multi sub infix:
(int $a, int $b --> int) { nqp::div_i($a, $b) } multi sub infix:
(uint $a, uint $b --> uint) { nqp::div_i($a, $b) } multi sub infix:<%>(Int:D $a, Int:D $b --> Int:D) { nqp::isbig_I($a) || nqp::isbig_I($b) ?? $b ?? nqp::mod_I($a,$b,Int) !! X::Numeric::DivideByZero.new(:using<%>, :numerator($a)).Failure !! nqp::isne_i($b,0) # quick fix https://github.com/Raku/old-issue-tracker/issues/4999 ?? nqp::mod_i(nqp::add_i(nqp::mod_i($a,$b),$b),$b) !! X::Numeric::DivideByZero.new(:using<%>, :numerator($a)).Failure } # relies on opcode or hardware to detect division by 0 # quick fix https://github.com/Raku/old-issue-tracker/issues/4999 multi sub infix:<%>(int $a, int $b --> int) { nqp::mod_i(nqp::add_i(nqp::mod_i($a,$b),$b),$b) } multi sub infix:<%>(uint $a, uint $b --> uint) { nqp::mod_i(nqp::add_i(nqp::mod_i($a,$b),$b),$b) } multi sub infix:<%%>(Int:D $a, Int:D $b) { nqp::isbig_I($a) || nqp::isbig_I($b) ?? $b ?? !nqp::mod_I($a,$b,Int) !! X::Numeric::DivideByZero.new( using => 'infix:<%%>', :numerator($a) ).Failure !! nqp::isne_i($b,0) ?? nqp::hllbool(nqp::not_i(nqp::mod_i($a,$b))) !! X::Numeric::DivideByZero.new( using => 'infix:<%%>', :numerator($a) ).Failure } multi sub infix:<%%>(int $a, int $b --> Bool:D) { nqp::hllbool(nqp::iseq_i(nqp::mod_i($a, $b), 0)) } multi sub infix:<%%>(uint $a, uint $b --> Bool:D) { nqp::hllbool(nqp::iseq_i(nqp::mod_i($a, $b), 0)) } multi sub infix:<**>(Int:D $a, Int:D $b --> Real:D) { nqp::isge_I($b,0) # when a**b is too big nqp::pow_I returns Inf ?? nqp::istype((my $power := nqp::pow_I($a,$b,Num,Int)),Int) ?? $power !! X::Numeric::Overflow.new.Failure # when a**b is too big nqp::pow_I returns Inf !! nqp::istype(($power := nqp::pow_I($a,nqp::neg_I($b,Int),Num,Int)),Num) || (nqp::istype(($power := CREATE_RATIONAL_FROM_INTS(1, $power, Int, Int)),Num) && nqp::iseq_n($power,0e0) && nqp::isne_I($a,0)) ?? X::Numeric::Underflow.new.Failure !! $power } multi sub infix:<**>( int $a, int $b --> int) { nqp::pow_i($a, $b) } multi sub infix:<**>(uint $a, uint $b --> uint) { nqp::pow_i($a, $b) } multi sub infix:(--> 1) { } multi sub infix:(Int:D $x) { $x } multi sub infix:(Int:D $a, Int:D $b --> Int:D) { nqp::lcm_I($a,$b,Int) } multi sub infix:( int $a, int $b --> int) { nqp::lcm_i($a,$b) } multi sub infix:(uint $a, uint $b --> uint) { nqp::lcm_i($a,$b) } multi sub infix:(Int:D $x) { $x } multi sub infix:(Int:D $a, Int:D $b --> Int:D) { nqp::gcd_I($a,$b,Int) } multi sub infix:( int $a, int $b --> int) { nqp::gcd_i($a,$b) } multi sub infix:(uint $a, uint $b --> uint) { nqp::gcd_i($a,$b) } multi sub infix:<===>(Int:D $a, Int:D $b --> Bool:D) { nqp::hllbool( nqp::eqaddr($a.WHAT,$b.WHAT) && nqp::iseq_I($a,$b) ) } # hey, the optimizer is smart enough to figure these ones out for us, no? multi sub infix:<===>( int $a, int $b --> Bool:D) { $a == $b } multi sub infix:<===>(uint $a, uint $b --> Bool:D) { $a == $b } multi sub infix:<==>(Int:D $a, Int:D $b --> Bool:D) { nqp::hllbool(nqp::iseq_I($a,$b)) } multi sub infix:<==>(int $a, int $b --> Bool:D) { nqp::hllbool(nqp::iseq_i($a,$b)) } multi sub infix:<==>(uint $a, uint $b --> Bool:D) { nqp::hllbool(nqp::iseq_u($a,$b)) } multi sub infix:<==>(int $a, uint $b --> Bool:D) { nqp::hllbool(nqp::isge_i($a,0) && nqp::iseq_u($a,$b)) } multi sub infix:<==>(uint $a, int $b --> Bool:D) { nqp::hllbool(nqp::isge_i($b,0) && nqp::iseq_u($a,$b)) } multi sub infix:(Int:D $a, Int:D $b --> Bool:D) { nqp::hllbool(nqp::isne_I($a,$b)) } multi sub infix:(int $a, int $b --> Bool:D) { nqp::hllbool(nqp::isne_i($a,$b)) } multi sub infix:(uint $a, uint $b --> Bool:D) { nqp::hllbool(nqp::isne_u($a,$b)) } multi sub infix:(int $a, uint $b --> Bool:D) { nqp::hllbool(nqp::isge_i($a,0) && nqp::isne_u($a,$b)) } multi sub infix:(uint $a, int $b --> Bool:D) { nqp::hllbool(nqp::isge_i($b,0) && nqp::isne_u($a,$b)) } multi sub infix:«<»(Int:D $a, Int:D $b --> Bool:D) { nqp::hllbool(nqp::islt_I($a,$b)) } multi sub infix:«<»(int $a, int $b --> Bool:D) { nqp::hllbool(nqp::islt_i($a,$b)) } multi sub infix:«<»(uint $a, uint $b --> Bool:D) { nqp::hllbool(nqp::islt_u($a,$b)) } multi sub infix:«<»(int $a, uint $b --> Bool:D) { nqp::hllbool(nqp::islt_i($a,0) || nqp::islt_u($a,$b)) } multi sub infix:«<»(uint $a, int $b --> Bool:D) { nqp::hllbool(nqp::isge_i($b,0) && nqp::islt_u($a,$b)) } multi sub infix:«<=»(Int:D $a, Int:D $b --> Bool:D) { nqp::hllbool(nqp::isle_I($a,$b)) } multi sub infix:«<=»(int $a, int $b --> Bool:D) { nqp::hllbool(nqp::isle_i($a,$b)) } multi sub infix:«<=»(uint $a, uint $b --> Bool:D) { nqp::hllbool(nqp::isle_u($a,$b)) } multi sub infix:«<=»(int $a, uint $b --> Bool:D) { nqp::hllbool(nqp::isle_i($a,0) || nqp::isle_u($a,$b)) } multi sub infix:«<=»(uint $a, int $b --> Bool:D) { nqp::hllbool(nqp::isge_i($b,0) && nqp::isle_u($a,$b)) } multi sub infix:«>»(Int:D $a, Int:D $b --> Bool:D) { nqp::hllbool(nqp::isgt_I($a,$b)) } multi sub infix:«>»(int $a, int $b --> Bool:D) { nqp::hllbool(nqp::isgt_i($a,$b)) } multi sub infix:«>»(uint $a, uint $b --> Bool:D) { nqp::hllbool(nqp::isgt_u($a,$b)) } multi sub infix:«>»(int $a, uint $b --> Bool:D) { nqp::hllbool(nqp::isgt_i($a,0) && nqp::isgt_u($a,$b)) } multi sub infix:«>»(uint $a, int $b --> Bool:D) { nqp::hllbool(nqp::islt_i($b,0) || nqp::isgt_u($a,$b)) } multi sub infix:«>=»(Int:D $a, Int:D $b --> Bool:D) { nqp::hllbool(nqp::isge_I($a,$b)) } multi sub infix:«>=»(int $a, int $b --> Bool:D) { nqp::hllbool(nqp::isge_i($a,$b)) } multi sub infix:«>=»(uint $a, uint $b --> Bool:D) { nqp::hllbool(nqp::isge_u($a,$b)) } multi sub infix:«>=»(int $a, uint $b --> Bool:D) { nqp::hllbool(nqp::isge_i($a,0) && nqp::isge_u($a,$b)) } multi sub infix:«>=»(uint $a, int $b --> Bool:D) { nqp::hllbool(nqp::isle_i($b,0) || nqp::isge_u($a,$b)) } multi sub infix:<+|>(Int:D $a, Int:D $b --> Int:D) { nqp::bitor_I($a,$b,Int) } multi sub infix:<+|>( int $a, int $b --> int) { nqp::bitor_i($a,$b) } multi sub infix:<+|>(uint $a, uint $b --> uint) { nqp::bitor_i($a,$b) } multi sub infix:<+&>(Int:D $a, Int:D $b --> Int:D) { nqp::bitand_I($a,$b,Int) } multi sub infix:<+&>( int $a, int $b --> int) { nqp::bitand_i($a,$b) } multi sub infix:<+&>(uint $a, uint $b --> uint) { nqp::bitand_i($a,$b) } multi sub infix:<+^>(Int:D $a, Int:D $b --> Int:D) { nqp::bitxor_I($a,$b,Int) } multi sub infix:<+^>( int $a, int $b --> int) { nqp::bitxor_i($a, $b) } multi sub infix:<+^>(uint $a, uint $b --> uint) { nqp::bitxor_i($a, $b) } multi sub infix:«+<»(Int:D $a, Int:D $b --> Int:D) { nqp::bitshiftl_I($a,$b,Int) } multi sub infix:«+<»(int $a, int $b --> int) { nqp::bitshiftl_i($a,$b); } multi sub infix:«+<»(uint $a, uint $b --> int) { nqp::bitshiftl_i($a,$b); } multi sub infix:«+>»(Int:D $a, Int:D $b --> Int:D) { nqp::bitshiftr_I($a,$b,Int) } multi sub infix:«+>»(int $a, int $b --> int) { nqp::bitshiftr_i($a,$b) } multi sub infix:«+>»(uint $a, uint $b --> int) { nqp::bitshiftr_i($a,$b) } multi sub prefix:<+^>(Int:D $a --> Int:D) { nqp::bitneg_I($a,Int) } multi sub prefix:<+^>( int $a --> int) { nqp::bitneg_i($a) } multi sub prefix:<+^>(uint $a --> uint) { nqp::bitneg_u($a) } proto sub is-prime($, *%) is pure {*} multi sub is-prime(\x --> Int:D) { x.is-prime } proto sub expmod($, $, $, *%) is pure {*} multi sub expmod(Int:D $base, Int:D $exp, Int:D $mod --> Int:D) { nqp::expmod_I($base,$exp,$mod,Int) } multi sub expmod(\base, \exp, \mod --> Int:D) { nqp::expmod_I( nqp::decont(base.Int), nqp::decont(exp.Int), nqp::decont(mod.Int), Int ) } proto sub lsb($, *%) {*} multi sub lsb(Int:D $a --> Int:D) { $a.lsb } proto sub msb($, *%) {*} multi sub msb(Int:D $a --> Int:D) { $a.msb } #line 1 SETTING::src/core.c/Bool.rakumod # enum Bool declared in BOOTSTRAP BEGIN { Bool.^add_method('Bool', my proto method Bool(|) {*}); Bool.^add_method('gist', my proto method gist(|) {*}); Bool.^add_method('Numeric', my proto method Numeric(|) {*}); Bool.^add_method('Int', my proto method Int(|) {*}); Bool.^add_method('ACCEPTS', my proto method ACCEPTS(|) {*}); Bool.^add_method('pick', my proto method pick(|) {*}); Bool.^add_method('roll', my proto method roll(|) {*}); Bool.^add_method('raku', my proto method raku(|) {*}); } BEGIN { Bool.^add_multi_method('Bool', my multi method Bool(Bool:D:) { self }); Bool.^add_multi_method('gist', my multi method gist(Bool:D:) { self ?? 'True' !! 'False' }); Bool.^add_multi_method('Str', my multi method Str(Bool:D:) { self ?? 'True' !! 'False' }); Bool.^add_multi_method('Numeric', my multi method Numeric(Bool:D:) { self ?? 1 !! 0 }); Bool.^add_multi_method('Int', my multi method Int(Bool:D:) { self ?? 1 !! 0 }); Bool.^add_multi_method('Real', my multi method Real(Bool:D:) { self ?? 1 !! 0 }); Bool.^add_multi_method('ACCEPTS', my multi method ACCEPTS(Bool:D: Mu \topic ) { self }); Bool.^add_multi_method('raku', my multi method raku(Bool:D:) { self ?? 'Bool::True' !! 'Bool::False' }); Bool.^add_multi_method('pick', my multi method pick(Bool:U:) { nqp::hllbool(nqp::isge_n(nqp::rand_n(2e0), 1e0)) }); Bool.^add_multi_method('roll', my multi method roll(Bool:U:) { nqp::hllbool(nqp::isge_n(nqp::rand_n(2e0), 1e0)) }); } BEGIN { Bool.^add_multi_method('ACCEPTS', my multi method ACCEPTS(Bool:U \SELF: Junction:D \topic ) { topic.THREAD: { SELF.ACCEPTS: $_ } }); } BEGIN { Bool.^add_multi_method('Bool', my multi method Bool(Bool:U:) { Bool::False }); Bool.^add_multi_method('ACCEPTS', my multi method ACCEPTS(Bool:U: Mu \topic ) { nqp::hllbool(nqp::istype(topic, self)) }); Bool.^add_multi_method('gist', my multi method gist(Bool:U:) { '(Bool)' }); Bool.^add_multi_method('raku', my multi method raku(Bool:U:) { 'Bool' }); Bool.^add_multi_method('pick', my multi method pick(Bool:U: $n) { self.^enum_value_list.pick($n) }); Bool.^add_multi_method('roll', my multi method roll(Bool:U: $n) { self.^enum_value_list.roll($n) }); Bool.^add_method('pred', my method pred() { Bool::False }); Bool.^add_method('succ', my method succ() { Bool::True }); Bool.^add_method('new', my method new(|) { X::Constructor::BadType.new(type => self.WHAT).throw }); Bool.^add_method('enums', my method enums() { self.^enum_values.Map }); Bool.^compose; } multi sub prefix:<++>(Bool $a is rw) { $a = True; } multi sub prefix:<-->(Bool $a is rw) { $a = False; } multi sub postfix:<++>(Bool:U $a is rw --> False) { $a = True } multi sub postfix:<-->(Bool:U $a is rw) { $a = False; } multi sub postfix:<++>(Bool:D $a is rw) { if $a { True } else { $a = True; False } } multi sub postfix:<-->(Bool:D $a is rw) { if $a { $a = False; True } else { False } } proto sub prefix:(Mu, *%) is pure {*} multi sub prefix:(Bool:D $a) { $a } multi sub prefix:(Bool:U --> Bool::False) { } multi sub prefix:(Mu \a) { a.Bool } proto sub prefix:(Mu, *%) is pure {*} multi sub prefix:(Bool:D $a) { $a } multi sub prefix:(Bool:U --> Bool::False) { } multi sub prefix:(Mu \a) { a.Bool } proto sub prefix:(Mu, *%) is pure {*} multi sub prefix:(Bool:D $a) { $a ?? False !! True } multi sub prefix:(Bool:U --> Bool::True) { } multi sub prefix:(Mu \a) { nqp::hllbool(nqp::not_i(nqp::istrue(a))) } multi sub prefix:(Mu \a, :$exists!) { die "Precedence issue with ! and :exists, perhaps you meant :!exists?" } proto sub prefix:(Mu, *%) is pure {*} multi sub prefix:(Bool:D $a) { $a ?? False !! True } multi sub prefix:(Bool:U --> Bool::True) { } multi sub prefix:(Mu \a) { nqp::hllbool(nqp::not_i(nqp::istrue(a))) } proto sub prefix:(Mu, *%) is pure {*} multi sub prefix:(Mu \a) { not a } proto sub infix:(Mu $?, Mu $?, *%) is pure {*} multi sub infix:(--> Bool::True) { } multi sub infix:(Mu $x) { $x.Bool } multi sub infix:(Mu \a, Mu \b) { a.Bool && b.Bool } proto sub infix:(Mu $?, Mu $?, *%) is pure {*} multi sub infix:(--> Bool::False) { } multi sub infix:(Mu $x) { $x.Bool } multi sub infix:(Mu \a, Mu \b) { a.Bool || b.Bool } proto sub infix:(Mu $?, Mu $?, *%) is pure {*} multi sub infix:(Mu $x = Bool::False) { $x.Bool } multi sub infix:(Mu \a, Mu \b) { nqp::hllbool(nqp::ifnull(nqp::xor(a.Bool,b.Bool), 0)) } # These operators are normally handled as macros in the compiler; # we define them here for use as arguments to functions. proto sub infix:<&&>(|) {*} multi sub infix:<&&>(--> Bool::True) { } multi sub infix:<&&>(Mu $x) { $x } multi sub infix:<&&>(Mu \a, &b) { a && b() } multi sub infix:<&&>(Mu \a, Mu \b) { a && b } multi sub infix:<&&>(+@a) { nqp::if( (my int $elems = @a.elems), # reifies nqp::stmts( (my $reified := nqp::getattr(@a,List,'$!reified')), (my int $i = -1), nqp::until( nqp::iseq_i(++$i,$elems) || nqp::isfalse( nqp::if( nqp::istype((my $value := nqp::atpos($reified,$i)),Callable), ($value := $value()), $value ) ), nqp::null ), $value ), True ) } proto sub infix:<||>(|) {*} multi sub infix:<||>(--> Bool::False) { } multi sub infix:<||>(Mu $x) { $x } multi sub infix:<||>(Mu \a, &b) { a || b() } multi sub infix:<||>(Mu \a, Mu \b) { a || b } multi sub infix:<||>(+@a) { nqp::if( (my int $elems = @a.elems), # reifies nqp::stmts( (my $reified := nqp::getattr(@a,List,'$!reified')), (my int $i = -1), nqp::until( nqp::iseq_i(++$i,$elems) || nqp::istrue( nqp::if( nqp::istype((my $value := nqp::atpos($reified,$i)),Callable), ($value := $value()), $value ) ), nqp::null ), $value ), False ) } proto sub infix:<^^>(|) {*} multi sub infix:<^^>(--> Bool::False) { } multi sub infix:<^^>(Mu $x) { $x } multi sub infix:<^^>(Mu \a, &b) { a ^^ b() } multi sub infix:<^^>(Mu \a, Mu \b) { a ^^ b } multi sub infix:<^^>(+@a) { my Mu $a = shift @a; while @a { my Mu $b := shift @a; $b := $b() if $b ~~ Callable; next unless $b; return Nil if $a; $a := $b; } $a; } proto sub infix:(|) {*} multi sub infix:() { Any } multi sub infix:(Mu $x = Any) { $x } multi sub infix:(Mu \a, &b) { a // b } # shouldn't that be b() ?? multi sub infix:(Mu \a, Mu \b) { a // b } multi sub infix:(+@a) { nqp::if( (my int $elems = @a.elems), # reifies nqp::stmts( (my $reified := nqp::getattr(@a,List,'$!reified')), (my int $i = -1), nqp::until( nqp::iseq_i(++$i,$elems) || nqp::if( nqp::istype((my $value := nqp::atpos($reified,$i)),Callable), ($value := $value()), $value ).defined, nqp::null ), $value ), Any ) } proto sub infix:(Mu $?, Mu $?, *%) {*} multi sub infix:(--> Bool::True) { } multi sub infix:(Mu $x) { $x } multi sub infix:(Mu \a, &b) { a && b } # shouldn't that be b() ?? multi sub infix:(Mu \a, Mu \b) { a && b } proto sub infix:(Mu $?, Mu $?, *%) {*} multi sub infix:(--> Bool::False) { } multi sub infix:(Mu $x) { $x } multi sub infix:(Mu \a, &b) { a || b } # shouldn't that be b() ?? multi sub infix:(Mu \a, Mu \b) { a || b } proto sub infix:(|) {*} multi sub infix:(--> Bool::False) { } multi sub infix:(Mu $x) { $x } multi sub infix:(Mu \a, &b) { a ^^ b } # shouldn't that be b() ?? multi sub infix:(Mu \a, Mu \b) { a ^^ b } multi sub infix:(|c) { &infix:<^^>(|c) } #line 1 SETTING::src/core.c/Order.rakumod ## Order enumeration, for cmp and <=> my enum Order (:Less(-1), :Same(0), :More(1)); role Rational { ... } sub ORDER(int $i --> Order) is implementation-detail { $i ?? nqp::islt_i($i,0) ?? Less !! More !! Same } proto sub infix:($, $, *% --> Order:D) is pure {*} multi sub infix:(\a, \b) { nqp::eqaddr(nqp::decont(a), nqp::decont(b)) ?? Same !! a.Stringy cmp b.Stringy } multi sub infix:(Real:D $a, \b) { $a === -Inf ?? Less !! $a === Inf ?? More !! $a.Stringy cmp b.Stringy } multi sub infix:(\a, Real:D $b) { $b === Inf ?? Less !! $b === -Inf ?? More !! a.Stringy cmp $b.Stringy } multi sub infix:(Real:D $a, Real:D $b) { nqp::istype($a,Rational) && nqp::istype($b,Rational) ?? $a.isNaN || $b.isNaN ?? $a.Num cmp $b.Num !! $a <=> $b !! (nqp::istype($a, Rational) && nqp::isfalse($a.denominator)) || (nqp::istype($b, Rational) && nqp::isfalse($b.denominator)) ?? $a.Bridge cmp $b.Bridge !! $a === -Inf || $b === Inf ?? Less !! $a === Inf || $b === -Inf ?? More !! $a.Bridge cmp $b.Bridge } multi sub infix:(Int:D $a, Rational:D $b) { $a.isNaN || $b.isNaN ?? $a.Num cmp $b.Num !! $a <=> $b } multi sub infix:(Rational:D $a, Int:D $b) { $a.isNaN || $b.isNaN ?? $a.Num cmp $b.Num !! $a <=> $b } multi sub infix:(Int:D $a, Int:D $b) { ORDER(nqp::cmp_I($a,$b)) } multi sub infix:(int $a, int $b) { ORDER(nqp::cmp_i($a, $b)) } multi sub infix:(Code:D $a, Code:D $b) { $a.name cmp $b.name } multi sub infix:(Code:D $a, \b) { $a.name cmp b.Stringy } multi sub infix:(\a, Code:D $b) { a.Stringy cmp $b.name } multi sub infix:(List:D \a, List:D \b) { nqp::if( a.is-lazy || b.is-lazy, infix:(a.iterator, b.iterator), nqp::stmts( (my int $elems-a = a.elems), # reifies (my int $elems-b = b.elems), # reifies (my $list-a := nqp::getattr(nqp::decont(a),List,'$!reified')), (my $list-b := nqp::getattr(nqp::decont(b),List,'$!reified')), nqp::if( (my int $elems = nqp::if( nqp::islt_i($elems-a,$elems-b), $elems-a, $elems-b )), nqp::stmts( # elements to compare (my $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::eqaddr( (my $order := infix:( nqp::atpos($list-a,$i), nqp::atpos($list-b,$i) )), Same ), nqp::null ), nqp::if( nqp::eqaddr($order,Same), ORDER(nqp::cmp_i($elems-a,$elems-b)), # same, length significant $order # element different ) ), ORDER(nqp::cmp_i($elems-a,$elems-b)), # only length significant ) ) ) } multi sub infix:( Iterator:D \iter-a, Iterator:D \iter-b ) is implementation-detail { nqp::until( nqp::eqaddr((my $a := iter-a.pull-one),IterationEnd) || nqp::eqaddr((my $b := iter-b.pull-one),IterationEnd) || nqp::not_i(nqp::eqaddr( (my $order := infix:($a,$b)), Same )), nqp::null ); nqp::if( nqp::eqaddr($order,Same), # ended because different? nqp::if( nqp::eqaddr($a,IterationEnd), # left exhausted? nqp::if( nqp::eqaddr(iter-b.pull-one,IterationEnd), # right exhausted? Same, Less ), More ), $order ) } proto sub infix:«<=>»($, $, *% --> Order:D) is pure {*} multi sub infix:«<=>»(\a, \b) { a.Real <=> b.Real } multi sub infix:«<=>»(Real $a, Real $b) { $a.Bridge <=> $b.Bridge } multi sub infix:«<=>»(Int:D $a, Int:D $b) { ORDER(nqp::cmp_I($a,$b)) } multi sub infix:«<=>»(int $a, int $b) { ORDER(nqp::cmp_i($a, $b)) } proto sub infix:($?, $?, *% --> Bool:D) is pure {*} multi sub infix:($? --> True) { } multi sub infix:($a, $b) { nqp::hllbool(nqp::eqaddr(($a cmp $b),Order::Less)) } proto sub infix:($?, $?, *% --> Bool:D) is pure {*} multi sub infix:($x? --> True) { } multi sub infix:($a, $b) { nqp::hllbool(nqp::eqaddr(($a cmp $b),Order::More)) } proto sub infix:($, $, *% --> Order:D) is pure {*} multi sub infix:(\a, \b) { a.Stringy cmp b.Stringy } proto sub infix:($, $, *% --> Order:D) is pure {*} # NOT is pure because of $*COLLATION proto sub infix:( $, $, *% --> Order:D) {*} # Now that we have the Order enum, we can use it to speed up # checks, rather than having the enum first be converted to an # integer value for comparison. augment class Any { # Make sure given comparator has an arity of 2 my sub aritize22(&by) { nqp::iseq_i(&by.arity,2) ?? &by !! { by($^a) cmp by($^b) } } # Common logic for minpairs / maxpairs method !minmaxpairs(\order, &by) { my &comparator := aritize22(&by); my $iter := self.pairs.iterator; my $result := nqp::create(IterationBuffer); nqp::until( nqp::eqaddr((my $pair := $iter.pull-one),IterationEnd) || nqp::isconcrete(my $target := $pair.value), nqp::null ); nqp::unless( nqp::eqaddr($pair,IterationEnd), nqp::stmts( # found at least one value nqp::push($result,$pair), nqp::until( nqp::eqaddr(nqp::bind($pair,$iter.pull-one),IterationEnd), nqp::if( nqp::isconcrete(my $value := $pair.value), nqp::if( nqp::eqaddr( (my $cmp-result := comparator($value,$target)), order ), nqp::stmts( # new best nqp::push(nqp::setelems($result,0),$pair), nqp::bind($target,$value) ), nqp::if( # additional best nqp::eqaddr($cmp-result,Order::Same), nqp::push($result,$pair) ) ) ) ) ) ); $result } proto method minpairs(|) {*} multi method minpairs(Any:D: &by = &infix:) { self!minmaxpairs(Order::Less, &by).List } proto method maxpairs(|) {*} multi method maxpairs(Any:D: &by = &infix:) { self!minmaxpairs(Order::More, &by).List } proto method min (|) is nodal {*} multi method min(Any:D:) { nqp::if( (my $iter := self.iterator-and-first(".min", my $min)), nqp::until( nqp::eqaddr((my $pulled := $iter.pull-one),IterationEnd), nqp::if( (nqp::isconcrete($pulled) && nqp::eqaddr($pulled cmp $min,Order::Less)), $min = $pulled ) ) ); nqp::defined($min) ?? $min !! Inf } multi method min(Any:D: &by) { my &comparator := aritize22(&by); nqp::if( (my $iter := self.iterator-and-first(".min", my $min)), nqp::until( nqp::eqaddr((my $pulled := $iter.pull-one),IterationEnd), nqp::if( (nqp::isconcrete($pulled) && nqp::eqaddr(comparator($pulled,$min),Order::Less)), $min = $pulled ) ) ); nqp::defined($min) ?? $min !! Inf } method !order-map(\order, &by, &mapper) { (nqp::elems(my $result := self!minmaxpairs(order, &by)) ?? $result.map(&mapper) !! $result ).List } multi method min(Any:D: &by = &infix:, :$k!) { $k ?? self!order-map(Order::Less, &by, *.key) !! self.min } multi method min(Any:D: &by = &infix:, :$v!) { $v ?? self!order-map(Order::Less, &by, *.value) !! self.min } multi method min(Any:D: &by = &infix:, :$kv!) { $kv ?? self!order-map(Order::Less, &by, { |(.key, .value) }) !! self.min } multi method min(Any:D: &by = &infix:, :$p!) { $p ?? self.minpairs(&by) !! self.min(&by) } proto method max (|) is nodal {*} multi method max(Any:D:) { nqp::if( (my $iter := self.iterator-and-first(".max", my $max)), nqp::until( nqp::eqaddr((my $pulled := $iter.pull-one),IterationEnd), nqp::if( (nqp::isconcrete($pulled) && nqp::eqaddr($pulled cmp $max,Order::More)), $max = $pulled ) ) ); nqp::defined($max) ?? $max !! -Inf } multi method max(Any:D: &by) { my &comparator := aritize22(&by); nqp::if( (my $iter := self.iterator-and-first(".max", my $max)), nqp::until( nqp::eqaddr((my $pulled := $iter.pull-one),IterationEnd), nqp::if( (nqp::isconcrete($pulled) && nqp::eqaddr(comparator($pulled,$max),Order::More)), $max = $pulled ) ) ); nqp::defined($max) ?? $max !! -Inf } multi method max(Any:D: &by = &infix:, :$k!) { $k ?? self!order-map(Order::More, &by, *.key) !! self.max } multi method max(Any:D: &by = &infix:, :$v!) { $v ?? self!order-map(Order::More, &by, *.value) !! self.max } multi method max(Any:D: &by = &infix:, :$kv!) { $kv ?? self!order-map(Order::More, &by, { |(.key, .value) }) !! self.max } multi method max(Any:D: &by = &infix:, :$p!) { $p ?? self.maxpairs(&by) !! self.max(&by) } method !minmax-range-init( $value, $mi is rw, $exmi is rw, $ma is rw, $exma is rw --> Nil) { $mi = $value.min; $exmi = $value.excludes-min; $ma = $value.max; $exma = $value.excludes-max; } method !minmax-range-check( $value, $mi is rw, $exmi is rw, $ma is rw, $exma is rw --> Nil) { nqp::if( nqp::eqaddr($value.min cmp $mi,Order::Less), nqp::stmts( ($mi = $value.min), ($exmi = $value.excludes-min) ) ); nqp::if( nqp::eqaddr($value.max cmp $ma,Order::More), nqp::stmts( ($ma = $value.max), ($exma = $value.excludes-max) ) ); } method !cmp-minmax-range-check( $value, &comparator, $mi is rw, $exmi is rw, $ma is rw, $exma is rw --> Nil) { nqp::if( # $cmp sigillless confuses the optimizer nqp::eqaddr(comparator($value.min,$mi),Order::Less), nqp::stmts( ($mi = $value.min), ($exmi = $value.excludes-min) ) ); nqp::if( nqp::eqaddr(comparator($value.max,$ma),Order::More), nqp::stmts( ($ma = $value.max), ($exma = $value.excludes-max) ) ); } proto method minmax (|) is nodal {*} multi method minmax(Any:D: ) { nqp::if( (my $iter := self.iterator-and-first(".minmax",my $pulled)), nqp::stmts( nqp::if( nqp::istype($pulled,Range), self!minmax-range-init($pulled, my $min,my int $excludes-min,my $max,my int $excludes-max), nqp::if( nqp::istype($pulled,Positional), self!minmax-range-init($pulled.minmax, # recurse for min/max $min,$excludes-min,$max,$excludes-max), ($min = $max = $pulled) ) ), nqp::until( nqp::eqaddr(($pulled := $iter.pull-one),IterationEnd), nqp::if( nqp::isconcrete($pulled), nqp::if( nqp::istype($pulled,Range), self!minmax-range-check($pulled, $min,$excludes-min,$max,$excludes-max), nqp::if( nqp::istype($pulled,Positional), self!minmax-range-check($pulled.minmax, $min,$excludes-min,$max,$excludes-max), nqp::if( nqp::eqaddr($pulled cmp $min,Order::Less), ($min = $pulled), nqp::if( nqp::eqaddr($pulled cmp $max,Order::More), ($max = $pulled) ) ) ) ) ) ) ) ); nqp::defined($min) ?? Range.new($min,$max,:$excludes-min,:$excludes-max) !! Range.Inf-Inf } multi method minmax(Any:D: &by) { nqp::if( (my $iter := self.iterator-and-first(".minmax",my $pulled)), nqp::stmts( (my &comparator = aritize22(&by)), nqp::if( nqp::istype($pulled,Range), self!minmax-range-init($pulled, my $min,my int $excludes-min,my $max,my int $excludes-max), nqp::if( nqp::istype($pulled,Positional), self!minmax-range-init($pulled.minmax(&by), # recurse min/max $min,$excludes-min,$max,$excludes-max), ($min = $max = $pulled) ) ), nqp::until( nqp::eqaddr(($pulled := $iter.pull-one),IterationEnd), nqp::if( nqp::isconcrete($pulled), nqp::if( nqp::istype($pulled,Range), self!cmp-minmax-range-check($pulled, &comparator,$min,$excludes-min,$max,$excludes-max), nqp::if( nqp::istype($pulled,Positional), self!cmp-minmax-range-check($pulled.minmax(&by), &comparator,$min,$excludes-min,$max,$excludes-max), nqp::if( nqp::eqaddr(comparator($pulled,$min),Order::Less), ($min = $pulled), nqp::if( nqp::eqaddr(comparator($pulled,$max),Order::More), ($max = $pulled) ) ) ) ) ) ) ) ); nqp::defined($min) ?? Range.new($min,$max,:$excludes-min,:$excludes-max) !! Range.Inf-Inf } } #line 1 SETTING::src/core.c/Rakudo/Sorting.rakumod my class Rakudo::Sorting { # https://en.wikipedia.org/wiki/Merge_sort#Bottom-up_implementation # The parameter is the HLL List to be sorted *in place* using simple cmp. method MERGESORT-REIFIED-LIST(\list) { nqp::if( nqp::isgt_i((my int $n = nqp::elems( # $A has the items to sort; $B is a work array my $A := nqp::getattr(list,List,'$!reified') )),2), nqp::stmts( # we actually need to sort (my $B := nqp::setelems(nqp::create(IterationBuffer),$n)), # Each 1-element run in $A is already "sorted" # Make successively longer sorted runs of length 2, 4, 8, 16... # until $A is wholly sorted (my int $width = 1), nqp::while( nqp::islt_i($width,$n), nqp::stmts( (my int $l = 0), # $A is full of runs of length $width nqp::while( nqp::islt_i($l,$n), nqp::stmts( (my int $left = $l), (my int $right = nqp::add_i($l,$width)), nqp::if(nqp::isge_i($right,$n),($right = $n)), (my int $end = nqp::add_i($l,nqp::add_i($width,$width))), nqp::if(nqp::isge_i($end,$n),($end = $n)), (my int $i = $left), (my int $j = $right), (my int $k = nqp::sub_i($left,1)), # Merge two runs: $A[i .. i+width-1] and # $A[i+width .. i+2*width-1] # to $B or copy $A[i..n-1] to $B[] ( if(i+width >= n) ) nqp::while( nqp::islt_i(++$k,$end), nqp::if( nqp::islt_i($i,$right) && ( nqp::isge_i($j,$end) || nqp::eqaddr( (my $cmp := nqp::atpos($A,$i) cmp nqp::atpos($A,$j)), Order::Less ) || (nqp::eqaddr($cmp,Order::Same) && nqp::iseq_i(nqp::cmp_i($i,$j),-1) ) ), nqp::stmts( (nqp::bindpos($B,$k,nqp::atpos($A,$i))), ++$i ), nqp::stmts( (nqp::bindpos($B,$k,nqp::atpos($A,$j))), ++$j ) ) ), ($l = nqp::add_i($l,nqp::add_i($width,$width))) ) ), # Now work array $B is full of runs of length 2*width. # Copy array B to array A for next iteration. A more # efficient implementation would swap the roles of A and B. (my $temp := $B),($B := $A),($A := $temp), # swap # Now array $A is full of runs of length 2*width. ($width = nqp::add_i($width,$width)) ) ) ), # N <= 2 nqp::if( nqp::iseq_i($n,2) && nqp::eqaddr( nqp::atpos($A,0) cmp nqp::atpos($A,1), Order::More ), nqp::push($A,nqp::shift($A)) # wrong order, so swap ) ); nqp::p6bindattrinvres(list,List,'$!reified',$A) } # Takes the HLL List to be sorted *in place* using a comparator method MERGESORT-REIFIED-LIST-WITH(\list, &comparator) { nqp::eqaddr(&comparator.returns,Order:D) ?? Rakudo::Sorting.MERGESORT-REIFIED-LIST-WITH-enum(list, &comparator) !! Rakudo::Sorting.MERGESORT-REIFIED-LIST-WITH-int(list, &comparator) } # Takes the HLL List to be sorted *in place* using the comparator # that is supposed to return some value that can be coerced to an int method MERGESORT-REIFIED-LIST-WITH-int(\list, &comparator) { nqp::if( nqp::isgt_i((my int $n = nqp::elems( # $A has the items to sort; $B is a work array my $A := nqp::getattr(list,List,'$!reified') )),2), nqp::stmts( # we actually need to sort (my $B := nqp::setelems(nqp::create(IterationBuffer),$n)), # Each 1-element run in $A is already "sorted" # Make successively longer sorted runs of length 2, 4, 8, 16... # until $A is wholly sorted (my int $width = 1), nqp::while( nqp::islt_i($width,$n), nqp::stmts( (my int $l = 0), # $A is full of runs of length $width nqp::while( nqp::islt_i($l,$n), nqp::stmts( (my int $left = $l), (my int $right = nqp::add_i($l,$width)), nqp::if(nqp::isge_i($right,$n),($right = $n)), (my int $end = nqp::add_i($l,nqp::add_i($width,$width))), nqp::if(nqp::isge_i($end,$n),($end = $n)), (my int $i = $left), (my int $j = $right), (my int $k = nqp::sub_i($left,1)), # Merge two runs: $A[i .. i+width-1] and # $A[i+width .. i+2*width-1] # to $B or copy $A[i..n-1] to $B[] ( if(i+width >= n) ) nqp::while( nqp::islt_i(++$k,$end), nqp::if( nqp::islt_i($i,$right) && ( nqp::isge_i($j,$end) || nqp::islt_i( (my int $cmp = comparator( nqp::atpos($A,$i), nqp::atpos($A,$j) )), 0 ) || (nqp::iseq_i($cmp,0) && nqp::islt_i(nqp::cmp_i($i,$j),0) ) ), nqp::stmts( (nqp::bindpos($B,$k,nqp::atpos($A,$i))), ++$i ), nqp::stmts( (nqp::bindpos($B,$k,nqp::atpos($A,$j))), ++$j ) ) ), ($l = nqp::add_i($l,nqp::add_i($width,$width))) ) ), # Now work array $B is full of runs of length 2*width. # Copy array B to array A for next iteration. A more # efficient implementation would swap the roles of A and B. (my $temp := $B),($B := $A),($A := $temp), # swap # Now array $A is full of runs of length 2*width. ($width = nqp::add_i($width,$width)) ) ) ), # N <= 2 nqp::if( nqp::iseq_i($n,2) && nqp::isgt_i( comparator(nqp::atpos($A,0),nqp::atpos($A,1)), 0 ), nqp::push($A,nqp::shift($A)) # wrong order, so swap ) ); nqp::p6bindattrinvres(list,List,'$!reified',$A) } # Takes the HLL List to be sorted *in place* using the comparator # that is supposed to return an Order enum method MERGESORT-REIFIED-LIST-WITH-enum(\list, &comparator) { nqp::if( nqp::isgt_i((my int $n = nqp::elems( # $A has the items to sort; $B is a work array my $A := nqp::getattr(list,List,'$!reified') )),2), nqp::stmts( # we actually need to sort (my $B := nqp::setelems(nqp::create(IterationBuffer),$n)), # Each 1-element run in $A is already "sorted" # Make successively longer sorted runs of length 2, 4, 8, 16... # until $A is wholly sorted (my int $width = 1), nqp::while( nqp::islt_i($width,$n), nqp::stmts( (my int $l = 0), # $A is full of runs of length $width nqp::while( nqp::islt_i($l,$n), nqp::stmts( (my int $left = $l), (my int $right = nqp::add_i($l,$width)), nqp::if(nqp::isge_i($right,$n),($right = $n)), (my int $end = nqp::add_i($l,nqp::add_i($width,$width))), nqp::if(nqp::isge_i($end,$n),($end = $n)), (my int $i = $left), (my int $j = $right), (my int $k = nqp::sub_i($left,1)), # Merge two runs: $A[i .. i+width-1] and # $A[i+width .. i+2*width-1] # to $B or copy $A[i..n-1] to $B[] ( if(i+width >= n) ) nqp::while( nqp::islt_i(++$k,$end), nqp::if( nqp::islt_i($i,$right) && ( nqp::isge_i($j,$end) || nqp::eqaddr( (my $cmp := comparator(nqp::atpos($A,$i),nqp::atpos($A,$j))), Order::Less ) || (nqp::eqaddr($cmp,Order::Same) && nqp::iseq_i(nqp::cmp_i($i,$j),-1) ) ), nqp::stmts( (nqp::bindpos($B,$k,nqp::atpos($A,$i))), ++$i ), nqp::stmts( (nqp::bindpos($B,$k,nqp::atpos($A,$j))), ++$j ) ) ), ($l = nqp::add_i($l,nqp::add_i($width,$width))) ) ), # Now work array $B is full of runs of length 2*width. # Copy array B to array A for next iteration. A more # efficient implementation would swap the roles of A and B. (my $temp := $B),($B := $A),($A := $temp), # swap # Now array $A is full of runs of length 2*width. ($width = nqp::add_i($width,$width)) ) ) ), # N <= 2 nqp::if( nqp::iseq_i($n,2) && nqp::eqaddr( comparator(nqp::atpos($A,0),nqp::atpos($A,1)), Order::More ), nqp::push($A,nqp::shift($A)) # wrong order, so swap ) ); nqp::p6bindattrinvres(list,List,'$!reified',$A) } # helper sub to handle degenerate sort for indices sub degenerate-indices(\list, :$swap --> Nil) { my $O := nqp::getattr(list,List,'$!reified'); nqp::if( $swap, nqp::stmts( # swapping, implies 2 elements nqp::bindpos($O,0,1), nqp::bindpos($O,1,0) ), nqp::if( (my int $n = nqp::elems($O)), nqp::stmts( nqp::bindpos($O,0,0), nqp::if( nqp::iseq_i($n,2), nqp::bindpos($O,1,1) ) ) ) ); } # Takes the HLL List to be sorted *in place* using the mapper method MERGESORT-REIFIED-LIST-AS(\list, &mapper, :$indices) { nqp::if( nqp::isgt_i((my int $n = nqp::elems( my $O := nqp::getattr(list,List,'$!reified') # Original )),2), nqp::stmts( # we actually need to sort (my $S := # the Schwartz nqp::setelems(nqp::create(IterationBuffer),$n)), (my $A := nqp::setelems(nqp::list_i,$n)), # indexes to sort (my $B := nqp::setelems(nqp::list_i,$n)), # work array (my int $s = -1), nqp::while( # set up the Schwartz and the initial indexes nqp::islt_i(($s = nqp::add_i($s,1)),$n), nqp::bindpos($S,nqp::bindpos_i($A,$s,$s), mapper(nqp::atpos($O,$s))) ), # Each 1-element run in $A is already "sorted" # Make successively longer sorted runs of length 2, 4, 8, 16... # until $A is wholly sorted (my int $width = 1), nqp::while( nqp::islt_i($width,$n), nqp::stmts( (my int $l = 0), # $A is full of runs of length $width nqp::while( nqp::islt_i($l,$n), nqp::stmts( (my int $left = $l), (my int $right = nqp::add_i($l,$width)), nqp::if(nqp::isge_i($right,$n),($right = $n)), (my int $end = nqp::add_i($l,nqp::add_i($width,$width))), nqp::if(nqp::isge_i($end,$n),($end = $n)), (my int $i = $left), (my int $j = $right), (my int $k = nqp::sub_i($left,1)), # Merge two runs: $A[i .. i+width-1] and # $A[i+width .. i+2*width-1] # to $B or copy $A[i..n-1] to $B[] ( if(i+width >= n) ) nqp::while( nqp::islt_i(++$k,$end), nqp::if( nqp::islt_i($i,$right) && ( nqp::isge_i($j,$end) || nqp::eqaddr( (my $cmp := nqp::atpos($S,nqp::atpos_i($A,$i)) cmp nqp::atpos($S,nqp::atpos_i($A,$j))), Order::Less ) || (nqp::eqaddr($cmp,Order::Same) && nqp::iseq_i(nqp::cmp_i($i,$j),-1) ) ), nqp::stmts( (nqp::bindpos_i($B,$k,nqp::atpos_i($A,$i))), ++$i ), nqp::stmts( (nqp::bindpos_i($B,$k,nqp::atpos_i($A,$j))), ++$j ) ) ), ($l = nqp::add_i($l,nqp::add_i($width,$width))) ) ), # Now work array $B is full of runs of length 2*width. # Copy array B to array A for next iteration. A more # efficient implementation would swap the roles of A and B. (my $temp := $B),($B := $A),($A := $temp), # swap # Now array $A is full of runs of length 2*width. ($width = nqp::add_i($width,$width)) ) ), ($s = -1), # repurpose the Schwartz for the result nqp::if( $indices, nqp::while( # indices only nqp::islt_i(++$s,$n), nqp::bindpos($S,$s,nqp::atpos_i($A,$s)) ), nqp::while( # actual values nqp::islt_i(++$s,$n), nqp::bindpos($S,$s,nqp::atpos($O,nqp::atpos_i($A,$s))) ) ), nqp::bindattr(list,List,'$!reified',$S) ), # N <= 2 nqp::if( nqp::iseq_i($n,2) && nqp::eqaddr( mapper(nqp::atpos($O,0)) cmp mapper(nqp::atpos($O,1)), Order::More ), nqp::if( # wrong order, so swap $indices, degenerate-indices(list, :swap), nqp::push($O,nqp::shift($O)) ), nqp::if( # no swapping needed $indices, degenerate-indices(list) ) ) ); list # we did changes in place } # Takes the HLL List to be sorted *in place* using the comparator # and always produce indices method MERGESORT-REIFIED-LIST-INDICES(\list, &comparator) { nqp::if( nqp::isgt_i((my int $n = nqp::elems( my $O := nqp::getattr(list,List,'$!reified') # Original )),2), nqp::stmts( # we actually need to sort (my $A := nqp::setelems(nqp::list_i,$n)), # indexes to sort (my $B := nqp::setelems(nqp::list_i,$n)), # work array (my int $s = -1), nqp::while( # set up the initial indexes nqp::islt_i(++$s,$n), nqp::bindpos_i($A,$s,$s) ), # Each 1-element run in $A is already "sorted" # Make successively longer sorted runs of length 2, 4, 8, 16... # until $A is wholly sorted (my int $width = 1), nqp::while( nqp::islt_i($width,$n), nqp::stmts( (my int $l = 0), # $A is full of runs of length $width nqp::while( nqp::islt_i($l,$n), nqp::stmts( (my int $left = $l), (my int $right = nqp::add_i($l,$width)), nqp::if(nqp::isge_i($right,$n),($right = $n)), (my int $end = nqp::add_i($l,nqp::add_i($width,$width))), nqp::if(nqp::isge_i($end,$n),($end = $n)), (my int $i = $left), (my int $j = $right), (my int $k = nqp::sub_i($left,1)), # Merge two runs: $A[i .. i+width-1] and # $A[i+width .. i+2*width-1] # to $B or copy $A[i..n-1] to $B[] ( if(i+width >= n) ) nqp::while( nqp::islt_i(++$k,$end), nqp::if( nqp::islt_i($i,$right) && ( nqp::isge_i($j,$end) || nqp::eqaddr( (my $cmp := comparator( nqp::atpos($O,nqp::atpos_i($A,$i)), nqp::atpos($O,nqp::atpos_i($A,$j)) )), Order::Less ) || (nqp::eqaddr($cmp,Order::Same) && nqp::iseq_i(nqp::cmp_i($i,$j),-1) ) ), nqp::stmts( (nqp::bindpos_i($B,$k,nqp::atpos_i($A,$i))), ++$i ), nqp::stmts( (nqp::bindpos_i($B,$k,nqp::atpos_i($A,$j))), ++$j ) ) ), ($l = nqp::add_i($l,nqp::add_i($width,$width))) ) ), # Now work array $B is full of runs of length 2*width. # Copy array B to array A for next iteration. A more # efficient implementation would swap the roles of A and B. (my $temp := $B),($B := $A),($A := $temp), # swap # Now array $A is full of runs of length 2*width. ($width = nqp::add_i($width,$width)) ) ), ($s = -1), # repurpose the Original for the indices nqp::while( # indices only nqp::islt_i(++$s,$n), nqp::bindpos($O,$s,nqp::atpos_i($A,$s)) ) ), # N <= 2 degenerate-indices( list, :swap(nqp::iseq_i($n,2) && nqp::eqaddr( comparator(nqp::atpos($O,0),nqp::atpos($O,1)), Order::More )) ) ); list # we did changes in place } #- start of generated part of sorting strarray logic -------------------------- #- Generated on 2022-02-17T16:35:04+01:00 by ./tools/build/makeNATIVE_SORTING.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE # https://en.wikipedia.org/wiki/Merge_sort#Bottom-up_implementation # Sort a native str array (or nqp::list_s) and return the result. # Uses the given str array as one of the buffers for performance reasons. # Please nqp::clone first if you want to keep the original intact. method MERGESORT-str(Mu \sortable) { nqp::if( nqp::isgt_i((my int $n = nqp::elems(sortable)),2), # $A has the items to sort; $B is a work array nqp::stmts( (my Mu $A := sortable), (my Mu $B := nqp::setelems(nqp::create(nqp::what(sortable)),$n)), # Each 1-element run in $A is already "sorted" # Make successively longer sorted runs of length 2, 4, 8, 16... # until $A is wholly sorted (my int $width = 1), nqp::while( nqp::islt_i($width,$n), nqp::stmts( (my int $l = 0), # $A is full of runs of length $width nqp::while( nqp::islt_i($l,$n), nqp::stmts( (my int $left = $l), (my int $right = nqp::add_i($l,$width)), nqp::if(nqp::isge_i($right,$n),($right = $n)), (my int $end = nqp::add_i($l,nqp::add_i($width,$width))), nqp::if(nqp::isge_i($end,$n),($end = $n)), (my int $i = $left), (my int $j = $right), (my int $k = nqp::sub_i($left,1)), # Merge two runs: $A[i .. i+width-1] and # $A[i+width .. i+2*width-1] # to $B or copy $A[i..n-1] to $B[] ( if(i+width >= n) ) nqp::while( nqp::islt_i(++$k,$end), nqp::if( nqp::islt_i($i,$right) && ( nqp::isge_i($j,$end) || nqp::islt_s( nqp::atpos_s($A,$i), nqp::atpos_s($A,$j) ) ), nqp::stmts( nqp::bindpos_s($B,$k,nqp::atpos_s($A,$i)), ++$i ), nqp::stmts( nqp::bindpos_s($B,$k,nqp::atpos_s($A,$j)), ++$j ) ) ), ($l = nqp::add_i($l,nqp::add_i($width,$width))) ) ), # Now work array $B is full of runs of length 2*width. # Copy array B to array A for next iteration. A more # efficient implementation would swap the roles of A and B. (my Mu $temp := $B),($B := $A),($A := $temp), # swap # Now array $A is full of runs of length 2*width. ($width = nqp::add_i($width,$width)) ) ), $A ), nqp::stmts( # 2 elements or less (my \result := nqp::clone(sortable)), nqp::unless( nqp::islt_i($n,2) || nqp::isle_s(nqp::atpos_s(result,0),nqp::atpos_s(result,1)), nqp::push_s(result,nqp::shift_s(result)) ), result ) ) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of sorting strarray logic ---------------------------- #- start of generated part of sorting intarray logic -------------------------- #- Generated on 2022-02-17T16:35:04+01:00 by ./tools/build/makeNATIVE_SORTING.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE # https://en.wikipedia.org/wiki/Merge_sort#Bottom-up_implementation # Sort a native int array (or nqp::list_i) and return the result. # Uses the given int array as one of the buffers for performance reasons. # Please nqp::clone first if you want to keep the original intact. method MERGESORT-int(Mu \sortable) { nqp::if( nqp::isgt_i((my int $n = nqp::elems(sortable)),2), # $A has the items to sort; $B is a work array nqp::stmts( (my Mu $A := sortable), (my Mu $B := nqp::setelems(nqp::create(nqp::what(sortable)),$n)), # Each 1-element run in $A is already "sorted" # Make successively longer sorted runs of length 2, 4, 8, 16... # until $A is wholly sorted (my int $width = 1), nqp::while( nqp::islt_i($width,$n), nqp::stmts( (my int $l = 0), # $A is full of runs of length $width nqp::while( nqp::islt_i($l,$n), nqp::stmts( (my int $left = $l), (my int $right = nqp::add_i($l,$width)), nqp::if(nqp::isge_i($right,$n),($right = $n)), (my int $end = nqp::add_i($l,nqp::add_i($width,$width))), nqp::if(nqp::isge_i($end,$n),($end = $n)), (my int $i = $left), (my int $j = $right), (my int $k = nqp::sub_i($left,1)), # Merge two runs: $A[i .. i+width-1] and # $A[i+width .. i+2*width-1] # to $B or copy $A[i..n-1] to $B[] ( if(i+width >= n) ) nqp::while( nqp::islt_i(++$k,$end), nqp::if( nqp::islt_i($i,$right) && ( nqp::isge_i($j,$end) || nqp::islt_i( nqp::atpos_i($A,$i), nqp::atpos_i($A,$j) ) ), nqp::stmts( nqp::bindpos_i($B,$k,nqp::atpos_i($A,$i)), ++$i ), nqp::stmts( nqp::bindpos_i($B,$k,nqp::atpos_i($A,$j)), ++$j ) ) ), ($l = nqp::add_i($l,nqp::add_i($width,$width))) ) ), # Now work array $B is full of runs of length 2*width. # Copy array B to array A for next iteration. A more # efficient implementation would swap the roles of A and B. (my Mu $temp := $B),($B := $A),($A := $temp), # swap # Now array $A is full of runs of length 2*width. ($width = nqp::add_i($width,$width)) ) ), $A ), nqp::stmts( # 2 elements or less (my \result := nqp::clone(sortable)), nqp::unless( nqp::islt_i($n,2) || nqp::isle_i(nqp::atpos_i(result,0),nqp::atpos_i(result,1)), nqp::push_i(result,nqp::shift_i(result)) ), result ) ) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of sorting intarray logic ---------------------------- #- start of generated part of sorting uintarray logic -------------------------- #- Generated on 2022-02-17T16:35:04+01:00 by ./tools/build/makeNATIVE_SORTING.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE # https://en.wikipedia.org/wiki/Merge_sort#Bottom-up_implementation # Sort a native uint array (or nqp::list_i) and return the result. # Uses the given uint array as one of the buffers for performance reasons. # Please nqp::clone first if you want to keep the original intact. method MERGESORT-uint(Mu \sortable) { nqp::if( nqp::isgt_i((my int $n = nqp::elems(sortable)),2), # $A has the items to sort; $B is a work array nqp::stmts( (my Mu $A := sortable), (my Mu $B := nqp::setelems(nqp::create(nqp::what(sortable)),$n)), # Each 1-element run in $A is already "sorted" # Make successively longer sorted runs of length 2, 4, 8, 16... # until $A is wholly sorted (my int $width = 1), nqp::while( nqp::islt_i($width,$n), nqp::stmts( (my int $l = 0), # $A is full of runs of length $width nqp::while( nqp::islt_i($l,$n), nqp::stmts( (my int $left = $l), (my int $right = nqp::add_i($l,$width)), nqp::if(nqp::isge_i($right,$n),($right = $n)), (my int $end = nqp::add_i($l,nqp::add_i($width,$width))), nqp::if(nqp::isge_i($end,$n),($end = $n)), (my int $i = $left), (my int $j = $right), (my int $k = nqp::sub_i($left,1)), # Merge two runs: $A[i .. i+width-1] and # $A[i+width .. i+2*width-1] # to $B or copy $A[i..n-1] to $B[] ( if(i+width >= n) ) nqp::while( nqp::islt_i(++$k,$end), nqp::if( nqp::islt_i($i,$right) && ( nqp::isge_i($j,$end) || nqp::islt_i( nqp::atpos_u($A,$i), nqp::atpos_u($A,$j) ) ), nqp::stmts( nqp::bindpos_u($B,$k,nqp::atpos_u($A,$i)), ++$i ), nqp::stmts( nqp::bindpos_u($B,$k,nqp::atpos_u($A,$j)), ++$j ) ) ), ($l = nqp::add_i($l,nqp::add_i($width,$width))) ) ), # Now work array $B is full of runs of length 2*width. # Copy array B to array A for next iteration. A more # efficient implementation would swap the roles of A and B. (my Mu $temp := $B),($B := $A),($A := $temp), # swap # Now array $A is full of runs of length 2*width. ($width = nqp::add_i($width,$width)) ) ), $A ), nqp::stmts( # 2 elements or less (my \result := nqp::clone(sortable)), nqp::unless( nqp::islt_i($n,2) || nqp::isle_i(nqp::atpos_u(result,0),nqp::atpos_u(result,1)), nqp::push_i(result,nqp::shift_i(result)) ), result ) ) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of sorting uintarray logic ---------------------------- #- start of generated part of sorting numarray logic -------------------------- #- Generated on 2022-02-17T16:35:04+01:00 by ./tools/build/makeNATIVE_SORTING.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE # https://en.wikipedia.org/wiki/Merge_sort#Bottom-up_implementation # Sort a native num array (or nqp::list_n) and return the result. # Uses the given num array as one of the buffers for performance reasons. # Please nqp::clone first if you want to keep the original intact. method MERGESORT-num(Mu \sortable) { nqp::if( nqp::isgt_i((my int $n = nqp::elems(sortable)),2), # $A has the items to sort; $B is a work array nqp::stmts( (my Mu $A := sortable), (my Mu $B := nqp::setelems(nqp::create(nqp::what(sortable)),$n)), # Each 1-element run in $A is already "sorted" # Make successively longer sorted runs of length 2, 4, 8, 16... # until $A is wholly sorted (my int $width = 1), nqp::while( nqp::islt_i($width,$n), nqp::stmts( (my int $l = 0), # $A is full of runs of length $width nqp::while( nqp::islt_i($l,$n), nqp::stmts( (my int $left = $l), (my int $right = nqp::add_i($l,$width)), nqp::if(nqp::isge_i($right,$n),($right = $n)), (my int $end = nqp::add_i($l,nqp::add_i($width,$width))), nqp::if(nqp::isge_i($end,$n),($end = $n)), (my int $i = $left), (my int $j = $right), (my int $k = nqp::sub_i($left,1)), # Merge two runs: $A[i .. i+width-1] and # $A[i+width .. i+2*width-1] # to $B or copy $A[i..n-1] to $B[] ( if(i+width >= n) ) nqp::while( nqp::islt_i(++$k,$end), nqp::if( nqp::islt_i($i,$right) && ( nqp::isge_i($j,$end) || nqp::islt_n( nqp::atpos_n($A,$i), nqp::atpos_n($A,$j) ) ), nqp::stmts( nqp::bindpos_n($B,$k,nqp::atpos_n($A,$i)), ++$i ), nqp::stmts( nqp::bindpos_n($B,$k,nqp::atpos_n($A,$j)), ++$j ) ) ), ($l = nqp::add_i($l,nqp::add_i($width,$width))) ) ), # Now work array $B is full of runs of length 2*width. # Copy array B to array A for next iteration. A more # efficient implementation would swap the roles of A and B. (my Mu $temp := $B),($B := $A),($A := $temp), # swap # Now array $A is full of runs of length 2*width. ($width = nqp::add_i($width,$width)) ) ), $A ), nqp::stmts( # 2 elements or less (my \result := nqp::clone(sortable)), nqp::unless( nqp::islt_i($n,2) || nqp::isle_n(nqp::atpos_n(result,0),nqp::atpos_n(result,1)), nqp::push_n(result,nqp::shift_n(result)) ), result ) ) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of sorting numarray logic ---------------------------- } #line 1 SETTING::src/core.c/Num.rakumod my class X::Cannot::Capture { ... } my class X::Numeric::DivideByZero { ... } my class X::Numeric::CannotConvert { ... } my class Num does Real { # declared in BOOTSTRAP # class Num is Cool # has num $!value is box_target; multi method WHICH(Num:D: --> ValueObjAt:D) { nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Num), 'Num|', nqp::concat(nqp::unbox_s(self.^name), '|') ), nqp::unbox_n(self) ), ValueObjAt ) } multi method Bool(Num:D:) { nqp::hllbool(nqp::isne_n(self,0e0)) } method Capture() { X::Cannot::Capture.new( :what(self) ).throw } method Num() { self } method Bridge(Num:) { self.defined ?? self !! self.Real::Bridge } method Range(Num:U:) { Range.new(-Inf,Inf) } method Int(Num:D:) { nqp::isnanorinf(nqp::unbox_n(self)) ?? X::Numeric::CannotConvert.new(:source(self), :target(Int)).fail !! nqp::fromnum_I(nqp::unbox_n(self),Int) } method sign(Num:D:) { nqp::isnanorinf(self) ?? self == Inf ?? 1 !! self == -Inf ?? -1 !! NaN !! self > 0 ?? 1 !! self < 0 ?? -1 !! 0 } multi method new() { nqp::box_n(0e0, self) } multi method new($n) { nqp::box_n($n.Num, self) } multi method raku(Num:D:) { my str $res = self.Str; nqp::isnanorinf(nqp::unbox_n(self)) || nqp::isge_i(nqp::index($res,'e'),0) || nqp::isge_i(nqp::index($res,'E'),0) ?? $res !! nqp::concat($res,'e0') } method Rat(Num:D: Real:D \epsilon = 1.0e-6, \RAT = Rat) { my num $num = self; return RAT.new( (nqp::iseq_n($num,$num) ?? nqp::iseq_n($num,Inf) ?? 1 !! -1 !! 0), 0 ) if nqp::isnanorinf($num); $num = nqp::neg_n($num) if (my int $signum = nqp::islt_n($num,0e0)); my num $r = nqp::sub_n($num,nqp::floor_n($num)); # basically have an Int if nqp::iseq_n($r,0e0) { RAT.new(nqp::fromnum_I(self,Int),1) } # find convergents of the continued fraction. else { my Int $a := 1; my Int $b := nqp::fromnum_I($num,Int); my Int $c := 0; my Int $d := 1; # bind some value to prevent Scalar container creation my Int $q := 0; my Int $orig_b := 0; my Int $orig_d := 0; my num $modf_arg; my num $epsilon = epsilon.Num; nqp::while( nqp::isne_n($r,0e0) && nqp::isgt_n( nqp::abs_n(nqp::sub_n($num,nqp::div_In($b,$d))), $epsilon ), nqp::stmts( ($modf_arg = nqp::div_n(1e0,$r)), ($q := nqp::fromnum_I($modf_arg,Int)), ($r = nqp::sub_n($modf_arg,nqp::floor_n($modf_arg))), ($orig_b := $b), ($b := nqp::add_I(nqp::mul_I($q,$b,Int),$a,Int)), ($a := $orig_b), ($orig_d := $d), ($d := nqp::add_I(nqp::mul_I($q,$d,Int),$c,Int)), ($c := $orig_d) ) ); # Note that this result has less error than any Rational with a # smaller denominator but it is not (necessarily) the Rational # with the smallest denominator that has less than $epsilon error. # However, to find that Rational would take more processing. RAT.new($signum ?? nqp::neg_I($b,Int) !! $b, $d) } } method FatRat(Num:D: Real $epsilon = 1.0e-6) { self.Rat($epsilon, FatRat); } multi method atan2(Num:D: Num:D $x = 1e0) { nqp::p6box_n(nqp::atan2_n(nqp::unbox_n(self), nqp::unbox_n($x))); } multi method Str(Num:D:) { nqp::p6box_s(nqp::unbox_n(self)); } method succ(Num:D:) { self + 1e0 } method pred(Num:D:) { self - 1e0 } method isNaN(Num:D: ) { self != self; } method abs(Num:D: ) { nqp::p6box_n(nqp::abs_n(nqp::unbox_n(self))); } multi method exp(Num:D: ) { nqp::p6box_n(nqp::exp_n(nqp::unbox_n(self))); } proto method log(|) {*} multi method log(Num:D: ) { nqp::p6box_n(nqp::log_n(nqp::unbox_n(self))); } multi method log(Num:D: Num \base) { self.log() / base.log(); } multi method sqrt(Num:D: ) { nqp::p6box_n(nqp::sqrt_n(nqp::unbox_n(self))); } method rand(Num:D: ) { nqp::p6box_n(nqp::rand_n(nqp::unbox_n(self))); } method ceiling(Num:D: ) { nqp::isnanorinf(nqp::unbox_n(self)) ?? self !! nqp::fromnum_I(nqp::ceil_n(nqp::unbox_n(self)), Int); } method floor(Num:D: ) { nqp::isnanorinf(nqp::unbox_n(self)) ?? self !! nqp::fromnum_I(nqp::floor_n(nqp::unbox_n(self)), Int); } proto method sin(|) {*} multi method sin(Num:D: ) { nqp::p6box_n(nqp::sin_n(nqp::unbox_n(self))); } proto method asin(|) {*} multi method asin(Num:D: ) { nqp::p6box_n(nqp::asin_n(nqp::unbox_n(self))); } proto method cos(|) {*} multi method cos(Num:D: ) { nqp::p6box_n(nqp::cos_n(nqp::unbox_n(self))); } proto method acos(|) {*} multi method acos(Num:D: ) { nqp::p6box_n(nqp::acos_n(nqp::unbox_n(self))); } proto method tan(|) {*} multi method tan(Num:D: ) { nqp::p6box_n(nqp::tan_n(nqp::unbox_n(self))); } proto method atan(|) {*} multi method atan(Num:D: ) { nqp::p6box_n(nqp::atan_n(nqp::unbox_n(self))); } proto method sec(|) {*} multi method sec(Num:D: ) { nqp::p6box_n(nqp::div_n(1e0, nqp::cos_n(nqp::unbox_n(self)))); } proto method asec(|) {*} multi method asec(Num:D: ) { nqp::p6box_n(nqp::acos_n(nqp::div_n(1e0, nqp::unbox_n(self)))); } method cosec(Num:D:) { nqp::p6box_n(nqp::div_n(1e0, nqp::sin_n(nqp::unbox_n(self)))); } method acosec(Num:D:) { nqp::p6box_n(nqp::asin_n(nqp::div_n(1e0, nqp::unbox_n(self)))); } method cotan(Num:D:) { nqp::p6box_n(nqp::div_n(1e0, nqp::tan_n(nqp::unbox_n(self)))); } method acotan(Num:D:) { nqp::p6box_n(nqp::atan_n(nqp::div_n(1e0, nqp::unbox_n(self)))); } proto method sinh(|) {*} multi method sinh(Num:D: ) { nqp::p6box_n(nqp::sinh_n(nqp::unbox_n(self))); } proto method asinh(|) {*} multi method asinh(Num:D: ) { nqp::isnanorinf(self) ?? self !! self >= 0 ?? (self + (self * self + 1e0).sqrt).log !! -(-1e0 * self).asinh } proto method cosh(|) {*} multi method cosh(Num:D: ) { nqp::p6box_n(nqp::cosh_n(nqp::unbox_n(self))); } proto method acosh(|) {*} multi method acosh(Num:D: ) { self < 1e0 ?? NaN !! (self + (self * self - 1e0).sqrt).log; } proto method tanh(|) {*} multi method tanh(Num:D: ) { nqp::p6box_n(nqp::tanh_n(nqp::unbox_n(self))); } proto method atanh(|) {*} multi method atanh(1e0:) { ∞ } multi method atanh(Num:D: ) { ((1e0 + self) / (1e0 - self)).log / 2e0; } proto method sech(|) {*} multi method sech(Num:D: ) { nqp::p6box_n(nqp::div_n(1e0, nqp::cosh_n(nqp::unbox_n(self)))); } proto method asech(|) {*} multi method asech(Num:D: ) { (1e0 / self).acosh; } proto method cosech(|) {*} multi method cosech(Num:D: ) { nqp::p6box_n(nqp::div_n(1e0, nqp::sinh_n(nqp::unbox_n(self)))); } proto method acosech(|) {*} multi method acosech(Num:D: ) { (1e0 / self).asinh; } proto method cotanh(|) {*} multi method cotanh(Num:D: ) { nqp::p6box_n(nqp::div_n(1e0, nqp::tanh_n(nqp::unbox_n(self)))); } proto method acotanh(|) {*} multi method acotanh(Num:D: ) { (1e0 / self).atanh; } method is-prime(--> Bool:D) { nqp::hllbool( nqp::if( nqp::isnanorinf(self), False, nqp::if( nqp::iseq_n(self,nqp::floor_n(self)), nqp::fromnum_I(self,Int).is-prime ) ) ) } method narrow(Num:D:) { my $i := self.Int; $i.defined && $i.Num ≅ self ?? $i !! self } method UPGRADE-RAT(\nu, \de) is raw { nqp::p6box_n(nqp::div_In(nu,de)) # downgrade to float } } my constant tau = 6.28318_53071_79586_476e0; my constant pi = 3.14159_26535_89793_238e0; my constant e = 2.71828_18284_59045_235e0; my constant π := pi; my constant τ := tau; my constant 𝑒 := e; multi sub prefix:<++>(Num:D $a is rw) { $a = nqp::p6box_n(nqp::add_n(nqp::unbox_n($a), 1e0)) } multi sub prefix:<++>(Num:U $a is rw) { $a = 1e0; } multi sub prefix:<++>(num $a is rw --> num) { $a = nqp::add_n($a, 1e0) } multi sub prefix:<-->(Num:D $a is rw) { $a = nqp::p6box_n(nqp::sub_n(nqp::unbox_n($a), 1e0)) } multi sub prefix:<-->(Num:U $a is rw) { $a = -1e0; } multi sub prefix:<-->(num $a is rw --> num) { $a = nqp::sub_n($a, 1e0) } multi sub postfix:<++>(Num:D $a is rw) { my $b = $a; $a = nqp::p6box_n(nqp::add_n(nqp::unbox_n($a), 1e0)); $b } multi sub postfix:<++>(Num:U $a is rw --> 0e0) { $a = 1e0; } multi sub postfix:<++>(num $a is rw --> num) { my num $b = $a; $a = nqp::add_n($a, 1e0); $b } multi sub postfix:<-->(Num:D $a is rw) { my $b = $a; $a = nqp::p6box_n(nqp::sub_n(nqp::unbox_n($a), 1e0)); $b } multi sub postfix:<-->(Num:U $a is rw --> 0e0) { $a = -1e0; } multi sub postfix:<-->(num $a is rw --> num) { my num $b = $a; $a = nqp::sub_n($a, 1e0); $b } multi sub prefix:<->(Num:D $a --> Num:D) { nqp::p6box_n(nqp::neg_n($a)) } multi sub prefix:<->(num $a --> num) { nqp::neg_n($a) } multi sub abs(Num:D $a --> Num:D) { nqp::p6box_n(nqp::abs_n($a)) } multi sub abs(num $a --> num) { nqp::abs_n($a) } multi sub infix:<+>(Num:D $a, Num:D $b) { nqp::p6box_n(nqp::add_n($a,$b)) } multi sub infix:<+>(num $a, num $b --> num) { nqp::add_n($a, $b) } multi sub infix:<->(Num:D $a, Num:D $b) { nqp::p6box_n(nqp::sub_n($a,$b)) } multi sub infix:<->(num $a, num $b --> num) { nqp::sub_n($a, $b) } multi sub infix:<*>(Num:D $a, Num:D $b) { nqp::p6box_n(nqp::mul_n($a,$b)) } multi sub infix:<*>(num $a, num $b --> num) { nqp::mul_n($a, $b) } multi sub infix:(Num:D $a, Num:D $b) { $b ?? nqp::p6box_n(nqp::div_n($a,$b)) !! X::Numeric::DivideByZero.new(:using, :numerator($a)).Failure } multi sub infix:(num $a, num $b --> num) { $b ?? nqp::div_n($a, $b) !! X::Numeric::DivideByZero.new(:using, :numerator($a)).Failure } multi sub infix:<%>(Num:D $a, Num:D $b) { $b ?? nqp::p6box_n(nqp::mod_n($a,$b)) !! X::Numeric::DivideByZero.new(:using<%>, :numerator($a)).Failure } multi sub infix:<%>(num $a, num $b --> num) { $b ?? nqp::mod_n($a, $b) !! X::Numeric::DivideByZero.new(:using<%>, :numerator($a)).Failure } # (If we get 0 here, must be underflow, since floating overflow provides Inf.) multi sub infix:<**>(Num:D $a, Num:D $b) { nqp::p6box_n(nqp::pow_n($a,$b)) or $a == 0e0 || $b.abs == Inf ?? 0e0 !! X::Numeric::Underflow.new.Failure } multi sub infix:<**>(num $a, num $b --> num) { nqp::pow_n($a, $b) or $a == 0e0 || $b.abs == Inf ?? 0e0 !! X::Numeric::Underflow.new.Failure } # Here we sort NaN in with string "NaN" multi sub infix:(Num:D $a, Num:D $b) { (my $cmp := nqp::cmp_n($a,$b)) ?? ORDER($cmp) !! $a === $b ?? Same # === cares about signed zeros, we don't, so: !! nqp::iseq_n($a,0e0) && nqp::iseq_n($b,0e0) ?? Same !! $a.Stringy cmp $b.Stringy; } multi sub infix:(num $a, num $b) { (my $cmp := nqp::cmp_n($a, $b)) ?? ORDER($cmp) !! $a === $b ?? Same # === cares about signed zeros, we don't, so: !! nqp::iseq_n($a, 0e0) && nqp::iseq_n($b, 0e0) ?? Same !! $a.Stringy cmp $b.Stringy; } # Here we treat NaN as undefined multi sub infix:«<=>»(Num:D $a, Num:D $b) { ORDER(nqp::cmp_n($a,$b)) or $a == $b ?? Same !! Nil } multi sub infix:«<=>»(num $a, num $b) { ORDER(nqp::cmp_n($a, $b)) or $a == $b ?? Same !! Nil } multi sub infix:<===>(Num:D $a, Num:D $b) { nqp::hllbool( nqp::eqaddr($a.WHAT,$b.WHAT) && (( # Both are NaNs nqp::not_i(nqp::isle_n($a,nqp::inf)) && nqp::not_i(nqp::isle_n($b,nqp::inf)) ) || (nqp::iseq_n($a,$b) && ( # if we're dealing with zeros, ensure the signs match nqp::isne_n($a,0e0) || nqp::if( # 1/-0 = -Inf; 1/0 = +Inf nqp::islt_n(nqp::div_n(1e0,$a),0e0), # a is -0, if true: nqp::islt_n(nqp::div_n(1e0,$b),0e0), # check b is -0 too nqp::isgt_n(nqp::div_n(1e0,$b),0e0), # check b is +0 too ) ) )) ) } multi sub infix:<===>(num $a, num $b --> Bool:D) { nqp::hllbool( nqp::eqaddr($a.WHAT,$b.WHAT) && (( # Both are NaNs nqp::not_i(nqp::isle_n($a,nqp::inf)) && nqp::not_i(nqp::isle_n($b,nqp::inf)) ) || (nqp::iseq_n($a,$b) && ( # if we're dealing with zeros, ensure the signs match nqp::isne_n($a, 0e0) || nqp::if( # 1/-0 = -Inf; 1/0 = +Inf nqp::islt_n(nqp::div_n(1e0,$a),0e0), # a is -0, if true: nqp::islt_n(nqp::div_n(1e0,$b),0e0), # check b is -0 too nqp::isgt_n(nqp::div_n(1e0,$b),0e0), # check b is +0 too ) ) )) ) } multi sub infix:<≅>( Inf, Inf) { Bool::True } multi sub infix:<≅>(-Inf, -Inf) { Bool::True } multi sub infix:<==>(Num:D $a, Num:D $b --> Bool:D) { nqp::hllbool(nqp::iseq_n($a,$b)) } multi sub infix:<==>(num $a, num $b --> Bool:D) { nqp::hllbool(nqp::iseq_n($a,$b)) } multi sub infix:(Num:D $a, Num:D $b --> Bool:D) { nqp::hllbool(nqp::isne_n($a,$b)) } multi sub infix:(num $a, num $b --> Bool:D) { nqp::hllbool(nqp::isne_n($a,$b)) } multi sub infix:«<»(Num:D $a, Num:D $b --> Bool:D) { nqp::hllbool(nqp::islt_n($a,$b)) } multi sub infix:«<»(num $a, num $b --> Bool:D) { nqp::hllbool(nqp::islt_n($a,$b)) } multi sub infix:«<=»(Num:D $a, Num:D $b --> Bool:D) { nqp::hllbool(nqp::isle_n($a,$b)) } multi sub infix:«<=»(num $a, num $b --> Bool:D) { nqp::hllbool(nqp::isle_n($a,$b)) } multi sub infix:«>»(Num:D $a, Num:D $b --> Bool:D) { nqp::hllbool(nqp::isgt_n($a,$b)) } multi sub infix:«>»(num $a, num $b --> Bool:D) { nqp::hllbool(nqp::isgt_n($a,$b)) } multi sub infix:«>=»(Num:D $a, Num:D $b --> Bool:D) { nqp::hllbool(nqp::isge_n($a,$b)) } multi sub infix:«>=»(num $a, num $b --> Bool:D) { nqp::hllbool(nqp::isge_n($a,$b)) } proto sub rand(*%) {*} multi sub rand(--> Num:D) { nqp::p6box_n(nqp::rand_n(1e0)) } proto sub srand($, *%) {*} multi sub srand(Int:D $seed --> Int:D) { nqp::p6box_i(nqp::srand($seed)) } multi sub atan2(Num:D $a, Num:D $b = 1e0) { nqp::p6box_n(nqp::atan2_n($a,$b)) } multi sub cosec(Num:D $x) { nqp::p6box_n(nqp::div_n(1e0,nqp::sin_n($x))) } multi sub acosec(Num:D $x) { nqp::p6box_n(nqp::asin_n(nqp::div_n(1e0,$x))) } multi sub log( num $x --> num) { nqp::log_n($x) } multi sub sin( num $x --> num) { nqp::sin_n($x) } multi sub asin(num $x --> num) { nqp::asin_n($x) } multi sub cos( num $x --> num) { nqp::cos_n($x) } multi sub acos(num $x --> num) { nqp::acos_n($x) } multi sub tan( num $x --> num) { nqp::tan_n($x) } multi sub atan(num $x --> num) { nqp::atan_n($x) } multi sub sec( num $x --> num) { nqp::div_n(1e0,nqp::cos_n($x)) } multi sub asec( num $x --> num) { nqp::acos_n(nqp::div_n(1e0,$x)) } multi sub cotan( num $x --> num) { nqp::div_n(1e0, nqp::tan_n($x)) } multi sub acotan( num $x --> num) { nqp::atan_n(nqp::div_n(1e0, $x)) } multi sub sinh( num $x --> num) { nqp::sinh_n($x) } multi sub cosh( num $x --> num) { nqp::cosh_n($x) } multi sub tanh( num $x --> num) { nqp::tanh_n($x) } multi sub sech( num $x --> num) { 1e0 / cosh($x) } multi sub asech( num $x --> num) { acosh(1e0 / $x) } multi sub cosech( num $x --> num) { 1e0 / sinh($x) } multi sub acosech(num $x --> num) { asinh(1e0 / $x) } multi sub cotanh( num $x --> num) { 1e0 / tanh($x) } multi sub acotanh(num $x --> num) { atanh(1e0 / $x) } multi sub asinh(num $x --> num) { # ln(x + √(x²+1)) nqp::isnanorinf($x) ?? $x !! $x >= 0 ?? nqp::log_n( nqp::add_n($x,nqp::pow_n(nqp::add_n(nqp::mul_n($x,$x),1e0),.5e0)) ) !! -asinh(-$x) } multi sub acosh(num $x --> num) { # ln(x + √(x²-1)) $x < 1e0 ?? NaN !! nqp::log_n( nqp::add_n($x,nqp::pow_n(nqp::sub_n(nqp::mul_n($x,$x),1e0),.5e0)) ) } multi sub atanh(num $x --> num) { $x == 1e0 ?? Inf !! log((1e0 + $x) / (1e0 - $x)) / 2e0; } multi sub floor( num $a --> num) { nqp::floor_n($a) } multi sub ceiling(num $a --> num) { nqp::ceil_n($a) } multi sub sqrt( num $a --> num) { nqp::sqrt_n($a) } #line 1 SETTING::src/core.c/Buf.rakumod my class X::Assignment::RO { ... } my class X::Buf::AsStr { ... } my class X::Buf::Pack { ... } my class X::Buf::Pack::NonASCII { ... } my class X::Experimental { ... } # externalize the endian indicators enum Endian ( NativeEndian => nqp::box_i(nqp::const::BINARY_ENDIAN_NATIVE,Int), LittleEndian => nqp::box_i(nqp::const::BINARY_ENDIAN_LITTLE,Int), BigEndian => nqp::box_i(nqp::const::BINARY_ENDIAN_BIG,Int), ); my role Blob[::T = uint8] does Positional[T] does Stringy is repr('VMArray') is array_type(T) { ... } #- start of generated part of Blob Signed role ------------------------------- #- Generated on 2022-03-08T14:31:37+01:00 by ./tools/build/makeBLOB_ROLES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE my role SignedBlob[::T] is repr('VMArray') is array_type(T) is implementation-detail { method !push-List(str $action, ::?CLASS:D $to, \from) { my Mu $reified := nqp::getattr(from,List,'$!reified'); if nqp::isconcrete($reified) { my int $elems = nqp::elems($reified); my int $j = nqp::elems($to); nqp::setelems($to, $j + $elems); # presize for efficiency my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::stmts( (my $got = nqp::hllize(nqp::atpos($reified,$i))), nqp::if( nqp::istype($got,Int), nqp::bindpos_i($to,$j++,$got), self!throw-typecheck-element($action, $i, $got) ) ) ); } $to } method !push-iterator(str $action, ::?CLASS:D $to, Iterator:D $iter) { my int $i; nqp::until( nqp::eqaddr((my $got := $iter.pull-one),IterationEnd), nqp::if( nqp::istype(nqp::hllize($got),Int), nqp::stmts( nqp::push_i($to,$got), ++$i ), self!throw-typecheck-element($action,$i,$got) ) ); $to } method !push-list(str $action, ::?CLASS:D $to, \from) { nqp::istype(from,List) ?? self!push-List($action, $to, from) !! self!push-iterator($action, $to, from.iterator) } method !spread-rest( int $i is copy, int $elems is copy, int $values, ::?CLASS:D $to, \from ) { --$i; # went one too far $elems = $elems + $values; my int $j = -1; if from.^array_type.^unsigned { nqp::bindpos_i($to,$i,nqp::atpos_u(from, ++$j % $values)) while nqp::islt_i(++$i,$elems); } else { nqp::bindpos_i($to,$i,nqp::atpos_i(from, ++$j % $values)) while nqp::islt_i(++$i,$elems); } $to } method !spread(::?CLASS:D $to, \from) { my int $values = nqp::elems(from); my int $elems = nqp::elems($to) - $values; my int $i = -$values; nqp::splice($to,from,$i,$values) while nqp::isle_i($i = $i + $values,$elems); nqp::isgt_i($i,$elems) # something left to init ?? self!spread-rest($i, $elems, $values, $to, from) !! $to } multi method allocate(::?CLASS:U: Int:D $elements, int $value) { my int $elems = $elements; my $blob := nqp::setelems(nqp::create(self),$elems); my int $i = -1; nqp::bindpos_i($blob,$i,$value) while nqp::islt_i(++$i,$elems); $blob } multi method AT-POS(::?ROLE:D: uint $pos) { nqp::isge_i($pos,nqp::elems(self)) ?? self!fail-range($pos) !! nqp::atpos_i(self,$pos) } multi method AT-POS(::?ROLE:D: Int:D $pos) { nqp::isge_i($pos,nqp::elems(self)) || nqp::islt_i($pos,0) ?? self!fail-range($pos) !! nqp::atpos_i(self,$pos) } multi method list(::?ROLE:D:) { my int $elems = nqp::elems(self); # presize memory, but keep it empty, so we can just push my $buffer := nqp::setelems( nqp::setelems(nqp::create(IterationBuffer),$elems), 0 ); my int $i = -1; nqp::push($buffer,nqp::atpos_i(self,$i)) while nqp::islt_i(++$i,$elems); $buffer.List } method reverse(::?CLASS:D:) { my int $elems = nqp::elems(self); my int $last = nqp::sub_i($elems,1); my $reversed := nqp::setelems(nqp::create(self),$elems); my int $i = -1; nqp::bindpos_i($reversed,nqp::sub_i($last,$i), nqp::atpos_i(self,$i)) while nqp::islt_i(++$i,$elems); $reversed } method COMPARE(::?CLASS:D: ::?CLASS:D \other) is implementation-detail { nqp::unless( nqp::cmp_i( (my int $elems = nqp::elems(self)), nqp::elems(my $other := nqp::decont(other)) ), nqp::stmts( # same number of elements (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::not_i( nqp::cmp_i(nqp::atpos_i(self,$i),nqp::atpos_i($other,$i)) ), nqp::null ), nqp::if( nqp::isne_i($i,$elems), nqp::cmp_i(nqp::atpos_i(self,$i),nqp::atpos_i($other,$i)) ) ) ) } method SAME(::?CLASS:D: Blob:D \other) is implementation-detail { nqp::if( nqp::iseq_i( (my int $elems = nqp::elems(self)), nqp::elems(my $other := nqp::decont(other)) ), nqp::stmts( # same number of elements (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::iseq_i(nqp::atpos_i(self,$i),nqp::atpos_i($other,$i)), nqp::null ), nqp::iseq_i($i,$elems) ) ) } method join(::?CLASS:D: $delim = '') { my int $elems = nqp::elems(self); my int $i = -1; my $list := nqp::setelems(nqp::setelems(nqp::list_s,$elems),0); nqp::push_s($list,nqp::atpos_i(self,$i)) while nqp::islt_i(++$i,$elems); nqp::join($delim.Str,$list) } } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of Blob Signed role --------------------------------- #- start of generated part of Blob Unsigned role ------------------------------- #- Generated on 2022-03-08T14:31:37+01:00 by ./tools/build/makeBLOB_ROLES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE my role UnsignedBlob[::T] is repr('VMArray') is array_type(T) is implementation-detail { method !push-List(str $action, ::?CLASS:D $to, \from) { my Mu $reified := nqp::getattr(from,List,'$!reified'); if nqp::isconcrete($reified) { my int $elems = nqp::elems($reified); my int $j = nqp::elems($to); nqp::setelems($to, $j + $elems); # presize for efficiency my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::stmts( (my $got = nqp::hllize(nqp::atpos($reified,$i))), nqp::if( nqp::istype($got,Int), nqp::bindpos_u($to,$j++,$got), self!throw-typecheck-element($action, $i, $got) ) ) ); } $to } method !push-iterator(str $action, ::?CLASS:D $to, Iterator:D $iter) { my int $i; nqp::until( nqp::eqaddr((my $got := $iter.pull-one),IterationEnd), nqp::if( nqp::istype(nqp::hllize($got),Int), nqp::stmts( nqp::push_i($to,my $ = $got), ++$i ), self!throw-typecheck-element($action,$i,$got) ) ); $to } method !push-list(str $action, ::?CLASS:D $to, \from) { nqp::istype(from,List) ?? self!push-List($action, $to, from) !! self!push-iterator($action, $to, from.iterator) } method !spread-rest( int $i is copy, int $elems is copy, int $values, ::?CLASS:D $to, \from ) { --$i; # went one too far $elems = $elems + $values; my int $j = -1; if from.^array_type.^unsigned { nqp::bindpos_u($to,$i,nqp::atpos_u(from, ++$j % $values)) while nqp::islt_i(++$i,$elems); } else { nqp::bindpos_u($to,$i,nqp::atpos_i(from, ++$j % $values)) while nqp::islt_i(++$i,$elems); } $to } method !spread(::?CLASS:D $to, \from) { my int $values = nqp::elems(from); my int $elems = nqp::elems($to) - $values; my int $i = -$values; nqp::splice($to,from,$i,$values) while nqp::isle_i($i = $i + $values,$elems); nqp::isgt_i($i,$elems) # something left to init ?? self!spread-rest($i, $elems, $values, $to, from) !! $to } multi method allocate(::?CLASS:U: Int:D $elements, int $value) { my int $elems = $elements; my $blob := nqp::setelems(nqp::create(self),$elems); my int $i = -1; nqp::bindpos_u($blob,$i,$value) while nqp::islt_i(++$i,$elems); $blob } multi method AT-POS(::?ROLE:D: uint $pos) { nqp::isge_i($pos,nqp::elems(self)) ?? self!fail-range($pos) !! nqp::atpos_u(self,$pos) } multi method AT-POS(::?ROLE:D: Int:D $pos) { nqp::isge_i($pos,nqp::elems(self)) || nqp::islt_i($pos,0) ?? self!fail-range($pos) !! nqp::atpos_u(self,$pos) } multi method list(::?ROLE:D:) { my int $elems = nqp::elems(self); # presize memory, but keep it empty, so we can just push my $buffer := nqp::setelems( nqp::setelems(nqp::create(IterationBuffer),$elems), 0 ); my int $i = -1; nqp::push($buffer,nqp::atpos_u(self,$i)) while nqp::islt_i(++$i,$elems); $buffer.List } method reverse(::?CLASS:D:) { my int $elems = nqp::elems(self); my int $last = nqp::sub_i($elems,1); my $reversed := nqp::setelems(nqp::create(self),$elems); my int $i = -1; nqp::bindpos_u($reversed,nqp::sub_i($last,$i), nqp::atpos_u(self,$i)) while nqp::islt_i(++$i,$elems); $reversed } method COMPARE(::?CLASS:D: ::?CLASS:D \other) is implementation-detail { nqp::unless( nqp::cmp_i( (my int $elems = nqp::elems(self)), nqp::elems(my $other := nqp::decont(other)) ), nqp::stmts( # same number of elements (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::not_i( nqp::cmp_i(nqp::atpos_u(self,$i),nqp::atpos_u($other,$i)) ), nqp::null ), nqp::if( nqp::isne_i($i,$elems), nqp::cmp_i(nqp::atpos_u(self,$i),nqp::atpos_u($other,$i)) ) ) ) } method SAME(::?CLASS:D: Blob:D \other) is implementation-detail { nqp::if( nqp::iseq_i( (my int $elems = nqp::elems(self)), nqp::elems(my $other := nqp::decont(other)) ), nqp::stmts( # same number of elements (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::iseq_i(nqp::atpos_u(self,$i),nqp::atpos_u($other,$i)), nqp::null ), nqp::iseq_i($i,$elems) ) ) } method join(::?CLASS:D: $delim = '') { my int $elems = nqp::elems(self); my int $i = -1; my $list := nqp::setelems(nqp::setelems(nqp::list_s,$elems),0); nqp::push_s($list,nqp::atpos_u(self,$i)) while nqp::islt_i(++$i,$elems); nqp::join($delim.Str,$list) } } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of Blob Unsigned role --------------------------------- my role Blob[::T = uint8] does Positional[T] does Stringy is repr('VMArray') is array_type(T) { die "Can only parameterize with native int types, not '{T.^name}'." unless nqp::objprimspec(T) == 1 || (nqp::objprimspec(T) >= 4 && nqp::objprimspec(T) <= 10); $?CLASS.^add_role(T.^unsigned ?? UnsignedBlob.^parameterize(T) !! SignedBlob.^parameterize(T)); # other then *8 not supported yet my int $bpe = try { (T.^nativesize / 8).Int } // 1; multi method WHICH(Blob:D: --> ValueObjAt:D) { nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Blob), 'Blob|', nqp::concat(nqp::unbox_s(self.^name), '|') ), nqp::sha1(self.decode("latin-1")) ), ValueObjAt ) } multi method new(Blob:) { nqp::create(self) } multi method new(Blob: Blob:D $blob) is default { nqp::splice(nqp::create(self),$blob,0,0) } multi method new(Blob: int @values) { nqp::splice(nqp::create(self),@values,0,0) } multi method new(Blob: @values) { nqp::create(self).STORE(@values, :INITIALIZE) } multi method new(Blob: *@values) { nqp::create(self).STORE(@values, :INITIALIZE) } # Because it is (apparently) impossible to stub the Buf role in the # setting, the lookup for Buf needs to be done at runtime, hence the # :: rather than just Buf. method Buf(Blob:D:) { (nqp::eqaddr(T,uint8) ?? :: !! ::.^parameterize(T)).new: self } proto method STORE(Blob:D: |) {*} multi method STORE(Blob:D: Iterable:D \iterable, :$INITIALIZE) { $INITIALIZE ?? iterable.is-lazy ?? self.throw-iterator-cannot-be-lazy('store') !! self!push-list("initializ",self,iterable) !! X::Assignment::RO.new(:value(self)).throw } multi method STORE(Blob:D: Any:D \non-iterable, :$INITIALIZE) { X::Assignment::RO.new(:value(self)).throw unless $INITIALIZE; my int $elems = non-iterable.elems; nqp::push_i(self,my $ = non-iterable.AT-POS($_)) for ^$elems; #FIXME needs to handle unsigned with push_u self } proto method allocate(|) {*} multi method allocate(Blob:U: Int:D $elements) { nqp::setelems(nqp::create(self),$elements) } multi method allocate(Blob:U: Int:D $elements, Int:D \value) { my int $value = value; self.allocate($elements,$value) } multi method allocate(Blob:U: Int:D $elements, Mu:D $got) { self!fail-typecheck('allocate',$got) } multi method allocate(Blob:U: Int:D $elements, int @values) { self!spread(nqp::setelems(nqp::create(self),$elements),@values) } multi method allocate(Blob:U: Int:D $elements, Blob:D $blob) { self!spread(nqp::setelems(nqp::create(self),$elements),$blob) } multi method allocate(Blob:U: Int:D $elements, @values) { self!spread(nqp::setelems(nqp::create(self),$elements),Blob.new(@values)) } multi method EXISTS-POS(Blob:D: int \pos) { nqp::hllbool( nqp::islt_i(pos,nqp::elems(self)) && nqp::isge_i(pos,0) ); } multi method EXISTS-POS(Blob:D: Int:D \pos) { nqp::hllbool( nqp::islt_i(pos,nqp::elems(self)) && nqp::isge_i(pos,0) ); } # for simplicity's sake, these are not multis method read-int8(::?ROLE:D: int $offset, Endian $? --> int) is raw { nqp::readint(self,$offset, nqp::bitor_i( nqp::const::BINARY_SIZE_8_BIT, nqp::const::BINARY_ENDIAN_NATIVE ) ) } method read-int16(::?ROLE:D: int $offset, Endian $endian = NativeEndian --> int ) is raw { nqp::readint(self,$offset, nqp::bitor_i(nqp::const::BINARY_SIZE_16_BIT,$endian)) } method read-int32(::?ROLE:D: int $offset, Endian $endian = NativeEndian --> int ) is raw { nqp::readint(self,$offset, nqp::bitor_i(nqp::const::BINARY_SIZE_32_BIT,$endian)) } method read-int64(::?ROLE:D: int $offset, Endian $endian = NativeEndian --> int ) is raw { nqp::readint(self,$offset, nqp::bitor_i(nqp::const::BINARY_SIZE_64_BIT,$endian)) } method read-int128(::?ROLE:D: int $offset, Endian $endian = NativeEndian --> Int ) is raw { my \unsigned := self.read-uint128($offset,$endian); unsigned >= 1 +< 127 ?? unsigned - 1 +< 128 !! unsigned } method read-uint8(::?ROLE:D: int $offset, Endian $? --> uint) is raw { nqp::readuint(self,$offset, nqp::bitor_i( nqp::const::BINARY_SIZE_8_BIT, nqp::const::BINARY_ENDIAN_NATIVE ) ) } method read-uint16(::?ROLE:D: int $offset, Endian $endian = NativeEndian --> uint ) is raw { nqp::readuint(self,$offset, nqp::bitor_i(nqp::const::BINARY_SIZE_16_BIT,$endian)) } method read-uint32(::?ROLE:D: int $offset, Endian $endian = NativeEndian --> uint ) is raw { nqp::readuint(self,$offset, nqp::bitor_i(nqp::const::BINARY_SIZE_32_BIT,$endian)) } method read-uint64(::?ROLE:D: int $offset, Endian $endian = NativeEndian --> uint ) is raw { my \signed := nqp::readuint(self,$offset, nqp::bitor_i(nqp::const::BINARY_SIZE_64_BIT,$endian)); signed < 0 ?? signed + 1 +< 64 !! signed } method read-uint128(::?ROLE:D: int $offset, Endian $endian = NativeEndian --> uint ) is raw { my \first := self.read-uint64($offset, $endian); my \second := self.read-uint64($offset + 8, $endian); $endian == BigEndian || ($endian == NativeEndian && Kernel.endian == BigEndian) ?? first +< 64 +| second !! second +< 64 +| first } method read-num32(::?ROLE:D: int $offset, Endian $endian = NativeEndian --> num ) is raw { nqp::readnum(self,$offset, nqp::bitor_i(nqp::const::BINARY_SIZE_32_BIT,$endian)) } method read-num64(::?ROLE:D: int $offset, Endian $endian = NativeEndian --> num ) is raw { nqp::readnum(self,$offset, nqp::bitor_i(nqp::const::BINARY_SIZE_64_BIT,$endian)) } method read-bits(::?ROLE:D \SELF: int $pos, Int:D $bits --> Int:D) { my $result := SELF.read-ubits($pos, $bits); $result > 1 +< ($bits - 1) - 1 ?? $result - 1 +< $bits !! $result } method read-ubits(::?ROLE:D \SELF: int $pos, Int:D $bits --> UInt:D) { # sanity checking die "Can only read from position 0..{ nqp::elems(self) * 8 - 1 } in buffer{ " '" ~ SELF.VAR.name ~ "'" if nqp::iscont(SELF) }, you tried: $pos" if $pos < 0; die "Can only read 1..{ nqp::elems(self) * 8 - $pos } bits from position $pos in buffer{ " '" ~ SELF.VAR.name ~ "'" if nqp::iscont(SELF) }, you tried: $bits" if ($pos + $bits - 1) +> 3 >= nqp::elems(self); # set up stuff to work with my int $first-bit = $pos +& 7; # 0 = left-aligned my int $last-bit = ($pos + $bits) +& 7; # 0 = right-aligned my int $first-byte = $pos +> 3; my int $last-byte = ($pos + $bits - 1) +> 3; # l=least significant byte, m=most significant byte # 00010010 00110100 01011100 01111000 10011010 # ________ mmmmmmmm llllllll ________ ________ 8,16 mmmmmmmm llllllll # ________ __mmmmmm llllllll ________ ________ 8,16 mmmmmm llllllll # ________ mmmmmmll llllll__ ________ ________ 8,16 mmmmmm llllllll # ________ __mmmmmm mmllllll ll______ ________ 10,16 mmmmmmmm llllllll # ________ ________ ______ll lll_____ ________ 21, 5 lllll # ________ ________ ________ __lllll_ ________ 26, 5 lllll nqp::if( nqp::iseq_i($first-byte,$last-byte), (my $result := self.AT-POS($first-byte)), nqp::stmts( ($result := 0), (my int $i = $first-byte - 1), nqp::while( nqp::isle_i(++$i,$last-byte), ($result := nqp::bitshiftl_I($result,8,Int) +| self.AT-POS($i)) ) ) ); $last-bit ?? ($result +> (8 - $last-bit)) # not right-aligned, so +& (1 +< $bits - 1) # shift and mask !! $first-bit # right-aligned ?? $result +& (1 +< $bits - 1) # but not left-aligned, so mask !! $result # also left-aligned, already done } multi method Bool(Blob:D:) { nqp::hllbool(nqp::elems(self)) } method Capture(Blob:D:) { self.List.Capture } multi method elems(Blob:D:) { nqp::elems(self) } method Numeric(Blob:D:) { nqp::p6box_i(nqp::elems(self)) } method Int(Blob:D:) { nqp::p6box_i(nqp::elems(self)) } method bytes(Blob:D:) { nqp::mul_i(nqp::elems(self),$bpe) } method chars(Blob:D:) { X::Buf::AsStr.new(object => self, method => 'chars').throw } method codes(Blob:D:) { X::Buf::AsStr.new(object => self, method => 'codes').throw } multi method Str(Blob:D:) { X::Buf::AsStr.new(object => self, method => 'Str' ).throw } multi method Stringy(Blob:D:) { X::Buf::AsStr.new(object => self, method => 'Stringy' ).throw } proto method decode(|) {*} multi method decode(Blob:D: $encoding = self.encoding // "utf-8") { nqp::p6box_s( nqp::decode(self, Rakudo::Internals.NORMALIZE_ENCODING($encoding)) ) } multi method decode(Blob:D: $encoding, Str :$replacement!, Bool:D :$strict = False) { nqp::p6box_s( nqp::decoderepconf(self, Rakudo::Internals.NORMALIZE_ENCODING($encoding), $replacement.defined ?? $replacement !! nqp::null_s(), $strict ?? 0 !! 1)) } multi method decode(Blob:D: $encoding, Bool:D :$strict = False) { nqp::p6box_s( nqp::decodeconf(self, Rakudo::Internals.NORMALIZE_ENCODING($encoding), $strict ?? 0 !! 1)) } my $char := nqp::list_s( '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' ); multi method gist(Blob:D:) { # (u)int don't have a nativesize, so just assume 64-bit for them my int $nativesize = nqp::div_i(T.^nativesize // $?BITS, 4) || 1; my int $todo = nqp::elems(self) min nqp::div_i(200,$nativesize); my int $i = -1; my $chunks := nqp::list_s; my int $unsigned = T.^unsigned; my int $elem; nqp::while( nqp::islt_i(++$i,$todo), nqp::stmts( nqp::if( $unsigned, ($elem = nqp::atpos_u(self,$i)), ($elem = nqp::atpos_i(self,$i)) ), (my $chunk := nqp::list_s), (my int $size = $nativesize), nqp::while( nqp::isgt_i($size,0), nqp::stmts( nqp::unshift_s( $chunk, nqp::atpos_s($char,nqp::bitand_i($elem,0xF)) ), ($elem = nqp::bitshiftr_i($elem,4)), ($size = nqp::sub_i($size,1)) ) ), nqp::push_s($chunks,nqp::join('',$chunk)) ) ); nqp::push_s($chunks,"...") if nqp::isgt_i(nqp::elems(self),$todo); nqp::join('',nqp::list_s(self.^name,':0x<',nqp::join(" ",$chunks),'>')) } multi method raku(Blob:D:) { self.^name ~ '.new(' ~ self.join(',') ~ ')'; } # Made this a sub instead of a private method so that the optimizer # doesn't need to put in IntLexRef's for the native int parameters. # Since we're not using any attributes, just self, that was an easy # choice to make. sub subbuf-end(\SELF, int $start, int $end, int $elems) { nqp::if( nqp::islt_i($start,0) || nqp::isgt_i($start,$elems), X::OutOfRange.new( what => '"From argument to subbuf', got => $start, range => "0.." ~ $elems ).Failure, nqp::if( nqp::isle_i( (my int $last = nqp::if(nqp::isge_i($end,$elems),$elems-1,$end)), $start - 1 ), # 0 elements to return nqp::create(SELF), # just create a new one nqp::slice(SELF,$start,$last) # do the actual slice ) ) } sub subbuf-length(\SELF, int $from, int $length, int $elems) { nqp::islt_i($length,0) ?? X::OutOfRange.new( what => 'Len element to subbuf', got => $length, range => "0.." ~ $elems ).Failure !! subbuf-end(SELF, $from, $from + $length - 1, $elems) } proto method subbuf(|) {*} multi method subbuf(Blob:D: Range:D $fromto) { nqp::if( nqp::getattr_i(nqp::decont($fromto),Range,'$!is-int'), nqp::stmts( (my int $start = nqp::add_i( nqp::unbox_i(nqp::getattr(nqp::decont($fromto),Range,'$!min')), nqp::getattr_i(nqp::decont($fromto),Range,'$!excludes-min') )), (my int $end = nqp::sub_i( nqp::unbox_i(nqp::getattr(nqp::decont($fromto),Range,'$!max')), nqp::getattr_i(nqp::decont($fromto),Range,'$!excludes-max') )), subbuf-end(self, $start, $end, nqp::elems(self)) ), X::AdHoc.new( payload => "Must specify a Range with integer bounds to subbuf" ).Failure ) } multi method subbuf(Blob:D: Int:D $From) { my int $elems = nqp::elems(self); my int $from = $From; subbuf-end(self, $from, $elems, $elems) } multi method subbuf(Blob:D: &From) { my int $elems = nqp::elems(self); my int $from = From(nqp::box_i($elems,Int)); subbuf-end(self, $from, $elems, $elems) } multi method subbuf(Blob:D: Int:D $From, Int:D $Length) { my int $from = $From; my int $length = $Length; subbuf-length(self, $from, $length, nqp::elems(self)) } multi method subbuf(Blob:D: Int:D $From, &End) { my int $elems = nqp::elems(self); my int $from = $From; my int $end = End(nqp::box_i($elems,Int)); subbuf-end(self, $from, $end, $elems) } multi method subbuf(Blob:D: &From, Int:D $Length) { my int $elems = nqp::elems(self); my int $from = From(nqp::box_i($elems,Int)); my int $length = $Length; subbuf-length(self, $from, $length, $elems) } multi method subbuf(Blob:D: &From, &End) { my int $elems = nqp::elems(self); my int $from = From(nqp::box_i($elems,Int)); my int $end = End(nqp::box_i($elems,Int)); subbuf-end(self, $from, $end, $elems) } multi method subbuf(Blob:D: \from, Whatever) { self.subbuf(from) } multi method subbuf(Blob:D: \from, Numeric \length) { length == Inf ?? self.subbuf(from) !! self.subbuf(from,length.Int) } proto method unpack(|) {*} multi method unpack(Blob:D: Str:D $template) { nqp::isnull(nqp::getlexcaller('EXPERIMENTAL-PACK')) ?? X::Experimental.new( feature => "the 'unpack' method", use => "pack" ).throw !! self.unpack($template.comb(/<[a..zA..Z]>[\d+|'*']?/)) } multi method unpack(Blob:D: @template) { nqp::isnull(nqp::getlexcaller('EXPERIMENTAL-PACK')) ?? X::Experimental.new( feature => "the 'unpack' method", use => "pack" ).throw !! nqp::getlexcaller('EXPERIMENTAL-PACK')(self, @template) } # XXX: the pack.t spectest file seems to require this method # not sure if it should be changed to list there... method contents(Blob:D:) { self.list } method encoding() { Any } method !unshift-list(\action,\to,\from) { if nqp::istype(from,List) { my Mu $from := nqp::getattr(from,List,'$!reified'); if nqp::defined($from) { my int $i = nqp::elems($from); nqp::istype((my $got = nqp::atpos($from,$i)),Int) ?? nqp::unshift_i(to,$got) !! self!fail-typecheck-element(action,$i,$got).throw while nqp::isge_i(--$i,0); } to } else { nqp::splice(to,self!push-list(action,nqp::create(self),from),0,0) } } method !fail-range($got) { X::OutOfRange.new( :what($*INDEX // 'Index'), :$got, :range("0..{nqp::elems(self)-1}") ).Failure } method !typecheck($action, $got) { X::TypeCheck.new( operation => $action ~ " to " ~ self.^name, got => $got, expected => T, ) } method !fail-typecheck($action,$got) { self!typecheck($action, $got).Failure } method !typecheck-element($action, $i, $got) { self!typecheck($action ~ "ing element #" ~ $i, $got) } method !fail-typecheck-element($action, $i, $got) { self!typecheck-element($action, $i, $got).Failure } method !throw-typecheck-element($action, $i, $got) { self!typecheck-element($action, $i, $got).throw } multi method ACCEPTS(Blob:D: Blob:D \Other) { nqp::hllbool( nqp::unless( nqp::eqaddr(self,my \other := nqp::decont(Other)), nqp::if( nqp::iseq_i( (my int $elems = nqp::elems(self)), nqp::elems(other) ), nqp::stmts( (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && (self[$i] == other[$i]), nqp::null ), nqp::iseq_i($i,$elems) ) ) ) ) } } constant blob8 = Blob[uint8]; constant blob16 = Blob[uint16]; constant blob32 = Blob[uint32]; constant blob64 = Blob[uint64]; my class utf8 does Blob[uint8] is repr('VMArray') { method encoding(--> "utf-8") { } multi method Str(utf8:D:) { self.decode } multi method Stringy(utf8:D:) { self.decode } } my class utf16 does Blob[uint16] is repr('VMArray') { method encoding(--> "utf-16") { } multi method Str(utf16:D:) { self.decode } multi method Stringy(utf16:D:) { self.decode } } my class utf32 does Blob[uint32] is repr('VMArray') { method encoding(--> "utf-32") { } multi method Str(utf32:D:) { self.decode } multi method Stringy(utf32:D:) { self.decode } } my role Buf[::T = uint8] does Blob[T] is repr('VMArray') is array_type(T) { #- start of generated part of Buf Signed role -------------------------------- #- Generated on 2022-02-17T17:11:17+01:00 by ./tools/build/makeBUF_ROLES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE my role SignedBuf[::T] is repr('VMArray') is array_type(T) is implementation-detail { multi method AT-POS(::?ROLE:D: uint $pos) is raw is default { nqp::atposref_i(self,$pos) } multi method AT-POS(::?ROLE:D: Int:D $pos) is raw is default { nqp::islt_i($pos,0) ?? self!fail-range($pos) !! nqp::atposref_i(self,$pos) } multi method ASSIGN-POS(::?ROLE:D: uint $pos, Mu \assignee) { nqp::bindpos_i(self,$pos,assignee) } multi method ASSIGN-POS(::?ROLE:D: Int:D $pos, Mu \assignee) { nqp::islt_i($pos,0) ?? self!fail-range($pos) !! nqp::bindpos_i(self,$pos,assignee) } multi method list(::?ROLE:D:) is default { my int $elems = nqp::elems(self); # presize memory, but keep it empty, so we can just push my $buffer := nqp::setelems( nqp::setelems(nqp::create(IterationBuffer),$elems), 0 ); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::push($buffer,nqp::atposref_i(self,$i)) ); $buffer.List } method write-ubits(::?ROLE \SELF: int $pos, Int:D $bits, UInt:D \value ) is raw { # sanity check POS-OOR(SELF, $pos) if $pos < 0; my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); # set up basic info my int $first-bit = $pos +& 7; my int $last-bit = ($pos + $bits) +& 7; my int $first-byte = $pos +> 3; my int $last-byte = ($pos + $bits - 1) +> 3; my $value := value +& (1 +< $bits - 1); # mask valid part $value := $value +< (8 - $last-bit) if $last-bit; # move into position my int $lmask = nqp::sub_i(1 +< $first-bit,1) +< (8 - $first-bit) if $first-bit; my int $rmask = 1 +< nqp::sub_i(8 - $last-bit,1) if $last-bit; # all done in a single byte if $first-byte == $last-byte { nqp::bindpos_i($self,$first-byte, $value +| (nqp::atpos_i($self,$first-byte) +& ($lmask +| $rmask)) ); } # spread over multiple bytes else { my int $i = $last-byte; # process last byte first if it is a partial if $last-bit { nqp::bindpos_i($self,$i, ($value +& 255) +| (nqp::atpos_i($self,$i) +& $rmask) ); $value := $value +> 8; } # not a partial, so make sure we process last byte later else { ++$i; } # walk from right to left, exclude left-most is partial my int $last = $first-byte + nqp::isgt_i($first-bit,0); nqp::while( nqp::isge_i(--$i,$last), nqp::stmts( nqp::bindpos_i($self,$i,($value +& 255)), ($value := $value +> 8) ) ); # process last byte if it was a partial nqp::bindpos_i($self,$i,($value +& 255) +| (nqp::atpos_i($self,$i) +& $lmask)) if $first-bit; } $self } } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of Buf Signed role ---------------------------------- #- start of generated part of Buf Unsigned role -------------------------------- #- Generated on 2022-02-17T17:11:17+01:00 by ./tools/build/makeBUF_ROLES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE my role UnsignedBuf[::T] is repr('VMArray') is array_type(T) is implementation-detail { multi method AT-POS(::?ROLE:D: uint $pos) is raw is default { nqp::atposref_u(self,$pos) } multi method AT-POS(::?ROLE:D: Int:D $pos) is raw is default { nqp::islt_i($pos,0) ?? self!fail-range($pos) !! nqp::atposref_u(self,$pos) } multi method ASSIGN-POS(::?ROLE:D: uint $pos, Mu \assignee) { nqp::bindpos_u(self,$pos,assignee) } multi method ASSIGN-POS(::?ROLE:D: Int:D $pos, Mu \assignee) { nqp::islt_i($pos,0) ?? self!fail-range($pos) !! nqp::bindpos_u(self,$pos,assignee) } multi method list(::?ROLE:D:) is default { my int $elems = nqp::elems(self); # presize memory, but keep it empty, so we can just push my $buffer := nqp::setelems( nqp::setelems(nqp::create(IterationBuffer),$elems), 0 ); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::push($buffer,nqp::atposref_u(self,$i)) ); $buffer.List } method write-ubits(::?ROLE \SELF: int $pos, Int:D $bits, UInt:D \value ) is raw { # sanity check POS-OOR(SELF, $pos) if $pos < 0; my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); # set up basic info my int $first-bit = $pos +& 7; my int $last-bit = ($pos + $bits) +& 7; my int $first-byte = $pos +> 3; my int $last-byte = ($pos + $bits - 1) +> 3; my $value := value +& (1 +< $bits - 1); # mask valid part $value := $value +< (8 - $last-bit) if $last-bit; # move into position my int $lmask = nqp::sub_i(1 +< $first-bit,1) +< (8 - $first-bit) if $first-bit; my int $rmask = 1 +< nqp::sub_i(8 - $last-bit,1) if $last-bit; # all done in a single byte if $first-byte == $last-byte { nqp::bindpos_u($self,$first-byte, $value +| (nqp::atpos_u($self,$first-byte) +& ($lmask +| $rmask)) ); } # spread over multiple bytes else { my int $i = $last-byte; # process last byte first if it is a partial if $last-bit { nqp::bindpos_u($self,$i, ($value +& 255) +| (nqp::atpos_u($self,$i) +& $rmask) ); $value := $value +> 8; } # not a partial, so make sure we process last byte later else { ++$i; } # walk from right to left, exclude left-most is partial my int $last = $first-byte + nqp::isgt_i($first-bit,0); nqp::while( nqp::isge_i(--$i,$last), nqp::stmts( nqp::bindpos_u($self,$i,($value +& 255)), ($value := $value +> 8) ) ); # process last byte if it was a partial nqp::bindpos_u($self,$i,($value +& 255) +| (nqp::atpos_u($self,$i) +& $lmask)) if $first-bit; } $self } } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of Buf Unsigned role ---------------------------------- $?CLASS.^add_role(T.^unsigned ?? UnsignedBuf[T] !! SignedBuf[T]); multi method WHICH(Buf:D:) { self.Mu::WHICH } method Blob(Blob:D:) { (nqp::eqaddr(T,uint8) ?? Blob !! Blob.^parameterize(T)).new: self } multi method STORE(Buf:D: Blob:D $blob) { nqp::splice(nqp::setelems(self,0),$blob,0,0) } # The "is default" is needed to prevent runtime dispatch errors multi method STORE(Buf:D: int @values) is default { nqp::splice(nqp::setelems(self,0),@values,0,0) } multi method STORE(Buf:D: Iterable:D \iterable) { iterable.is-lazy ?? self.throw-iterator-cannot-be-lazy('store') !! self!push-list("initializ",nqp::setelems(self,0),iterable); } multi method STORE(Buf:D: Any:D \non-iterable) { my int $elems = non-iterable.elems; nqp::setelems(self,0); nqp::push_i(self,my $ = non-iterable.AT-POS($_)) for ^$elems; #FIXME needs to handle unsigned with push_u self } # for simplicity's sake, these are not multis method write-int8(::?ROLE: int $offset, int8 $value, Endian $endian = NativeEndian ) is raw { my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); nqp::writeint($self,$offset,$value, nqp::bitor_i(nqp::const::BINARY_SIZE_8_BIT,$endian)); $self } method write-int16(::?ROLE: int $offset, int16 $value, Endian $endian = NativeEndian ) is raw { my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); nqp::writeint($self,$offset,$value, nqp::bitor_i(nqp::const::BINARY_SIZE_16_BIT,$endian)); $self } method write-int32(::?ROLE: int $offset, int32 $value, Endian $endian = NativeEndian ) is raw { my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); nqp::writeint($self,$offset,$value, nqp::bitor_i(nqp::const::BINARY_SIZE_32_BIT,$endian)); $self } method write-int64(::?ROLE: int $offset, Int:D $value, Endian $endian = NativeEndian ) is raw { my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); nqp::writeint($self,$offset,$value, nqp::bitor_i(nqp::const::BINARY_SIZE_64_BIT,$endian)); $self } method write-int128(::?ROLE: int $offset, Int:D $value, Endian $endian = NativeEndian ) is raw { # These uints are intentional to keep the value within 64 bits my uint $first = ($value +> 64) +& (1 +< 64 - 1); my uint $second = $value +& (1 +< 64 - 1); my $be = $endian == BigEndian || ($endian == NativeEndian && Kernel.endian == BigEndian); my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); $self.write-uint64($offset, $be ?? $first !! $second, $endian); $self.write-uint64($offset + 8, $be ?? $second !! $first, $endian); $self } method write-uint8(::?ROLE: int $offset, uint8 $value, Endian $endian = NativeEndian ) is raw { my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); nqp::writeuint($self,$offset,$value, nqp::bitor_i(nqp::const::BINARY_SIZE_8_BIT,$endian)); $self } method write-uint16(::?ROLE: int $offset, uint16 $value, Endian $endian = NativeEndian ) is raw { my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); nqp::writeuint($self,$offset,$value, nqp::bitor_i(nqp::const::BINARY_SIZE_16_BIT,$endian)); $self } method write-uint32(::?ROLE: int $offset, uint32 $value, Endian $endian = NativeEndian ) is raw { my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); nqp::writeuint($self,$offset,$value, nqp::bitor_i(nqp::const::BINARY_SIZE_32_BIT,$endian)); $self } method write-uint64(::?ROLE: int $offset, UInt:D $value, Endian $endian = NativeEndian ) is raw { my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); nqp::writeuint($self,$offset,$value, nqp::bitor_i(nqp::const::BINARY_SIZE_64_BIT,$endian)); $self } method write-uint128(::?ROLE: int $offset, UInt:D $value, Endian $endian = NativeEndian ) is raw { my \first := $value +> 64; my \second := $value +& ( 1 +< 64 - 1 ); my $be = $endian == BigEndian || ($endian == NativeEndian && Kernel.endian == BigEndian); my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); $self.write-uint64($offset, $be ?? first !! second, $endian); $self.write-uint64($offset + 8, $be ?? second !! first, $endian); $self } method write-num32(::?ROLE: int $offset, num32 $value, Endian $endian = NativeEndian ) is raw { my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); nqp::writenum($self,$offset,$value, nqp::bitor_i(nqp::const::BINARY_SIZE_32_BIT,$endian)); $self } method write-num64(::?ROLE: int $offset, num64 $value, Endian $endian = NativeEndian ) is raw { my $self := nqp::isconcrete(self) ?? self !! nqp::create(self); nqp::writenum($self,$offset,$value, nqp::bitor_i(nqp::const::BINARY_SIZE_64_BIT,$endian)); $self } sub POS-OOR(\SELF, int $pos --> Nil) is hidden-from-backtrace { die "Can only write from position 0..* in buffer{ " '" ~ SELF.VAR.name ~ "'" if nqp::iscont(SELF) }, you tried: $pos" } method write-bits(::?ROLE \SELF: int $pos, Int:D $bits, Int:D \value ) is raw { SELF.write-ubits($pos, $bits, value +& (1 +< $bits - 1)) } proto method pop(|) { * } multi method pop(Buf:D:) { nqp::elems(self) ?? nqp::pop_i(self) !! self.fail-cannot-be-empty('pop') } proto method shift(|) { * } multi method shift(Buf:D:) { nqp::elems(self) ?? nqp::shift_i(self) !! self.fail-cannot-be-empty('shift') } method reallocate(Buf:D: Int:D $elements) { nqp::setelems(self,$elements) } my $empty := nqp::list_i; proto method splice(|) { * } multi method splice(Buf:D \SELF:) { my $buf = SELF; SELF = Buf.new; $buf } multi method splice(Buf:D: Int:D $offset, $size = Whatever) { my int $remove = self!remove($offset,$size); my $result := $remove ?? self.subbuf($offset,$remove) # until something smarter !! nqp::create(self); nqp::splice(self,$empty,$offset,$remove); $result } multi method splice(Buf:D: Int:D $offset, $size, int $got) { self!splice-native($offset,$size,$got) } multi method splice(Buf:D: Int:D $offset, $size, Int:D $got) { self!splice-native($offset,$size,$got) } multi method splice(Buf:D: Int:D $offset, $size, Mu:D $got) { self!fail-typecheck('splice',$got) } multi method splice(Buf:D: Int:D $offset, $size, Buf:D $buf) { self!splice-native($offset,$size,$buf) } multi method splice(Buf:D: Int:D $offset, $size, int @values) { self!splice-native($offset,$size,@values) } multi method splice(Buf:D: Int:D $offset, $size, @values) { self!splice-native($offset,$size, self!push-list("splic",nqp::create(self),@values)) } method !remove(\offset,\size) { nqp::istype(size,Whatever) ?? nqp::elems(self) - offset !! nqp::istype(size,Int) ?? size !! size.Int } method !splice-native(Buf:D: Int:D $offset, $size, \x) { my int $remove = self!remove($offset,$size); my $result := $remove ?? self.subbuf($offset,$remove) # until something smarter !! nqp::create(self); nqp::splice( self,nqp::islist(x) ?? x !! nqp::list_i(x),$offset,$remove); $result } proto method push(|) { * } multi method push(Buf:D: int $got) { nqp::push_i(self,$got); self } multi method push(Buf:D: Int:D $got) { nqp::push_i(self,$got); self } multi method push(Buf:D: Mu:D $got) { self!fail-typecheck('push',$got) } multi method push(Buf:D: Blob:D $buf) { nqp::splice(self,$buf,nqp::elems(self),0) } multi method push(Buf:D: **@values) { self!pend(@values,'push') } proto method append(|) { * } multi method append(Buf:D: int $got) { nqp::push_i(self,$got); self } multi method append(Buf:D: Int:D $got) { nqp::push_i(self,$got); self } multi method append(Buf:D: Mu:D $got) { self!fail-typecheck('append',$got) } multi method append(Buf:D: Blob:D $buf) { nqp::splice(self,$buf,nqp::elems(self),0) } multi method append(Buf:D: int @values) { nqp::splice(self,@values,nqp::elems(self),0) } multi method append(Buf:D: @values) { self!pend(@values,'append') } multi method append(Buf:D: *@values) { self!pend(@values,'append') } proto method unshift(|) { * } multi method unshift(Buf:D: int $got) { nqp::unshift_i(self,$got); self } multi method unshift(Buf:D: Int:D $got) { nqp::unshift_i(self,$got); self } multi method unshift(Buf:D: Mu:D $got) { self!fail-typecheck('unshift',$got) } multi method unshift(Buf:D: Blob:D $buf) { nqp::splice(self,$buf,0,0) } multi method unshift(Buf:D: **@values) { self!pend(@values,'unshift') } proto method prepend(|) { * } multi method prepend(Buf:D: int $got) { nqp::unshift_i(self,$got); self } multi method prepend(Buf:D: Int:D $got) { nqp::unshift_i(self,$got); self } multi method prepend(Buf:D: Mu:D $got) { self!fail-typecheck('prepend',$got) } multi method prepend(Buf:D: Blob:D $buf) { nqp::splice(self,$buf,0,0) } multi method prepend(Buf:D: int @values) { nqp::splice(self,@values,0,0) } multi method prepend(Buf:D: @values) { self!pend(@values,'prepend') } multi method prepend(Buf:D: *@values) { self!pend(@values,'prepend') } method !pend(Buf:D: @values, $action) { @values.is-lazy ?? self.fail-iterator-cannot-be-lazy($action) !! $action eq 'push' || $action eq 'append' ?? self!push-list($action,self,@values) !! self!unshift-list($action,self,@values) } method subbuf-rw($from = 0, $elems = self.elems - $from) is rw { my Blob $subbuf = self.subbuf($from, $elems); Proxy.new( FETCH => sub ($) { $subbuf }, STORE => sub ($, Blob:D $new) { nqp::splice(self,nqp::decont($new),$from,$elems) } ); } } constant buf8 = Buf[uint8]; constant buf16 = Buf[uint16]; constant buf32 = Buf[uint32]; constant buf64 = Buf[uint64]; multi sub prefix:<~>(Blob:D \a) { X::Buf::AsStr.new(object => a, method => '~' ).throw } multi sub infix:<~>(Blob:D $a) { $a } multi sub infix:<~>(Blob:D $a, Blob:D $b) { my $res := nqp::create(nqp::eqaddr($a.WHAT,$b.WHAT) ?? $a !! Buf.^pun); my $adc := nqp::decont($a); my $bdc := nqp::decont($b); my int $alen = nqp::elems($adc); my int $blen = nqp::elems($bdc); nqp::setelems($res, $alen + $blen); nqp::splice($res, $adc, 0, $alen); nqp::splice($res, $bdc, $alen, $blen); } multi sub prefix:<~^>(UnsignedBlob:D $a) { my int $elems = nqp::elems($a); my $r := nqp::create($a); nqp::setelems($a,$elems); my int $i = -1; nqp::bindpos_u($r,$i,nqp::bitneg_i(nqp::atpos_u($a,$i))) while nqp::islt_i(++$i,$elems); $r } multi sub prefix:<~^>(SignedBlob:D $a) { my int $elems = nqp::elems($a); my $r := nqp::create($a); nqp::setelems($a,$elems); my int $i = -1; nqp::bindpos_i($r,$i,nqp::bitneg_i(nqp::atpos_i($a,$i))) while nqp::islt_i(++$i,$elems); $r } multi sub infix:<~&>(UnsignedBlob:D $a, UnsignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_u($r,$i, nqp::bitand_i(nqp::atpos_u($a,$i),nqp::atpos_u($b,$i))) while nqp::islt_i(++$i,$do); --$i; # went one too far nqp::bindpos_u($r,$i,0) while nqp::islt_i(++$i,$max); $r } multi sub infix:<~&>(UnsignedBlob:D $a, SignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_u($r,$i, nqp::bitand_i(nqp::atpos_u($a,$i),nqp::atpos_i($b,$i))) while nqp::islt_i(++$i,$do); --$i; # went one too far nqp::bindpos_u($r,$i,0) while nqp::islt_i(++$i,$max); $r } multi sub infix:<~&>(SignedBlob:D $a, UnsignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_i($r,$i, nqp::bitand_i(nqp::atpos_i($a,$i),nqp::atpos_u($b,$i))) while nqp::islt_i(++$i,$do); --$i; # went one too far nqp::bindpos_u($r,$i,0) while nqp::islt_i(++$i,$max); $r } multi sub infix:<~&>(SignedBlob:D $a, SignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_i($r,$i, nqp::bitand_i(nqp::atpos_i($a,$i),nqp::atpos_i($b,$i))) while nqp::islt_i(++$i,$do); --$i; # went one too far nqp::bindpos_u($r,$i,0) while nqp::islt_i(++$i,$max); $r } multi sub infix:<~|>(UnsignedBlob:D $a, UnsignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $from := $elemsa > $elemsb ?? $a !! $b; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_u($r,$i, nqp::bitor_i(nqp::atpos_u($a,$i),nqp::atpos_u($b,$i))) while nqp::islt_i(++$i,$do); $i = $i - 1; # went one too far nqp::bindpos_u($r,$i,nqp::atpos_u($from,$i)) while nqp::islt_i(++$i,$max); $r } multi sub infix:<~|>(UnsignedBlob:D $a, SignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_u($r,$i, nqp::bitor_i(nqp::atpos_u($a,$i),nqp::atpos_i($b,$i))) while nqp::islt_i(++$i,$do); $i = $i - 1; # went one too far if $elemsa > $elemsb { nqp::bindpos_u($r,$i,nqp::atpos_u($a,$i)) while nqp::islt_i(++$i,$max); } else { nqp::bindpos_u($r,$i,nqp::atpos_i($b,$i)) while nqp::islt_i(++$i,$max); } $r } multi sub infix:<~|>(SignedBlob:D $a, UnsignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_i($r,$i, nqp::bitor_i(nqp::atpos_i($a,$i),nqp::atpos_u($b,$i))) while nqp::islt_i(++$i,$do); $i = $i - 1; # went one too far if $elemsa > $elemsb { nqp::bindpos_i($r,$i,nqp::atpos_i($a,$i)) while nqp::islt_i(++$i,$max); } else { nqp::bindpos_i($r,$i,nqp::atpos_u($b,$i)) while nqp::islt_i(++$i,$max); } $r } multi sub infix:<~|>(SignedBlob:D $a, SignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $from := $elemsa > $elemsb ?? $a !! $b; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_i($r,$i, nqp::bitor_i(nqp::atpos_i($a,$i),nqp::atpos_i($b,$i))) while nqp::islt_i(++$i,$do); $i = $i - 1; # went one too far nqp::bindpos_u($r,$i,nqp::atpos_i($from,$i)) while nqp::islt_i(++$i,$max); $r } multi sub infix:<~^>(UnsignedBlob:D $a, UnsignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $from := $elemsa > $elemsb ?? $a !! $b; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_u($r,$i, nqp::bitxor_i(nqp::atpos_u($a,$i),nqp::atpos_u($b,$i))) while nqp::islt_i(++$i,$do); --$i; # went one too far nqp::bindpos_u($r,$i,nqp::atpos_u($from,$i)) while nqp::islt_i(++$i,$max); $r } multi sub infix:<~^>(UnsignedBlob:D $a, SignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $from := $elemsa > $elemsb ?? $a !! $b; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_u($r,$i, nqp::bitxor_i(nqp::atpos_u($a,$i),nqp::atpos_i($b,$i))) while nqp::islt_i(++$i,$do); --$i; # went one too far if $elemsa > $elemsb { nqp::bindpos_u($r,$i,nqp::atpos_u($a,$i)) while nqp::islt_i(++$i,$max); } else { nqp::bindpos_u($r,$i,nqp::atpos_i($b,$i)) while nqp::islt_i(++$i,$max); } $r } multi sub infix:<~^>(SignedBlob:D $a, UnsignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $from := $elemsa > $elemsb ?? $a !! $b; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_i($r,$i, nqp::bitxor_i(nqp::atpos_i($a,$i),nqp::atpos_u($b,$i))) while nqp::islt_i(++$i,$do); --$i; # went one too far if $elemsa > $elemsb { nqp::bindpos_i($r,$i,nqp::atpos_i($a,$i)) while nqp::islt_i(++$i,$max); } else { nqp::bindpos_i($r,$i,nqp::atpos_u($b,$i)) while nqp::islt_i(++$i,$max); } $r } multi sub infix:<~^>(SignedBlob:D $a, SignedBlob:D $b) { my int $elemsa = nqp::elems($a); my int $elemsb = nqp::elems($b); my int $do = $elemsa > $elemsb ?? $elemsb !! $elemsa; my int $max = $elemsa > $elemsb ?? $elemsa !! $elemsb; my $from := $elemsa > $elemsb ?? $a !! $b; my $r := nqp::create($a); nqp::setelems($r,$max); my int $i = -1; nqp::bindpos_i($r,$i, nqp::bitxor_i(nqp::atpos_i($a,$i),nqp::atpos_i($b,$i))) while nqp::islt_i(++$i,$do); --$i; # went one too far nqp::bindpos_i($r,$i,nqp::atpos_i($from,$i)) while nqp::islt_i(++$i,$max); $r } multi sub infix:(Blob:D $a, Blob:D $b --> Bool:D) { nqp::hllbool( nqp::eqaddr($a,$b) || (nqp::eqaddr($a.WHAT,$b.WHAT) && $a.SAME($b)) ) } multi sub infix:(Blob:D $a, Blob:D $b) { ORDER($a.COMPARE($b)) } multi sub infix: (Blob:D $a, Blob:D $b --> Bool:D) { nqp::hllbool(nqp::eqaddr($a,$b) || $a.SAME($b)) } multi sub infix: (Blob:D $a, Blob:D $b --> Bool:D) { nqp::hllbool( nqp::not_i(nqp::eqaddr($a,$b) || $a.SAME($b)) ) } multi sub infix: (Blob:D $a, Blob:D $b) { nqp::hllbool(nqp::iseq_i($a.COMPARE($b),-1)) } multi sub infix: (Blob:D $a, Blob:D $b) { nqp::hllbool(nqp::iseq_i($a.COMPARE($b),1)) } multi sub infix: (Blob:D $a, Blob:D $b) { nqp::hllbool(nqp::isne_i($a.COMPARE($b),1)) } multi sub infix: (Blob:D $a, Blob:D $b) { nqp::hllbool(nqp::isne_i($a.COMPARE($b),-1)) } proto sub subbuf-rw($, $?, $?, *%) {*} multi sub subbuf-rw(Buf:D $b) is rw { $b.subbuf-rw(0, $b.elems); } multi sub subbuf-rw(Buf:D $b, Int() $from) is rw { $b.subbuf-rw($from, $b.elems - $from) } multi sub subbuf-rw(Buf:D $b, $from, $elems) is rw { $b.subbuf-rw($from, $elems) } #line 1 SETTING::src/core.c/Uni.rakumod my class NFC is repr('VMArray') is array_type(uint32) { ... } my class NFD is repr('VMArray') is array_type(uint32) { ... } my class NFKC is repr('VMArray') is array_type(uint32) { ... } my class NFKD is repr('VMArray') is array_type(uint32) { ... } my class X::InvalidCodepoint { ... } my class Encoding::Registry { ... } my class Uni does Positional[uint32] does Stringy is repr('VMArray') is array_type(uint32) { multi method new(Uni:) { nqp::create(self) } multi method new(Uni: *@codes) { @codes.elems; # reify and test for lazy sequences my $uni := nqp::create(self); my $codepoints := nqp::getattr(@codes,List,'$!reified'); my $code; nqp::while( nqp::elems($codepoints), nqp::if(nqp::isgt_i($code = nqp::shift($codepoints), 0x10ffff) || (nqp::isle_i(0xd800, $code) && nqp::isle_i($code, 0xdfff)) || nqp::islt_i($code, 0), X::InvalidCodepoint.new(:$code).throw, nqp::push_i($uni,$code))); $uni } # array[uint32] candidate added in core_epilogue my class UniList does PredictiveIterator { has $!uni; has int $!els; has int $!i; method !SET-SELF(\uni) { $!uni := uni; $!i = -1; $!els = nqp::elems(uni); self } method new (\uni) { nqp::create(self)!SET-SELF: uni } method pull-one { nqp::islt_i(++$!i,$!els) ?? nqp::atpos_u($!uni, $!i) !! IterationEnd } method skip-one { nqp::islt_i(++$!i,$!els) } method push-all(\target --> IterationEnd) { my $uni := $!uni; # lexicals faster than attrs my int $els = $!els; my int $i = $!i; nqp::while( nqp::islt_i(++$i,$els), target.push: nqp::atpos_u($uni, $i) ); $!i = $i; } method count-only(--> Int:D) { nqp::p6box_i($!els - $!i - nqp::islt_i($!i,$!els)) } method sink-all(--> IterationEnd) { $!i = $!els } } method list(Uni:D:) { Seq.new(UniList.new(self)) } method Uni(Uni:D:) { self } method NFC(Uni:D:) { nqp::normalizecodes(self, nqp::const::NORMALIZE_NFC, nqp::create(NFC)) } method NFD(Uni:D:) { nqp::normalizecodes(self, nqp::const::NORMALIZE_NFD, nqp::create(NFD)) } method NFKC(Uni:D:) { nqp::normalizecodes(self, nqp::const::NORMALIZE_NFKC, nqp::create(NFKC)) } method NFKD(Uni:D:) { nqp::normalizecodes(self, nqp::const::NORMALIZE_NFKD, nqp::create(NFKD)) } multi method Str(Uni:D:) { nqp::strfromcodes(self) } multi method Bool(Uni:D:) { nqp::hllbool(nqp::elems(self)); } method codes(Uni:D:) { nqp::elems(self) } method elems(Uni:D:) { nqp::elems(self) } method Numeric(Uni:D:) { nqp::elems(self) } method Int(Uni:D:) { nqp::elems(self) } multi method ACCEPTS(Uni:D: Uni:D $other --> Bool:D) { nqp::hllbool( nqp::iseq_i(nqp::elems(self),nqp::elems($other)) && nqp::stmts( (my int $i = -1), (my uint $elems = nqp::elems(self)), nqp::while( nqp::islt_i(++$i,$elems) && nqp::iseq_i( nqp::atpos_u(self,$i), nqp::atpos_u($other,$i) ), nqp::null ), nqp::iseq_i($i,$elems) ) ) } multi method EXISTS-POS(Uni:D: uint $pos) { nqp::hllbool(nqp::islt_i($pos,nqp::elems(self))) } multi method EXISTS-POS(Uni:D: Int:D $pos) { $pos < nqp::elems(self) && $pos >= 0; } multi method AT-POS(Uni:D: uint $pos) { nqp::isge_i($pos,nqp::elems(self)) ?? X::OutOfRange.new( :what($*INDEX // 'Index'), :got($pos), :range("0..{nqp::elems(self)-1}") ).Failure !! nqp::atpos_u(self,$pos) } multi method AT-POS(Uni:D: Int:D $pos) { nqp::isge_i($pos,nqp::elems(self)) || nqp::islt_i($pos,0) ?? X::OutOfRange.new( :what($*INDEX // 'Index'), :got($pos), :range("0..{nqp::elems(self)-1}") ).Failure !! nqp::atpos_u(self,$pos) } multi method gist(Uni:D:) { self.^name ~ ':0x<' ~ self.list.fmt('%04x', ' ') ~ '>' } multi method raku(Uni:D:) { 'Uni.new(' ~ self.list.fmt('0x%04x', ', ') ~ ')' ~ (self.WHAT === Uni ?? '' !! '.' ~ self.^name); } method encode(Str:D $encoding = 'utf8', :$replacement, Bool() :$translate-nl = False, :$strict --> Blob:D) { my $encoder = Encoding::Registry.find($encoding) .encoder(:$replacement, :$translate-nl, :$strict); my $buf = Buf.new; for self.list -> $character { $buf.append($encoder.encode-chars($character.chr)); } $buf; } } my class NFD is Uni { method new(|) { die "Cannot create an NFD directly"; # XXX typed, better message } method NFD() { self } } my class NFC is Uni { method new(|) { die "Cannot create an NFC directly"; # XXX typed, better message } method NFC() { self } } my class NFKD is Uni { method new(|) { die "Cannot create an NFKD directly"; # XXX typed, better message } method NFKD() { self } } my class NFKC is Uni { method NFKC() { self } method new(|) { die "Cannot create an NFKC directly"; # XXX typed, better message } } multi sub infix:(Uni:D $a, Uni:D $b) { my uint $elems-a = nqp::elems($a); my uint $elems-b = nqp::elems($b); my uint $elems = nqp::islt_i($elems-a,$elems-b) ?? $elems-a !! $elems-b; my int $i = -1; nqp::until( nqp::isge_i(++$i,$elems) || (my $res = nqp::cmp_i(nqp::atpos_u($a,$i),nqp::atpos_u($b,$i))), nqp::null ); ORDER($res || nqp::cmp_i($elems-a,$elems-b)) } #line 1 SETTING::src/core.c/Collation.rakumod class Collation { has int $.collation-level = 85; has $!Country = 'International'; method gist { "collation-level => $!collation-level, Country => $!Country, " ~ "Language => None, primary => {self.primary}, secondary => {self.secondary}, " ~ "tertiary => {self.tertiary}, quaternary => {self.quaternary}" } method set ( Int :$primary = 1, Int :$secondary = 1, Int :$tertiary = 1, Int :$quaternary = 1) { my int $i = 0; $i += 1 if $primary.sign == 1; $i += 2 if $primary.sign == -1; $i += 4 if $secondary.sign == 1; $i += 8 if $secondary.sign == -1; $i += 16 if $tertiary.sign == 1; $i += 32 if $tertiary.sign == -1; $i += 64 if $quaternary.sign == 1; $i += 128 if $quaternary.sign == -1; $!collation-level = $i; self; } method check ($more, $less) { # Hopefully the user didn't set collation-level manually to have a level # both enabled *and* disabled. But check if this is the case anyway. return 0 if $!collation-level +& all($more,$less); return 1 if $!collation-level +& $more; return -1 if $!collation-level +& $less; return 0; } method primary { self.check( 1, 2) } method secondary { self.check( 4, 8) } method tertiary { self.check(16, 32) } method quaternary { self.check(64, 128) } } Rakudo::Internals.REGISTER-DYNAMIC: '$*COLLATION', { PROCESS::<$COLLATION> := Collation.new; } #line 1 SETTING::src/core.c/Encoding/Decoder.rakumod role Encoding::Decoder { method add-bytes(Blob:D $bytes --> Nil) { ... } method consume-available-chars(--> Str:D) { ... } method consume-all-chars(--> Str:D) { ... } method consume-exactly-chars(int $chars, Bool:D :$eof = False --> Str) { ... } method set-line-separators(@seps --> Nil) { ... } method consume-line-chars(Bool:D :$chomp = False, Bool:D :$eof = False --> Str) { ... } method is-empty(--> Bool) { ... } method bytes-available(--> Int:D) { ... } method consume-exactly-bytes(int $bytes --> Blob) { ... } } #line 1 SETTING::src/core.c/Encoding/Decoder/Builtin.rakumod my class Encoding::Decoder::Builtin is repr('Decoder') does Encoding::Decoder { method new(str $encoding, :$translate-nl, :$replacement, :$strict) { nqp::decoderconfigure(nqp::create(self), $encoding, nqp::hash( 'translate_newlines', $translate-nl ?? 1 !! 0, 'replacement', $replacement.defined ?? nqp::unbox_s($replacement) !! nqp::null_s(), 'config', $strict ?? 0 !! 1 # Config set to 0 uses the decoder's new default, which is strict # decoding. Setting to 1 uses the 6.c specced functionality where # unmapped codepoints will still decode, e.g. codepoint 129, which # in windows-1252 does not exist. # In 6.d, 'config' will default to 0 ) ) } method add-bytes(Blob:D $bytes --> Nil) { nqp::decoderaddbytes(self, nqp::decont($bytes)); } method consume-available-chars(--> Str:D) { nqp::decodertakeavailablechars(self) } method consume-all-chars(--> Str:D) { nqp::decodertakeallchars(self) } method consume-exactly-chars(int $chars, Bool:D :$eof = False --> Str) { my str $result = $eof ?? nqp::decodertakecharseof(self, $chars) !! nqp::decodertakechars(self, $chars); nqp::isnull_s($result) ?? Str !! $result } method set-line-separators(@seps --> Nil) { my $sep-strs := nqp::list_s(); nqp::push_s($sep-strs, .Str) for @seps; nqp::decodersetlineseps(self, $sep-strs); } method consume-line-chars(Bool:D :$chomp = False, Bool:D :$eof = False --> Str) { my str $line = nqp::decodertakeline(self, $chomp, $eof); nqp::isnull_s($line) ?? Str !! $line } method is-empty() { nqp::hllbool(nqp::decoderempty(self)) } method bytes-available() { nqp::decoderbytesavailable(self) } method consume-exactly-bytes(int $bytes --> Blob) { nqp::ifnull(nqp::decodertakebytes(self, nqp::create(buf8.^pun), $bytes), Blob) } } my class Supply { ... } my class Encoding::Registry { ... } augment class Rakudo::Internals { method BYTE_SUPPLY_DECODER(Supply:D $bin-supply, Str:D $enc, :$translate-nl) { supply { my $decoder = Encoding::Registry.find($enc).decoder(:$translate-nl); my $valid = True; whenever $bin-supply { $decoder.add-bytes($_); my $available; { CATCH { $valid = False; } $available = $decoder.consume-available-chars(); } emit $available if $available ne ''; LAST { # XXX The `with` is required due to a bug where the # LAST phaser is not properly scoped if we don't get # any bytes. Since that means there's nothing to emit # anyway, we'll not worry about this case for now. # # --- or at least that was the the idea before we fixed # that bug: https://colabti.org/irclogger/irclogger_log/perl6?date=2016-12-07#l1192 # and tried removing the `with` in 58cdfd8, but then the error # `No such method 'consume-all-chars' for invocant of type 'Any` # started popping up on Proc::Async tests, so... # there may be some other bug affecting this? if $valid { with $decoder { my $rest = .consume-all-chars(); emit $rest if $rest ne ''; } } } } } } } #line 1 SETTING::src/core.c/Encoding/Encoder.rakumod role Encoding::Encoder { method encode-chars(Str:D --> Blob:D) { ... } } #line 1 SETTING::src/core.c/Encoding/Encoder/Builtin.rakumod my class Encoding::Encoder::Builtin does Encoding::Encoder { has str $!encoding; has Blob $!type; has $!replacement; has int $!config; method new(Str $encoding, Blob:U $type, :$replacement, :$strict) { nqp::create(self)!setup($encoding, $type, :$replacement, :$strict) } method !setup($encoding, $type, :$replacement, :$strict) { $!encoding = $encoding; $!type := nqp::can($type.HOW, 'pun') ?? $type.^pun !! $type.WHAT; $!replacement = $replacement.defined ?? $replacement !! nqp::null_s(); $!config = $strict ?? 0 !! 1; self } method encode-chars(str $str --> Blob:D) { nqp::encoderepconf($str, $!encoding, $!replacement, nqp::create($!type), $!config) } } #line 1 SETTING::src/core.c/Encoding/Encoder/TranslateNewlineWrapper.rakumod my class Encoding::Encoder::TranslateNewlineWrapper does Encoding::Encoder { has Encoding::Encoder $!delegate; method new(Encoding::Encoder $delegate) { nqp::create(self)!setup($delegate) } method !setup(Encoding::Encoder $delegate) { $!delegate := $delegate; self } method encode-chars(Str:D $str --> Blob:D) { $!delegate.encode-chars(Rakudo::Internals.TRANSPOSE($str, "\n", "\r\n")) } } #line 1 SETTING::src/core.c/Encoding.rakumod role Encoding { method name(--> Str) { ... } method alternative-names() { Empty } method encoder(*%options --> Encoding::Encoder) { ... } method decoder(*%options --> Encoding::Decoder) { ... } } #line 1 SETTING::src/core.c/Encoding/Builtin.rakumod class Encoding::Builtin does Encoding { has Str $.name; has $!alternative-names; method new() { X::Cannot::New.new(class => self.WHAT).throw } method SET-SELF(\name, \alternatives) is implementation-detail { $!name := name; $!alternative-names := alternatives; self } method alternative-names() { $!alternative-names } method decoder(:$replacement, :$translate-nl, :$strict --> Encoding::Decoder) { my $decoder = $replacement.DEFINITE && $replacement !=== False ?? Encoding::Decoder::Builtin.new($!name, :$strict, :$translate-nl, :replacement(self!rep-char($replacement))) !! Encoding::Decoder::Builtin.new($!name, :$strict, :$translate-nl); } my int $is-win = Rakudo::Internals.IS-WIN; method encoder(:$replacement, :$translate-nl, :$strict --> Encoding::Encoder) { my $encoder = $replacement.DEFINITE && $replacement !=== False ?? Encoding::Encoder::Builtin.new($!name, self!buf-type(), :$strict, :replacement(self!rep-char($replacement))) !! Encoding::Encoder::Builtin.new($!name, self!buf-type(), :$strict); $translate-nl && $is-win ?? Encoding::Encoder::TranslateNewlineWrapper.new($encoder) !! $encoder } my constant $enc_type = nqp::hash( 'utf8',utf8,'utf16',utf16,'utf32',utf32 ); method !buf-type() { nqp::ifnull(nqp::atkey($enc_type, $!name), blob8) } method !rep-char($replacement) { nqp::istype($replacement, Bool) ?? ($!name.starts-with('utf') ?? "\x[FFFD]" !! "?") !! $replacement.Str } } #line 1 SETTING::src/core.c/Encoding/Registry.rakumod my class X::Encoding::Unknown { ... } my class X::Encoding::AlreadyRegistered { ... } my class Encoding::Registry { my $lock := Lock.new; my %lookup; # access for registering builtins at compile time my $lookup := nqp::getattr(%lookup,Map,'$!storage'); # access for runtime BEGIN { my $lookup := nqp::bindattr(%lookup,Map,'$!storage',nqp::hash); ### If updating encodings, also update src/core.c/Rakudo/Internals.rakumod my $encodings := nqp::list( nqp::list('utf8', 'utf-8'), nqp::list('utf8-c8', 'utf8c8', 'utf-8-c8'), nqp::list('utf16', 'utf-16'), nqp::list('utf16le', 'utf-16le', 'utf16-le', 'utf-16-le'), nqp::list('utf16be', 'utf-16be', 'utf16-be', 'utf-16-be'), nqp::list('ascii'), nqp::list('iso-8859-1','iso_8859-1:1987','iso_8859-1','iso-ir-100', 'latin1','latin-1','csisolatin1','l1','ibm819','cp819'), nqp::list('windows-1251', 'windows1251'), nqp::list('windows-1252', 'windows1252'), nqp::list('windows-932', 'windows932'), nqp::list('gb2312', 'gb2312'), nqp::list('gb18030', 'gb18030'), ); my int $i = -1; my int $elems = nqp::elems($encodings); while nqp::islt_i(++$i,$elems) { my $names := nqp::atpos($encodings,$i); my $builtin := nqp::create(Encoding::Builtin).SET-SELF( nqp::shift($names),nqp::clone($names)); nqp::bindkey($lookup,$builtin.name,$builtin); while nqp::elems($names) { nqp::bindkey($lookup,nqp::shift($names),$builtin); } } } method register(Encoding $enc --> Nil) { $lock.protect: { nqp::existskey($lookup,(my str $key = $enc.name.fc)) ?? X::Encoding::AlreadyRegistered.new(name => $enc.name).throw !! nqp::bindkey($lookup,$key,$enc); my $names := nqp::getattr($enc.alternative-names,List,'$!reified'); my int $elems = nqp::elems($names); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::existskey($lookup,($key = nqp::atpos($names,$i).fc)), X::Encoding::AlreadyRegistered.new( name => nqp::atpos($names,$i)).throw, nqp::bindkey($lookup,$key,$enc) ) ); } } method find(Str() $name) { $lock.protect: { nqp::ifnull( nqp::atkey($lookup,$name.fc), X::Encoding::Unknown.new(:$name).throw ) } } } #line 1 SETTING::src/core.c/Str.rakumod my class Date { ... } my class Range { ... } my class Match { ... } my class Version { ... } my class X::Cannot::Capture { ... } my class X::Str::InvalidCharName { ... } my class X::Str::Numeric { ... } my class X::Str::Match::x { ... } my class X::Str::Subst::Adverb { ... } my class X::Str::Trans::IllegalKey { ... } my class X::Str::Trans::InvalidArg { ... } my class X::NoZeroArgMeaning { ... } my class X::Numeric::Confused { ... } my class X::Syntax::Number::RadixOutOfRange { ... } my constant $?TABSTOP = 8; my class Str does Stringy { # declared in BOOTSTRAP # class Str is Cool # has str $!value is box_target; my $empty := nqp::list; # for nqp::splice # cache cursor initialization lookup my $cursor-init := Match.^lookup("!cursor_init"); my \CURSOR-GLOBAL := Match.^lookup("CURSOR_MORE" ); # :g my \CURSOR-OVERLAP := Match.^lookup("CURSOR_OVERLAP"); # :ov my \CURSOR-EXHAUSTIVE := Match.^lookup("CURSOR_NEXT" ); # :ex my &POST-MATCH := Match.^lookup("MATCH" ); # Match object my &POST-STR := Match.^lookup("STR" ); # Str object my &POPULATE := Match.^lookup("MATCH" ); # populate Match object multi method IO(Str:D:) { IO::Path.new(self) } multi method WHY('Life, the Universe and Everything': --> 42) { } multi method WHICH(Str:D: --> ValueObjAt:D) { nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Str), 'Str|', nqp::concat(nqp::unbox_s(self.^name), '|') ), $!value ), ValueObjAt ) } submethod BUILD(Str() :$value = '' --> Nil) { nqp::bindattr_s(self, Str, '$!value', nqp::unbox_s($value)) } multi method Bool(Str:D: --> Bool:D) { nqp::hllbool(nqp::chars($!value)); } method Capture() { X::Cannot::Capture.new( :what(self) ).throw } multi method Str(Str:D:) { self } multi method Stringy(Str:D:) { self } multi method DUMP(Str:D: --> Str:D) { self.raku } proto method COERCE(|) {*} multi method COERCE(Mu \s) { self.new(:value(nqp::p6box_s(s))) } method Int(Str:D: --> Int:D) { nqp::istype((my $n := self.Numeric),Int) || nqp::istype($n,Failure) ?? $n !! $n.Int } method Num(Str:D: --> Num:D) { nqp::if( nqp::istype((my $numeric := self.Numeric),Failure), $numeric, $numeric.Num || nqp::if( # handle sign of zero. While self.Numeric will give correctly # signed zero for nums in strings, it won't for other types, # and since this method is `Num` we want to return proper zero. # Find first non-whitespace char and check whether it is one # of the minuses. nqp::chars(self) && ( nqp::iseq_i( (my $ch := nqp::ord( nqp::substr( self, nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE, self, 0, nqp::sub_i(nqp::chars(self), 1) ), 1, ) )), 45, # '-' minus ) || nqp::iseq_i($ch, 8722) # '−' minus ), -0e0, 0e0 ) ) } method Version(Str:D: --> Version:D) { Version.new(self) } multi method ACCEPTS(Str:D: Str:D $other --> Bool:D) { nqp::hllbool(nqp::iseq_s($other,$!value)); } multi method ACCEPTS(Str:D: Any:D \other --> Bool:D) { nqp::hllbool(nqp::iseq_s(other.Str,$!value)); } multi method chomp(Str:D: --> Str:D) { nqp::box_s( nqp::substr( self, 0, nqp::chars(self) - nqp::iscclass( #?js: NFG nqp::const::CCLASS_NEWLINE,self,nqp::chars(self) - 1 #?js: NFG ) ), self ) } multi method chomp(Str:D: Str:D $needle--> Str:D) { my int $offset = nqp::sub_i(nqp::chars(self),nqp::chars($needle)); nqp::eqat(self,$needle,$offset) ?? nqp::substr(self,0,$offset) !! self } multi method chop(Str:D: --> Str:D) { nqp::box_s( nqp::substr( self, 0, nqp::chars(self) && nqp::chars(self) - 1 ), self ) } multi method chop(Str:D: Int:D $chopping --> Str:D) { nqp::box_s( nqp::substr( self, 0, nqp::not_i(nqp::isbig_I(nqp::decont($chopping))) && nqp::isgt_i(nqp::chars(self),$chopping) && nqp::sub_i(nqp::chars(self),$chopping) ), self ) } multi method chop(Str:D: $chopping --> Str:D) { self.chop($chopping.Int) } multi method starts-with(Str:D: Str:D $needle, :i(:$ignorecase)!, :m(:$ignoremark) --> Bool:D) { nqp::hllbool($ignorecase ?? $ignoremark ?? nqp::eqaticim(self,$needle,0) !! nqp::eqatic(self,$needle,0) !! $ignoremark ?? nqp::eqatim(self,$needle,0) !! nqp::eqat(self,$needle,0) ) } multi method starts-with(Str:D: Str:D $needle, :m(:$ignoremark)! --> Bool:D) { nqp::hllbool($ignoremark ?? nqp::eqatim(self,$needle,0) !! nqp::eqat(self,$needle,0) ) } multi method starts-with(Str:D: Str:D $needle --> Bool:D) { nqp::hllbool(nqp::eqat(self, $needle, 0)) } multi method ends-with(Str:D: Str:D $needle, :i(:$ignorecase)!, :m(:$ignoremark) --> Bool:D) { nqp::hllbool($ignorecase ?? $ignoremark ?? nqp::eqaticim(self,$needle, nqp::sub_i(nqp::chars(self),nqp::chars($needle))) !! nqp::eqatic(self,$needle, nqp::sub_i(nqp::chars(self),nqp::chars($needle))) !! $ignoremark ?? nqp::eqatim(self,$needle, nqp::sub_i(nqp::chars(self),nqp::chars($needle))) !! nqp::eqat(self,$needle, nqp::sub_i(nqp::chars(self),nqp::chars($needle))) ) } multi method ends-with(Str:D: Str:D $needle, :m(:$ignoremark)! --> Bool:D) { nqp::hllbool($ignoremark ?? nqp::eqatim(self,$needle, nqp::sub_i(nqp::chars(self),nqp::chars($needle))) !! nqp::eqat(self,$needle, nqp::sub_i(nqp::chars(self),nqp::chars($needle))) ) } multi method ends-with(Str:D: Str:D $needle --> Bool:D) { nqp::hllbool( nqp::eqat( self,$needle,nqp::sub_i(nqp::chars(self),nqp::chars($needle)) ) ) } multi method substr-eq(Str:D: Str:D $needle, Int:D $pos, :i(:$ignorecase)!, :m(:$ignoremark) --> Bool:D) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! nqp::hllbool($ignorecase ?? $ignoremark ?? nqp::eqaticim(self,$needle,$pos) !! nqp::eqatic(self,$needle,$pos) !! $ignoremark ?? nqp::eqatim(self,$needle,$pos) !! nqp::eqat(self,$needle,$pos) ) } multi method substr-eq(Str:D: Str:D $needle, Int:D $pos, :m(:$ignoremark)! --> Bool:D) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! nqp::hllbool($ignoremark ?? nqp::eqatim(self,$needle,$pos) !! nqp::eqat(self,$needle,$pos) ) } multi method substr-eq(Str:D: Str:D $needle --> Bool:D) { self.starts-with($needle, |%_) } multi method substr-eq(Str:D: Str:D $needle, Int:D $pos --> Bool:D) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! nqp::hllbool(nqp::eqat(self,$needle,$pos)) } multi method contains(Str:D: Str:D $needle, :i(:$ignorecase)!, :m(:$ignoremark) --> Bool:D) { nqp::hllbool( nqp::isne_i($ignorecase ?? $ignoremark ?? nqp::indexicim(self,$needle,0) !! nqp::indexic(self,$needle,0) !! $ignoremark ?? nqp::indexim(self,$needle,0) !! nqp::index(self,$needle,0), -1 ) ) } multi method contains(Str:D: Str:D $needle, :m(:$ignoremark)! --> Bool:D) { nqp::hllbool( nqp::isne_i($ignoremark ?? nqp::indexim(self,$needle,0) !! nqp::index(self,$needle,0), -1 ) ) } multi method contains(Str:D: Str:D $needle --> Bool:D) { nqp::hllbool(nqp::isne_i(nqp::index($!value,$needle,0),-1)) } multi method contains(Str:D: Regex:D $needle --> Bool:D) { nqp::hllbool( nqp::isge_i( nqp::getattr_i($needle($cursor-init(Match,self,:0c)),Match,'$!pos'), 0 ) ) } multi method contains(Str:D: Str:D $needle, Int:D $pos, :i(:$ignorecase)!, :m(:$ignoremark) --> Bool:D) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! nqp::hllbool( nqp::isne_i($ignorecase ?? $ignoremark ?? nqp::indexicim(self,$needle,$pos) !! nqp::indexic(self,$needle,$pos) !! $ignoremark ?? nqp::indexim(self,$needle,$pos) !! nqp::index(self,$needle,$pos), -1 ) ) } multi method contains(Str:D: Str:D $needle, Int:D $pos, :m(:$ignoremark)! --> Bool:D) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! nqp::hllbool( nqp::isne_i( $ignoremark ?? nqp::indexim(self,$needle,$pos) !! nqp::index(self,$needle,$pos), -1, ) ) } multi method contains(Str:D: Str:D $needle, Int:D $pos --> Bool:D) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! nqp::hllbool(nqp::isne_i(nqp::index(self,$needle,$pos),-1)) } multi method contains(Str:D: Regex:D $needle, Int:D $pos --> Bool:D) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! nqp::hllbool( nqp::islt_i($pos,nqp::chars(self)) && nqp::isge_i( nqp::getattr_i( $needle($cursor-init(Match,self,:c($pos))),Match,'$!pos' ), 0 ) ) } # create indices using index method !indices(str $needle, $overlap, int $start) { my $indices := nqp::create(IterationBuffer); my int $add = $overlap ?? 1 !! nqp::chars($needle) || 1; my int $pos = $start; my int $index; nqp::while( nqp::isne_i(($index = nqp::index(self,$needle,$pos)),-1), nqp::stmts( nqp::push($indices,nqp::p6box_i($index)), ($pos = nqp::add_i($index,$add)) ) ); $indices.List } # create indices using index with ignorecase method !indicesic(str $needle, $overlap, int $start) { my $indices := nqp::create(IterationBuffer); my int $add = $overlap ?? 1 !! nqp::chars($needle) || 1; my int $pos = $start; my int $index; nqp::while( nqp::isne_i(($index = nqp::indexic(self,$needle,$pos)),-1), nqp::stmts( nqp::push($indices,nqp::p6box_i($index)), ($pos = nqp::add_i($index,$add)) ) ); $indices.List } # create indices using index with ignoremark method !indicesim(str $needle, $overlap, int $start) { my $indices := nqp::create(IterationBuffer); my int $add = $overlap ?? 1 !! nqp::chars($needle) || 1; my int $pos = $start; my int $index; nqp::while( nqp::isne_i(($index = nqp::indexim(self,$needle,$pos)),-1), nqp::stmts( nqp::push($indices,nqp::p6box_i($index)), ($pos = nqp::add_i($index,$add)) ) ); $indices.List } # create indices using index with ignorecase and ignoremark method !indicesicim(str $needle, $overlap, int $start) { my $indices := nqp::create(IterationBuffer); my int $add = $overlap ?? 1 !! nqp::chars($needle) || 1; my int $pos = $start; my int $index; nqp::while( nqp::isne_i(($index = nqp::indexicim(self,$needle,$pos)),-1), nqp::stmts( nqp::push($indices,nqp::p6box_i($index)), ($pos = nqp::add_i($index,$add)) ) ); $indices.List } multi method indices(Str:D: Str:D $needle, :i(:$ignorecase), :m(:$ignoremark), :$overlap ) { $ignorecase ?? $ignoremark ?? self!indicesicim($needle, $overlap, 0) !! self!indicesic($needle, $overlap, 0) !! $ignoremark ?? self!indicesim($needle, $overlap, 0) !! self!indices($needle, $overlap, 0) } multi method indices(Str:D: Str:D $needle, Int:D $pos, :i(:$ignorecase), :m(:$ignoremark), :$overlap ) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! $ignorecase ?? $ignoremark ?? self!indicesicim($needle, $overlap, $pos) !! self!indicesic($needle, $overlap, $pos) !! $ignoremark ?? self!indicesim($needle, $overlap, $pos) !! self!indices($needle, $overlap, $pos) } multi method indices(Str:D: Str:D $needle, :$overlap) { self!indices($needle, $overlap, 0) } multi method indices(Str:D: Str:D $needle, Int:D $pos, :$overlap) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! self!indices($needle, $overlap, $pos) } multi method index(Str:D: Str:D $needle, :i(:$ignorecase)!, :m(:$ignoremark) --> Int:D) { nqp::isne_i( (my $index := $ignorecase ?? $ignoremark ?? nqp::indexicim(self,$needle,0) !! nqp::indexic(self,$needle,0) !! $ignoremark ?? nqp::indexim(self,$needle,0) !! nqp::index(self,$needle,0) ),-1 ) ?? $index !! Nil } multi method index(Str:D: Str:D $needle, Int:D $pos, :i(:$ignorecase)!, :m(:$ignoremark) --> Int:D) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! nqp::isne_i( (my $index := $ignorecase ?? $ignoremark ?? nqp::indexicim(self,$needle,$pos) !! nqp::indexic(self,$needle,$pos) !! $ignoremark ?? nqp::indexim(self,$needle,$pos) !! nqp::index(self,$needle,$pos) ),-1 ) ?? $index !! Nil } multi method index(Str:D: @needles, :i(:$ignorecase)!, :m(:$ignoremark) --> Int:D) { my int $i; my int $index = -1; my int $chars = nqp::chars(self); if $ignorecase { if $ignoremark { $chars = $index = $i if ($i = nqp::indexicim(nqp::substr(self,0,$chars),.Str,0) ) > -1 for @needles; } else { $chars = $index = $i if ($i = nqp::indexic(nqp::substr(self,0,$chars),.Str,0) ) > -1 for @needles; } } elsif $ignoremark { $chars = $index = $i if ($i = nqp::indexim(nqp::substr(self,0,$chars),.Str,0)) > -1 for @needles; } else { $chars = $index = $i if ($i = nqp::index(nqp::substr(self,0,$chars),.Str,0)) > -1 for @needles; } $index == -1 ?? Nil !! $index } multi method index(Str:D: Str:D $needle, :m(:$ignoremark)! --> Int:D) { nqp::isne_i( (my $index := $ignoremark ?? nqp::indexim(self,$needle,0) !! nqp::index(self,$needle,0) ),-1 ) ?? $index !! Nil } multi method index(Str:D: Str:D $needle, Int:D $pos, :m(:$ignoremark)! --> Int:D) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! nqp::isne_i( (my $index := $ignoremark ?? nqp::indexim(self,$needle,$pos) !! nqp::index(self,$needle,$pos) ),-1 ) ?? $index !! Nil } multi method index(Str:D: @needles, :m(:$ignoremark)! --> Int:D) { my int $i; my int $index = -1; my int $chars = nqp::chars(self); if $ignoremark { $chars = $index = $i if ($i = nqp::indexim(nqp::substr(self,0,$chars),.Str,0)) > -1 for @needles; } else { $chars = $index = $i if ($i = nqp::index(nqp::substr(self,0,$chars),.Str)) > -1 for @needles; } $index == -1 ?? Nil !! $index } multi method index(Str:D: Str:D $needle --> Int:D) { nqp::isne_i((my $index := nqp::index(self,$needle)),-1) ?? $index !! Nil } multi method index(Str:D: Str:D $needle, Int:D $pos --> Int:D) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! nqp::isne_i((my $index := nqp::index(self,$needle,$pos)),-1) ?? $index !! Nil } multi method index(Str:D: @needles --> Int:D) { my int $i; my int $index = -1; my int $chars = nqp::chars(self); $chars = $index = $i if ($i = nqp::index(nqp::substr(self,0,$chars), .Str)) > -1 for @needles; $index == -1 ?? Nil !! $index } # helper method for failing with out of range exception method !fail-oor($got) { X::OutOfRange.new( :what("Position in calling '{ callframe(2).code.name }'"), :$got, :range("0..{ nqp::chars(self) }") ).Failure } multi method rindex(Str:D: Str:D $needle --> Int:D) { nqp::isne_i((my $index := nqp::rindex($!value,$needle)),-1) ?? $index !! Nil } multi method rindex(Str:D: Str:D $needle, Int:D $pos --> Int:D) { nqp::isbig_I(nqp::decont($pos)) || nqp::islt_i($pos,0) ?? self!fail-oor($pos) !! nqp::isne_i((my $index := nqp::rindex(self,$needle,$pos)),-1) ?? $index !! Nil } multi method rindex(Str:D: @needles --> Int:D) { my int $i; my int $index = -1; $index = $i if ($i = nqp::rindex(self,.Str)) > $index for @needles; $index == -1 ?? Nil !! $index } method pred(Str:D: --> Str:D) { (my int $chars = Rakudo::Internals.POSSIBLE-MAGIC-CHARS(self)) ?? nqp::istype( (my $pred := Rakudo::Internals.PRED(self,$chars - 1)), Failure ) ?? $pred !! nqp::box_s($pred,self) !! self } method succ(Str:D: --> Str:D) { (my int $chars = Rakudo::Internals.POSSIBLE-MAGIC-CHARS(self)) ?? nqp::istype( (my $succ := Rakudo::Internals.SUCC(self,$chars - 1)), Failure ) ?? $succ !! nqp::box_s($succ,self) !! self } method !combiners() { X::Str::Numeric.new( source => self, pos => 1, reason => "combining codepoints found at" ).Failure } multi method Numeric(Str:D: Bool :$fail-or-nil --> Numeric:D) { # check for any combining characters nqp::isne_i(nqp::chars(self),nqp::codes(self)) ?? self!combiners !! nqp::iseq_i( # all numeric? nqp::findnotcclass( nqp::const::CCLASS_NUMERIC,self,0,nqp::chars(self)), nqp::chars(self) ) ?? nqp::isle_i(nqp::chars($!value),18) # upto 18 digits can be native ?? nqp::atpos(nqp::radix(10,self,0,0),0) # quick conversion, "" also !! nqp::atpos(nqp::radix_I(10,self,0,0,Int),0) !! nqp::atpos( # try parsing as an Int (my $n := nqp::radix_I(10,self,0,0b10,Int)), 2 ) == nqp::chars(self) ?? nqp::atpos($n,0) # fast path Int ok !! nqp::findnotcclass( # any non-whitespace? nqp::const::CCLASS_WHITESPACE, self,0,nqp::chars(self) ) == nqp::chars(self) ?? 0 # just spaces !! val(self, :val-or-fail, :$fail-or-nil) # take the slow route } multi method gist(Str:D:) { self } multi method raku(Str:D: --> Str:D) { nqp::chars(self) ?? nqp::findnotcclass( nqp::const::CCLASS_WORD,self,0,nqp::chars(self) ) == nqp::chars(self) ?? nqp::concat('"',nqp::concat(self,'"')) # fast path alpha !! self!rakufy # slow path non-alpha !! '""' # empty string } # Special case escape values for rakufication my constant $escapes = do { my $list := nqp::list; nqp::bindpos($list,nqp::ord("\0"), '\0'); nqp::bindpos($list,nqp::ord('$'), '\$'); nqp::bindpos($list,nqp::ord('@'), '\@'); nqp::bindpos($list,nqp::ord('%'), '\%'); nqp::bindpos($list,nqp::ord('&'), '\&'); nqp::bindpos($list,nqp::ord('{'), '\{'); nqp::bindpos($list,nqp::ord("\b"), '\b'); nqp::bindpos($list,nqp::ord("\x0A"),'\n'); nqp::bindpos($list,nqp::ord("\r"), '\r'); nqp::bindpos($list,nqp::ord("\t"), '\t'); nqp::bindpos($list,nqp::ord('"'), '\"'); nqp::bindpos($list,nqp::ord('\\'), '\\\\'); $list } # Helper method to create hex representation of char method !hexify(str $char) is pure { nqp::concat( '\x[', nqp::concat( $char.NFC.map( *.base(16) ).join(','), ']' ) ) } # Under NFG-supporting implementations, must be sure that any leading # combiners are escaped, otherwise they will be combined onto the " # under concatenation closure, which ruins round-tripping. Also handle # the \r\n grapheme correctly. method !rakufy() { my $rakufied := nqp::list_s('"'); # array add chars to my int $chars = nqp::chars(self); my int $i = -1; my str $char; my int $ord; nqp::while( nqp::islt_i(++$i,$chars), # check all chars nqp::stmts( ($char = nqp::substr(self,$i,1)), ($ord = nqp::ord($char)), nqp::push_s( $rakufied, nqp::if( nqp::isge_i($ord,768) # different from "0" ?? && nqp::isgt_i( nqp::atpos( nqp::radix_I(10, # failure -> value 0 nqp::getuniprop_str( $ord, nqp::unipropcode('Canonical_Combining_Class') ), 0,0,Int ), 0 ), 0 ), self!hexify($char), # escape since > 0 nqp::if( nqp::iseq_s($char,"\r\n"), # <-- this is a synthetic codepoint '\r\n', # it's the common LF nqp::ifnull( # not a common LF nqp::atpos($escapes,$ord), nqp::if( nqp::iscclass(nqp::const::CCLASS_PRINTING,$char,0), $char, # it's a printable self!hexify($char) # need to escape ) ) ) ) ) ) ); nqp::push_s($rakufied,'"'); nqp::join('',$rakufied) } my class CombAll does PredictiveIterator { has str $!str; has Mu $!what; has int $!pos; method !SET-SELF($string) { $!str = $string; $!what := $string.WHAT; $!pos = -1; self } method new($string) { nqp::create(self)!SET-SELF($string) } method pull-one() { nqp::islt_i(++$!pos,nqp::chars($!str)) ?? nqp::box_s(nqp::substr($!str,$!pos,1),$!what) #?js: NFG !! IterationEnd } method skip-one() { nqp::islt_i(++$!pos,nqp::chars($!str)) } method push-all(\target --> IterationEnd) { my str $str = $!str; # locals are faster my int $pos = $!pos; my int $chars = nqp::chars($str); my Mu $what := $!what; nqp::while( nqp::islt_i(++$pos,$chars), target.push(nqp::box_s(nqp::substr($str,$pos,1),$what)) #?js: NFG ); $!pos = $pos; } method count-only(--> Int:D) { nqp::box_i( nqp::chars($!str) - $!pos - nqp::islt_i($!pos,nqp::chars($!str)), Int ) } method sink-all(--> IterationEnd) { $!pos = nqp::chars($!str) } } multi method comb(Str:D: --> Seq:D) { Seq.new(CombAll.new(self)) } multi method comb(Str:D: Int:D $size, $limit = * --> Seq:D) { $size <= 1 && (nqp::istype($limit,Whatever) || $limit == Inf) ?? self.comb !! Seq.new: Rakudo::Iterator.NGrams: self, $size, $limit, $size, True } my class CombPat does Iterator { has str $!str; has Mu $!what; has str $!pat; has int $!patsz; has int $!pos; method !SET-SELF($string, $pat) { $!str = $string; $!what := $string.WHAT; $!pat = $pat; $!patsz = nqp::chars($!pat); self } method new($string, $pat) { nqp::create(self)!SET-SELF($string, $pat) } method pull-one() { nqp::if( nqp::islt_i( (my int $found = nqp::index($!str,$!pat,$!pos)), 0 ), IterationEnd, nqp::stmts( $!pos = nqp::add_i($found,$!patsz), nqp::box_s($!pat,$!what) ) ) } } multi method comb(Str:D: Str:D $pat --> Seq:D) { $pat ?? Seq.new(CombPat.new(self,$pat)) !! self.comb } my class CombPatLimit does Iterator { has str $!str; has Mu $!what; has str $!pat; has int $!pos; has int $!todo; method !SET-SELF($string, $pat, $limit) { $!str = $string; $!what := $string.WHAT; $!pat = $pat; $!todo = $limit.Int; self } method new($string, $pat, $limit) { nqp::create(self)!SET-SELF($string, $pat, $limit) } method pull-one() { nqp::if( nqp::islt_i( (my int $found = nqp::index($!str, $!pat, $!pos)), 0 ) || nqp::iseq_i($!todo,0), IterationEnd, nqp::stmts( ($!pos = nqp::add_i($found,1)), --$!todo, nqp::box_s($!pat,$!what) ) ) } } multi method comb(Str:D: Str:D $pat, $limit --> Seq:D) { nqp::istype($limit,Whatever) || $limit == Inf ?? self.comb($pat) !! $pat ?? Seq.new(CombPatLimit.new(self, $pat, $limit)) !! self.comb(1, $limit) } # iterate with post-processing my class POST-ITERATOR does Iterator { has Mu $!cursor; # cannot put these 3 lines in role has Mu $!move; has Mu $!post; method !SET-SELF(\cursor,\move,\post) { $!cursor := cursor; $!move := move; $!post := post; self } method new(\c,\t,\p) { nqp::create(self)!SET-SELF(c,t,p) } method pull-one() is raw { nqp::if( nqp::isge_i(nqp::getattr_i($!cursor,Match,'$!pos'),0), nqp::stmts( (my $pulled := $!cursor), ($!cursor := $!move($!cursor)), $!post($pulled) ), IterationEnd ) } method skip-one() is raw { $!cursor := $!move($!cursor) if nqp::isge_i(nqp::getattr_i($!cursor,Match,'$!pos'),0) } method push-all(\target --> IterationEnd) { nqp::while( nqp::isge_i(nqp::getattr_i($!cursor,Match,'$!pos'),0), nqp::stmts( target.push($!post($!cursor)), ($!cursor := $!move($!cursor)) ) ) } } # iterate returning Matches my class CURSOR-ITERATOR does Iterator { has Mu $!cursor; has Mu $!move; method !SET-SELF(\cursor,\move) { $!cursor := cursor; $!move := move; self } method new(\c,\t) { nqp::create(self)!SET-SELF(c,t) } method pull-one() is raw { nqp::if( nqp::isge_i(nqp::getattr_i($!cursor,Match,'$!pos'),0), nqp::stmts( (my $pulled := $!cursor), ($!cursor := $!move($!cursor)), $pulled ), IterationEnd ) } method skip-one() is raw { $!cursor := $!move($!cursor) if nqp::isge_i(nqp::getattr_i($!cursor,Match,'$!pos'),0) } method push-all(\target --> IterationEnd) { nqp::while( nqp::isge_i(nqp::getattr_i($!cursor,Match,'$!pos'),0), nqp::stmts( target.push($!cursor), ($!cursor := $!move($!cursor)) ) ) } } multi method comb(Str:D: Regex:D $regex, $limit = *, :$match! --> Seq:D) { Seq.new: $match ?? Rakudo::Iterator.MatchMatch: $regex, self, $limit !! Rakudo::Iterator.MatchStr: $regex, self, $limit } multi method comb(Str:D: Regex:D $regex --> Seq:D) { Seq.new: Rakudo::Iterator.MatchStr: $regex, self, * } # Look for short/long named parameter and remove it from the hash sub fetch-short-long(\opts, str $short, str $long, \store --> Nil) { nqp::if( nqp::existskey(opts,$short), nqp::stmts( (store = nqp::atkey(opts,$short)), nqp::deletekey(opts,$short) ), nqp::if( nqp::existskey(opts,$long), nqp::stmts( (store = nqp::atkey(opts,$long)), nqp::deletekey(opts,$long) ) ) ) } # Look for named parameters, do not remove from hash sub fetch-all-of(\opts, @names, \store --> Nil) { my int $elems = @names.elems; # reifies my $list := nqp::getattr(@names,List,'$!reified'); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::existskey(opts,nqp::unbox_s(nqp::atpos($list,$i))), (store = nqp::atkey(opts,nqp::unbox_s(nqp::atpos($list,$i)))), ) ); } sub die-before-first($got) { die "Attempt to retrieve before :1st match -- :nth({ $got // $got.^name })" } # All of these !match methods take a nqp::getlexcaller value for the $/ # to be set as the first parameter. The second parameter is usually # the Match object to be used (or something from which a Match can # be made). # Generic fallback for matching with a pattern method !match-pattern(Mu \slash, $pattern, str $name, $value, \opts) { my $opts := nqp::getattr(opts,Map,'$!storage'); nqp::bindkey($opts,$name,$value); fetch-short-long($opts, "p", "pos", my $p); fetch-short-long($opts, "c", "continue", my $c); nqp::unless( nqp::defined($c), $c = 0 ); nqp::if( nqp::elems($opts), nqp::if( nqp::defined($p), self!match-cursor(slash, $pattern($cursor-init(Match,self,:$p)), '', 0, $opts), self!match-cursor(slash, $pattern($cursor-init(Match,self,:$c)), '', 0, $opts) ), nqp::if( nqp::defined($p), self!match-one(slash, $pattern($cursor-init(Match,self,:$p))), self!match-one(slash, $pattern($cursor-init(Match,self,:$c))) ) ) } # Generic fallback for matching with a cursor. This is typically # called if more than one named parameter was specified. Arguments # 3/4 are the initial named parameter matched: instead of flattening # the named parameter into another slurpy hash, we pass the name and # the value as extra parameters, and add it back in the hash with # named parameters. method !match-cursor(Mu \slash, \cursor, str $name, $value, \opts) { my $opts := nqp::getattr(opts,Map,'$!storage'); nqp::bindkey($opts,$name,$value) if nqp::chars($name); fetch-short-long($opts, "ex", "exhaustive", my $ex); fetch-short-long($opts, "ov", "overlap", my $ov); my \move := $ex ?? CURSOR-EXHAUSTIVE !! $ov ?? CURSOR-OVERLAP !! CURSOR-GLOBAL; fetch-short-long($opts, "as", "as", my $as); my \post := nqp::istype($as,Str) ?? &POST-STR !! &POST-MATCH; fetch-short-long($opts, "g", "global", my $g); nqp::if( nqp::elems($opts), nqp::stmts( fetch-short-long($opts, "x", "x", my $x), fetch-all-of($opts, , my $nth), nqp::if( nqp::defined($nth), nqp::if( nqp::defined($x), # :nth && :x self!match-x(slash, self!match-nth(slash, cursor, move, post, $nth, nqp::hash).iterator, $x), self!match-nth(slash, cursor, move, post, $nth, nqp::hash) # nth ), nqp::if( nqp::defined($x), self!match-x(slash, # :x POST-ITERATOR.new(cursor, move, post), $x), nqp::if( # only :ex|ov|g $ex || $ov || $g, self!match-list(slash, cursor, move, post), self!match-one(slash, cursor) ) ) ) ), nqp::if( # only :ex|ov|g $ex || $ov || $g, self!match-list(slash, cursor, move, post), self!match-one(slash, cursor) ) ) } # Match object at given position method !match-one(Mu \slash, \cursor) { slash = my $match := nqp::isge_i(nqp::getattr_i(cursor,Match,'$!pos'),0) ?? cursor.MATCH !! Nil; $match } # Some object at given position method !match-as-one(Mu \slash, \cursor, \as) { slash = my $match := nqp::if( nqp::isge_i(nqp::getattr_i(cursor,Match,'$!pos'),0), nqp::if(nqp::istype(as,Str), &POST-STR, &POST-MATCH)(cursor), Nil ); $match } # Create list from the appropriate Sequence given the move method !match-list(Mu \slash, \cursor, \move, \post) { slash = my $match := nqp::isge_i(nqp::getattr_i(cursor,Match,'$!pos'),0) ?? Seq.new(POST-ITERATOR.new(cursor, move, post)).list !! List.new; $match } # Handle matching of the nth match specification. method !match-nth(Mu \slash, \cursor, \move, \post, $nth, %opts) { nqp::if( nqp::elems(nqp::getattr(%opts,Map,'$!storage')), self!match-cursor(slash, cursor, 'nth', $nth, %opts), nqp::if( nqp::defined($nth), nqp::if( nqp::istype($nth,Whatever), self!match-last(slash, cursor, move), nqp::if( nqp::istype($nth,Numeric), nqp::if( $nth == Inf, self!match-last(slash, cursor, move), nqp::if( $nth < 1, die-before-first($nth), self!match-nth-int(slash, cursor, move, post, $nth.Int) ) ), nqp::if( nqp::istype($nth,WhateverCode), nqp::if( nqp::iseq_i((my int $tail = abs($nth(-1))),1), self!match-last(slash, cursor, move), self!match-nth-tail(slash, cursor, move, $tail) ), nqp::if( nqp::istype($nth,Callable), self!match-nth-int(slash, cursor, move, post, $nth()), self!match-nth-iterator(slash, POST-ITERATOR.new(cursor, move, post), $nth.iterator) ) ) ) ), self!match-one(slash, cursor) ) ) } # Give back the nth match found method !match-nth-int(Mu \slash, \cursor, \move, \post, int $nth) { slash = my $match := nqp::isge_i(nqp::getattr_i(cursor,Match,'$!pos'),0) ?? nqp::eqaddr( (my $pulled := POST-ITERATOR.new(cursor, move, post) .skip-at-least-pull-one(nqp::sub_i($nth,1))), IterationEnd ) ?? Nil # not enough matches !! $pulled # found it! !! Nil; # no matches whatsoever $match } # Give back the N-tail match found method !match-nth-tail(Mu \slash, \cursor, \move, int $tail) { slash = my $match := nqp::eqaddr( (my $pulled := Rakudo::Iterator.LastNValues( CURSOR-ITERATOR.new(cursor, move), $tail, 'match', 1 ).pull-one), IterationEnd ) ?? Nil !! $pulled.MATCH; $match } # Give last value of given iterator, or Nil if none method !match-last(Mu \slash, \cursor, \move) { slash = my $match := nqp::eqaddr( (my $pulled := Rakudo::Iterator.LastValue( CURSOR-ITERATOR.new(cursor, move), 'match') ), IterationEnd ) ?? Nil !! $pulled.MATCH; $match } # These !match methods take an iterator instead of a cursor. # Give list with matches found given a range with :nth method !match-nth-range(Mu \slash, \iterator, $min, $max) { slash = my $match := nqp::stmts( (my int $skip = $min), nqp::if( nqp::islt_i($skip,1), die-before-first($min), nqp::stmts( nqp::while( nqp::isgt_i($skip,1) && iterator.skip-one, --$skip ), nqp::if( nqp::iseq_i($skip,1), nqp::if( # did not exhaust while skipping $max == Inf, # * is Inf in N..* nqp::stmts( # open ended (my $matches := nqp::create(IterationBuffer)), nqp::until( nqp::eqaddr( (my $pulled := iterator.pull-one), IterationEnd ), nqp::push($matches,$pulled) ), $matches.List ), nqp::stmts( # upto the max index (my int $todo = $max - $min + 1), ($matches := nqp::setelems(nqp::create(IterationBuffer),$todo)), (my int $i = -1), nqp::until( nqp::iseq_i(++$i,$todo) || nqp::eqaddr( ($pulled := iterator.pull-one),IterationEnd), nqp::bindpos($matches,$i,$pulled) ), nqp::if( nqp::iseq_i($i,$todo), $matches.List, # found all values Empty # no match, since not all values ) ) ), Empty # exhausted while skipping ) ) ) ); $match } # Give list with matches found given an iterator with :nth method !match-nth-iterator(Mu \slash, \source, \indexes) { slash = my $match := nqp::stmts( Seq.new(Rakudo::Iterator.MonotonicIndexes( source, indexes, 1, -> $got,$next { nqp::if( $next == 1, die-before-first($got), (die "Attempt to fetch match #$got after #{$next - 1}") ) } )).list ); $match } # Give list with matches found given an iterator with :x method !match-x(Mu \slash, $iterator, $x) { nqp::if( nqp::istype($x,Whatever), Seq.new($iterator).list, nqp::if( nqp::istype($x,Numeric), nqp::if( $x == Inf, Seq.new($iterator).list, nqp::if( nqp::istype($x,Int), self!match-x-range(slash, $iterator, $x, $x), nqp::stmts( (my int $xint = $x.Int), self!match-x-range(slash, $iterator, $xint, $xint) ) ) ), nqp::if( nqp::istype($x,Range), self!match-x-range(slash, $iterator, $x.min, $x.max), nqp::stmts( (slash = Nil), X::Str::Match::x.new(:got($x)).Failure ) ) ) ) } # Give list with matches found given a range with :x method !match-x-range(Mu \slash, $iterator, $min, $max) { slash = my $match := nqp::stmts( (my int $todo = nqp::if($max == Inf, 0x7fffffff, $max)), (my $matches := nqp::create(IterationBuffer)), nqp::until( nqp::islt_i(--$todo, 0) || nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), nqp::push($matches,$pulled) ), nqp::if( nqp::elems($matches) >= $min, $matches.List, () ) ); $match } proto method match(Any, |) {*} multi method match(Cool:D $pattern, |c) { $/ := nqp::getlexcaller('$/'); self.match(/ "$pattern": /,|c) } # All of these .match candidates take a single required named parameter # so that handling specification of a single named parameter can be much # quicker. Unfortunately, we cannot cheaply do MMD on an empty slurpy # hash, which would make things much more simple. multi method match(Regex:D $pattern, :continue(:$c)!, *%_) { nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? self!match-pattern( nqp::getlexcaller('$/'), $pattern, 'c', $c, %_) !! self!match-one( nqp::getlexcaller('$/'),$pattern($cursor-init(Match,self,:$c))) } multi method match(Regex:D $pattern, :pos(:$p)!, *%_) { nqp::if( nqp::elems(nqp::getattr(%_,Map,'$!storage')), self!match-pattern(nqp::getlexcaller('$/'), $pattern, 'p', $p, %_), nqp::if( nqp::defined($p), self!match-one(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:$p))), self!match-one(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c))) ) ) } multi method match(Regex:D $pattern, :global(:$g)!, *%_) { nqp::if( nqp::elems(nqp::getattr(%_,Map,'$!storage')), self!match-cursor(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), 'g', $g, %_), nqp::if( $g, self!match-list(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), CURSOR-GLOBAL, &POST-MATCH), self!match-one(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c))) ) ) } multi method match(Regex:D $pattern, :overlap(:$ov)!, *%_) { nqp::if( nqp::elems(nqp::getattr(%_,Map,'$!storage')), self!match-cursor(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), 'ov', $ov, %_), nqp::if( $ov, self!match-list(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), CURSOR-OVERLAP, &POST-MATCH), self!match-one(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c))) ) ) } multi method match(Regex:D $pattern, :exhaustive(:$ex)!, *%_) { nqp::if( nqp::elems(nqp::getattr(%_,Map,'$!storage')), self!match-cursor(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), 'ex', $ex, %_), nqp::if( $ex, self!match-list(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), CURSOR-EXHAUSTIVE, &POST-MATCH), self!match-one(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c))) ) ) } multi method match(Regex:D $pattern, :$x!, *%_) { nqp::if( nqp::elems(nqp::getattr(%_,Map,'$!storage')), self!match-cursor(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), 'x', $x, %_), nqp::if( nqp::defined($x), self!match-x(nqp::getlexcaller('$/'), POST-ITERATOR.new($pattern($cursor-init(Match,self,:0c)), CURSOR-GLOBAL, &POST-MATCH ), $x), self!match-one(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), $x) ) ) } multi method match(Regex:D $pattern, :$st!, *%_) { self!match-nth(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), CURSOR-GLOBAL, &POST-MATCH, $st, %_) } multi method match(Regex:D $pattern, :$nd!, *%_) { self!match-nth(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), CURSOR-GLOBAL, &POST-MATCH, $nd, %_) } multi method match(Regex:D $pattern, :$rd!, *%_) { self!match-nth(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), CURSOR-GLOBAL, &POST-MATCH, $rd, %_) } multi method match(Regex:D $pattern, :$th!, *%_) { self!match-nth(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), CURSOR-GLOBAL, &POST-MATCH, $th, %_) } multi method match(Regex:D $pattern, :$nth!, *%_) { self!match-nth(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), CURSOR-GLOBAL, &POST-MATCH, $nth, %_) } multi method match(Regex:D $pattern, :$as!, *%_) { nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? self!match-cursor(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), 'as', $as, %_) !! self!match-as-one(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), $as) } multi method match(Regex:D $pattern, *%_) { nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? self!match-cursor(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c)), '', 0, %_) !! self!match-one(nqp::getlexcaller('$/'), $pattern($cursor-init(Match,self,:0c))) } proto method subst-mutate(|) {*} multi method subst-mutate( Str:D $self is rw: Any:D $matcher, $replacement, :ii(:$samecase), :ss(:$samespace), :mm(:$samemark), *%options ) { my $global = %options || %options; my \caller_dollar_slash := nqp::getlexcaller('$/'); my $SET_DOLLAR_SLASH = nqp::istype($matcher, Regex); my $word_by_word = so $samespace || %options || %options; my \matches := %options ?? self.match($matcher, |%options) !! self.match($matcher); # 30% faster nqp::if( nqp::istype(matches,Failure) || nqp::isfalse(matches), $SET_DOLLAR_SLASH && (try caller_dollar_slash = $/), nqp::stmts( ($self = nqp::box_s( $self!APPLY-MATCHES( matches, $replacement, caller_dollar_slash, $SET_DOLLAR_SLASH, $word_by_word, $samespace, $samecase, $samemark ), self )), $SET_DOLLAR_SLASH && (try caller_dollar_slash = matches), ) ); matches } multi method subst(Str:D: Str:D $original, Str:D $final = "", *%options) { my $result := nqp::if( (my $opts := nqp::getattr(%options,Map,'$!storage')) && nqp::isgt_i(nqp::elems($opts),1), self!SUBST(nqp::getlexcaller('$/'),$original,$final,|%options), nqp::if( nqp::elems($opts), nqp::if( # one named nqp::atkey($opts,'g') || nqp::atkey($opts,'global'), Rakudo::Internals.TRANSPOSE(self, $original, $final), nqp::if( # no trueish g/global nqp::existskey($opts,'g') || nqp::existskey($opts,'global'), Rakudo::Internals.TRANSPOSE-ONE(self, $original, $final), self!SUBST(nqp::getlexcaller('$/'),$original,$final,|%options) ) ), Rakudo::Internals.TRANSPOSE-ONE(self, $original, $final) # no nameds ) ); nqp::istype($result,Failure) ?? $result !! nqp::box_s($result,self) } multi method subst(Str:D: $matcher, $replacement = "", *%options) { nqp::istype( (my $result := self!SUBST( nqp::getlexcaller('$/'), $matcher, $replacement, |%options )), Failure ) ?? $result !! nqp::box_s($result,self) } method !SUBST(Str:D: \caller_dollar_slash, $matcher, $replacement, :global(:$g), :ii(:$samecase), :ss(:$samespace), :mm(:$samemark), *%options ) { X::Str::Subst::Adverb.new(:name($_), :got(%options{$_})).throw if %options{$_} for ; my $SET_DOLLAR_SLASH = nqp::istype($matcher, Regex); my $word_by_word = so $samespace || %options || %options; my \matches := %options ?? self.match($matcher, :$g, |%options) !! self.match($matcher, :$g); # 30% faster nqp::if( nqp::istype(matches, Failure), nqp::stmts( $SET_DOLLAR_SLASH && (try caller_dollar_slash = Nil), matches), nqp::if( matches, nqp::stmts( (my \res := self!APPLY-MATCHES: matches, $replacement, caller_dollar_slash, $SET_DOLLAR_SLASH, $word_by_word, $samespace, $samecase, $samemark), $SET_DOLLAR_SLASH && (try caller_dollar_slash = matches), res), nqp::stmts( $SET_DOLLAR_SLASH && (try caller_dollar_slash = matches), self))) } # NOTE: this method is also called by s/// op in src/Perl6/Actions.nqp method !APPLY-MATCHES(\matches,$replacement,\cds,\SDS,\word_by_word,\space,\case,\mark) { my \callable := nqp::istype($replacement,Callable); my int $prev; my str $str = nqp::unbox_s(self); my Mu $result := nqp::list_s(); # need to do something special if SDS || space || case || mark || callable { my \noargs := callable ?? $replacement.count == 0 !! False; my \fancy := space || case || mark || word_by_word; my \case-and-mark := case && mark; # fast path for something like `s:g[ \w+ ] = "foo"` if !fancy && !callable { for (nqp::istype(matches, Capture) ?? flat matches !! matches.list) -> $m { cds = $m if nqp::isrwcont(cds); nqp::push_s( $result,nqp::substr($str,$prev,nqp::unbox_i($m.from) - $prev) ); $prev = nqp::unbox_i($m.to); } nqp::push_s($result,nqp::substr($str,$prev)); nqp::p6box_s(nqp::join(nqp::unbox_s(~$replacement),$result)); } else { for (nqp::istype(matches, Capture) ?? flat matches !! matches.list) -> $m { cds = $m if nqp::isrwcont(cds) && SDS; nqp::push_s( $result,nqp::substr($str,$prev,nqp::unbox_i($m.from) - $prev) ); if fancy { my $mstr := $m.Str; my $it := ~(callable ?? (noargs ?? $replacement() !! $replacement($m)) !! $replacement ); if word_by_word { # all spacers delegated to word-by-word my &filter := case-and-mark ?? -> $w,$p { $w.samemark($p).samecase($p) } !! case ?? -> $w,$p { $w.samecase($p) } !! -> $w,$p { $w.samemark($p) } nqp::push_s($result,nqp::unbox_s( $it!word-by-word($mstr,&filter,:samespace(?space)) ) ); } elsif case-and-mark { nqp::push_s($result,nqp::unbox_s( $it.samecase($mstr).samemark($mstr) ) ); } elsif case { nqp::push_s($result,nqp::unbox_s($it.samecase(~$m))); } else { # mark nqp::push_s($result,nqp::unbox_s($it.samemark(~$m))); } } else { nqp::push_s($result,nqp::unbox_s( ~(callable ?? (noargs ?? $replacement() !! $replacement($m)) !! $replacement ) ) ); } $prev = nqp::unbox_i($m.to); } nqp::push_s($result,nqp::substr($str,$prev)); nqp::p6box_s(nqp::join('',$result)); } } # simple string replacement else { for (nqp::istype(matches, Capture) ?? flat matches !! matches.list) -> $m { nqp::push_s( $result,nqp::substr($str,$prev,nqp::unbox_i($m.from) - $prev) ); $prev = nqp::unbox_i($m.to); } nqp::push_s($result,nqp::substr($str,$prev)); nqp::p6box_s(nqp::join(nqp::unbox_s(~$replacement),$result)); } } multi method lines(Str:D: :$count! --> Int:D) { # we should probably deprecate this feature $count ?? self.lines.elems !! self.lines; } multi method lines(Str:D: $limit --> Seq:D) { self.lines.head($limit) } multi method lines(Str:D: $limit, Bool :$chomp! --> Seq:D) { self.lines(:$chomp).head($limit) } my class Lines does PredictiveIterator { has str $!str; has Mu $!what; has int $!chars; has int $!pos; method !SET-SELF($string) { $!str = $string; $!what := $string.WHAT; $!chars = nqp::chars($!str); $!pos = 0; self } method new($string) { nqp::create(self)!SET-SELF($string) } method pull-one() { nqp::if( (my int $left = $!chars - $!pos) > 0, nqp::stmts( (my int $findpos = nqp::findcclass( nqp::const::CCLASS_NEWLINE, $!str, $!pos, $left)), (my $found := nqp::box_s( nqp::substr($!str, $!pos, $findpos - $!pos), $!what )), ($!pos = $findpos + 1 ), $found ), IterationEnd ) } method push-all(\target --> IterationEnd) { my int $left; while ($left = $!chars - $!pos) > 0 { my int $findpos = nqp::findcclass( nqp::const::CCLASS_NEWLINE, $!str, $!pos, $left); target.push(nqp::box_s( nqp::substr($!str, $!pos, $findpos - $!pos), $!what )); $!pos = $findpos + 1 ; } } method count-only(--> Int:D) { my int $left; my int $seen; my int $pos = $!pos; my int $chars = $!chars; while ($left = $chars - $pos) > 0 { $pos = nqp::findcclass( nqp::const::CCLASS_NEWLINE, $!str, $pos, $left) + 1; $seen = $seen + 1; } nqp::p6box_i($seen) } method bool-only(--> Bool:D) { nqp::hllbool(nqp::islt_i($!pos,$!chars)) } method sink-all(--> IterationEnd) { } } my class LinesKeepNL is Lines { has str $!str; has Mu $!what; has int $!chars; has int $!pos; method !SET-SELF($string) { $!str = $string; $!what := $string.WHAT; $!chars = nqp::chars($!str); $!pos = 0; self } method new($string) { nqp::create(self)!SET-SELF($string) } method pull-one() { nqp::if( (my int $left = $!chars - $!pos) > 0, nqp::stmts( (my int $findpos = nqp::findcclass( nqp::const::CCLASS_NEWLINE, $!str, $!pos, $left)), (my int $nextpos = $findpos + 1 ), (my $found := nqp::box_s( nqp::substr($!str, $!pos, $nextpos - $!pos), $!what )), ($!pos = $nextpos), $found ), IterationEnd ) } method push-all(\target --> IterationEnd) { my int $left; while ($left = $!chars - $!pos) > 0 { my int $findpos = nqp::findcclass( nqp::const::CCLASS_NEWLINE, $!str, $!pos, $left); my int $nextpos = $findpos + 1 ; target.push(nqp::box_s( nqp::substr($!str, $!pos, $nextpos - $!pos), $!what )); $!pos = $nextpos; } } method count-only(--> Int:D) { my int $left; my int $seen; my int $pos = $!pos; my int $chars = $!chars; while ($left = $chars - $pos) > 0 { $pos = nqp::findcclass( nqp::const::CCLASS_NEWLINE, $!str, $pos, $left) + 1; $seen = $seen + 1; } nqp::p6box_i($seen) } method bool-only(--> Bool:D) { nqp::hllbool(nqp::islt_i($!pos,$!chars)) } } multi method lines(Str:D: --> Seq:D) { Seq.new(Lines.new(self)) } multi method lines(Str:D: Bool :$chomp! --> Seq:D) { Seq.new(($chomp ?? Lines !! LinesKeepNL).new(self)) } method !ensure-split-sanity($v, $k, $kv, $p) { # cannot combine these my int $any = $v.Bool + $k.Bool + $kv.Bool + $p.Bool; X::Adverb.new( what => 'split', source => 'Str', nogo => (:$v, :$k, :$kv, :$p).grep(*.value).map(*.key), ).throw if nqp::isgt_i($any,1); $any } method !ensure-limit-sanity(\limit --> Nil) { X::TypeCheck.new( operation => 'split ($limit argument)', expected => 'any Real type (non-NaN) or Whatever', got => limit.raku, ).throw if limit === NaN; limit = Inf if nqp::istype(limit,Whatever); } proto method parse-base(|) {*} multi method parse-base(Str:D: Int:D $radix --> Numeric:D) { 2 <= $radix <= 36 # (0..9,"a".."z").elems == 36 ?? nqp::chars(self) ?? nqp::atpos( # something to parse (my $r := nqp::radix_I($radix,self,0,0x02,Int)), 2 ) == nqp::chars(self) ?? nqp::atpos($r,0) !! self!slow-parse-base($radix,nqp::atpos($r,0),nqp::atpos($r,2)) !! self!parse-fail($radix, 0) # nothing to parse !! X::Syntax::Number::RadixOutOfRange.new(:$radix).Failure } # Shortcut for generating parsing Failure method !parse-fail($radix, $pos --> Failure) { X::Str::Numeric.new( :source(self), :$pos, :reason("malformed base-$radix number"), ).Failure } # Slow path for non-simple integer values method !slow-parse-base(int $radix, \whole, int $failed-at --> Numeric:D) { $failed-at == -1 # nothing parsed ?? nqp::eqat(self,'.',0) # .x ?? ?? self!parse-rat($radix, 0, 1) !! nqp::eqat(self,'.',1) # -.x −.x +.x ?? ?? nqp::eqat(self,'-',0) || nqp::eqat(self,'−',0) # -. −. ?? nqp::istype((my $f := self!parse-rat($radix, 0, 2)),Failure) ?? $f # fail !! -$f # negate val !! nqp::eqat(self,'+',0) # +. ?? self!parse-rat($radix, 0, 2) !! self!parse-fail($radix, 0) !! self!parse-fail($radix, 0) !! nqp::eqat(self,'.',$failed-at) # 123. ?? ?? self!parse-rat($radix, whole, $failed-at + 1) !! self!parse-fail($radix, $failed-at) } # Helper path for parsing rats method !parse-rat(int $radix, Int:D $whole, int $offset --> Numeric:D) { my $fract := nqp::radix_I($radix,self,$offset,0,Int); my $base := nqp::pow_I(nqp::box_i($radix,Int),nqp::atpos($fract,1),Num,Int); nqp::atpos($fract,2) == nqp::chars(self) # fraction parsed entirely? ?? DIVIDE_NUMBERS( nqp::islt_I($whole,0) ?? nqp::sub_I( nqp::mul_I($whole,$base,Int), nqp::atpos($fract,0), Int ) !! nqp::add_I( nqp::mul_I($whole,$base,Int), nqp::atpos($fract,0), Int ), $base, Rat, Rat ) !! self!parse-fail($radix, nqp::atpos($fract,2) max $offset) } method !eggify($egg --> Int:D) { self.trans($egg => "01").parse-base(2) } multi method parse-base(Str:D: "camel" --> Int:D) { self!eggify: "🐪🐫" } multi method parse-base(Str:D: "beer" --> Int:D) { self!eggify: "🍺🍻" } multi method split(Str:D: Regex:D $regex, $limit = Whatever;; :$v , :$k, :$kv, :$p, :$skip-empty --> Seq:D) { Seq.new: self!ensure-split-sanity($v,$k,$kv,$p) ?? Rakudo::Iterator.MatchSplitMap( # additional mapping needed $regex, self, $k # mapper ?? { 0 } # just dummy keys !! $v ?? &POPULATE # full Match objects !! $kv ?? { (0, POPULATE($_)) } # alternating key/Match !! { 0 => POPULATE($_) }, # key => Match $limit, $skip-empty) !! $skip-empty # no additional mapping ?? Rakudo::Iterator.Truthy( # skip empties Rakudo::Iterator.MatchSplit($regex, self, $limit)) !! Rakudo::Iterator.MatchSplit( # produce all strings $regex, self, $limit) } multi method split(Str:D: Str(Cool) $match;; :$v is copy, :$k, :$kv, :$p, :$skip-empty --> Seq:D) { my int $any = self!ensure-split-sanity($v,$k,$kv,$p); # nothing to work with my str $needle = nqp::unbox_s($match); my int $chars = nqp::chars($needle); return Seq.new($chars && !$skip-empty ?? Rakudo::Iterator.OneValue(self) !! Rakudo::Iterator.Empty ) unless self.chars; # split really, really fast in NQP, also supports "" my $matches := nqp::split($needle,nqp::unbox_s(self)); # handle subclassed strings unless nqp::eqaddr(self.WHAT,Str) { my $subclassed := nqp::list; my $what := self.WHAT; nqp::while( nqp::elems($matches), nqp::push( $subclassed, nqp::box_s(nqp::shift($matches),$what) ) ); $matches := $subclassed; } # interleave the necessary strings if needed if $chars { if $any { my $match-list := $v ?? nqp::list($needle) !! $k ?? nqp::list(0) !! $kv ?? nqp::list(0,$needle) !! nqp::list(Pair.new(0,$needle)); # $p if $match-list { my int $i = nqp::elems($matches); if $skip-empty { nqp::splice($matches,$match-list,$i, nqp::not_i(nqp::isne_i( nqp::chars(nqp::atpos($matches,$i)),0))) while --$i; nqp::splice($matches,$empty,0,1) unless nqp::chars(nqp::atpos($matches,0)); } else { nqp::splice($matches,$match-list,$i,0) while --$i; } } } elsif $skip-empty { my int $i = nqp::elems($matches); my $match-list := nqp::list; while nqp::isge_i(--$i,0) { nqp::splice($matches,$match-list,$i,1) if nqp::iseq_i(nqp::chars(nqp::atpos($matches,$i)),0); } } } # single chars need empty before/after, unless inhibited elsif !$skip-empty { nqp::unshift($matches,""); nqp::push($matches,""); } Seq.new(Rakudo::Iterator.ReifiedList($matches)) } my class SplitStrLimit does Iterator { has str $!string; has Mu $!what; has int $!chars; has str $!match; has int $!match-chars; has int $!todo; has int $!pos; method !SET-SELF($string, $match, $todo) { $!string = $string; $!what := $string.WHAT; $!chars = nqp::chars($!string); $!match = $match; $!match-chars = nqp::chars($!match); $!todo = $todo - 1; self } method new($string, $match, $todo) { nqp::create(self)!SET-SELF($string, $match, $todo) } method !last-part() is raw { my str $string = nqp::substr($!string,$!pos); $!pos = $!chars + 1; $!todo = 0; nqp::box_s($string,$!what) } method !next-part(int $found) is raw { my str $string = nqp::substr($!string,$!pos, $found - $!pos); $!pos = $found + $!match-chars; nqp::box_s($string,$!what); } method pull-one() is raw { if $!todo { $!todo = $!todo - 1; my int $found = nqp::index($!string,$!match,$!pos); nqp::islt_i($found,0) ?? nqp::isle_i($!pos,$!chars) ?? self!last-part !! IterationEnd !! self!next-part($found); } else { nqp::isle_i($!pos,$!chars) ?? self!last-part !! IterationEnd } } method push-all(\target --> IterationEnd) { while $!todo { $!todo = $!todo - 1; my int $found = nqp::index($!string,$!match,$!pos); nqp::islt_i($found,0) ?? ($!todo = 0) !! target.push(self!next-part($found)); } target.push(self!last-part) if nqp::isle_i($!pos,$!chars); } method sink-all(--> IterationEnd) { } } my class SplitEmptyLimit does PredictiveIterator { has str $!string; has Mu $!what; has int $!todo; has int $!chars; has int $!pos; has int $!first; has int $!last; method !SET-SELF($string, $todo, $skip-empty) { $!string = $string; $!what := $string.WHAT; $!chars = nqp::chars($!string); $!todo = $todo; $!first = !$skip-empty; if $!todo > $!chars + 2 { # will return all chars $!todo = $!chars + 1; $!last = !$skip-empty; } else { $!todo = $!todo - 1; $!last = !$skip-empty && ($!todo == $!chars + 1); } self } method new($string, $todo, $skip-empty) { nqp::create(self)!SET-SELF($string, $todo, $skip-empty) } method pull-one() is raw { if $!first { # do empty string first $!first = 0; $!todo = $!todo - 1; "" } elsif $!todo { # next char $!todo = $!todo - 1; nqp::box_s(nqp::substr($!string,$!pos++,1),$!what) } elsif $!last { # do final empty string $!last = 0; "" } elsif nqp::islt_i($!pos,$!chars) { # do rest of string my str $rest = nqp::substr($!string,$!pos); $!pos = $!chars; nqp::box_s($rest,$!what) } else { IterationEnd } } method push-all(\target --> IterationEnd) { target.push("") if $!first; $!todo = $!todo - 1; while $!todo { target.push( nqp::box_s(nqp::substr($!string,$!pos++,1),$!what)); $!todo = $!todo - 1; } target.push(nqp::box_s(nqp::substr($!string,$!pos),$!what)) if nqp::islt_i($!pos,$!chars); target.push("") if $!last; } method count-only() { nqp::p6box_i($!todo + $!first + $!last) } method sink-all(--> IterationEnd) { } } multi method split(Str:D: Str(Cool) $match, $limit is copy = Inf;; :$v is copy, :$k, :$kv, :$p, :$skip-empty) { my int $any = self!ensure-split-sanity($v,$k,$kv,$p); self!ensure-limit-sanity($limit); return Seq.new(Rakudo::Iterator.Empty) if $limit <= 0; # nothing to work with my int $chars = $match.chars; if !self.chars { $chars ?? self.list !! (); } # nothing to do elsif $limit == 1 { self.list; } # want them all elsif $limit == Inf { self.split($match,:$v,:$k,:$kv,:$p,:$skip-empty); } # we have something to split on elsif $chars { $any || $skip-empty # let the multi-needle handler handle all nameds ?? self.split(($match,),$limit,:$v,:$k,:$kv,:$p,:$skip-empty) # make the sequence !! Seq.new: SplitStrLimit.new(self, $match, $limit) } # just separate chars else { Seq.new: SplitEmptyLimit.new(self, $limit, $skip-empty) } } multi method split(Str:D: @needles, $parts is copy = Inf;; :$v is copy, :$k, :$kv, :$p, :$skip-empty --> Seq:D) { my int $any = self!ensure-split-sanity($v,$k,$kv,$p); # must all be Cool, otherwise we'll just use a regex return self.split(rx/ @needles /,:$v,:$k,:$kv,:$p,:$skip-empty) # / hl unless Rakudo::Internals.ALL_TYPE(@needles,Cool); self!ensure-limit-sanity($parts); return Seq.new(Rakudo::Iterator.Empty) if $parts <= 0; my int $limit = $parts.Int unless nqp::istype($parts,Whatever) || $parts == Inf; my str $str = nqp::unbox_s(self); my $positions := nqp::list; my $needles := nqp::list_s; my $needle-chars := nqp::list_i; my $needles-seen := nqp::hash; my int $tried; my int $fired; # search using all needles my int $index = 0; for @needles -> $needle { my str $need = nqp::unbox_s($needle.DEFINITE ?? $needle.Str !! ""); my int $chars = nqp::chars($need); nqp::push_s($needles,$need); nqp::push_i($needle-chars,$chars); # search for this needle if there is one, and not done before nqp::if( nqp::isgt_i($chars,0) && nqp::not_i(nqp::existskey($needles-seen,$need)), nqp::stmts( nqp::bindkey($needles-seen,$need,1), (my int $pos), (my int $i), (my int $seen = nqp::elems($positions)), nqp::if( nqp::isgt_i($limit,0), # 0 = no limit nqp::stmts( (my int $todo = $limit), nqp::while( nqp::isge_i(--$todo,0) && nqp::isge_i($i = nqp::index($str,$need,$pos),0), nqp::stmts( nqp::push($positions,nqp::list_i($i,$index)), ($pos = nqp::add_i($i,1)), ) ) ), nqp::while( nqp::isge_i($i = nqp::index($str,$need,$pos),0), nqp::stmts( nqp::push($positions,nqp::list_i($i,$index)), ($pos = nqp::add_i($i,1)) ) ) ), ($tried = nqp::add_i($tried,1)), ($fired = nqp::add_i($fired,nqp::isge_i(nqp::elems($positions),$seen))) ) ); ++$index; } # no needle tried, assume we want chars return self.split("",$limit) if nqp::not_i($tried); # sort by position if more than one needle fired $positions := nqp::getattr( Rakudo::Sorting.MERGESORT-REIFIED-LIST-WITH-int( nqp::p6bindattrinvres( nqp::create(List),List,'$!reified',$positions ), -> \a, \b { nqp::cmp_i( nqp::atpos_i(a,0), nqp::atpos_i(b,0) ) || nqp::cmp_i( nqp::atpos_i($needle-chars,nqp::atpos_i(b,1)), nqp::atpos_i($needle-chars,nqp::atpos_i(a,1)) ) } ), List, '$!reified' ) if nqp::isgt_i($fired,1); # remove elements we do not want if nqp::isgt_i($limit,0) { my int $limited = 1; # split one less than entries returned my int $elems = nqp::elems($positions); my int $pos; my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems) && nqp::islt_i($limited,$limit), nqp::if( nqp::isge_i( # not hidden by other needle nqp::atpos_i(nqp::atpos($positions,$i),0), $pos ), nqp::stmts( ++$limited, ($pos = nqp::add_i( nqp::atpos_i(nqp::atpos($positions,$i),0), nqp::atpos_i($needle-chars, nqp::atpos_i(nqp::atpos($positions,$i),1)) )) ) ) ); nqp::splice( $positions,$empty,$i,nqp::sub_i(nqp::elems($positions),$i) ) if nqp::islt_i($i,$elems); } # create the final result my int $skip = ?$skip-empty; my int $pos = 0; my $result := nqp::create(IterationBuffer); if $any { my int $i = -1; my int $elems = nqp::elems($positions); nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::isge_i( # not hidden by other needle (my int $from = nqp::atpos_i( (my $pair := nqp::atpos($positions,$i)),0) ), $pos ), nqp::stmts( (my int $needle-index = nqp::atpos_i($pair,1)), nqp::unless( $skip && nqp::iseq_i($from,$pos), nqp::push( $result, nqp::box_s( nqp::substr($str,$pos,nqp::sub_i($from,$pos)), self ) ) ), nqp::if($k || $kv, nqp::push($result,nqp::clone($needle-index)) ), nqp::if($v || $kv, nqp::push($result,nqp::atpos_s($needles,$needle-index)) ), nqp::if($p, nqp::push($result,Pair.new( $needle-index,nqp::atpos_s($needles,$needle-index))) ), ($pos = nqp::add_i( $from, nqp::atpos_i($needle-chars,$needle-index) )) ) ) ); } else { my int $i = -1; my int $elems = nqp::elems($positions); nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::isge_i( # not hidden by other needle (my int $from = nqp::atpos_i( (my $pair := nqp::atpos($positions,$i)),0) ), $pos ), nqp::stmts( nqp::unless( $skip && nqp::iseq_i($from,$pos), nqp::push( $result, nqp::box_s( nqp::substr($str,$pos,nqp::sub_i($from,$pos)), self ) ), ), ($pos = nqp::add_i($from, nqp::atpos_i($needle-chars,nqp::atpos_i($pair,1)) )) ) ) ); } nqp::push( $result, nqp::box_s(nqp::substr($str,$pos),self) ) unless $skip && nqp::iseq_i($pos,nqp::chars($str)); Seq.new(Rakudo::Iterator.ReifiedList($result)) } # Note that in these same* methods, as used by s/LHS/RHS/, the # pattern is actually the original string matched by LHS, while the # invocant "original" is really the replacement RHS part. Confusing... multi method samecase(Str:D: Str:D $pattern --> Str:D) { nqp::if( nqp::chars(nqp::unbox_s($pattern)), # something to work with nqp::stmts( (my $result := nqp::list_s), (my $cases := nqp::getattr($pattern,Str,'$!value')), (my int $base-chars = nqp::chars($!value)), (my int $cases-chars = nqp::if( nqp::isgt_i(nqp::chars($cases),$base-chars), $base-chars, nqp::chars($cases) )), (my int $i = 0), (my int $j = 0), (my int $prev-case = nqp::if( # set up initial case nqp::iscclass(nqp::const::CCLASS_LOWERCASE,$cases,0), -1, nqp::iscclass(nqp::const::CCLASS_UPPERCASE,$cases,0) )), nqp::while( # other chars in pattern nqp::islt_i(++$i,$cases-chars), nqp::stmts( (my int $case = nqp::if( # -1 =lc, 1 = uc, 0 = else nqp::iscclass(nqp::const::CCLASS_LOWERCASE,$cases,$i), -1, nqp::iscclass(nqp::const::CCLASS_UPPERCASE,$cases,$i) )), nqp::if( nqp::isne_i($case,$prev-case), nqp::stmts( # seen a change nqp::push_s($result,nqp::if( nqp::iseq_i($prev-case,-1), # coming from lc nqp::lc(nqp::substr($!value,$j,nqp::sub_i($i,$j))), nqp::if( nqp::iseq_i($prev-case,1), # coming from uc nqp::uc(nqp::substr($!value,$j,nqp::sub_i($i,$j))), nqp::substr($!value,$j,nqp::sub_i($i,$j)) ) )), ($prev-case = $case), ($j = $i) ) ) ) ), nqp::if( # something left nqp::islt_i($j,$base-chars), nqp::push_s($result,nqp::if( nqp::iseq_i($prev-case,-1), # must become lc nqp::lc(nqp::substr($!value,$j,nqp::sub_i($base-chars,$j))), nqp::if( nqp::iseq_i($prev-case,1), # must become uc nqp::uc(nqp::substr($!value,$j,nqp::sub_i($base-chars,$j))), nqp::substr($!value,$j,nqp::sub_i($base-chars,$j)) ) )) ), nqp::box_s(nqp::join("",$result),self) # wrap it up ), self # nothing to be done ) } multi method samemark(Str:D: Str:D $pattern --> Str:D) { nqp::if( nqp::chars(nqp::unbox_s($pattern)), # something to work with nqp::stmts( (my $base := nqp::split("",$!value)), (my $marks := nqp::split("",nqp::unbox_s($pattern))), (my int $base-elems = nqp::elems($base)), (my int $marks-elems = nqp::elems($marks) min $base-elems), (my $result := nqp::setelems(nqp::list_s,$base-elems)), (my int $i = -1), nqp::while( # for all marks nqp::islt_i(++$i,$marks-elems), nqp::bindpos_s($result,$i, # store the result of: nqp::stmts( (my $marks-nfd := nqp::strtocodes( # char + accents of mark nqp::atpos($marks,$i), nqp::const::NORMALIZE_NFD, nqp::create(NFD) )), nqp::shift_i($marks-nfd), # lose the char (my $marks-base := nqp::strtocodes( # char + accents of base nqp::atpos($base,$i), nqp::const::NORMALIZE_NFD, nqp::create(NFD) )), nqp::strfromcodes( # join base+rest of marks nqp::splice( $marks-base, $marks-nfd, 1, nqp::sub_i(nqp::elems($marks-base),1) ) ) ) ) ), --$i, nqp::while( # remaining base chars nqp::islt_i(++$i,$base-elems), nqp::bindpos_s($result,$i, # store the result of: nqp::stmts( ($marks-base := nqp::strtocodes( # char+all accents of base nqp::atpos($base,$i), nqp::const::NORMALIZE_NFD, nqp::create(NFD) )), nqp::strfromcodes( # join base+rest of marks nqp::splice( $marks-base, $marks-nfd, # NOTE: state of last iteration previous loop 1, nqp::sub_i(nqp::elems($marks-base),1) ) ) ) ) ), nqp::box_s(nqp::join("",$result),self) # wrap it up ), self # nothing to be done ) } multi method samespace(Str:D: Str:D $pattern) { self!word-by-word($pattern, :samespace) } method !word-by-word(Str:D $pattern, &filter?, Bool :$samespace) { my str $str = nqp::unbox_s(self); my str $pat = nqp::unbox_s($pattern); my Mu $ret := nqp::list_s; my int $chars = nqp::chars($str); my int $pos = 0; my int $nextpos; my int $patchars = nqp::chars($pat); my int $patpos = 0; my int $patnextpos; my int $left; my $patword; # Still something to look for? while ($left = $chars - $pos) > 0 { $nextpos = nqp::findcclass( nqp::const::CCLASS_WHITESPACE, $str, $pos, $left); $patnextpos = nqp::findcclass(nqp::const::CCLASS_WHITESPACE, $pat, $patpos, $patchars - $patpos); if &filter { # We latch on last pattern word if pattern runs out of words first. $patword := nqp::p6box_s(nqp::substr($pat, $patpos, $patnextpos - $patpos)) if $patpos < $patchars; nqp::push_s($ret, nqp::unbox_s(filter(nqp::substr($str, $pos, $nextpos - $pos), $patword))); } else { nqp::push_s($ret, nqp::substr($str, $pos, $nextpos - $pos)); } # Did we have the last word? last if $nextpos >= $chars; $pos = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE, $str, $nextpos, $chars - $nextpos); if $patnextpos >= $patchars { # No more pat space, just copy original space. nqp::push_s($ret, nqp::substr($str, $nextpos, $pos - $nextpos)); $patpos = $patnextpos; } else { # Traverse pat space, use if wanted $patpos = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE, $pat, $patnextpos, $patchars - $patnextpos); if $samespace { # Carry over pattern space? nqp::push_s($ret, nqp::substr($pat, $patnextpos, $patpos - $patnextpos)); } else { # Nope, just use original space. nqp::push_s($ret, nqp::substr($str, $nextpos, $pos - $nextpos)); } } } nqp::box_s(nqp::join("",$ret),self) } multi method trim(Str:D: --> Str:D) { my int $left = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE, self, 0, (my int $pos = nqp::chars(self)) ); nqp::while( nqp::isgt_i(--$pos,$left) && nqp::iscclass(nqp::const::CCLASS_WHITESPACE,self,$pos), nqp::null ); nqp::box_s(nqp::substr(self,$left,$pos + 1 - $left),self) } multi method trim-leading(Str:D: --> Str:D) { nqp::box_s(nqp::substr( self, nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE,self,0,nqp::chars(self) ) ),self) } multi method trim-trailing(Str:D: --> Str:D) { nqp::if( nqp::iscclass( nqp::const::CCLASS_WHITESPACE, self, (my int $pos = nqp::chars(self) - 1) ), nqp::stmts( # at least one trailing whitespace nqp::while( nqp::isge_i(--$pos,0) && nqp::iscclass(nqp::const::CCLASS_WHITESPACE,self,$pos), nqp::null ), nqp::box_s(nqp::substr(self,0,$pos + 1),self) ), self # no whitespace, so done ) } multi method words(Str:D: $limit --> Seq:D) { self.words.head($limit) } my class Words does PredictiveIterator { has str $!str; has Mu $!what; has int $!chars; has int $!pos; method !SET-SELF($string) { $!str = $string; $!what := $string.WHAT; $!chars = nqp::chars($!str); $!pos = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE, $!str, 0, $!chars); self } method new($string) { nqp::create(self)!SET-SELF($string) } method pull-one() { nqp::if( (my int $left = $!chars - $!pos) > 0, nqp::stmts( (my int $nextpos = nqp::findcclass( nqp::const::CCLASS_WHITESPACE, $!str, $!pos, $left)), (my $found := nqp::box_s( nqp::substr($!str, $!pos, $nextpos - $!pos), $!what )), ($!pos = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE, $!str, $nextpos, $!chars - $nextpos)), $found ), IterationEnd ) } method push-all(\target --> IterationEnd) { my int $left; my int $nextpos; while ($left = $!chars - $!pos) > 0 { $nextpos = nqp::findcclass( nqp::const::CCLASS_WHITESPACE, $!str, $!pos, $left); target.push(nqp::box_s( nqp::substr($!str, $!pos, $nextpos - $!pos), $!what )); $!pos = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE, $!str, $nextpos, $!chars - $nextpos); } } method count-only(--> Int:D) { my int $left; my int $nextpos; my int $seen; my int $pos = $!pos; my int $chars = $!chars; while ($left = $chars - $pos) > 0 { $nextpos = nqp::findcclass( nqp::const::CCLASS_WHITESPACE, $!str, $pos, $left); $pos = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE, $!str, $nextpos, $chars - $nextpos); $seen = $seen + 1; } $seen } method bool-only(--> Bool:D) { nqp::hllbool(nqp::islt_i($!pos,$!chars)) } method sink-all(--> IterationEnd) { } } multi method words(Str:D: --> Seq:D) { Seq.new(Words.new(self)) } # Internal method, used in RakuAST::QuotedString for word lists. Called # either at compile time for literal lists or runtime otherwise. my Mu $nbsp := nqp::hash( "\x00A0", True, "\x2007", True, "\x202F", True, "\xFEFF", True, ); method WORDS_AUTODEREF(Str:D:) is implementation-detail { my $result := nqp::list(); my int $pos = 0; my int $eos = nqp::chars(self); my int $ws; while ($pos = nqp::findnotcclass(nqp::const::CCLASS_WHITESPACE, self, $pos, $eos)) < $eos { # Search for another white space character as long as we hit non-breakable spaces. $ws = $pos; $ws++ while nqp::existskey($nbsp, nqp::substr(self, $ws = nqp::findcclass(nqp::const::CCLASS_WHITESPACE, self, $ws, $eos), 1)); nqp::push($result, nqp::substr(self, $pos, $ws - $pos)); $pos = $ws; } nqp::hllize($result) } multi method encode(Str:D $encoding = 'utf8', :$replacement, Bool() :$translate-nl = False, :$strict --> Blob:D) { Encoding::Registry.find($encoding) .encoder(:$replacement, :$translate-nl, :$strict) .encode-chars(self) } my &SMART-WORDS = / [<:L> \w* ] +% <['\-]> /; multi method wordcase(Str:D: :&filter, Mu :$where = True --> Str:D) { my int $c; my int $pos; my int $from; my str $word; my $parts := nqp::list_s; nqp::until( nqp::islt_i( ($pos = nqp::getattr_i( (my $m := SMART-WORDS($cursor-init(Match,self,:$c))), Match, '$!pos' )), 0 ), nqp::stmts( nqp::if( nqp::isgt_i(($from = nqp::getattr_i($m,Match,'$!from')),$c), nqp::push_s($parts,nqp::substr($!value,$c,nqp::sub_i($from,$c))) ), ($word = nqp::substr($!value,$from,nqp::sub_i($pos,$from))), nqp::push_s( $parts, nqp::if( $where.ACCEPTS($word), nqp::if(&filter,filter($word),nqp::tclc($word)), $word ) ), ($c = $pos) ) ); nqp::push_s( $parts, nqp::substr($!value,$c,nqp::sub_i(nqp::chars($!value),$c)) ) if nqp::islt_i($c,nqp::chars($!value)); nqp::box_s(nqp::join('',$parts),self) } multi method trans(Str:D: Pair:D $what, *%n --> Str:D) { my $from := $what.key; my $to := $what.value; $/ := nqp::getlexcaller('$/'); return self.trans(($what,), |%n) if !nqp::istype($from,Str) # from not a string || !$from.defined # or a type object || !nqp::istype($to,Str) # or to not a string || !$to.defined # or a type object || %n; # or any named params passed # from 1 char return nqp::box_s( Rakudo::Internals.TRANSPOSE(self, $from, $to.substr(0,1)), self ) if $from.chars == 1; my str $sfrom = Rakudo::Internals.EXPAND-LITERAL-RANGE($from,0); my str $str = self; my str $chars = nqp::chars($str); my Mu $result := nqp::list_s(); my str $check; my int $i = -1; # something to convert to if $to.chars -> $tochars { nqp::setelems($result,$chars); # all convert to one char if $tochars == 1 { my str $sto = nqp::unbox_s($to); while nqp::islt_i(++$i,$chars) { $check = nqp::substr($str,$i,1); nqp::bindpos_s( $result, $i, nqp::iseq_i(nqp::index($sfrom,$check),-1) ?? $check !! $sto ); } } # multiple chars to convert to else { my str $sto = Rakudo::Internals.EXPAND-LITERAL-RANGE($to,0); my int $sfl = nqp::chars($sfrom); my int $found; # repeat until mapping complete $sto = $sto ~ $sto while nqp::islt_i(nqp::chars($sto),$sfl); while nqp::islt_i(++$i,$chars) { $check = nqp::substr($str,$i,1); $found = nqp::index($sfrom,$check); nqp::bindpos_s($result, $i, nqp::iseq_i($found,-1) ?? $check !! nqp::substr($sto,$found,1) ); } } } # just remove else { while nqp::islt_i(++$i,$chars) { $check = nqp::substr($str,$i,1); nqp::push_s($result, $check) if nqp::iseq_i(nqp::index($sfrom,$check),-1); } } nqp::box_s(nqp::join('',$result),self); } my class LSM { has str $!source; has Mu $!what; has $!substitutions; has int $!squash; has int $!complement; has str $!prev_result; has int $!index; has int $!next_match; has int $!substitution_length; has $!first_substitution; # need this one for :c with arrays has $!next_substitution; has $!match_obj; has $!last_match_obj; has str $!unsubstituted_text; has str $!substituted_text; method !SET-SELF($source, \substitutions, $squash, $complement) { $!source = $source; $!what := $source.WHAT; $!substitutions := nqp::getattr(substitutions,List,'$!reified'); $!squash = $squash.Bool; $!complement = $complement.Bool; $!prev_result = ''; self } method new($source, \substitutions, $squash, $complement) { nqp::create(self)!SET-SELF( $source, substitutions, $squash, $complement ) } method !compare_substitution( $substitution, int $pos, int $length --> Nil ) { if nqp::isgt_i($!next_match,$pos) || nqp::iseq_i($!next_match,$pos) && nqp::islt_i($!substitution_length,$length) { $!next_match = $pos; $!substitution_length = $length; $!next_substitution = $substitution; $!match_obj = $!last_match_obj; } } method !increment_index($s --> Nil) { $/ := nqp::getlexcaller('$/'); if nqp::istype($s,Regex) { $!index = $!next_match + ( substr($!source,$!index) ~~ $s ?? $/.chars !! 0 ); $!last_match_obj = $/; } else { $!index = $!next_match + nqp::chars(nqp::istype($s,Str) ?? $s !! $s.Str); } } # note: changes outer $/ method get_next_substitution_result { my $value = $!complement ?? $!first_substitution.value !! $!next_substitution.value; my $outer_slash := nqp::getlexcaller('$/'); $/ := nqp::getlexcaller('$/'); $outer_slash = $!match_obj; my str $result = nqp::istype($value,Callable) ?? $value().Str !! nqp::istype($value,Str) ?? $value !! $value.Str; my str $orig_result = $result; $result = '' if $!squash && nqp::chars($!prev_result) && nqp::iseq_s($!prev_result,$result) && nqp::iseq_s($!unsubstituted_text,''); $!prev_result = $orig_result; $result } method next_substitution() { $/ := nqp::getlexcaller('$/'); $!next_match = nqp::chars($!source); $!first_substitution = nqp::atpos($!substitutions,0) unless nqp::defined($!first_substitution); # triage substitutions left to do my $todo := nqp::list; my int $i = -1; while ++$i < nqp::elems($!substitutions) { my $this := nqp::atpos($!substitutions,$i); my $key := $this.key; if nqp::istype($key,Regex) { if $!source.match($key, :continue($!index)) -> \m { $!last_match_obj = $/; self!compare_substitution($this, m.from, m.to - m.from); nqp::push($todo,$this); } } elsif nqp::istype($key,Cool) { my str $skey = nqp::istype($key,Str) ?? $key !! $key.Str; my int $pos = nqp::index($!source,$skey,$!index); if nqp::isge_i($pos,0) { self!compare_substitution($this,$pos,nqp::chars($skey)); nqp::push($todo,$this); } } else { X::Str::Trans::IllegalKey.new(key => $this).throw; } } $!substitutions := $todo; $!unsubstituted_text = nqp::substr($!source,$!index,$!next_match - $!index); if $!next_substitution.defined { if $!complement { my $oldidx = $!index; if nqp::chars($!unsubstituted_text) -> \todo { my $result = self.get_next_substitution_result; self!increment_index($!next_substitution.key); $!substituted_text = nqp::substr( $!source, $oldidx + todo, $!index - $oldidx - todo, ); $!unsubstituted_text = $!squash ?? $result !! $result x todo; } else { return if $!next_match == nqp::chars($!source); my $result = self.get_next_substitution_result; self!increment_index($!next_substitution.key); $!substituted_text = ''; $!unsubstituted_text = nqp::substr($!source,$oldidx,$!index - $oldidx); } } else { return if $!next_match == nqp::chars($!source); $!substituted_text = self.get_next_substitution_result; self!increment_index($!next_substitution.key); } } nqp::islt_i($!next_match,nqp::chars($!source)) && nqp::elems($!substitutions) } method result() { $/ := nqp::getlexcaller('$/'); my Mu $result := nqp::list_s; while self.next_substitution { nqp::push_s($result,$!unsubstituted_text); nqp::push_s($result,$!substituted_text); } nqp::push_s($result,$!unsubstituted_text); nqp::box_s(nqp::join('', $result),$!what) } } multi method trans(Str:D: *@changes, :c(:$complement), :s(:$squash), :d(:$delete) --> Str:D) { # nothing to do return self unless self.chars; $/ := nqp::getlexcaller('$/'); my sub myflat(*@s) { @s.map: { nqp::istype($_, Iterable) ?? .list.Slip !! $_ } } my sub expand($s) { nqp::istype($s,Iterable) || nqp::istype($s,Positional) ?? (my @ = myflat($s.list).Slip) !! Rakudo::Internals.EXPAND-LITERAL-RANGE($s,1) } my int $just-strings = !$complement && !$squash; my int $just-chars = $just-strings; my $needles := nqp::list; my $pins := nqp::list; my $substitutions := nqp::list; for @changes -> $p { X::Str::Trans::InvalidArg.new(got => $p).throw unless nqp::istype($p,Pair); my $key := $p.key; my $value := $p.value; if nqp::istype($key,Regex) { $just-strings = 0; nqp::push($substitutions,$p); } elsif nqp::istype($value,Callable) { $just-strings = 0; nqp::push($substitutions,Pair.new($_,$value)) for expand $key; } else { my $from := nqp::getattr(expand($key), List,'$!reified'); my $to := nqp::getattr(expand($value),List,'$!reified'); my $from-elems = nqp::elems($from); my $to-elems = nqp::elems($to); my $padding = $delete ?? '' !! $to-elems ?? nqp::atpos($to,$to-elems - 1) !! ''; my int $i = -1; while nqp::islt_i($i = $i + 1,$from-elems) { my $key := nqp::atpos($from,$i); my $value := nqp::islt_i($i,$to-elems) ?? nqp::atpos($to,$i) !! $padding; nqp::push($substitutions,Pair.new($key,$value)); if $just-strings { if nqp::istype($key,Str) && nqp::istype($value,Str) { $key := nqp::unbox_s($key); $just-chars = 0 if nqp::isgt_i(nqp::chars($key),1); nqp::push($needles,$key); nqp::push($pins,nqp::unbox_s($value)); } else { $just-strings = 0; } } } } } # can do special cases for just strings if $just-strings { # only need to go through string once if $just-chars { my $lookup := nqp::hash; my int $elems = nqp::elems($needles); my int $i = -1; nqp::bindkey($lookup, nqp::atpos($needles,$i),nqp::atpos($pins,$i)) while nqp::islt_i($i = $i + 1,$elems); my $result := nqp::split("",nqp::unbox_s(self)); $i = -1; $elems = nqp::elems($result); nqp::bindpos($result,$i, nqp::atkey($lookup,nqp::atpos($result,$i))) if nqp::existskey($lookup,nqp::atpos($result,$i)) while nqp::islt_i($i = $i + 1,$elems); nqp::box_s(nqp::join("",$result),self) } # use multi-needle split with in-place mapping else { my $iterator := self.split($needles,:k).iterator; my $strings := nqp::list_s($iterator.pull-one); nqp::until( nqp::eqaddr((my $i := $iterator.pull-one),IterationEnd), nqp::stmts( nqp::push_s($strings,nqp::atpos($pins,$i)), nqp::push_s($strings,$iterator.pull-one) ) ); nqp::box_s(nqp::join("",$strings),self) } } # alas, need to use more complex route else { LSM.new(self,$substitutions,$squash,$complement).result; } } # Zero indent does nothing multi method indent(Str:D: Int() $steps where { $_ == 0 }) { self; } # Positive indent does indent multi method indent(Int() $steps where { $_ > 0 }) { nqp::box_s(self.lines(:!chomp).map({ given $_.Str { when /^ \n? $ / { $_; } # Use the existing space character if they're all the same # (but tabs are done slightly differently) when /^(\t+) ([ \S .* | $ ])/ { $0 ~ "\t" x ($steps div $?TABSTOP) ~ ' ' x ($steps mod $?TABSTOP) ~ $1 } when /^(\h) $0* [ \S | $ ]/ { $0 x $steps ~ $_ } # Otherwise we just insert spaces after the existing leading space default { $_ ~~ /^(\h*) (.*)$/; $0 ~ (' ' x $steps) ~ $1 } } }).join,self) } # Negative indent (de-indent) multi method indent(Int() $steps where { $_ < 0 }) { de-indent(self, $steps); } # Whatever indent (de-indent) multi method indent(Whatever $steps) { de-indent(self, $steps); } sub de-indent($obj, $steps) { # Loop through all lines to get as much info out of them as possible my @lines = $obj.lines(:!chomp).map({ # Split the line into indent and content my ($indent, $rest) = @($_ ~~ /^(\h*) (.*)$/); # Split the indent into characters and annotate them # with their visual size my $indent-size = 0; my @indent-chars = $indent.comb.map(-> $char { my $width = $char eq "\t" ?? $?TABSTOP - ($indent-size mod $?TABSTOP) !! 1; $indent-size += $width; $char => $width; }).eager; { :$indent-size, :@indent-chars, :rest(~$rest) }; }); # Figure out the amount * should de-indent by, we also use this for warnings my $common-prefix = min @lines.grep({ . || . ~~ /\S/}).map({ $_ }); return $obj if $common-prefix === Inf; # Set the actual de-indent amount here my Int $de-indent = nqp::istype($steps,Whatever) ?? $common-prefix !! -$steps; warn "Asked to remove $de-indent spaces, but the shortest indent is $common-prefix spaces" if $de-indent > $common-prefix; # Work forwards from the left end of the indent whitespace, removing # array elements up to # (or over, in the case of tab-explosion) # the specified de-indent amount. nqp::box_s(@lines.map(-> $l { my $pos = 0; while $l and $pos < $de-indent { if $l.shift.key eq "\t" { $pos -= $pos % $?TABSTOP; $pos += $?TABSTOP; } else { ++$pos } } if $l and $pos % $?TABSTOP { my $check = $?TABSTOP - $pos % $?TABSTOP; $check = $l[lazy 0..^$check].first(*.key eq "\t",:k); with $check { $l.shift for 0..$check; $pos -= $pos % $?TABSTOP; $pos += $?TABSTOP; } } $l».key.join ~ ' ' x ($pos - $de-indent) ~ $l; }).join,nqp::decont($obj)) } method !SUBSTR-START-OOR($from) { X::OutOfRange.new( :what('Start argument to substr'), :got($from.gist), :range("0.." ~ nqp::chars(self)), :comment( nqp::istype($from, Callable) || -$from > nqp::chars(self) ?? '' !! "use *-{abs $from} if you want to index relative to the end"), ).Failure } method !SUBSTR-CHARS-OOR($chars) { X::OutOfRange.new( :what('Number of characters argument to substr'), :got($chars.gist), :range<0..^Inf>, :comment("use *-{abs $chars} if you want to index relative to the end"), ).Failure } multi method substr(Str:D: --> Str:D) { self } multi method substr(Str:D: Int:D $from --> Str:D) { nqp::islt_i($from,0) || nqp::isgt_i($from,nqp::chars(self)) #?js: NFG ?? self!SUBSTR-START-OOR($from) !! nqp::box_s(nqp::substr(self,$from),self) #?js: NFG } multi method substr(Str:D: Int:D $from, Int:D $want --> Str:D) { nqp::islt_i($from,0) || nqp::isgt_i($from,nqp::chars(self)) #?js: NFG ?? self!SUBSTR-START-OOR($from) !! nqp::islt_i($want,0) ?? self!SUBSTR-CHARS-OOR($want) !! nqp::box_s(nqp::substr(self,$from,$want),self) #?js: NFG } multi method substr(Str:D: Int:D $from, &want --> Str:D) { self.substr( $from, want(nqp::sub_i(nqp::chars(self),$from)).Int #?js: NFG ) } multi method substr(Str:D: Int:D $from, Whatever --> Str:D) { self.substr($from) } multi method substr(Str:D: Int:D $from, Num:D $want --> Str:D) { nqp::isnanorinf($want) ?? $want == Inf ?? self.substr($from) !! self!SUBSTR-CHARS-OOR($want) !! self.substr($from, $want.Int) } multi method substr(Str:D: &want --> Str:D) { self.substr(want(nqp::chars(self)).Int) #?js: NFG } multi method substr(Str:D: &from, Int:D $want --> Str:D) { self.substr(from(nqp::chars(self)).Int, $want) #?js: NFG } multi method substr(Str:D: &from, &want --> Str:D) { my int $from = from(nqp::chars(self)).Int; self.substr($from, want(nqp::sub_i(nqp::chars(self),$from)).Int) } multi method substr(Str:D: Range:D \start --> Str:D) { nqp::islt_i((my int $from = (start.min + start.excludes-min).Int),0) || nqp::isgt_i($from,nqp::chars($!value)) #?js: NFG ?? self!SUBSTR-START-OOR($from) !! nqp::box_s( (start.max == Inf ?? nqp::substr($!value,$from) #?js: NFG !! nqp::substr( $!value, $from, (start.max - start.excludes-max - $from + 1).Int #?js: NFG ) ), self ) } multi method substr(Str:D: Regex:D, $) { # GH 1314 die "You cannot use a Regex on 'substr', did you mean 'subst'?" } multi method substr(Str:D: \start --> Str:D) { self.substr(start.Int) } multi method substr(Str:D: \from, \want --> Str:D) { nqp::istype(want,Whatever) || (! nqp::istype(want, Callable) && want == Inf) ?? self.substr(from) !! self.substr(nqp::istype(from, Callable) ?? from !! from.Int, nqp::istype(want, Callable) ?? want !! want.Int) } multi method substr-rw(Str:D \SELF:) is rw { SELF.substr-rw(0, nqp::chars($!value), self) } multi method substr-rw(Str:D \SELF: \start) is rw { SELF.substr-rw(start, Whatever, self) } multi method substr-rw(Str:D \SELF: \start, \want) is rw { SELF.substr-rw(start, want, self) } multi method substr-rw(Str:D \SELF: \start, $want, \what ) is rw is implementation-detail { my int $max = nqp::chars($!value); my int $from = nqp::istype(start,Callable) ?? (start)($max) !! nqp::istype(start,Range) ?? start.min + start.excludes-min !! start.Int; return self!SUBSTR-START-OOR($from) if nqp::islt_i($from,0) || nqp::isgt_i($from,$max); my int $chars = nqp::istype(start,Range) ?? start.max == Inf ?? nqp::sub_i($max,$from) !! start.max - start.excludes-max - $from + 1 !! nqp::istype($want,Whatever) || $want == Inf ?? nqp::sub_i($max,$from) !! nqp::istype($want,Callable) ?? $want(nqp::sub_i($max,$from)) !! $want.Int; nqp::islt_i($chars,0) ?? self!SUBSTR-CHARS-OOR($chars) !! Proxy.new( FETCH => sub ($) { # need to access updated HLL Str nqp::substr(nqp::unbox_s(SELF),$from,$chars) }, STORE => sub ($, Str() $new) { SELF = nqp::box_s( # need to make it a new HLL Str nqp::concat( nqp::substr($!value,0,$from), nqp::concat( nqp::unbox_s($new), nqp::substr($!value,nqp::add_i($from,$chars)) ) ), what ) } ) } multi method codes(Str:D: --> Int:D) { nqp::codes(self) } multi method codes(Str:U: --> Int:D) { self.Str; # generate undefined warning 0 } multi method chars(Str:D: --> Int:D) { nqp::p6box_i(nqp::chars($!value)) #?js: NFG } multi method chars(Str:U: --> Int:D) { self.Str; # generate undefined warning 0 } multi method uc(Str:D: --> Str:D) { nqp::box_s(nqp::uc($!value),self) } multi method uc(Str:U: --> Str:D) { self.Str } multi method lc(Str:D: --> Str:D) { nqp::box_s(nqp::lc($!value),self) } multi method lc(Str:U: --> Str:D) { self.Str } multi method tc(Str:D: --> Str:D) { nqp::box_s( nqp::concat( #?js: NFG nqp::tc(nqp::substr(self,0,1)), nqp::substr(self,1) ), self ) } multi method tc(Str:U: --> Str:D) { self.Str } multi method fc(Str:D: --> Str:D) { nqp::box_s(nqp::fc($!value),self) } multi method fc(Str:U: --> Str:D) { self.Str } multi method tclc(Str:D: --> Str:D) { nqp::box_s(nqp::tclc($!value),self) } multi method tclc(Str:U: --> Str:D) { self.Str } multi method flip(Str:D: --> Str:D) { nqp::box_s(nqp::flip($!value),self) } multi method flip(Str:U: --> Str:D) { self.Str } method Date(Str:D:) { Date.new(self) } method DateTime(Str:D:) { DateTime.new(self) } # A naive word wrapper intended to be used for aligning error messages. # Naive in the sense that it assumes all graphemes are the same width, # and words that are too long for a line, will simply live on a line of # their own, even if that is longer than the given maximum width. method naive-word-wrapper( int :$max = 72, str :$indent = "", --> Str:D) is implementation-detail { my $lines := nqp::list_s; my $line := nqp::list_s; my int $width = nqp::chars($indent); for self.words -> str $word { my int $visible-width = $word.subst(/\e '[' \d+ m/, :global).chars; if $width + $visible-width >= $max { if nqp::elems($line) { nqp::push_s( $lines, nqp::concat($indent,nqp::join(" ",$line)) ); $line := nqp::list_s($word); $width = nqp::chars($indent) + $visible-width; } else { nqp::push_s($lines,nqp::concat($indent,$word)); $width = nqp::chars($indent); } } else { nqp::push_s($line,$word); $width = $width + 1 + $visible-width; } # double space after . or ? if $word.ends-with('.') || $word.ends-with('?') { nqp::push_s($line,""); ++$width; } } nqp::pop_s($line) if nqp::elems($line) && nqp::atpos_s($line,-1) eq ""; nqp::push_s($lines,nqp::concat($indent,nqp::join(" ",$line))) if nqp::elems($line); nqp::join("\n",$lines) } method is-whitespace(Str:D: --> Bool:D) is implementation-detail { nqp::hllbool( nqp::iseq_i( nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE,self,0,nqp::chars(self) ), nqp::chars(self) ) ) } method leading-whitespace(Str:D: --> Str:D) is implementation-detail { nqp::substr(self,0,nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE,self,0,nqp::chars(self) )) } method trailing-whitespace(Str:D: --> Str:D) is implementation-detail { nqp::substr(self,nqp::chars(self) - nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE,nqp::flip(self),0,nqp::chars(self) )) } method no-zero-arg(Str:D: ) is implementation-detail is hidden-from-backtrace { X::NoZeroArgMeaning.new(:name(self)).Failure } } multi sub prefix:<~>(Str:D $a --> Str:D) { $a.Str } multi sub prefix:<~>(str $a --> str) { $a } multi sub infix:<~>(str $a, str $b --> str) { nqp::concat($a, $b) } multi sub infix:<~>(Str:D $a, str $b --> str) { nqp::concat(nqp::unbox_s($a),$b) } multi sub infix:<~>(str $a, Str:D $b --> str) { nqp::concat($a,nqp::unbox_s($b)) } multi sub infix:<~>(Str:D $a, Str:D $b --> Str:D) { nqp::box_s( nqp::concat(nqp::unbox_s($a),nqp::unbox_s($b)), Str ) } multi sub infix:<~>(Cool:D $a, Str:D $b --> Str:D) { nqp::box_s( nqp::concat(nqp::unbox_s($a.Str),nqp::unbox_s($b)), Str ) } multi sub infix:<~>(Str:D $a, Cool:D $b --> Str:D) { nqp::box_s( nqp::concat(nqp::unbox_s($a),nqp::unbox_s($b.Str)), Str ) } multi sub infix:<~>(Cool:D $a, Cool:D $b --> Str:D) { nqp::box_s( nqp::concat(nqp::unbox_s($a.Str),nqp::unbox_s($b.Str)), Str ) } multi sub infix:<~>(Any:D \a, Str:D $b --> Str:D) { nqp::box_s( nqp::concat(nqp::unbox_s(a.Stringy), nqp::unbox_s($b)), Str ) } multi sub infix:<~>(Str:D $a, Any:D \b --> Str:D) { nqp::box_s( nqp::concat(nqp::unbox_s($a), nqp::unbox_s(b.Stringy)), Str ) } # Any/Any candidate in src/core.c/Stringy.rakumod multi sub infix:<~>(str @args --> str) { nqp::join('',@args) } multi sub infix:<~>(@args) { @args.join } multi sub infix:<~>(*@args) { @args.join } multi sub infix:(Str:D $s, Bool:D $repetition --> Str:D) { $repetition ?? $s !! '' } multi sub infix:(Str:D $s, Int:D $repetition --> Str:D) { nqp::islt_i($repetition,1) ?? '' !! nqp::x($s, $repetition) } multi sub infix:(str $s, int $repetition --> str) { nqp::islt_i($repetition,1) ?? '' !! nqp::x($s, $repetition) } multi sub infix:(Str:D $a, Str:D $b) { ORDER(nqp::cmp_s(nqp::unbox_s($a), nqp::unbox_s($b))) } multi sub infix:(str $a, str $b) { ORDER(nqp::cmp_s($a, $b)) } multi sub infix:<===>(Str:D $a, Str:D $b --> Bool:D) { nqp::hllbool( nqp::eqaddr($a.WHAT,$b.WHAT) && nqp::iseq_s(nqp::unbox_s($a),nqp::unbox_s($b)) #?js: NFG ) } multi sub infix:<===>(str $a, str $b --> Bool:D) { nqp::hllbool(nqp::iseq_s($a, $b)) #?js: NFG } multi sub infix:(Str:D $a, Str:D $b) { ORDER(nqp::cmp_s(nqp::unbox_s($a),nqp::unbox_s($b))) } multi sub infix:(str $a, str $b) { ORDER(nqp::cmp_s($a, $b)) } multi sub infix:(Str:D $a, Str:D $b --> Bool:D) { nqp::hllbool(nqp::iseq_s(nqp::unbox_s($a),nqp::unbox_s($b))) #?js: NFG } multi sub infix:(str $a, str $b --> Bool:D) { nqp::hllbool(nqp::iseq_s($a, $b)) #?js: NFG } multi sub infix:(Str:D $a, Str:D $b --> Bool:D) { nqp::hllbool(nqp::isne_s(nqp::unbox_s($a),nqp::unbox_s($b))) } multi sub infix:(str $a, str $b --> Bool:D) { nqp::hllbool(nqp::isne_s($a, $b)) } multi sub infix:(Str:D $a, Str:D $b --> Bool:D) { nqp::hllbool(nqp::islt_s(nqp::unbox_s($a),nqp::unbox_s($b))) } multi sub infix:(str $a, str $b --> Bool:D) { nqp::hllbool(nqp::islt_s($a, $b)) } multi sub infix:(Str:D $a, Str:D $b --> Bool:D) { nqp::hllbool(nqp::isle_s(nqp::unbox_s($a),nqp::unbox_s($b))) } multi sub infix:(str $a, str $b --> Bool:D) { nqp::hllbool(nqp::isle_s($a, $b)) } multi sub infix:(Str:D $a, Str:D $b --> Bool:D) { nqp::hllbool(nqp::isgt_s(nqp::unbox_s($a),nqp::unbox_s($b))) } multi sub infix:(str $a, str $b --> Bool:D) { nqp::hllbool(nqp::isgt_s($a, $b)) } multi sub infix:(Str:D $a, Str:D $b --> Bool:D) { nqp::hllbool(nqp::isge_s(nqp::unbox_s($a),nqp::unbox_s($b))) } multi sub infix:(str $a, str $b --> Bool:D) { nqp::hllbool(nqp::isge_s($a, $b)) } multi sub infix:<~|>(Str:D $a, Str:D $b --> Str:D) { nqp::p6box_s(nqp::bitor_s(nqp::unbox_s($a),nqp::unbox_s($b))) } multi sub infix:<~|>(str $a, str $b --> str) { nqp::bitor_s($a, $b) } multi sub infix:<~&>(Str:D $a, Str:D $b --> Str:D) { nqp::p6box_s(nqp::bitand_s(nqp::unbox_s($a),nqp::unbox_s($b))) } multi sub infix:<~&>(str $a, str $b --> str) { nqp::bitand_s($a, $b) } multi sub infix:<~^>(Str:D $a, Str:D $b --> Str:D) { nqp::p6box_s(nqp::bitxor_s(nqp::unbox_s($a),nqp::unbox_s($b))) } multi sub infix:<~^>(str $a, str $b --> str) { nqp::bitxor_s($a, $b) } multi sub prefix:<~^>(Str $) { NYI "prefix:<~^>" # XXX } # XXX: String-wise shifts NYI multi sub infix:«~>»(Str:D $, Int:D $) { NYI("infix:«~>»").throw; } multi sub infix:«~>»(str $, int $) { NYI("infix:«~>»").throw; } multi sub infix:«~<»(Str:D $, Int:D $ --> Str:D) { NYI("infix:«~<»").throw; } multi sub infix:«~<»(str $, int $) { NYI("infix:«~<»").throw; } proto sub trim($, *%) {*} multi sub trim(Cool:D $s --> Str:D) { $s.trim } proto sub trim-leading($, *%) {*} multi sub trim-leading (Cool:D $s --> Str:D) { $s.trim-leading } proto sub trim-trailing($, *%) {*} multi sub trim-trailing(Cool:D $s --> Str:D) { $s.trim-trailing } # the opposite of Real.base, used for :16($hex_str) proto sub UNBASE ($, $, *%) is implementation-detail {*} multi sub UNBASE(Int:D $base, Any:D $num) { X::Numeric::Confused.new(:$num, :$base).throw; } multi sub UNBASE(Int:D $base, Str:D $str --> Numeric:D) { my Str $ch = substr($str, 0, 1); if $ch eq '0' { $ch = substr($str, 1, 1); if $base <= 11 && $ch eq any() or $base <= 24 && $ch eq any or $base <= 33 && $ch eq 'x' { $str.Numeric; } else { ":{$base}<$str>".Numeric; } } elsif $ch eq ':' && substr($str, 1, 1) ~~ ('1'..'9') { $str.Numeric; } else { ":{$base}<$str>".Numeric; } } # for :16[1, 2, 3] sub UNBASE_BRACKET($base, @a) is implementation-detail { my $v = 0; my $denom = 1; my Bool $seen-dot = False; for @a { if $seen-dot { die "Only one decimal dot allowed" if $_ eq '.'; $denom *= $base; $v += $_ / $denom } elsif $_ eq '.' { $seen-dot = True; } else { $v = $v * $base + $_; } } $v; } proto sub parse-base($, $, *%) {*} multi sub parse-base(Str:D $str, Int:D $radix) { $str.parse-base($radix) } proto sub substr($, $?, $?, *%) {*} multi sub substr(str $s) { $s } multi sub substr(str $s, int $f) { nqp::substr($s,$f) } multi sub substr(str $s, int $f, int $c) { nqp::substr($s,$f,$c) } multi sub substr(\what --> Str:D) { what.substr } multi sub substr(\what, \from --> Str:D) { what.substr(from) } multi sub substr(\what, \from, \chars --> Str:D) { what.substr(from,chars) } proto sub substr-rw($, $?, $?, *%) is rw {*} multi sub substr-rw(\what) is rw { what.substr-rw } multi sub substr-rw(\what, \from) is rw { what.substr-rw(from) } multi sub substr-rw(\what, \from, \chars) is rw { what.substr-rw(from,chars) } multi sub infix:(Str:D $a, Str:D $b --> Bool:D) { nqp::hllbool( nqp::unless( nqp::eqaddr($a,$b), nqp::eqaddr($a.WHAT,$b.WHAT) && nqp::iseq_s($a,$b) ) ) } proto sub samemark($, $, *%) {*} multi sub samemark($s, $pat --> Str:D) { $s.samemark($pat) } #line 1 SETTING::src/core.c/Capture.rakumod my class Capture { # declared in BOOTSTRAP # class Capture is Any # has @!list; # positional parameters # has %!hash; # named parameters method from-args(|c) { c } method item() is raw { my $ = self } submethod BUILD(:@list, :%hash --> Nil) { my Int:D $elems = @list.elems; # force reification of all nqp::bindattr(self, Capture, '@!list', nqp::getattr(nqp::decont(@list.list), List, '$!reified')) if $elems; my Mu $source-hash := nqp::getattr(nqp::decont(%hash), Map, '$!storage'); nqp::bindattr(self,Capture,'%!hash', $source-hash) if nqp::ishash($source-hash); } multi method WHICH (Capture:D: --> ValueObjAt:D) { my Mu $WHICH := nqp::list_s(nqp::eqaddr(self.WHAT,Capture) ?? 'Capture' !! nqp::unbox_s(self.^name)); if nqp::isconcrete(@!list) && nqp::elems(@!list) { nqp::push_s($WHICH, '|'); my Mu $list := nqp::clone(@!list); nqp::while( nqp::elems($list), nqp::stmts( (my Mu \value = nqp::shift($list)), nqp::push_s($WHICH, '('), nqp::push_s($WHICH, nqp::unbox_s(value.VAR.WHICH)), nqp::push_s($WHICH, ')') ) ); } if nqp::isconcrete(%!hash) && nqp::elems(%!hash) { nqp::push_s($WHICH, '|'); for nqp::hllize(%!hash).keys.sort -> str \key { nqp::push_s($WHICH, key); nqp::push_s($WHICH, '('); nqp::push_s($WHICH, nqp::unbox_s(nqp::atkey(%!hash,key).WHICH)); nqp::push_s($WHICH, ')'); } } nqp::box_s(nqp::join('',$WHICH),ValueObjAt) } multi method AT-KEY(Capture:D: Str() $key) is raw { nqp::isconcrete(%!hash) ?? nqp::ifnull(nqp::atkey(%!hash,$key), Nil) !! Nil } sub OUT_OF_RANGE(int $got) { X::OutOfRange.new( :what($*INDEX // 'Index'), :$got, :range<0..^Inf> ).Failure } multi method AT-POS(Capture:D: uint $pos) is raw { nqp::ifnull(nqp::atpos(@!list,$pos),Nil) } multi method AT-POS(Capture:D: Int() $pos) is raw { nqp::islt_i($pos,0) ?? OUT_OF_RANGE($pos) !! nqp::ifnull(nqp::atpos(@!list,$pos),Nil) } method hash(Capture:D:) { (nqp::isconcrete(%!hash) && nqp::elems(%!hash)) ?? nqp::p6bindattrinvres(nqp::create(Map),Map,'$!storage',%!hash) !! nqp::create(Map) } multi method EXISTS-KEY(Capture:D: Str() $key) { nqp::hllbool( nqp::isconcrete(%!hash) && nqp::existskey(%!hash, $key) ) } multi method EXISTS-POS(Capture:D: uint $pos) { nqp::hllbool( nqp::isconcrete(@!list) && nqp::existspos(@!list, $pos) ) } multi method EXISTS-POS(Capture:D: Int() $pos) { nqp::islt_i($pos,0) ?? OUT_OF_RANGE($pos) !! nqp::hllbool( nqp::isconcrete(@!list) && nqp::existspos(@!list, $pos) ) } method list(Capture:D:) { nqp::isconcrete(@!list) && nqp::elems(@!list) ?? nqp::p6bindattrinvres(nqp::create(List),List,'$!reified',@!list) !! nqp::create(List) } method elems(Capture:D:) { nqp::isconcrete(@!list) && nqp::elems(@!list) } multi method Str(Capture:D:) { my Mu $str := nqp::list_s(); if @!list { my Mu $iter := nqp::iterator(@!list); nqp::push_s($str, nqp::unbox_s(nqp::shift($iter).Str)) while $iter; } if %!hash { my Mu $iter := nqp::iterator(%!hash); while $iter { my $kv := nqp::shift($iter); nqp::push_s($str, nqp::unbox_s((nqp::p6box_s(nqp::iterkey_s($kv)) => nqp::iterval($kv).Str).Str)); } } nqp::p6box_s(nqp::join(' ', $str)) } multi method gist(Capture:D:) { self.Capture::raku } multi method raku(Capture:D:) { my int $has-list = nqp::isconcrete(@!list) && nqp::elems(@!list); my int $has-hash = nqp::isconcrete(%!hash) && nqp::elems(%!hash); my Mu $raku := nqp::list_s(); if nqp::eqaddr(self.WHAT, Capture) { nqp::push_s($raku, '\('); if $has-list { my $positionals := nqp::clone(@!list); nqp::push_s( $raku, nqp::unbox_s(nqp::shift($positionals).raku(:arglist)) ); nqp::while( nqp::elems($positionals), nqp::push_s( $raku, nqp::concat( ', ', nqp::unbox_s(nqp::shift($positionals).raku(:arglist)) ) ) ); nqp::push_s($raku, ', ') if $has-hash; } if $has-hash { nqp::push_s($raku, nqp::unbox_s(self.Capture::hash.sort.map(*.raku).join(', '))); } nqp::push_s($raku, ')'); } else { nqp::push_s($raku, nqp::concat(nqp::unbox_s(self.^name), '.new')); if $has-list || $has-hash { nqp::push_s($raku, '('); if $has-list { my $positionals := nqp::clone(@!list); nqp::push_s($raku, 'list => ('); nqp::push_s( $raku, nqp::unbox_s(nqp::shift($positionals).raku(:arglist)) ); nqp::while( nqp::elems($positionals), nqp::push_s( $raku, nqp::concat( ', ', nqp::unbox_s(nqp::shift($positionals).raku(:arglist)) ) ) ); nqp::push_s($raku, ')'); nqp::push_s($raku, ', ') if $has-hash; } if $has-hash { nqp::push_s($raku, 'hash => {'); nqp::push_s($raku, nqp::unbox_s(self.Capture::hash.sort.map(*.raku).join(', '))); nqp::push_s($raku, '}'); } nqp::push_s($raku, ')'); } } nqp::p6box_s(nqp::join('', $raku)) } multi method Bool(Capture:D:) { nqp::hllbool( (nqp::isconcrete(@!list) && nqp::elems(@!list)) || (nqp::isconcrete(%!hash) && nqp::elems(%!hash)) ) } method Capture(Capture:D:) { self } multi method Numeric(Capture:D:) { self.Capture::elems } method FLATTENABLE_LIST() is raw is implementation-detail { nqp::isconcrete(@!list) ?? @!list !! nqp::list } method FLATTENABLE_HASH() is raw is implementation-detail { nqp::isconcrete(%!hash) ?? %!hash !! nqp::hash } multi method keys(Capture:D:) { (self.Capture::list.keys, self.Capture::hash.keys).flat; } multi method kv(Capture:D:) { (self.Capture::list.kv, self.Capture::hash.kv).flat; } multi method values(Capture:D:) { (self.Capture::list.values, self.Capture::hash.values).flat; } multi method pairs(Capture:D:) { (self.Capture::list.pairs, self.Capture::hash.pairs).flat; } multi method antipairs(Capture:D:) { (self.Capture::list.antipairs, self.Capture::hash.antipairs).flat; } } multi sub infix:(Capture:D $a, Capture:D $b --> Bool:D) { nqp::hllbool( nqp::eqaddr($a,$b) || (nqp::eqaddr($a.WHAT,$b.WHAT) && $a.Capture::list eqv $b.Capture::list && $a.Capture::hash eqv $b.Capture::hash) ) } #line 1 SETTING::src/core.c/IterationBuffer.rakumod # IterationBuffer is used when the list/iteration implementation needs a # lightweight way to store/transmit values. Replaces the use of nqp::list in # the list guts, which is an impediment to introspectability and also to # allowing the implementation of custom iterators (though in reality most # folks won't implement Iterator directly, but instead use gather/take or lazy # loops). It doesn't make Scalar containers, and only supports mutation # through implementing push and BIND-POS, and access by implementing AT-POS. # Hot-paths are free to use the nqp:: op set directly on this, and do things # outside the scope of the method API it exposes. This type is engineered for # performance over friendliness, and should not be encountered in normal use # of Raku. Do NOT add any checks and validation to methods in here. They # need to remain trivially inlinable for performance reasons. my class IterationBuffer { multi method new(IterationBuffer:U: Iterable:D \iterable) { iterable.iterator.push-all(my \buffer := nqp::create(self)); buffer } method clear(IterationBuffer:D: --> Nil) { nqp::setelems(self, 0) } method elems() { nqp::elems(self) } method push(Mu \value) { nqp::push(self, value) } method append(IterationBuffer:D $buffer) { nqp::splice(self,$buffer,nqp::elems(self),0) } method unshift(Mu \value) { nqp::unshift(self, value) } method prepend(IterationBuffer:D $buffer) { nqp::splice(self,$buffer,0,0) } proto method AT-POS(|) {*} multi method AT-POS(IterationBuffer:D: int $pos) is raw { nqp::atpos(self, $pos) } multi method AT-POS(IterationBuffer:D: Int:D $pos) is raw { nqp::atpos(self, $pos) } proto method BIND-POS(|) {*} multi method BIND-POS(IterationBuffer:D: int $pos, Mu \value) { nqp::bindpos(self, $pos, value) } multi method BIND-POS(IterationBuffer:D: Int:D $pos, Mu \value) { nqp::bindpos(self, $pos, value) } # For maintainability mainly, and possibly for creating smaller, more # inlinable candidates method Slip(IterationBuffer:D:) { nqp::p6bindattrinvres(nqp::create(Slip),List,'$!reified',self) } # For maintainability mainly, and possibly for creating smaller, more # inlinable candidates method List(IterationBuffer:D:) { nqp::p6bindattrinvres(nqp::create(List),List,'$!reified',self) } # For maintainability mainly, and possibly for creating smaller, more # inlinable candidates method Seq(IterationBuffer:D:) { Seq.new(Rakudo::Iterator.ReifiedList(self)) } # For maintainability mainly, and possibly for creating smaller, more # inlinable candidates method iterator(IterationBuffer:D:) { Rakudo::Iterator.ReifiedList(self) } # For core debugging purposes only: basically warp the IterationBuffer # into a full-fledged List and .raku that. We don't care that it will # not round-trip. multi method raku(IterationBuffer:D:) { self.List.raku ~ '.IterationBuffer' } } #line 1 SETTING::src/core.c/atomicops.rakumod #== Atomics available on all backends ========================================== #-- fetching a value atomically proto sub atomic-fetch($, *%) {*} multi sub atomic-fetch($source is rw) { nqp::atomicload($source) } proto sub prefix:<⚛>($, *%) {*} multi sub prefix:<⚛>($source is rw) { nqp::atomicload($source) } #-- assigning a value atomically proto sub atomic-assign($, $, *%) {*} multi sub atomic-assign($target is rw, \value) { nqp::atomicstore($target, value) } #-- atomic compare and swap proto sub cas(Mu $, Mu $, Mu $?, *%) {*} multi sub cas(Mu $target is rw, Mu \expected, Mu \value) { nqp::cas($target, expected, value) } multi sub cas(Mu $target is rw, &code) { my $current := nqp::atomicload($target); nqp::until( nqp::stmts( (my $updated := code($current)), (my $seen := nqp::cas($target, $current, $updated)), nqp::eqaddr($seen, $current) ), $current := $seen ); $updated } #== Native integer atomics only available on MoarVM ============================ my native atomicint is repr('P6int') is Int is ctype('atomic') { } #-- fetching a native integer value atomically multi sub atomic-fetch(atomicint $source is rw) { nqp::atomicload_i($source) } multi sub prefix:<⚛>(atomicint $source is rw) { nqp::atomicload_i($source) } #-- assigning a native integer value atomically multi sub atomic-assign(atomicint $target is rw, int $value) { nqp::atomicstore_i($target, $value) } multi sub atomic-assign(atomicint $target is rw, Int:D $value) { nqp::atomicstore_i($target, $value) } multi sub atomic-assign(atomicint $target is rw, $value) { nqp::atomicstore_i($target, $value.Int) } proto sub infix:<⚛=>($, $, *%) {*} multi sub infix:<⚛=>($target is rw, \value) { nqp::atomicstore($target, value) } multi sub infix:<⚛=>(atomicint $target is rw, int $value) { nqp::atomicstore_i($target, $value) } multi sub infix:<⚛=>(atomicint $target is rw, Int:D $value) { nqp::atomicstore_i($target, $value) } multi sub infix:<⚛=>(atomicint $target is rw, $value) { nqp::atomicstore_i($target, $value.Int) } #-- atomically fetch native integer value and increment it proto sub atomic-fetch-inc($, *%) {*} multi sub atomic-fetch-inc(atomicint $target is rw --> atomicint) { nqp::atomicinc_i($target) } proto sub postfix:<⚛++>($, *%) {*} multi sub postfix:<⚛++>(atomicint $target is rw --> atomicint) { nqp::atomicinc_i($target) } #-- atomically increment native integer value and fetch it proto sub atomic-inc-fetch($, *%) {*} multi sub atomic-inc-fetch(atomicint $target is rw --> atomicint) { my atomicint $ = nqp::atomicinc_i($target) + 1 } proto sub prefix:<++⚛>($, *%) {*} multi sub prefix:<++⚛>(atomicint $target is rw --> atomicint) { my atomicint $ = nqp::atomicinc_i($target) + 1 } #-- atomically fetch native integer value and decrement it proto sub atomic-fetch-dec($, *%) {*} multi sub atomic-fetch-dec(atomicint $target is rw --> atomicint) { nqp::atomicdec_i($target) } proto sub postfix:<⚛-->($, *%) {*} multi sub postfix:<⚛-->(atomicint $target is rw --> atomicint) { nqp::atomicdec_i($target) } #-- atomically decrement native integer value and fetch it proto sub atomic-dec-fetch($, *%) {*} multi sub atomic-dec-fetch(atomicint $target is rw --> atomicint) { my atomicint $ = nqp::atomicdec_i($target) - 1 } proto sub prefix:<--⚛>($, *%) {*} multi sub prefix:<--⚛>(atomicint $target is rw --> atomicint) { my atomicint $ = nqp::atomicdec_i($target) - 1 } #-- atomically fetch native integer value and then add given value to it proto sub atomic-fetch-add($, $, *%) {*} multi sub atomic-fetch-add(atomicint $target is rw, int $add --> atomicint) { nqp::atomicadd_i($target, $add) } multi sub atomic-fetch-add(atomicint $target is rw, Int:D $add --> atomicint) { nqp::atomicadd_i($target, $add) } multi sub atomic-fetch-add(atomicint $target is rw, $add --> atomicint) { nqp::atomicadd_i($target, $add.Int) } #-- atomically add given native integer value to value and return that proto sub atomic-add-fetch($, $, *%) {*} multi sub atomic-add-fetch(atomicint $target is rw, int $add --> atomicint) { my atomicint $ = nqp::atomicadd_i($target, $add) + $add } multi sub atomic-add-fetch(atomicint $target is rw, Int:D $add --> atomicint) { my atomicint $ = nqp::atomicadd_i($target, $add) + $add } multi sub atomic-add-fetch(atomicint $target is rw, $add --> atomicint) { my int $add-int = $add.Int; my atomicint $ = nqp::atomicadd_i($target, $add-int) + $add-int } proto sub infix:<⚛+=>($, $, *%) {*} multi sub infix:<⚛+=>(atomicint $target is rw, int $add --> atomicint) { my atomicint $ = nqp::atomicadd_i($target, $add) + $add } multi sub infix:<⚛+=>(atomicint $target is rw, Int:D $add --> atomicint) { my atomicint $ = nqp::atomicadd_i($target, $add) + $add } multi sub infix:<⚛+=>(atomicint $target is rw, $add --> atomicint) { my int $add-int = $add.Int; my atomicint $ = nqp::atomicadd_i($target, $add-int) + $add-int } #-- atomically fetch native integer value and then subtract given value from it proto sub atomic-fetch-sub($, $, *%) {*} multi sub atomic-fetch-sub(atomicint $target is rw, int $add --> atomicint) { nqp::atomicadd_i($target, nqp::neg_i($add)) } multi sub atomic-fetch-sub(atomicint $target is rw, Int:D $add --> atomicint) { nqp::atomicadd_i($target, nqp::neg_i($add)) } multi sub atomic-fetch-sub(atomicint $target is rw, $add --> atomicint) { nqp::atomicadd_i($target, nqp::neg_i($add.Int)) } #-- atomically subtract given native integer value from value and return that proto sub atomic-sub-fetch($, $, *%) {*} multi sub atomic-sub-fetch(atomicint $target is rw, int $add --> atomicint) { my atomicint $ = nqp::atomicadd_i($target, nqp::neg_i($add)) - $add } multi sub atomic-sub-fetch(atomicint $target is rw, Int:D $add --> atomicint) { my atomicint $ = nqp::atomicadd_i($target, nqp::neg_i($add)) - $add } multi sub atomic-sub-fetch(atomicint $target is rw, $add --> atomicint) { my int $add-int = nqp::neg_i($add.Int); my atomicint $ = nqp::atomicadd_i($target, $add-int) + $add-int } proto sub infix:<⚛-=>($, $, *%) {*} multi sub infix:<⚛-=>(atomicint $target is rw, int $add --> atomicint) { my atomicint $ = nqp::atomicadd_i($target, nqp::neg_i($add)) - $add } multi sub infix:<⚛-=>(atomicint $target is rw, Int:D $add --> atomicint) { my atomicint $ = nqp::atomicadd_i($target, nqp::neg_i($add)) - $add } multi sub infix:<⚛-=>(atomicint $target is rw, $add --> atomicint) { my int $add-int = nqp::neg_i($add.Int); my atomicint $ = nqp::atomicadd_i($target, $add-int) + $add-int } my constant &infix:<⚛−=> := &infix:<⚛-=>; #-- provide full barrier semantics proto sub full-barrier(*%) {*} multi sub full-barrier(--> Nil) { nqp::barrierfull() } #-- atomic compare and swap a native integer multi sub cas(atomicint $target is rw, int $expected, int $value) { nqp::cas_i($target, $expected, $value) } multi sub cas(atomicint $target is rw, Int:D $expected, Int:D $value) { nqp::cas_i($target, $expected, $value) } multi sub cas(atomicint $target is rw, $expected, $value) { nqp::cas_i($target, $expected.Int, $value.Int) } multi sub cas(atomicint $target is rw, &code) { my int $current = nqp::atomicload_i($target); loop { my int $updated = code($current); my int $seen = nqp::cas_i($target, $current, $updated); return $updated if $seen == $current; $current = $seen; } } #line 1 SETTING::src/core.c/Sequence.rakumod # A Sequence represents anything that can lazily produce a sequence of values. # There are various concrete implementations of Sequence, the most common # being Seq, which represents a sequentially produced sequence. # # Sequences are born in a state where iterating them will consume the values. # However, calling .cache will return a List that will lazily reify to the # values in the Sequence. The List is memoized, so that subsequent calls to # .cache will always return the same List (safe as List is immutable). More # than one call to .iterator throws an exception (and calling .cache calls the # .iterator method the first time also). The memoization can be avoided by # asking very specifically for the Seq to be coerced to a List (using .List # or .list), a Slip (.Slip) or an Array (.Array). # # The actual memoization functionality is factored out into a role, # PositionalBindFailover, which is used by the binder to identify types that, # on failure to bind to an @-sigilled thing, can have .cache called on them # and get memoization semantics. This decouples this functionality from the # Sequence role, so other user-defined types can get access to this # functionality. my role PositionalBindFailover { has $!list; method cache() { nqp::isconcrete($!list) ?? $!list !! ($!list := List.from-iterator(self.iterator)) } multi method list(::?CLASS:D:) { nqp::isconcrete($!list) ?? $!list !! List.from-iterator(self.iterator) } method iterator() { die "Method 'iterator' must be implemented by " ~ self.^name ~ " because it is required by roles: PositionalBindFailover"; } } nqp::p6configposbindfailover(Positional, PositionalBindFailover); # Binder Routine.'!configure_positional_bind_failover'(Positional, PositionalBindFailover); # Multi-dispatch my role Sequence does PositionalBindFailover { multi method Array(::?CLASS:D:) { Array.from-iterator(self.iterator) } multi method List(::?CLASS:D:) { self.list.List } multi method Slip(::?CLASS:D:) { self.list.Slip } multi method Str(::?CLASS:D:) { self.cache.Str } multi method Stringy(::?CLASS:D:) { self.cache.Stringy } method Numeric(::?CLASS:D:) { self.cache.elems } multi method AT-POS(::?CLASS:D: uint $idx) is raw { self.cache.AT-POS($idx) } multi method AT-POS(::?CLASS:D: Int:D $idx) is raw { self.cache.AT-POS($idx) } multi method EXISTS-POS(::?CLASS:D: uint $idx) { self.cache.EXISTS-POS($idx) } multi method EXISTS-POS(::?CLASS:D: Int:D $idx) { self.cache.EXISTS-POS($idx) } multi method eager(::?CLASS:D:) { List.from-iterator(self.iterator).eager } multi method fmt(Sequence:D: |c) { self.cache.fmt(|c) } multi method gist(::?CLASS:D:) { self.cache.gist } } #line 1 SETTING::src/core.c/Seq.rakumod my class X::Seq::Consumed { ... } my class X::Seq::NotIndexable { ... } my class Seq is Cool does Iterable does Sequence { # The underlying iterator that iterating this sequence will work its # way through. Can only be obtained once. has Iterator $!iter; # The only valid way to create a Seq directly is by giving it the # iterator it will consume and maybe memoize. proto method new(Seq: |) {*} multi method new(Seq: Iterator:D $iter) { nqp::p6bindattrinvres(nqp::create(self),Seq,'$!iter',nqp::decont($iter)) } # This candidate exists purely for being able to EVAL a .raku # representation of a Seq of which the iterator has already been taken, multi method new(Seq:) { nqp::create(self) } method iterator(Seq:D:) { nqp::if( nqp::isconcrete(my \iter = $!iter), nqp::stmts( ($!iter := Iterator), iter ), nqp::if( nqp::isconcrete($!list), $!list.iterator, X::Seq::Consumed.new.throw ) ) } multi method is-lazy(Seq:D:) { nqp::isconcrete($!iter) ?? $!iter.is-lazy !! nqp::isconcrete($!list) ?? $!list.is-lazy !! X::Seq::Consumed.new.throw } multi method Seq(Seq:D:) { self } method Capture() { self.List.Capture } method elems() { self.is-lazy ?? self.fail-iterator-cannot-be-lazy('.elems',"") !! nqp::isconcrete($!iter) && nqp::istype($!iter,PredictiveIterator) ?? $!iter.count-only !! self.cache.elems } method Numeric() { self.elems } method Int() { self.elems } method Bool(Seq:D:) { nqp::isconcrete($!iter) && nqp::istype($!iter,PredictiveIterator) ?? $!iter.bool-only !! self.cache.Bool } multi method raku(Seq:D \SELF:) { # If we don't have an iterator, someone grabbed it already; # Check for cached $!list; if that's missing too, we're consumed my $raku; if not $!iter.DEFINITE and not $!list.DEFINITE { # cannot call .cache on a Seq that's already been iterated, # so we need to produce a string that, when EVAL'd, reproduces # an already iterated Seq. # compare https://github.com/Raku/old-issue-tracker/issues/5124 $raku = self.^name ~ '.new()'; } else { $raku = self.cache.raku ~ '.Seq' } nqp::iscont(SELF) ?? '$(' ~ $raku ~ ')' !! $raku } method join(Seq:D: $separator = '' --> Str:D) { nqp::if( (my $iterator := self.iterator).is-lazy, '...', nqp::stmts( (my $strings := nqp::list_s), nqp::until( nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), nqp::push_s($strings,nqp::unbox_s( nqp::if( nqp::isconcrete($pulled) && nqp::istype($pulled,Str), $pulled, nqp::if( nqp::can($pulled,'Str'), $pulled.Str, nqp::box_s($pulled,Str) ) ) )) ), nqp::box_s(nqp::join(nqp::unbox_s($separator.Str),$strings),Str) ) ) } method reverse(--> Seq:D) is nodal { nqp::if( (my $iterator := self.iterator).is-lazy, self.fail-iterator-cannot-be-lazy('.reverse'), nqp::stmts( $iterator.push-all(my \buffer := nqp::create(IterationBuffer)), Seq.new: Rakudo::Iterator.ReifiedReverse(buffer, Mu) ) ) } method rotate(Int(Cool) $rotate = 1 --> Seq:D) is nodal { nqp::if( (my $iterator := self.iterator).is-lazy, self.fail-iterator-cannot-be-lazy('.rotate'), nqp::if( $rotate, Seq.new( nqp::if( $rotate > 0, Rakudo::Iterator.Rotate($rotate, $iterator), nqp::stmts( $iterator.push-all(my \buffer := nqp::create(IterationBuffer)), Rakudo::Iterator.ReifiedRotate($rotate, buffer, Mu) ) )), self ) ) } multi method slice(Seq:D: Iterable:D \iterable --> Seq:D) { Seq.new( Rakudo::Iterator.MonotonicIndexes( self.iterator, iterable.iterator, 0, -> $index, $next { die "Provided index $index, which is lower than $next"; } ) ) } multi method slice(Seq:D: *@indices --> Seq:D) { self.slice(@indices) } method sink(--> Nil) { nqp::if( nqp::isconcrete($!iter), nqp::stmts( $!iter.sink-all, ($!iter := Iterator) ), nqp::if( nqp::isconcrete($!list), $!list.sink ) ) } # This method is mainly called from Actions.nqp proto method from-loop(|) {*} multi method from-loop(&body, :$label) { Seq.new: Rakudo::Iterator.Loop(&body, $label) } multi method from-loop(&body, &cond, :$repeat!, :$label) { Seq.new: $repeat ?? Rakudo::Iterator.RepeatLoop(&body, &cond, $label) !! Rakudo::Iterator.WhileLoop(&body, &cond, $label) } multi method from-loop(&body, &cond, :$label) { Seq.new: Rakudo::Iterator.WhileLoop(&body, &cond, $label) } multi method from-loop(&body, &cond, &afterwards, :$label) { Seq.new: Rakudo::Iterator.CStyleLoop(&body, &cond, &afterwards, $label) } multi method ACCEPTS(Seq:D: Iterable:D \iterable --> Bool:D) { nqp::if( (my \riter := self.iterator).is-lazy, False, nqp::if( (my \liter := iterable.iterator).is-lazy, False, nqp::stmts( nqp::until( nqp::eqaddr((my \left := liter.pull-one),IterationEnd), nqp::if( nqp::eqaddr((my \right := riter.pull-one),IterationEnd) || nqp::not_i(right.ACCEPTS(left)), (return False) ) ), nqp::hllbool(nqp::eqaddr(riter.pull-one,IterationEnd)) ) ) ) } } sub GATHER(&block) is implementation-detail { Seq.new(Rakudo::Iterator.Gather(&block)) } #line 1 SETTING::src/core.c/Rakudo/Internals/HyperWorkBatch.rakumod # A batch of work sent to a worker in a hyper or race operation. It is an # Iterable, and iterates to the items in the batch. This is so that it can be # easily processed in terms of (non-hyper) Iterable implementations. my class Rakudo::Internals::HyperWorkBatch does Iterable { # The items in the batch. has IterationBuffer $.items; # Sequence number of the batch, starting from zero. has int $.sequence-number; # Is this the first batch that was produced at the last fork point or the # last batch that the fork point will produce? has Bool $.first; has Bool $.last; method !SET-SELF(\sequence-number, \items, \first, \last) { $!sequence-number = sequence-number; $!items := items; $!first = first.Bool; $!last = last.Bool; self } method new(\sn,\it,\f,\l) { nqp::create(self)!SET-SELF(sn,it,f,l) } # Iterator for a HyperWorkBatch; my class HyperWorkBatchIterator does Iterator { has $!items; has int $!i; has int $!n; method !SET-SELF(\items) { $!items := items; $!i = -1; $!n = nqp::elems(items); self } method new(\items) { nqp::create(self)!SET-SELF(items) } method pull-one() { ++$!i < $!n ?? nqp::atpos($!items, $!i) !! IterationEnd } } method iterator(--> Iterator) { HyperWorkBatchIterator.new($!items) } method replace-with(IterationBuffer $ib --> Nil) { $!items := $ib; } } #line 1 SETTING::src/core.c/Rakudo/Internals/HyperWorkStage.rakumod # Work stages are individual steps in a hyper/race pipeline. They are chained # in a linked list by the source attribute. Roles for different kinds of stages # follow. my role Rakudo::Internals::HyperWorkStage { has Rakudo::Internals::HyperWorkStage $.source; } # A HyperBatcher stage produces batches of work to do. It will typically be # created with an Iterable of some kind, and divide up the work into batches # of the appropriate size. Such a stage always lives at the start of a piece # of parallel processing pipeline. my role Rakudo::Internals::HyperBatcher does Rakudo::Internals::HyperWorkStage { method produce-batch(int $batch-size --> Rakudo::Internals::HyperWorkBatch) { ... } } # A HyperProcessor performs some operation in a work batch, updating it to # reflect the results of the operation. my role Rakudo::Internals::HyperProcessor does Rakudo::Internals::HyperWorkStage { method process-batch(Rakudo::Internals::HyperWorkBatch $batch --> Nil) { ... } } # A HyperRebatcher is given batches, and may produce zero or more batches as a # result. The produced batches will be passed on to the next pipeline stages. # This is intended only for steps that need to look across multiple batches, # but that work in a "streaming" way rather than being a full bottleneck in # the pipeline. A HyperRebatcher should produce one output batch for each # input batch it gets (though may produce no batches on one call, and two on # the next, for example). my role Rakudo::Internals::HyperRebatcher does Rakudo::Internals::HyperWorkStage { method rebatch(Rakudo::Internals::HyperWorkBatch $batch --> List) { ... } } # Comes at the end of a pipeline, or a stage in a multi-stage pipeline (that # is, one with a step in it where all results are needed). The batch-used # method should be called whenever a batch passed to consume-batch has been # used. This allows for backpressure control: a sequential iterator at the # end of a parallel pipeline can choose to call batch-used only at the point # when the downstream iterator has actually eaten all the values in a batch. my role Rakudo::Internals::HyperJoiner does Rakudo::Internals::HyperWorkStage { has $!batch-used-channel; method consume-batch(Rakudo::Internals::HyperWorkBatch $batch --> Nil) { ... } method consume-error(Exception \e) { ... } method batch-used(--> Nil) { $!batch-used-channel.send(True); } method SET-BATCH-USED-CHANNEL($!batch-used-channel) {} } #line 1 SETTING::src/core.c/Rakudo/Internals/HyperPipeline.rakumod # Takes a linked list of pipeline stages and assembles them into a pipeline. # Given a pipeline must end with a HyperJoiner, it expects to be passed # something of this type. my class Rakudo::Internals::HyperPipeline { method start(Rakudo::Internals::HyperJoiner $stage, HyperConfiguration $config) { # Create channel that the last non-join operation in the pipeline will # put its results into, and start a worker to handle the channel. my $cur-dest-channel = Channel.new; self!join-worker($stage, $cur-dest-channel); # Create a channel that will signal we're ready for more batches, # and set join stage to send on it when batch-used is called. my $ready-channel = Channel.new; $stage.SET-BATCH-USED-CHANNEL($ready-channel); # Go through the rest of the stages. my $cur-stage = $stage.source; my @processors; while $cur-stage { my $next-stage = $cur-stage.source; given $cur-stage { when Rakudo::Internals::HyperProcessor { # Unshift them so a sequence will be in application order. unshift @processors, $_; } when Rakudo::Internals::HyperBatcher { if $next-stage { die "A HyperBatcher may only be at the pipeline start"; } $cur-dest-channel = self!maybe-processor-workers: [@processors], $cur-dest-channel, $config.degree; @processors = (); self!batch-worker($cur-stage, $cur-dest-channel, $ready-channel, $config.batch); } default { die "Unrecognized hyper pipeline stage " ~ .^name(); } } $cur-stage = $next-stage; } # Set off $degree batches. $ready-channel.send(True) for ^$config.degree; } method !batch-worker(Rakudo::Internals::HyperBatcher $stage, Channel $dest-channel, Channel $ready-channel, int $size) { start { my $AWAITER := $*AWAITER; loop { CATCH { default { $dest-channel.fail($_); } } $AWAITER.await($ready-channel); my $batch := $stage.produce-batch($size); $dest-channel.send($batch); last if $batch.last; } } } method !maybe-processor-workers(@processors, Channel $dest-channel, Int:D $degree) { return $dest-channel unless @processors; my $source-channel := Channel.new; for ^$degree { start { CATCH { when X::Channel::ReceiveOnClosed { $dest-channel.close; } default { $dest-channel.fail($_); } } my $AWAITER := $*AWAITER; loop { my $batch := $AWAITER.await($source-channel); for @processors { .process-batch($batch); } $dest-channel.send($batch); } } } return $source-channel; } method !join-worker(Rakudo::Internals::HyperJoiner $stage, Channel $source) { start { CATCH { when X::Channel::ReceiveOnClosed { # We got everything; quietly exit the start block. } default { $stage.consume-error($_); CATCH { default { # Error handling code blew up; let the scheduler's # error handler do it, which will typically bring # the program down. Should never get here unless # we've some bug in a joiner implementation. $*SCHEDULER.handle_uncaught($_); } } } } my $AWAITER := $*AWAITER; loop { $stage.consume-batch($AWAITER.await($source)); } } } } #line 1 SETTING::src/core.c/Rakudo/Internals/HyperIteratorBatcher.rakumod # Batches values sourced from an iterator, producing a work batch from them. my class Rakudo::Internals::HyperIteratorBatcher does Rakudo::Internals::HyperBatcher { my constant NO_LOOKAHEAD = Mu.CREATE; has Iterator $!iterator; has $!lookahead; has int $!seq-num; submethod BUILD(Iterator :$iterator!) { $!iterator := $iterator; $!lookahead := NO_LOOKAHEAD; } method produce-batch(int $batch-size --> Rakudo::Internals::HyperWorkBatch) { my $items := nqp::create(IterationBuffer); nqp::unless( (my int $first = nqp::eqaddr($!lookahead,NO_LOOKAHEAD)), nqp::push($items,$!lookahead) # not first, get from previous ); nqp::unless( (my int $last = nqp::eqaddr( $!iterator.push-exactly( $items, nqp::sub_i($batch-size,nqp::not_i($first)) ), IterationEnd )), ($last = nqp::eqaddr( # not last batch ($!lookahead := $!iterator.pull-one), IterationEnd # but is in this case )) ); Rakudo::Internals::HyperWorkBatch.new($!seq-num++,$items,$first,$last) } } #line 1 SETTING::src/core.c/Rakudo/Internals/HyperToIterator.rakumod my class Backtrace { ... } my role X::HyperRace::Died { has $.start-backtrace; multi method gist(::?CLASS:D:) { "A worker in a parallel iteration (hyper or race) initiated here:\n" ~ ((try $!start-backtrace ~ "\n") // '') ~ "Died at:\n" ~ callsame().indent(4) } } my class Rakudo::Internals::HyperToIterator does Rakudo::Internals::HyperJoiner does Iterator { has int $!seen-last; has int $!offset; has $!batches; has $!waiting; has $!current-items; my constant EMPTY_BUFFER = nqp::create(IterationBuffer); submethod TWEAK() { $!batches := Channel.new; $!waiting := nqp::list; $!current-items := EMPTY_BUFFER; } method consume-batch(Rakudo::Internals::HyperWorkBatch $batch --> Nil) { nqp::bindpos( # store the batch at its place $!waiting, nqp::sub_i($batch.sequence-number,$!offset), $batch ); nqp::until( # feed valid batches in order nqp::isnull(nqp::atpos($!waiting,0)), nqp::stmts( $!batches.send(nqp::shift($!waiting)), ++$!offset ) ); $!seen-last = 1 # set flag we've seen last one if $batch.last; $!batches.close # close channel if we're done if $!seen-last && nqp::not_i(nqp::elems($!waiting)); } method consume-error(Exception $e --> Nil) { $!batches.fail($e); } method pull-one() is raw { until nqp::elems($!current-items) { # handles empty batches CATCH { when X::Channel::ReceiveOnClosed { return IterationEnd; } ($_ but X::HyperRace::Died(Backtrace.new(5))).rethrow unless nqp::istype($_, X::HyperRace::Died); } $!current-items := $!batches.receive.items; self.batch-used(); } nqp::shift($!current-items) } method skip-at-least(int $skipping) { my int $toskip = $skipping; while $toskip { CATCH { when X::Channel::ReceiveOnClosed { return 0; } ($_ but X::HyperRace::Died(Backtrace.new(5))).rethrow unless nqp::istype($_, X::HyperRace::Died); } if nqp::isge_i(nqp::elems($!current-items),$toskip) { nqp::splice($!current-items,EMPTY_BUFFER,0,$toskip); return 1; } $toskip = nqp::sub_i($toskip,nqp::elems($!current-items)); $!current-items := $!batches.receive.items; self.batch-used(); } 0 } method push-all(\target) { loop { CATCH { when X::Channel::ReceiveOnClosed { return IterationEnd; } ($_ but X::HyperRace::Died(Backtrace.new(5))).rethrow unless nqp::istype($_, X::HyperRace::Died); } target.append($!current-items); $!current-items := $!batches.receive.items; self.batch-used(); } } } #line 1 SETTING::src/core.c/Rakudo/Internals/RaceToIterator.rakumod my class Rakudo::Internals::RaceToIterator does Rakudo::Internals::HyperJoiner does Iterator { has Channel $.batches .= new; has int $!last-target = -1; has int $!batches-seen = 0; method consume-batch(Rakudo::Internals::HyperWorkBatch $batch --> Nil) { $!batches.send($batch); ++$!batches-seen; if $batch.last { $!last-target = $batch.sequence-number; } if $!last-target >= 0 && $!batches-seen == $!last-target + 1 { $!batches.close; } } method consume-error(Exception $e --> Nil) { $!batches.fail($e); } my constant EMPTY_BUFFER = IterationBuffer.CREATE; has IterationBuffer $!current-items = EMPTY_BUFFER; method pull-one() { until nqp::elems(nqp::decont($!current-items)) { # Handles empty batches CATCH { when X::Channel::ReceiveOnClosed { return IterationEnd; } unless nqp::istype($_, X::HyperRace::Died) { ($_ but X::HyperRace::Died(Backtrace.new(5))).rethrow } } my $batch = $!batches.receive; self.batch-used(); $!current-items = $batch.items; } nqp::shift(nqp::decont($!current-items)) } } #line 1 SETTING::src/core.c/Rakudo/Internals/HyperRaceSharedImpl.rakumod # Implementations shared between HyperSeq and RaceSeq. class Rakudo::Internals::HyperRaceSharedImpl { my class Grep does Rakudo::Internals::HyperProcessor { has $!matcher is built; method process-batch(Rakudo::Internals::HyperWorkBatch $batch --> Nil) { my $items := $batch.items; my $elems := nqp::elems($items); my &matcher := nqp::istype($!matcher, Callable) ?? $!matcher.clone !! $!matcher; my int $from = -1; my int $to = -1; nqp::if( nqp::istype(&matcher,Callable) && nqp::not_i(nqp::istype(&matcher,Regex)), nqp::while( nqp::islt_i(++$from,$elems), nqp::if( matcher(my $item := nqp::atpos($items,$from)), nqp::bindpos($items,++$to,$item) ) ), nqp::while( nqp::islt_i(++$from,$elems), nqp::if( &matcher.ACCEPTS($item := nqp::atpos($items,$from)), nqp::bindpos($items,++$to,$item) ) ) ); nqp::setelems($items,nqp::add_i($to,1)) } } multi method grep(\hyper, $source, \matcher, %options) { if %options || nqp::istype(matcher, Code) && matcher.count > 1 { # Fall back to sequential grep for cases we can't yet handle self.rehyper(hyper, hyper.Any::grep(matcher, |%options)) } elsif nqp::istype(matcher,Block) && matcher.has-phasers { NYI('Phasers in hyper/race').throw; } else { hyper.bless: configuration => hyper.configuration, work-stage-head => Grep.new(:$source, :matcher(matcher)) } } my class Map does Rakudo::Internals::HyperProcessor { has &!mapper is built; method process-batch(Rakudo::Internals::HyperWorkBatch $batch --> Nil) { my $result := nqp::create(IterationBuffer); my $items := $batch.items; my int $n = $items.elems; my &mapper := &!mapper.clone; my int $i = -1; nqp::while( nqp::islt_i(++$i,$n), nqp::if( nqp::istype((my \val = mapper(nqp::atpos($items, $i))),Slip) && nqp::not_i(nqp::iscont(val)), val.iterator.push-all($result), nqp::push($result,val) ) ); $batch.replace-with($result) } } multi method map(\hyper, $source, &mapper, %options) { NYI('Phasers in hyper/race').throw if nqp::istype(&mapper,Block) && &mapper.has-phasers; if %options || &mapper.count > 1 { # Fall back to sequential map for cases we can't yet handle self.rehyper(hyper, hyper.Any::map(&mapper, |%options)) } else { hyper.bless: configuration => hyper.configuration, work-stage-head => Map.new(:$source, :&mapper) } } multi method invert(\hyper, $source) { hyper.bless: configuration => hyper.configuration, work-stage-head => Map.new(:$source,:mapper(-> Pair:D $p {$p.antipair})) } my class Sink does Rakudo::Internals::HyperJoiner { has Promise $.complete .= new; has int $!last-target = -1; has int $!batches-seen = 0; method consume-batch(Rakudo::Internals::HyperWorkBatch $batch --> Nil) { ++$!batches-seen; self.batch-used(); if $batch.last { $!last-target = $batch.sequence-number; } if $!last-target >= 0 && $!batches-seen == $!last-target + 1 { $!complete.keep(True); } } method consume-error(Exception $e --> Nil) { $!complete.break($e); } } method sink(\hyper, $source --> Nil) { if hyper.DEFINITE { CATCH { unless nqp::istype($_, X::HyperRace::Died) { ($_ but X::HyperRace::Died(Backtrace.new(5))).rethrow } } my $sink = Sink.new(:$source); Rakudo::Internals::HyperPipeline.start($sink, hyper.configuration); $*AWAITER.await($sink.complete); } } proto method rehyper($, $) {*} multi method rehyper(HyperSeq \hyper, \seq) { my \conf = hyper.configuration; seq.hyper(:degree(conf.degree), :batch(conf.batch)) } multi method rehyper(RaceSeq \hyper, \seq) { my \conf = hyper.configuration; seq.race(:degree(conf.degree), :batch(conf.batch)) } } #line 1 SETTING::src/core.c/ParallelSequence.rakumod # ParallelSequence role implements common functionality of HyperSeq and RaceSeq classes. my role ParallelSequence[::Joiner] does Iterable does Sequence { has HyperConfiguration $.configuration; has Rakudo::Internals::HyperWorkStage $!work-stage-head; has atomicint $!has-iterator; submethod BUILD(:$!configuration!, :$!work-stage-head!) { $!has-iterator = 0; } method iterator(::?CLASS:D: --> Iterator) { X::Seq::Consumed.new(:kind(::?CLASS)).throw if nqp::cas_i($!has-iterator, 0, 1); my $joiner := Joiner.new: source => $!work-stage-head; Rakudo::Internals::HyperPipeline.start($joiner, $!configuration); $joiner } method grep(::?CLASS:D: $matcher, *%options) { Rakudo::Internals::HyperRaceSharedImpl.grep: self, $!work-stage-head, $matcher, %options } method map(::?CLASS:D: $matcher, *%options) { Rakudo::Internals::HyperRaceSharedImpl.map: self, $!work-stage-head, $matcher, %options } method invert(::?CLASS:D:) { Rakudo::Internals::HyperRaceSharedImpl.invert(self, $!work-stage-head) } method hyper(::?CLASS:D:) {...} method race(::?CLASS:D:) {...} method is-lazy(--> False) { } multi method serial(::?CLASS:D:) { self.Seq } method sink(--> Nil) { Rakudo::Internals::HyperRaceSharedImpl.sink(self, $!work-stage-head) } } #line 1 SETTING::src/core.c/HyperSeq.rakumod # A HyperSeq performs batches of work in parallel, but retains order of output # values relative to input values. my class HyperSeq does ParallelSequence[Rakudo::Internals::HyperToIterator] { method hyper(HyperSeq:D:) { self } method race(HyperSeq:D:) { RaceSeq.new( :$!configuration, work-stage-head => Rakudo::Internals::HyperIteratorBatcher.new(:$.iterator)) } } #line 1 SETTING::src/core.c/RaceSeq.rakumod # A RaceSeq performs batches of work in parallel, and will deliver the results # in the order they are produced (so potentially disordering them relative to # the input). my class RaceSeq does ParallelSequence[Rakudo::Internals::RaceToIterator] { method hyper(RaceSeq:D:) { HyperSeq.new( :$!configuration, work-stage-head => Rakudo::Internals::HyperIteratorBatcher.new(:$.iterator)) } method race(RaceSeq:D:) { self } } #line 1 SETTING::src/core.c/Nil.rakumod my class Nil is Cool { # declared in BOOTSTRAP method !die(str $method) is hidden-from-backtrace { nqp::istype(self,Failure) ?? self.throw !! die "Use of Nil.$method not allowed"; } method !warn(str $method) is hidden-from-backtrace { nqp::istype(self,Failure) ?? self.throw !! warn "Use of Nil.$method coerced to empty string"; } # core functionality method sink(--> Nil) { } # required by RESTRICTED setting method FALLBACK(| --> Nil) { } method STORE(|) { X::Assignment::RO.new(:value(Nil)).throw } # interface methods that should silently return Nil multi method new(Nil: *@ --> Nil) { } multi method iterator(Nil:) { Rakudo::Iterator.OneValue(Nil) } method AT-POS(| --> Nil) { } method AT-KEY(| --> Nil) { } # method ACCEPTS(*@ --> Nil) { } # XXX spec says Nil, but makes install fail # interface methods that should fail multi method BIND-POS(Nil: |) { X::Bind.new(:target).Failure } multi method BIND-KEY(Nil: |) { X::Bind.new(:target).Failure } # interface methods that should throw multi method ASSIGN-POS(Nil: |) { self!die: 'ASSIGN-POS' } multi method ASSIGN-KEY(Nil: |) { self!die: 'ASSIGN-KEY' } multi method push(Nil: |) { self!die: 'push' } multi method append(Nil: |) { self!die: 'append' } multi method unshift(Nil: |) { self!die: 'unshift' } multi method prepend(Nil: |) { self!die: 'prepend' } # Cool methods that should just warn multi method chars(Nil: --> '') { self!warn: 'chars' } multi method chomp(Nil: --> '') { self!warn: 'chomp' } multi method chop(Nil: | --> '') { self!warn: 'chop' } multi method codes(Nil: | --> '') { self!warn: 'codes' } multi method comb(Nil: | --> '') { self!warn: 'comb' } multi method contains(Nil: | --> '') { self!warn: 'contains' } multi method ends-with(Nil: | --> '') { self!warn: 'ends-with' } multi method flip(Nil: --> '') { self!warn: 'flip' } multi method indent(Nil: | --> '') { self!warn: 'indent' } multi method index(Nil: | --> '') { self!warn: 'index' } multi method indices(Nil: | --> '') { self!warn: 'indices' } multi method lc(Nil: --> '') { self!warn: 'lc' } multi method lines(Nil: | --> '') { self!warn: 'lines' } multi method tc(Nil: --> '') { self!warn: 'tc' } multi method tclc(Nil: --> '') { self!warn: 'tclc' } multi method rindex(Nil: | --> '') { self!warn: 'rindex' } multi method starts-with(Nil: | --> '') { self!warn: 'starts-with' } multi method trans(Nil: | --> '') { self!warn: 'trans' } multi method substr(Nil: | --> '') { self!warn: 'substr' } multi method subst(Nil: | --> '') { self!warn: 'subst' } multi method substr-eq(Nil: | --> '') { self!warn: 'substr-eq' } multi method substr-rw(Nil: | --> '') { self!warn: 'substr-rw' } multi method wordcase(Nil: --> '') { self!warn: 'wordcase' } multi method words(Nil: | --> '') { self!warn: 'words' } multi method uc(Nil: --> '') { self!warn: 'uc' } # numeric coercions multi method Int(Nil: | --> 0) { warn "Use of Nil in numeric context" } method Numeric( --> 0) { warn "Use of Nil in numeric context" } # string coercions multi method gist(Nil: --> "Nil") { } multi method Str(Nil: --> "") { warn "Use of Nil in string context" } multi method raku(Nil: --> "Nil") { } # QuantHash coercions multi method Set(Nil:) { Set.new(Nil) } multi method SetHash(Nil:) { SetHash.new(Nil) } multi method Bag(Nil:) { Bag.new(Nil) } multi method BagHash(Nil:) { BagHash.new(Nil) } multi method Mix(Nil:) { Mix.new(Nil) } multi method MixHash(Nil:) { MixHash.new(Nil) } } #line 1 SETTING::src/core.c/Range.rakumod my class X::Immutable { ... } my class X::Range::InvalidArg { ... } my class X::Range::Incomparable { ... } my class Range is Cool does Iterable does Positional { has $.min; has $.max; has int $!excludes-min; has int $!excludes-max; has int $!infinite; has int $!is-int; method !SET-SELF(\min, \max, \excludes-min, \excludes-max, \infinite) { $!min := nqp::decont(min); $!max := nqp::decont(max); $!excludes-min = excludes-min // 0; $!excludes-max = excludes-max // 0; $!infinite = infinite; $!is-int = nqp::istype($!min,Int) && nqp::istype($!max,Int); self } multi method is-lazy(Range:D:) { self.infinite } multi method contains(Range:D: \needle) { warn "Applying '.contains' to a Range will look at its .Str representation. Did you mean 'needle (elem) Range'?".naive-word-wrapper; self.Str.contains(needle) } multi method index(Range:D: \needle) { warn "Applying '.index' to a Range will look at its .Str representation. Did you mean 'Range.first(needle, :k)'?".naive-word-wrapper; self.Str.index(needle) } # The order of "method new" declarations matters here, to ensure # appropriate candidate tiebreaking when mixed type arguments # are present (e.g., Range,Whatever or Real,Range). proto method new(|) {*} multi method new(Range $min, \max, :$excludes-min, :$excludes-max) { X::Range::InvalidArg.new(:got($min)).throw; } multi method new(\min, Range $max, :$excludes-min, :$excludes-max) { X::Range::InvalidArg.new(:got($max)).throw; } multi method new(Seq \min, \max, :$excludes-min, :$excludes-max) { X::Range::InvalidArg.new(:got(Seq)).throw; } multi method new(\min , Seq \max, :$excludes-min, :$excludes-max) { X::Range::InvalidArg.new(:got(Seq)).throw; } multi method new(Complex \min, \max, :$excludes-min, :$excludes-max) { X::Range::InvalidArg.new(:got(min)).throw; } multi method new(\min , Complex \max, :$excludes-min, :$excludes-max) { X::Range::InvalidArg.new(:got(max)).throw; } multi method new(Whatever \min,Whatever \max,:$excludes-min,:$excludes-max){ nqp::create(self)!SET-SELF(-Inf,Inf,$excludes-min,$excludes-max,1); } multi method new(Whatever \min, \max, :$excludes-min, :$excludes-max) { nqp::create(self)!SET-SELF(-Inf,max,$excludes-min,$excludes-max,1); } multi method new(\min, Whatever \max, :$excludes-min, :$excludes-max) { nqp::create(self)!SET-SELF(min,Inf,$excludes-min,$excludes-max,1); } multi method new(Real \min, Real(Cool) $max, :$excludes-min, :$excludes-max) { nqp::create(self)!SET-SELF( min,$max,$excludes-min,$excludes-max, $max == Inf || $max === NaN || min == -Inf || min === NaN ); } multi method new(List:D \min, \max, :$excludes-min, :$excludes-max) { nqp::create(self)!SET-SELF( +min, nqp::istype(max,List) || nqp::istype(max,Match) ?? +max !! max, $excludes-min, $excludes-max, 0); } multi method new(Match:D \min, \max, :$excludes-min, :$excludes-max) { nqp::create(self)!SET-SELF( +min, nqp::istype(max,List) || nqp::istype(max,Match) ?? +max !! max, $excludes-min, $excludes-max, 0); } multi method new(\min, \max, :$excludes-min, :$excludes-max!) { nqp::create(self)!SET-SELF(min, max,$excludes-min,$excludes-max,0); } multi method new(\min, \max, :$excludes-min!, :$excludes-max) { nqp::create(self)!SET-SELF(min,max,$excludes-min,$excludes-max,0); } multi method new(\min, \max) { nqp::create(self)!SET-SELF(min,max,0,0,0) } method excludes-min() { nqp::hllbool($!excludes-min) } method excludes-max() { nqp::hllbool($!excludes-max) } method infinite() { nqp::hllbool($!infinite) } method is-int() { nqp::hllbool($!is-int) } method !IS-NATIVE-INT() { $!is-int && nqp::not_i(nqp::isbig_I($!min) || nqp::isbig_I($!max)) } multi method WHICH(Range:D: --> ValueObjAt:D) { nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Range), 'Range|', nqp::concat(self.^name, '|') ), self.raku ), ValueObjAt ) } multi method EXISTS-POS(Range:D: uint $pos) { $pos < self.elems; } multi method EXISTS-POS(Range:D: Int:D $pos) { 0 <= $pos < self.elems; } method elems { $!is-int ?? 0 max $!max - $!excludes-max - $!min - $!excludes-min + 1 !! $!infinite ?? self.fail-iterator-cannot-be-lazy('.elems',"") !! nextsame } my class NegativeInf does Iterator { method new() { nqp::create(self) } method pull-one() { -Inf } method is-lazy() { True } } my class NumFromInf does Iterator { has $!i; method !SET-SELF(\i) { $!i = i - 1; self } method new(\i) { nqp::create(self)!SET-SELF(i) } method pull-one() { ++$!i } method is-lazy(--> True) { } method is-monotonically-increasing(--> True) { } } method iterator() { $!min after $!max # nothing to iterate over ?? Rakudo::Iterator.Empty() !! nqp::istype($!min,Int) && nqp::not_i(nqp::isbig_I(nqp::decont($!min))) && ((nqp::istype($!max,Int) && nqp::not_i(nqp::isbig_I(nqp::decont($!max)))) || $!max == Inf) # can use native ints ?? Rakudo::Iterator.IntRange( $!min + $!excludes-min, $!max - $!excludes-max ) !! $!min === Inf # doesn't make much sense, but there you go ?? NegativeInf.new !! $!max === Inf ?? nqp::istype($!min, Numeric) # something quick and easy for 1..* style things ?? NumFromInf.new($!min + $!excludes-min) # open-ended general case !! Rakudo::Iterator.SuccFromInf( $!excludes-min ?? $!min.succ !! $!min ) !! nqp::istype($!min,Str) # we have a string range ?? $!min.chars == 1 && $!max.chars == 1 # we have (simple) char range ?? Rakudo::Iterator.CharFromTo( $!min,$!max.Str,$!excludes-min,$!excludes-max ) # generic string sequence !! SEQUENCE( ($!excludes-min ?? $!min.succ !! $!min), $!max, :exclude_end($!excludes-max) ) # general case !! Rakudo::Iterator.SuccFromTo( $!excludes-min ?? $!min.succ !! $!min, $!excludes-max, $!max ) } multi method list(Range:D:) { List.from-iterator(self.iterator) } method flat(Range:D:) { Seq.new(self.iterator) } my class NativeIntReverse does PredictiveIterator { has int $!i; has int $!n; method !SET-SELF(\i,\n) { $!i = i + 1; $!n = n; self } method new(\i,\n) { nqp::create(self)!SET-SELF(i,n) } method pull-one() { ( $!i = $!i - 1 ) >= $!n ?? $!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::p6box_i($i)) while ($i = $i - 1) >= $n; $!i = $i; } method count-only(--> Int:D) { nqp::p6box_i($!i - $!n + nqp::isgt_i($!n,$!i)) } method sink-all(--> IterationEnd) { $!i = $!n } } my class InfReverse does Iterator { method new() { nqp::create(self) } method pull-one(--> Inf) { } method is-lazy(--> True) { } } my class NumReverse does Iterator { has $!i; method !SET-SELF(\i) { $!i = i; self } method new(\i) { nqp::create(self)!SET-SELF(i) } method pull-one() { $!i-- } method is-lazy() { True } } my class CharReverse does PredictiveIterator { has int $!i; has int $!n; method !SET-SELF(\from,\end) { $!i = nqp::ord(nqp::unbox_s(from)) + 1; $!n = nqp::ord(nqp::unbox_s(end)); self } method new(\from,\end) { nqp::create(self)!SET-SELF(from,end) } 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) { nqp::p6box_i($!i - $!n + nqp::isgt_i($!n,$!i)) } method sink-all(--> IterationEnd) { $!i = $!n } } my class Pred does Iterator { has $!i; has $!e; has int $!exclude; method !SET-SELF(\i,\exclude,\e) { $!i = i; $!exclude = exclude.Int; $!e = e; self } method new(\i,\exclude,\e) { nqp::create(self)!SET-SELF(i,exclude,e) } method pull-one() { if $!exclude ?? $!i after $!e !! not $!i before $!e { my Mu $i = $!i; $!i = $i.pred; $i } else { IterationEnd } } method push-all(\target --> IterationEnd) { my Mu $i = $!i; my Mu $e = $!e; if $!exclude { while $i after $e { target.push(nqp::clone($i)); $i = $i.pred; } } else { while not $i before $e { target.push(nqp::clone($i)); $i = $i.pred; } } } method sink-all(--> IterationEnd) { $!i = $!e } } method !reverse-iterator() { # can use native ints if self!IS-NATIVE-INT() { NativeIntReverse.new( $!max - $!excludes-max, $!min + $!excludes-min ) } # can never go down to -Inf elsif $!max === -Inf { Rakudo::Iterator.Empty } # endpoints are same elsif $!min === $!max { $!excludes-min || $!excludes-max ?? Rakudo::Iterator.Empty !! Rakudo::Iterator.OneValue($!min) } # Also something quick and easy for -Inf..42 style things elsif nqp::istype($!min, Numeric) && $!min === -Inf { NumReverse.new($!max - $!excludes-max) } # if we have (simple) char range elsif nqp::istype($!min,Str) { my $max = $!excludes-max ?? $!max.pred !! $!max; $max before $!min ?? ().iterator !! $max.chars == 1 && nqp::istype($!min,Str) && $!min.chars == 1 ?? CharReverse.new($max,$!excludes-min ?? $!min.succ !! $!min) !! SEQUENCE($max,$!min,:exclude_end($!excludes-min)) } # General case according to spec else { Pred.new( $!excludes-max ?? $!max.pred !! $!max,$!excludes-min,$!min ) } } method reverse(Range:D:) { Seq.new(self!reverse-iterator) } method first($test) { if %_ { my \res := self.reverse.first($test, |%_, :!end); if %_ and nqp::istype(res, Numeric) { self.elems - res - 1 } elsif %_
), :actions($LANG)) if $LANG); } if $check { Nil } else { nqp::forceouterctx( nqp::getattr($compiled,ForeignCode,'$!do'),$eval_ctx ); $compiled() } } multi sub EVAL( $code, Str :$lang where { ($lang // '') eq 'Perl5' }, PseudoStash :$context, Str() :$filename = Str, :$check, ) { if $check { NYI(":check on EVAL :from").throw; } else { my $?FILES := $filename // 'EVAL_' ~ Rakudo::Internals::EvalIdSource.next-id; Rakudo::Internals.PERL5.run: nqp::istype($code,Blob) ?? Blob.new($code).decode('utf8-c8') !! $code.Str } } proto sub EVALFILE($, *%) {*} multi sub EVALFILE($filename, :$lang = 'Raku', :$check) { EVAL slurp(:bin, $filename), :$lang, :$check, :context(CALLER::LEXICAL::), :$filename } #line 1 SETTING::src/core.c/Rakudo/SEQUENCE.rakumod sub SEQUENCE( \left, Mu \right, :$exclude_end --> Iterator:D) is implementation-detail { my \righti := (nqp::iscont(right) ?? right !! [right]).iterator; my $endpoint := righti.pull-one; $endpoint.throw if nqp::istype($endpoint,Failure); Any.throw-cannot-be-empty( 'get sequence endpoint', 'list (use * or :!elems instead?)' ) if nqp::eqaddr($endpoint,IterationEnd); my $infinite := nqp::istype($endpoint,Whatever) || $endpoint === Inf; $endpoint := False if $infinite; my $end_code_arity := 0; if nqp::istype($endpoint,Code) && nqp::not_i(nqp::istype($endpoint,Regex)) { $end_code_arity := $endpoint.arity; $end_code_arity := $endpoint.count if $end_code_arity == 0; $end_code_arity := -Inf if $end_code_arity == Inf; } my sub succpred($a,$b) { my $cmp := $a cmp $b; if nqp::eqaddr($a.WHAT,$b.WHAT) && nqp::eqaddr($b.WHAT,$endpoint.WHAT) { $cmp < 0 && nqp::istype($a,Stringy) ?? { my $new := .succ; $new after $endpoint || $new.chars > $endpoint.chars ?? (last) !! $new } !! $cmp < 0 ?? { my $new := .succ; $new after $endpoint ?? (last) !! $new } !! $cmp > 0 ?? { $_ before $endpoint ?? (last) !! .pred } !! { $_ } } else { $cmp < 0 ?? { .succ } !! $cmp > 0 ?? { .pred } !! { $_ } } } my sub unisuccpred($a,$b) { my $cmp := $a.ord cmp $b.ord; $cmp < 0 ?? { .ord.succ.chr } !! $cmp > 0 ?? { .ord.pred.chr } !! { $_ } } my \gathered = GATHER({ my \lefti := left.iterator; my &producer; my int $stop; my int $looped; my @tail; my @end_tail; until nqp::eqaddr((my \value := lefti.pull-one),IterationEnd) { $looped = 1; if nqp::istype(value,Code) { &producer = value; last } if $end_code_arity != 0 { @end_tail.push(value); if @end_tail.elems >= $end_code_arity { @end_tail.shift xx (@end_tail.elems - $end_code_arity) unless $end_code_arity == -Inf; if $endpoint(|@end_tail) { $stop = 1; @tail.push(value) unless $exclude_end; last; } } } elsif $endpoint.ACCEPTS(value) { $stop = 1; @tail.push(value) unless $exclude_end; last; } @tail.push(value); } Any.throw-cannot-be-empty( 'get sequence start value', 'list' ) unless $looped; if $stop { my $ = take $_ for @tail; # don't sink return of take() } else { my $badseq; my $a; my $b; my $c; unless &producer { my $ = take @tail.shift while @tail.elems > 3; # don't sink return of take() $a := @tail[0]; $b := @tail[1]; $c := @tail[2]; } if &producer { } elsif @tail.grep(Real).elems != @tail.elems { if @tail.elems > 1 { &producer = @tail.tail.WHAT === $endpoint.WHAT ?? succpred(@tail.tail, $endpoint) !! succpred(@tail[*-2], @tail.tail); } elsif nqp::istype($endpoint,Stringy) && nqp::istype($a,Stringy) && nqp::isconcrete($endpoint) { if $a.codes == 1 && $endpoint.codes == 1 { &producer = unisuccpred($a, $endpoint); } elsif $a.codes == $endpoint.codes { my @a = $a.comb; my @e = $endpoint.comb; my @ranges; for flat @a Z @e -> $from, $to { @ranges.push: $($from ... $to); } my $ = .take for flat [X~] @ranges; # don't sink return of take() $stop = 1; } elsif $a lt $endpoint { $stop = 1 if $a gt $endpoint; &producer = { my $new := .succ; $new gt $endpoint || $new.chars > $endpoint.chars ?? (last) !! $new } } else { $stop = 1 if $a lt $endpoint; &producer = { my $new := .pred; $new lt $endpoint ?? (last) !! $new } } } elsif $infinite or nqp::istype($endpoint, Code) { &producer = *.succ; } else { &producer = succpred($a,$endpoint); } } elsif @tail.elems == 3 { my $ab := $b - $a; if $ab == $c - $b { if $ab != 0 || nqp::istype($a,Real) && nqp::istype($b,Real) && nqp::istype($c,Real) { if nqp::istype($endpoint, Real) && nqp::not_i(nqp::istype($endpoint,Bool)) && nqp::isconcrete($endpoint) { if $ab > 0 { $stop = 1 if $a > $endpoint; &producer = { my $new := $_ + $ab; $new > $endpoint ?? (last) !! $new } } else { $stop = 1 if $a < $endpoint; &producer = { my $new := $_ + $ab; $new < $endpoint ?? (last) !! $new } } } else { &producer = { $_ + $ab } } } else { &producer = succpred($b, $c) } } elsif $a != 0 && $b != 0 && $c != 0 { $ab := $b / $a; if $ab == $c / $b { # XXX TODO: this code likely has a 2 bugs: # 1) It should check Rational, not just Rat # 2) Currently Rats aren't guaranteed to be always # normalized, so denominator might not be 1, even if # it could be, if normalized $ab := $ab.Int if nqp::istype($ab, Rat) && $ab.denominator == 1; if nqp::istype($endpoint,Real) && nqp::not_i(nqp::istype($endpoint,Bool)) && nqp::isconcrete($endpoint) { if $ab > 0 { if $ab > 1 { $stop = 1 if $a > $endpoint; &producer = { my $new := $_ * $ab; $new > $endpoint ?? (last) !! $new } } else { $stop = 1 if $a < $endpoint; &producer = { my $new := $_ * $ab; $new < $endpoint ?? (last) !! $new } } } else { &producer = { my $new := $_ * $ab; my $absend := $endpoint.abs; sign(.abs - $absend) == -sign($new.abs - $absend) ?? (last) !! $new } } } else { &producer = { $_ * $ab } } } } if &producer { @tail.pop; @tail.pop; } else { $badseq := "$a,$b,$c"; } } elsif @tail.elems == 2 { my $ab := $b - $a; if $ab != 0 || nqp::istype($a,Real) && nqp::istype($b,Real) { if nqp::istype($endpoint,Real) && nqp::not_i(nqp::istype($endpoint,Bool)) && nqp::isconcrete($endpoint) { if $ab > 0 { $stop = 1 if $a > $endpoint; &producer = { my $new := $_ + $ab; $new > $endpoint ?? (last) !! $new } } else { $stop = 1 if $a < $endpoint; &producer = { my $new := $_ + $ab; $new < $endpoint ?? (last) !! $new } } } else { &producer = { $_ + $ab } } } else { &producer = succpred($a, $b) } @tail.pop; } elsif @tail.elems == 1 { if nqp::istype($endpoint,Code) || nqp::not_i(nqp::isconcrete($endpoint)) { &producer = *.succ } elsif nqp::istype($endpoint,Real) && nqp::not_i(nqp::istype($endpoint,Bool)) && nqp::istype($a,Real) { if $a < $endpoint { &producer = { my $new := .succ; $new > $endpoint ?? (last) !! $new } } else { &producer = { my $new := .pred; $new < $endpoint ?? (last) !! $new } } } else { &producer = *.succ; } } elsif @tail.elems == 0 { &producer = {()} } if $stop { } elsif &producer { my $ = .take for @tail; # don't sink return of take() my $count := &producer.count; until $stop { @tail.shift while @tail.elems > $count; my \value = producer(|@tail); if $end_code_arity != 0 { @end_tail.push(value); if @end_tail.elems >= $end_code_arity { @end_tail.shift xx ( @end_tail.elems - $end_code_arity ) unless $end_code_arity == -Inf; if $endpoint(|@end_tail) { my $ = value.take unless $exclude_end; # don't sink return of take() $stop = 1; } } } elsif $endpoint.ACCEPTS(value) { my $ = value.take unless $exclude_end; # don't sink return of take() $stop = 1; } if $stop { } else { @tail.push(value); my $ = value.take; # don't sink return of take() } } } elsif $badseq { X::Sequence::Deduction.new(:from($badseq)).throw; } else { X::Sequence::Deduction.new.throw; } } }); $infinite ?? (gathered.Slip, Slip.from-iterator(righti)).lazy.iterator !! (gathered.Slip, Slip.from-iterator(righti)).iterator } #line 1 SETTING::src/core.c/operators.rakumod ## miscellaneous operators can go here. ## generic numeric operators are in Numeric.rakumod ## generic string operators are in Stringy.rakumod ## Int/Rat/Num operators are in {Int|Rat|Num}.rakumod # infix:<=> only exists to allow it to be referenced as an operator in # meta-operator usage. You cannot add other candidates for it. Therefore # it doesn't make sense to make it a multi. only sub infix:<=>(Mu \a, Mu \b) is raw { nqp::p6store(a, b) } my class X::Does::TypeObject is Exception { has Mu $.type; has %.nameds; method message() { "Cannot use 'does' operator on a type object {$!type.^name}." ~ ("\nAdditional named parameters: {%!nameds.raku}." if %!nameds) } } proto sub infix:(Mu, |) {*} multi sub infix:(Int:D, |) { die "Cannot use 'does' operator on an Int, did you mean 'but'?"; } multi sub infix:(Str:D, |) { die "Cannot use 'does' operator on a Str, did you mean 'but'?"; } multi sub infix:(Mu:D \obj, Mu:U \rolish) is raw { # XXX Mutability check. my $role := rolish.HOW.archetypes.composable() ?? rolish !! rolish.HOW.archetypes.composalizable() ?? rolish.HOW.composalize(rolish) !! X::Mixin::NotComposable.new(:target(obj), :rolish(rolish)).throw; obj.^mixin($role).BUILD_LEAST_DERIVED({}); } multi sub infix:(Mu:D \obj, Mu:U \rolish, :$value! is raw) is raw { # XXX Mutability check. my $role := rolish.HOW.archetypes.composable() ?? rolish !! rolish.HOW.archetypes.composalizable() ?? rolish.HOW.composalize(rolish) !! X::Mixin::NotComposable.new(:target(obj), :rolish(rolish)).throw; my \mixedin = obj.^mixin($role, :need-mixin-attribute); mixedin.BUILD_LEAST_DERIVED({ substr(mixedin.^mixin_attribute.Str,2) => $value }); } multi sub infix:(Mu:U \obj, Mu:U \role, *%_) is raw { X::Does::TypeObject.new(type => obj, nameds => %_).throw } multi sub infix:(Mu:D \obj, **@roles) is raw { # XXX Mutability check. my \real-roles = eager @roles.map: -> \rolish { rolish.DEFINITE ?? GENERATE-ROLE-FROM-VALUE(rolish) !! rolish.HOW.archetypes.composable() ?? rolish !! rolish.HOW.archetypes.composalizable() ?? rolish.HOW.composalize(rolish) !! X::Mixin::NotComposable.new(:target(obj), :rolish(rolish)).throw } obj.^mixin(|real-roles).BUILD_LEAST_DERIVED({}); } multi sub infix:(Mu:U \obj, **@roles) is raw { X::Does::TypeObject.new(type => obj).throw } proto sub infix:(Mu, |) is pure {*} multi sub infix:(Mu:D \obj, Mu:U \rolish) { my $role := rolish.HOW.archetypes.composable() ?? rolish !! rolish.HOW.archetypes.composalizable() ?? rolish.HOW.composalize(rolish) !! X::Mixin::NotComposable.new(:target(obj), :rolish(rolish)).throw; obj.clone.^mixin($role).BUILD_LEAST_DERIVED({}); } multi sub infix:(Mu:D \obj, Mu:U \rolish, :$value! is raw) { my $role := rolish.HOW.archetypes.composable() ?? rolish !! rolish.HOW.archetypes.composalizable() ?? rolish.HOW.composalize(rolish) !! X::Mixin::NotComposable.new(:target(obj), :rolish(rolish)).throw; my \mixedin = obj.clone.^mixin($role, :need-mixin-attribute); my \attr = mixedin.^mixin_attribute; my $mixin-value := $value; unless nqp::istype($value, attr.type) { if attr.type.HOW.^name eq 'Perl6::Metamodel::EnumHOW' { $mixin-value := attr.type.($value); } } mixedin.BUILD_LEAST_DERIVED({ substr(attr.Str,2) => $mixin-value }); } multi sub infix:(Mu:U \obj, Mu:U \rolish) { my $role := rolish.HOW.archetypes.composable() ?? rolish !! rolish.HOW.archetypes.composalizable() ?? rolish.HOW.composalize(rolish) !! X::Mixin::NotComposable.new(:target(obj), :rolish(rolish)).throw; obj.^mixin($role); } sub GENERATE-ROLE-FROM-VALUE($val) is implementation-detail { my $role := Metamodel::ParametricRoleHOW.new_type(); # The auto-generated role doesn't use any of 6.e features. Thus can safely be proclaimed as 6.c. $role.^set_language_revision(1); my $meth := method () { $val }; $meth.set_name($val.^name); $role.^add_method($meth.name, $meth); $role.^set_body_block( -> |c { nqp::list($role, nqp::hash('$?CLASS', c<$?CLASS>)) }); $role.^compose; } multi sub infix:(Mu \obj, Mu:D $val) is raw { obj.clone.^mixin(GENERATE-ROLE-FROM-VALUE($val)); } multi sub infix:(Mu:D \obj, **@roles) { my \real-roles := eager @roles.map: -> \rolish { rolish.DEFINITE ?? GENERATE-ROLE-FROM-VALUE(rolish) !! rolish.HOW.archetypes.composable() ?? rolish !! rolish.HOW.archetypes.composalizable() ?? rolish.HOW.composalize(rolish) !! X::Mixin::NotComposable.new(:target(obj), :rolish(rolish)).throw } obj.clone.^mixin(|real-roles).BUILD_LEAST_DERIVED({}); } multi sub infix:(Mu:U \obj, **@roles) { my \real-roles := eager @roles.map: -> \rolish { rolish.DEFINITE ?? GENERATE-ROLE-FROM-VALUE(rolish) !! rolish.HOW.archetypes.composable() ?? rolish !! rolish.HOW.archetypes.composalizable() ?? rolish.HOW.composalize(rolish) !! X::Mixin::NotComposable.new(:target(obj), :rolish(rolish)).throw } obj.^mixin(|real-roles) } # XXX Wants to be macros when we have them. only sub HOW (Mu \x) { x.HOW } only sub VAR (Mu \x) { x.VAR } only sub WHAT(Mu \x) { x.WHAT } only sub WHERE(Mu \x) { x.WHERE } only sub WHICH(Mu \x) { x.WHICH } only sub WHO(Mu \x) { x.WHO } proto sub infix:<...>(|) {*} multi sub infix:<...>(\a, Mu \b) { Seq.new(SEQUENCE(a, b)) } multi sub infix:<...>(|lol) { my @lol := lol.list; my @end; my @seq; my @excl; my $ret := (); my int $i = 0; my int $m = +@lol - 1; while $i <= $m { @seq[$i] := @lol[$i].iterator; if $i { @end[$i-1] := @seq[$i].pull-one; if @end[$i-1] ~~ Numeric | Stringy { @seq[$i] := @lol[$i].iterator; @excl[$i-1] = True; } } ++$i; } $i = 0; while $i < $m { $ret := ($ret.Slip, Seq.new(SEQUENCE( (Slip.from-iterator(@seq[$i]),), @end[$i], :exclude_end(so @excl[$i]) )).Slip ); ++$i; } if @seq[$m] =:= Empty { Seq.new($ret.iterator); } else { Seq.new(($ret.Slip, Slip.from-iterator(@seq[$m])).iterator); } } # U+2026 HORIZONTAL ELLIPSIS my constant &infix:<…> := &infix:<...>; proto sub infix:<...^>($, Mu, *%) {*} multi sub infix:<...^>(\a, Mu \b) { Seq.new(SEQUENCE(a, b, :exclude_end)) } # U+2026 HORIZONTAL ELLIPSIS, U+005E CIRCUMFLEX ACCENT my constant &infix:<…^> := &infix:<...^>; proto sub infix:<^...>(|) {*} multi sub infix:<^...>(\a, Mu \b) { Seq.new: Rakudo::Iterator.AllButFirst(SEQUENCE(a, b)) } multi sub infix:<^...>(|lol) { Seq.new: Rakudo::Iterator.AllButFirst(infix:<...>(|lol).iterator) } # U+005E CIRCUMFLEX ACCENT, U+2026 HORIZONTAL ELLIPSIS my constant &infix:<^…> := &infix:<^...>; proto sub infix:<^...^>(|) {*} multi sub infix:<^...^>(\a, Mu \b) { Seq.new: Rakudo::Iterator.AllButFirst(SEQUENCE(a, b, :exclude_end)) } multi sub infix:<^...^>(|lol) { Seq.new: Rakudo::Iterator.AllButFirst(infix:<...>(|lol).iterator) # XXX } # U+005E CIRCUMFLEX ACCENT, U+2026 HORIZONTAL ELLIPSIS, U+005E CIRCUMFLEX ACCENT my constant &infix:<^…^> := &infix:<^...^>; proto sub undefine(Mu, *%) is raw {*} multi sub undefine(Mu \x) is raw { x = Nil } multi sub undefine(Array \x) is raw { x = Empty } multi sub undefine(Hash \x) is raw { x = Empty } sub prefix:(Mu \cont) is raw { Rakudo::Internals.TEMP-LET(nqp::getlexcaller('!TEMP-RESTORE'),cont,'temp') } sub prefix:(Mu \cont) is raw { Rakudo::Internals.TEMP-LET(nqp::getlexcaller('!LET-RESTORE'),cont,'let') } # this implements the ::() indirect lookup sub INDIRECT_NAME_LOOKUP($root, *@chunks) is raw is implementation-detail { sub not-found($symbol = "") { X::NoSuchSymbol.new(:$symbol).Failure } # Note that each part of @chunks itself can contain double colons. # That's why joining and re-splitting is necessary if @chunks.join('::') -> str $name is copy { my $parts := nqp::split('::',$name); my str $first = nqp::shift($parts); if nqp::elems($parts) { # move the sigil to the last part of the name if available my str $sigil = nqp::substr($first,0,1); nqp::if( nqp::iseq_s($sigil,'$') || nqp::iseq_s($sigil,'@') || nqp::iseq_s($sigil,'%') || nqp::iseq_s($sigil,'&'), nqp::stmts( nqp::push($parts,nqp::concat($sigil,nqp::pop($parts))), ($first = nqp::substr($first,1)) ) ); nqp::unless( $first, nqp::stmts( ($first = nqp::shift($parts)), ($name = nqp::join("::",$parts)), ) ) } my Mu $thing := $root.EXISTS-KEY('%?REQUIRE-SYMBOLS') && (my $REQUIRE_SYMBOLS := $root.AT-KEY('%?REQUIRE-SYMBOLS')) && $REQUIRE_SYMBOLS.EXISTS-KEY($first) ?? $REQUIRE_SYMBOLS.AT-KEY($first) !! $root.EXISTS-KEY($first) ?? $root.AT-KEY($first) !! GLOBAL::.EXISTS-KEY($first) ?? GLOBAL::.AT-KEY($first) !! nqp::iseq_s($first,'GLOBAL') ?? GLOBAL !! not-found($name); nqp::while( nqp::elems($parts) && nqp::not_i(nqp::istype($thing,Failure)), $thing := nqp::if( $thing.WHO.EXISTS-KEY(my $part := nqp::shift($parts)), $thing.WHO.AT-KEY($part), not-found($name) ) ); $thing } else { not-found } } sub REQUIRE_IMPORT( $compunit, $existing-path,$top-existing-pkg,$stubname, *@syms --> Nil ) is implementation-detail { my $handle := $compunit.handle; my $DEFAULT := $handle.export-package().WHO; my $GLOBALish := $handle.globalish-package; my @missing; my $block := CALLER::.EXISTS-KEY('%?REQUIRE-SYMBOLS') ?? CALLER::MY:: !! CALLER::OUTER::; my $merge-globals-target := $block; my $targetWHO; my $sourceWHO; if $existing-path { my @existing-path = @$existing-path; my $topname := @existing-path.shift; $targetWHO := $top-existing-pkg.WHO; $sourceWHO := $GLOBALish.AT-KEY($topname).WHO; # Yes! the target CAN be the source if it's something like Cool::Utils # because Cool is common to both compunits..so no need to do anything unless $targetWHO === $sourceWHO { # We want to skip over the parts of the Package::That::Already::Existed for @existing-path { $targetWHO := $targetWHO.AT-KEY($_).WHO; $sourceWHO := $sourceWHO.AT-KEY($_).WHO; } # Now we are just above our target stub. If it exists # delete it so it can be replaced by the real one we're importing. if $stubname { $targetWHO.DELETE-KEY($stubname); } $targetWHO.merge-symbols($sourceWHO); } $merge-globals-target := $top-existing-pkg; } elsif $stubname { $targetWHO := $block.AT-KEY($stubname).WHO; $sourceWHO := $GLOBALish.AT-KEY($stubname).WHO; $targetWHO.merge-symbols($sourceWHO); } # Set the runtime values for compile time stub symbols for @syms { unless $DEFAULT.EXISTS-KEY($_) { @missing.push: $_; next; } $block{$_} := $DEFAULT{$_}; } if @missing { X::Import::MissingSymbols.new(:from($compunit.short-name), :@missing).throw; } try nqp::gethllsym('Raku','ModuleLoader').merge_globals( $merge-globals-target.AT-KEY($stubname).WHO, $GLOBALish, ) if $stubname; # Merge GLOBAL from compunit. nqp::gethllsym('Raku','ModuleLoader').merge_globals( $block<%?REQUIRE-SYMBOLS>, $GLOBALish, ); } proto sub infix:(|) {*} multi sub infix:(+a) { # We need to be able to process `Empty` in our args, which we can get # when we're chained with, say, `andthen`. Since Empty disappears in normal # arg handling, we use nqp::p6argvmarray op to fetch the args, and then # emulate the `+@foo` slurpy by inspecting the list the op gave us. nqp::if( (my int $els = nqp::elems(my $args := nqp::p6argvmarray)), nqp::stmts( (my $current := nqp::atpos($args, 0)), nqp::if( # emulate the +@foo slurpy nqp::iseq_i($els, 1) && nqp::istype($current, Iterable), nqp::stmts( ($args := $current.List), ($current := $args[0]), $els = $args.elems)), (my int $i), nqp::until( nqp::iseq_i($els,++$i) || ( # if $current not defined, set it to Empty and bail from the loop nqp::isfalse($current.defined) && nqp::stmts(($current := Empty), 1) ), ($current := nqp::if( nqp::istype(($_ := $args[$i]), Callable), nqp::if(.count, $_($current), $_()), $_)), :nohandler), # do not handle control stuff in thunks $current), # either the last arg or Empty if any but last were undefined True) # We were given no args, return True } proto sub infix:(|) {*} multi sub infix:(+a) { # We need to be able to process `Empty` in our args, which we can get # when we're chained with, say, `andthen`. Since Empty disappears in normal # arg handling, we use nqp::p6argvmarray op to fetch the args, and then # emulate the `+@foo` slurpy by inspecting the list the op gave us. nqp::if( (my int $els = nqp::elems(my $args := nqp::p6argvmarray)), nqp::stmts( (my $current := nqp::atpos($args, 0)), nqp::if( # emulate the +@foo slurpy nqp::iseq_i($els, 1) && nqp::istype($current, Iterable), nqp::stmts( ($args := $current.List), ($current := $args[0]), $els = $args.elems)), (my int $i), nqp::until( nqp::iseq_i($els,++$i) || ( # if $current is defined, set it to Empty and bail from the loop $current.defined && nqp::stmts(($current := Empty), 1) ), ($current := nqp::if( nqp::istype(($_ := $args[$i]), Callable), nqp::if(.count, $_($current), $_()), $_)), :nohandler), # do not handle control stuff in thunks $current), # either the last arg or Empty if any but last were undefined True) # We were given no args, return True } proto sub infix:(|) {*} multi sub infix:(+$) { # We need to be able to process `Empty` in our args, which we can get # when we're chained with, say, `andthen`. Since Empty disappears in normal # arg handling, we use nqp::p6argvmarray op to fetch the args, and then # emulate the `+@foo` slurpy by inspecting the list the op gave us. nqp::if( (my int $els = nqp::elems(my $args := nqp::p6argvmarray)), nqp::stmts( (my $current := nqp::atpos($args, 0)), nqp::if( # emulate the +@foo slurpy nqp::iseq_i($els, 1) && nqp::istype($current, Iterable), nqp::stmts( ($args := $current.List), ($current := $args[0]), $els = $args.elems)), (my int $i), nqp::until( nqp::iseq_i($els,++$i) || $current.defined, ($current := nqp::if( nqp::istype(($_ := $args[$i]), Callable), nqp::if(.count, $_($current), $_()), $_)), :nohandler), # do not handle control stuff in thunks $current), Nil) # We were given no args, return Nil } proto sub infix: (&?, &?, *%) {*} multi sub infix: () { -> \v { v } } multi sub infix: (&f) { &f } multi sub infix: (&f, &g --> Block:D) { my \ret = &f.count > 1 ?? -> |args { f |g |args } !! -> |args { f g |args } my role FakeSignature[$arity, $count, $of] { method arity { $arity } method count { $count } method of { $of } } ret.^mixin(FakeSignature[&g.arity, &g.count, &f.of]); ret } # U+2218 RING OPERATOR my constant &infix:<∘> := &infix:; # to allow =~ to work with "no isms ", otherwise caught in compilation sub infix:<=~>(\a,\b) { a = ~b } #line 1 SETTING::src/core.c/Hyper.rakumod # A class to perform hyper operations of the form left op right class Hyper { has $.operator is built(:bind); # for some reason this cant be &.operator has int8 $.dwim-left; # left side wont end has int8 $.dwim-right; # right side wont end has int8 $.assigns; # assigns to left side method new(\op, Bool() :$dwim-left, Bool() :$dwim-right) { self.bless( :operator(op), :$dwim-left, :$dwim-right, :assigns(op.name.ends-with(' + {assigning}')), ) } # for error messages method name() { my str $name = $!operator.name || 'infix:'; my int $start = nqp::index($name,"«"); $start = nqp::index($name,"<") if $start == -1; my int $end = nqp::index($name,"»"); $end = nqp::index($name,">") if $end == -1; ($!dwim-left ?? '<<' !! '>>') ~ nqp::substr($name,$start + 1,$end - $start -1) ~ ($!dwim-right ?? '>>' !! '<<') } proto method infix(|) {*} # x >>op<< y multi method infix(\left, \right) is raw { $!operator(left,right) } # %x >>op<< %y multi method infix( Associative:D \left, Associative:D \right, --> Associative:D) is default { nqp::istype(left,Pair) ?? nqp::istype(right,Pair) ?? self!pair-pair(left,right) !! self!pair-mu(left,right) !! nqp::istype(right,Pair) ?? self!mu-pair(left,right) !! nqp::istype(left,Hash::Object) || nqp::istype(right,Hash::Object) ?? self!obj-associatives(left,right) !! self!str-associatives(left,right) } # %x >>op<< ... multi method infix(Associative:D \left, List:D \right) { die "{left.^name} $.name {right.^name} can never work reliably: order of keys in {left.^name} is indeterminate" } # %x >>op<< y multi method infix(Associative:D \left, \right --> Associative:D) { if nqp::istype(left,Pair) { self!pair-mu(left,right) } elsif $!assigns { self.infix(left.values,right); left } else { my \result := nqp::create(left.WHAT).STORE( left.keys, self.infix(left.values,right), :INITIALIZE ); nqp::iscont(left) ?? result.item !! result; } } # ... >>op<< %y multi method infix(List:D \left, Associative:D \right) { die "{left.^name} $.name {right.^name} can never work reliably: order of keys in {right.^name} is indeterminate" } # x >>op<< %y multi method infix(\left, Associative:D \right --> Associative:D) { if nqp::istype(right,Pair) { self!mu-pair(left,right) } else { my \result := nqp::create(right.WHAT).STORE( right.keys, self.infix(left,right.values), :INITIALIZE ); nqp::iscont(right) ?? result.item !! result; } } # [x] >>op<< y multi method infix(Positional:D \left, \right --> Positional:D) { X::HyperOp::Infinite.new(:side, :$!operator).throw if left.is-lazy; my int $left-elems = left.elems; X::HyperOp::NonDWIM.new( :$!operator, :$left-elems, :right-elems(1), :recursing ).throw unless $left-elems == 1 or $left-elems > 1 and $!dwim-right or $left-elems == 0 and $!dwim-left || $!dwim-right; my \iterator := left.iterator; if $!assigns { nqp::until( nqp::eqaddr((my \value := iterator.pull-one),IterationEnd), self.infix(value,right) ); left } else { my \values := nqp::create(IterationBuffer); nqp::until( nqp::eqaddr((my \value := iterator.pull-one),IterationEnd), nqp::push(values, self.infix(value,right)) ); my \result := nqp::eqaddr(left.WHAT,List) || nqp::eqaddr(left.WHAT,Slip) ?? nqp::p6bindattrinvres( nqp::create(left.WHAT),List,'$!reified',values ) !! nqp::can(left,"STORE") ?? left.WHAT.new(nqp::p6bindattrinvres( nqp::create(List),List,'$!reified',values )) !! nqp::p6bindattrinvres( nqp::create(List),List,'$!reified',values ); nqp::iscont(left) ?? result.item !! result } } # x >>op<< [y] multi method infix(\left, Positional:D \right --> Positional:D) { X::HyperOp::Infinite.new(:side, :$!operator).throw if right.is-lazy; my int $right-elems = right.elems; X::HyperOp::NonDWIM.new( :$!operator, :left-elems(1), :$right-elems, :recursing ).throw unless $right-elems == 1 or $right-elems > 1 and $!dwim-left or $right-elems == 0 and $!dwim-left || $!dwim-right; my \values := nqp::create(IterationBuffer); my \iterator := right.iterator; nqp::until( nqp::eqaddr((my \value := iterator.pull-one),IterationEnd), nqp::push(values, self.infix(left,value)) ); my \result := nqp::eqaddr(right.WHAT,List) || nqp::eqaddr(right.WHAT,Slip) ?? nqp::p6bindattrinvres( # List or Slip nqp::create(right.WHAT),List,'$!reified',values ) !! nqp::can(right,"STORE") ?? right.WHAT.new(nqp::p6bindattrinvres( nqp::create(List),List,'$!reified',values )) !! nqp::p6bindattrinvres(nqp::create(List),List,'$!reified',values); nqp::iscont(right) ?? result.item !! result } # ... >>op<< ... multi method infix( Iterable:D \left, Iterable:D \right --> Iterable:D ) { my \left-iterator = left.iterator; my \right-iterator = right.iterator; # Check whether any side is lazy. They must not be to proceed. if left-iterator.is-lazy { X::HyperOp::Infinite.new(:side, :$!operator).throw if right-iterator.is-lazy; X::HyperOp::Infinite.new(:side, :$!operator).throw if nqp::not_i($!dwim-left) || $!dwim-right; } X::HyperOp::Infinite.new(:side, :$!operator).throw if right-iterator.is-lazy and (nqp::not_i($!dwim-right) || $!dwim-left); my \values := $!dwim-left ?? $!dwim-right ?? self!iterators-left-right(left-iterator,right-iterator) !! self!iterators-left(left-iterator,right-iterator) !! $!dwim-right ?? self!iterators-right(left-iterator,right-iterator) !! nqp::istype(left-iterator,PredictiveIterator) && nqp::istype(right-iterator,PredictiveIterator) ?? self!predictive-iterators(left-iterator,right-iterator) !! self!iterators(left-iterator,right-iterator) ; my \result := nqp::p6bindattrinvres( nqp::create( nqp::istype(left,List) ?? left.WHAT !! List # keep subtype ), List, '$!reified', values ); nqp::iscont(left) ?? result.item !! result; } # :x >>op<< y method !pair-mu(\left,\right) { # multi method infix(Pair:D \left, \right) { nqp::p6bindattrinvres( nqp::clone(left), Pair, '$!value', self.infix(nqp::getattr(left,Pair,'$!value'), right) ) } # x >>op<< :y method !mu-pair(\left,\right) { # multi method infix(\left, Pair:D \right) { nqp::p6bindattrinvres( nqp::clone(right), Pair, '$!value', self.infix(left, nqp::getattr(right,Pair,'$!value')) ) } # :x >>op<< :y method !pair-pair(\left, \right) { # multi method infix(Pair:D \left, Pair:D \right) { nqp::getattr(left,Pair,'$!key').WHICH eq nqp::getattr(right,Pair,'$!key').WHICH ?? nqp::p6bindattrinvres( nqp::clone(left),Pair,'$!value',self.infix( nqp::getattr(left, Pair,'$!value'), nqp::getattr(right,Pair,'$!value') ) ) !! Nil } # using an infix on a one element list in a meta op multi method infix(\object) { nqp::can($!operator,"nodal") ?? object.nodemap($!operator) !! object.deepmap($!operator) } #--- Private helper methods ---------------------------------------------------- # ... >>op<< ... method !predictive-iterators( PredictiveIterator:D \left, PredictiveIterator:D \right, ) { X::HyperOp::NonDWIM.new( :$!operator, :left-elems(left.count-only), :right-elems(right.count-only), :recursing ).throw if left.count-only != right.count-only; # sure they have same number of elems, so only need to check one my \result := nqp::create(IterationBuffer); nqp::until( nqp::eqaddr((my \leftv := left.pull-one),IterationEnd), nqp::push(result,self.infix(leftv,right.pull-one)) ); result } # ... >>op<< ... method !iterators(Iterator:D \left, Iterator:D \right) { my \result := nqp::create(IterationBuffer); nqp::until( nqp::eqaddr((my \leftv := left.pull-one),IterationEnd) || nqp::eqaddr((my \rightv := right.pull-one),IterationEnd), nqp::push(result, self.infix(leftv, rightv)) ); nqp::if( nqp::eqaddr(rightv,IterationEnd), self!right-exhausted(left,nqp::elems(result)), nqp::unless( nqp::eqaddr(right.pull-one,IterationEnd), self!left-exhausted(right,nqp::elems(result)) ) ); result } # ... <>op>> ... method !iterators-right(Iterator:D \left, Iterator:D \right) { my \righti := Rakudo::Iterator.DWIM(right); my \result := nqp::create(IterationBuffer); my \rightv := righti.pull-one; nqp::unless( righti.ended, nqp::until( nqp::eqaddr((my \leftv := left.pull-one),IterationEnd), nqp::stmts( nqp::push(result, self.infix(leftv,rightv)), nqp::bind(rightv,righti.pull-one) ) ) ); result } # ... <> ... method !iterators-left-right(Iterator:D \left, Iterator:D \right) { my \lefti := Rakudo::Iterator.DWIM(left); my \righti := Rakudo::Iterator.DWIM(right); my \result := nqp::create(IterationBuffer); my \leftv := lefti.pull-one; my \rightv := righti.pull-one; nqp::unless( lefti.ended || righti.ended, nqp::until( lefti.ended && righti.ended, nqp::stmts( nqp::push(result,self.infix(leftv,rightv)), nqp::bind(leftv, lefti.pull-one), nqp::bind(rightv,righti.pull-one) ) ) ); result } # handle normal hashes method !str-associatives(\left, \right) { my $keys := nqp::hash; if $!dwim-left { nqp::bindkey($keys,$_,1) if right.EXISTS-KEY($_) for left.keys; } else { nqp::bindkey($keys,$_,1) for left.keys; } if nqp::not_i($!dwim-right) { nqp::bindkey($keys,$_,1) for right.keys; } # create HLL version of keys my @keys is List = nqp::p6bindattrinvres(nqp::create(Map),Map,'$!storage',$keys).keys; self!associatives(@keys, left, right) } # handle object hashes / QuantHashes method !obj-associatives(\left, \right) { my $keys := nqp::hash; if $!dwim-left { nqp::bindkey($keys,.WHICH,$_) if right.EXISTS-KEY($_) for left.keys; } else { nqp::bindkey($keys,.WHICH,$_) for left.keys; } if nqp::not_i($!dwim-right) { nqp::bindkey($keys,.WHICH,$_) for right.keys; } # create HLL version of keys my @keys is List = nqp::p6bindattrinvres(nqp::create(Map),Map,'$!storage',$keys).values; self!associatives(@keys, left, right) } # actually handle 2 associatives method !associatives(@keys, \left, \right) { my \values := nqp::p6bindattrinvres( nqp::create(List),List,'$!reified', quietly self!iterators( Rakudo::Iterator.AssociativeIterableKeys(left, @keys), Rakudo::Iterator.AssociativeIterableKeys(right,@keys), ) ); if $!assigns { left } else { my \result := nqp::create(left.WHAT).STORE(@keys, values, :INITIALIZE); nqp::iscont(left) ?? result.item !! result } } # error when left side of non-DWIM exhausted method !left-exhausted(Iterator:D \iterator, int $left-elems) { my int $right-elems = $left-elems + 1; nqp::until( nqp::eqaddr(iterator.pull-one,IterationEnd), ++$right-elems ); X::HyperOp::NonDWIM.new( :$!operator, :$left-elems, :$right-elems, :recursing ).throw; } # error when right side of non-DWIM exhausted method !right-exhausted(Iterator:D \iterator, int $right-elems) { my int $left-elems = $right-elems + 1; nqp::until( nqp::eqaddr(iterator.pull-one,IterationEnd), ++$left-elems ); X::HyperOp::NonDWIM.new( :$!operator, :$left-elems, :$right-elems, :recursing ).throw; } } #line 1 SETTING::src/core.c/OperatorProperties.rakumod class OperatorProperties { # defined in src/Raku/ast/operator-properties # has str $.precedence; # has str $.sub-precedence; # has str $.associative; # has str $.thunky; # has str $.dba; # has str $.next-term; # has int $.iffy; # has int $.diffy; # has int $.fiddly; # has int $.adverb # has int $.ternary multi method WHICH(OperatorProperties:D: --> ValueObjAt:D) { my $parts := nqp::list_s('OperatorProperties'); if $.precedence -> str $precedence { nqp::push_s($parts,nqp::concat('precedence=',$precedence)); } if $.sub-precedence -> str $sub-precedence { nqp::push_s($parts,nqp::concat('sub-precedence=',$sub-precedence)); } if $.associative -> str $associative { nqp::push_s($parts,nqp::concat('associative=',$associative)); } if $.thunky -> str $thunky { nqp::push_s($parts,nqp::concat('thunky=',$thunky)) } nqp::push_s($parts,'iffy') if $.iffy; nqp::push_s($parts,'diffy') if $.diffy; nqp::push_s($parts,'fiddly') if $.fiddly; nqp::push_s($parts,'adverb') if $.adverb; nqp::push_s($parts,'ternary') if $.ternary; nqp::box_s(nqp::join('|',$parts),ValueObjAt) } # Return handler for reducing with these operator properties method reducer() { ::(self.reducer-name) } } #------------------------------------------------------------------------------- # The REST of this file can be REMOVED **AFTER** the Raku grammar has # become the grammar to build the setting with. XXX # Attach operator properties to all of the built-in operators. This is # done here instead as traits on the op bodies, since some of the things # that the traits are implement in, are using features that aren't defined # that early. BEGIN { # prefixes .set_op_props for &prefix:«!», &prefix:«+», &prefix:«++», &prefix:«+^», &prefix:«-», &prefix:«--», &prefix:«?», &prefix:«?^», &prefix:«^», &prefix:«not», &prefix:«so», &prefix:«|», &prefix:«~», &prefix:«~^», &prefix:<⚛>, # infixes &infix:«!=», &infix:«!~~», &infix:«%», &infix:«%%», &infix:«&», &infix:«&&», &infix:«(&)», &infix:«(+)», &infix:«(-)», &infix:«(.)», &infix:«(<)», &infix:«(<+)», &infix:«(<=)», &infix:«(==)», &infix:«(>)», &infix:«(>+)», &infix:«(>=)», &infix:«(^)», &infix:«(cont)», &infix:«(elem)», &infix:«(|)», &infix:«*», &infix:«**», &infix:«+», &infix:«+>», &infix:«+<», &infix:«+&», &infix:«+^», &infix:«+|», &infix:«,», &infix:«-», &infix:«...», &infix:«...^», &infix:«..», &infix:«..^», &infix:«/», &infix:«//», &infix:«<», &infix:«<=», &infix:«<=>», &infix:«=>», &infix:«=:=», &infix:«==», &infix:«===», &infix:«=», &infix:«>», &infix:«>=», &infix:«?&», &infix:«?^», &infix:«?|», &infix:«X», &infix:«Z», &infix:«^..», &infix:«^...», &infix:«^..^», &infix:«^...^», &infix:«^», &infix:«^^», &infix:«after», &infix:«andthen», &infix:«and», &infix:«before», &infix:«but», &infix:«cmp», &infix:«coll», &infix:«div», &infix:«does», &infix:«eqv», &infix:«eq», &infix:«gcd», &infix:«ge», &infix:«gt», &infix:«lcm», &infix:«leg», &infix:«le», &infix:«lt», &infix:«max», &infix:«minmax», &infix:«min», &infix:«mod», &infix:«ne», &infix:«notandthen», &infix:«orelse», &infix:«or», &infix:«unicmp», &infix:«xor», &infix:«xx», &infix:«x», &infix:«|», &infix:«||», &infix:«~», &infix:«~&», &infix:«~^», &infix:«~|», &infix:«~>», &infix:«~<», &infix:«~~», &infix:«=~», &infix:«∉», # U+2209 NOT AN ELEMENT OF &infix:«∌», # U+220C DOES NOT CONTAIN AS MEMBER &infix:«≢», # U+2262 NOT IDENTICAL TO &infix:«⊄», # U+2284 NOT A SUBSET OF &infix:«⊅», # U+2285 NOT A SUPERSET OF &infix:«⊈», # U+2288 NEITHER A SUBSET OF NOR EQUAL TO &infix:«⊉», # U+2289 NEITHER A SUPERSET OF NOR EQUAL TO &infix:«≼», # U+227C PRECEDES OR EQUAL TO &infix:«≽», # U+227D SUCCEEDS OR EQUAL TO &infix:<⚛+=>, &infix:<⚛-=>, &infix:<⚛=>, &prefix:<++⚛>, &prefix:<--⚛>, &postfix:<⚛++>, &postfix:<⚛-->, # postfixes &postfix:«++», &postfix:«--», &postfix:«i», # postcircumfixes &postcircumfix:<[ ]>, &postcircumfix:<{ }>, ; } #line 1 SETTING::src/core.c/metaops.rakumod sub METAOP_ASSIGN(\op) is implementation-detail { Rakudo::Internals.METAOP_ASSIGN(op) } sub METAOP_TEST_ASSIGN:(\lhs, $rhs) is raw is implementation-detail { lhs // (lhs = $rhs()) } sub METAOP_TEST_ASSIGN:<||>(\lhs, $rhs) is raw is implementation-detail { lhs || (lhs = $rhs()) } sub METAOP_TEST_ASSIGN:<&&>(\lhs, $rhs) is raw is implementation-detail { lhs && (lhs = $rhs()) } sub METAOP_TEST_ASSIGN:(\lhs, $rhs) is raw is implementation-detail { lhs or (lhs = $rhs()) } sub METAOP_TEST_ASSIGN:(\lhs, $rhs) is raw is implementation-detail { lhs and (lhs = $rhs()) } sub METAOP_TEST_ASSIGN:(\lhs, $rhs) is raw is implementation-detail { lhs andthen (lhs = $rhs()) } sub METAOP_TEST_ASSIGN:(\lhs, $rhs) is raw is implementation-detail { lhs notandthen (lhs = $rhs()) } sub METAOP_TEST_ASSIGN:(\lhs, $rhs) is raw is implementation-detail { lhs orelse (lhs = $rhs()) } sub METAOP_NEGATE(\op) is implementation-detail { -> |c { c.elems > 1 ?? !op.(|c) !! True } } sub METAOP_REVERSE(\op) is implementation-detail { -> |args { op.(|args.reverse) } } sub METAOP_CROSS(\op, &reduce) is implementation-detail { nqp::if(op.thunky.starts-with('.'), -> +lol { my $rop = lol.elems == 2 ?? op !! &reduce(op); my $laze = False; my @loi is List = eager for lol -> \elem { if nqp::iscont(elem) { $laze = False; (elem,).iterator } else { $laze = True if elem.is-lazy; elem.iterator } } my Mu $cache := nqp::list(); my int $i = 0; for ^lol.elems { $i = $_; my Mu $rpa := nqp::list(); nqp::bindpos($cache, $i, $rpa); } my int $n = lol.elems - 1; my $j = 0; my @j; my @v; $i = 0; gather { while $i >= 0 { my Mu $sublist := nqp::atpos($cache, $i); if $j < nqp::elems($sublist) { my Mu $o := nqp::atpos($sublist, $j); @v[$i] := $o; $j = $j + 1; if $i >= $n { take lol.elems == 2 ?? $rop(|@v) !! $rop(@v); } else { $i = $i + 1; @j.push($j); $j = 0; } } elsif nqp::not_i(nqp::eqaddr((my \value = @loi[$i].pull-one),IterationEnd)) { nqp::bindpos($sublist, $j, value); redo; } else { $i = $i - 1; if $i { $j = @j.pop if $i > 0 } # continue previous dimension where we left off else { $j = 0; my Mu $sublist := nqp::atpos($cache,$i); nqp::pop($sublist); # don't cache 1st dimension (could be infinite) } } } }.lazy-if($laze); }, -> +lol { Seq.new(Rakudo::Iterator.CrossIterablesOp(lol,op)) } ) } sub METAOP_ZIP(\op, &reduce) is implementation-detail { nqp::if(op.thunky.starts-with('.'), -> +lol { my $arity = lol.elems; my $rop = $arity == 2 ?? op !! &reduce(op); my $laze = True; my @loi is List = eager for lol -> \elem { if nqp::iscont(elem) { $laze = False; Rakudo::Iterator.OneValue(elem) } else { $laze = False unless elem.is-lazy; Rakudo::Iterator.Whatever(elem.iterator) } } gather { loop { my \z = @loi.map: { my \value = .pull-one; last if nqp::eqaddr(value,IterationEnd); value }; my $z = List.from-iterator(z.iterator); $z.eager; last if $z.elems < $arity; take-rw $arity == 2 ?? $rop(|$z) !! $rop(@$z); } }.lazy-if($laze) }, -> +lol { Seq.new(Rakudo::Iterator.ZipIterablesOp(lol,op)) } ) } proto sub METAOP_REDUCE_LEFT(|) is implementation-detail {*} multi sub METAOP_REDUCE_LEFT(\op, \triangle) { if op.count > 2 and op.count < Inf { my $count = op.count; sub (+values) { my \source = values.iterator; my \first = source.pull-one; return () if nqp::eqaddr(first,IterationEnd); my @args.push: first; GATHER({ take first; until nqp::eqaddr((my \current = source.pull-one),IterationEnd) { @args.push: current; if @args.elems == $count { my \val = op.(|@args); take val; @args = (); @args.push: val; # use of push allows op to return a Slip } } }).lazy-if(source.is-lazy); } } else { sub (+values) { my \source = values.iterator; my \first = source.pull-one; return () if nqp::eqaddr(first,IterationEnd); my $result := first; GATHER({ take first; until nqp::eqaddr((my \value = source.pull-one),IterationEnd) { take ($result := op.($result, value)); } }).lazy-if(source.is-lazy); } } } multi sub METAOP_REDUCE_LEFT(\op) { if op.count > 2 and op.count < Inf { my $count = op.count; sub (+values) { my \iter = values.iterator; my \first = iter.pull-one; return op.() if nqp::eqaddr(first,IterationEnd); my @args.push: first; my $result := first; until nqp::eqaddr((my \value = iter.pull-one),IterationEnd) { @args.push: value; if @args.elems == $count { my \val = op.(|@args); @args = (); @args.push: val; # use of push allows op to return a Slip $result := val; } } $result; } } else { sub (+values) { my $iter := values.iterator; nqp::if( nqp::eqaddr((my $result := $iter.pull-one),IterationEnd), op.(), # identity nqp::if( nqp::eqaddr((my $value := $iter.pull-one),IterationEnd), nqp::if( nqp::isle_i(op.arity,1), op.($result), # can call with 1 param $result # what we got ), nqp::stmts( ($result := op.($result,$value)), nqp::until( nqp::eqaddr(($value := $iter.pull-one),IterationEnd), ($result := op.($result,$value)) ), $result # final result ) ) ) } } } proto sub METAOP_REDUCE_RIGHT(|) is implementation-detail {*} multi sub METAOP_REDUCE_RIGHT(\op, \triangle) { nqp::if( op.count < Inf && nqp::isgt_i((my int $count = op.count),2), sub (+values) { Seq.new(nqp::if( nqp::isge_i((my int $i = (my $v := nqp::if(nqp::istype(values,List),values,values.List) ).elems), # reifies $count ), # reifies class :: does Iterator { has $!op; has $!reified; has $!result; has int $!count; has int $!i; method !SET-SELF(\op,\list,\count,\index) { $!op := op; $!reified := nqp::getattr(list,List,'$!reified'); $!result := nqp::null; $!count = count; $!i = index; self } method new(\op,\list,\count,\index) { nqp::create(self)!SET-SELF(op,list,count,index) } method pull-one() is raw { nqp::if( nqp::isnull($!result), ($!result := nqp::atpos($!reified,--$!i)), nqp::stmts( (my $args := nqp::list($!result)), nqp::until( nqp::iseq_i(nqp::elems($args),$!count) || nqp::islt_i(--$!i,0), nqp::unshift($args,nqp::atpos($!reified,$!i)) ), nqp::if( nqp::isgt_i(nqp::elems($args),1), ($!result := op.(|nqp::hllize($args))), IterationEnd ) ) ) } }.new(op,$v,$count,$i), Rakudo::Iterator.OneValue( $i ?? op.(|nqp::getattr($v,List,'$!reified')) !! op.() ) )) }, sub (+values) { Seq.new(nqp::if( nqp::isgt_i((my int $i = (my $v := nqp::if(nqp::istype(values,List),values,values.List) ).elems), # reifies 1 ), class :: does Iterator { has $!op; has $!reified; has $!result; has int $!i; method !SET-SELF(\op,\list,\count) { $!op := op; $!reified := nqp::getattr(list,List,'$!reified'); $!result := nqp::null; $!i = count; self } method new(\op,\li,\co) { nqp::create(self)!SET-SELF(op,li,co) } method pull-one() is raw { nqp::if( nqp::isnull($!result), ($!result := nqp::atpos($!reified,--$!i)), nqp::if( nqp::isge_i(--$!i,0), ($!result := $!op.(nqp::atpos($!reified,$!i),$!result)), IterationEnd ) ) } }.new(op,$v,$i), Rakudo::Iterator.OneValue( $i ?? op.(nqp::atpos(nqp::getattr($v,List,'$!reified'),0)) !! op.() ) )) } ) } multi sub METAOP_REDUCE_RIGHT(\op) { nqp::if( op.count < Inf && nqp::isgt_i((my int $count = op.count),2), sub (+values) { nqp::if( nqp::isge_i((my int $i = (my $v := nqp::if(nqp::istype(values,List),values,values.List) ).elems), # reifies $count ), # reifies nqp::stmts( (my $args := nqp::list( my $result := nqp::atpos( (my $reified := nqp::getattr($v,List,'$!reified')), --$i ) )), nqp::until( nqp::islt_i(--$i,0), nqp::stmts( nqp::unshift($args,nqp::atpos($reified,$i)), nqp::if( nqp::iseq_i(nqp::elems($args),$count), nqp::stmts( ($result := op.(|nqp::hllize($args))), nqp::bindpos(nqp::setelems($args,1),0,$result) ) ) ) ), nqp::if( nqp::isgt_i(nqp::elems($args),1), op.(|nqp::hllize($args)), # something left to process $result ) ), nqp::if( $i, op.(|nqp::getattr($v,List,'$!reified')), op.() ) ) }, sub (+values) { nqp::if( nqp::isgt_i((my int $i = (my $v := nqp::if(nqp::istype(values,List),values,values.List) ).elems), # reifies 1 ), nqp::stmts( (my $result := nqp::atpos( nqp::getattr($v,List,'$!reified'), --$i )), nqp::while( nqp::isge_i(--$i,0), ($result := op.( nqp::atpos(nqp::getattr($v,List,'$!reified'),$i), $result )) ), $result ), nqp::if( $i, op.(nqp::atpos(nqp::getattr($v,List,'$!reified'),0)), op.() ) ) } ) } proto sub METAOP_REDUCE_LIST(|) is implementation-detail {*} multi sub METAOP_REDUCE_LIST(\op, \triangle) { sub (+values) { GATHER({ my @list; for values -> \v { @list.push(v); take op.(|@list); } }).lazy-if(values.is-lazy); } } multi sub METAOP_REDUCE_LIST(\op) { sub (+values) { op.(|values) } } proto sub METAOP_REDUCE_LISTINFIX(|) is implementation-detail {*} multi sub METAOP_REDUCE_LISTINFIX(\op, \triangle) { sub (|values) { my \p = values[0]; return () unless p.elems; my int $i; GATHER({ my @list; while $i < p.elems { @list.push(p[$i++]); take op.(|@list.map({nqp::decont($_)})); } }).lazy-if(p.is-lazy); } } multi sub METAOP_REDUCE_LISTINFIX(\op) { sub (+values) { op.(|values.map({nqp::decont($_)})); } } proto sub METAOP_REDUCE_CHAIN(|) is implementation-detail {*} multi sub METAOP_REDUCE_CHAIN(\op, \triangle) { sub (+values) { my $state = True; my \iter = values.iterator; my Mu $current = iter.pull-one; gather { take $state; while $state && nqp::not_i(nqp::eqaddr((my $next := iter.pull-one),IterationEnd)) { $state = op.($current, $next); take $state; $current := $next; } unless $state { take False until nqp::eqaddr(iter.pull-one,IterationEnd); } }.lazy-if(values.is-lazy); } } multi sub METAOP_REDUCE_CHAIN(\op) { sub (+values) { nqp::if( nqp::eqaddr( (my $current := (my $iter := values.iterator).pull-one), IterationEnd ), True, nqp::stmts( nqp::while( nqp::not_i(nqp::eqaddr((my $next := $iter.pull-one),IterationEnd)) && op.($current,$next), $current := $next ), nqp::hllbool(nqp::eqaddr($next,IterationEnd)) ) ) } } sub METAOP_REDUCE_XOR(\op, $triangle?) is implementation-detail { NYI('xor reduce').throw; } sub METAOP_HYPER(\op, *%opt) is implementation-detail { -> Mu \a, Mu \b { HYPER(op, a, b, |%opt) } } proto sub METAOP_HYPER_POSTFIX(|) is implementation-detail {*} multi sub METAOP_HYPER_POSTFIX(&op) { nqp::can(&op,"nodal") ?? *.nodemap(&op) !! *.deepmap(&op) } # no indirection for subscripts and such proto sub METAOP_HYPER_POSTFIX_ARGS(|) is implementation-detail {*} multi sub METAOP_HYPER_POSTFIX_ARGS(\obj, &op) { nqp::can(&op,"nodal") ?? obj.nodemap(&op) !! obj.deepmap(&op) } multi sub METAOP_HYPER_POSTFIX_ARGS(\obj, @args, &op) { nqp::can(&op,"nodal") ?? obj.nodemap(-> \o { op(o, @args) }) !! obj.deepmap(-> \o { op(o, @args) }) } multi sub METAOP_HYPER_POSTFIX_ARGS(\obj, \args, &op) { nqp::can(&op,"nodal") ?? obj.nodemap( -> \o { op(o,|args) }) !! obj.deepmap( -> \o { op(o,|args) }) } sub METAOP_HYPER_PREFIX(&op) is implementation-detail { nqp::can(&op,"nodal") ?? *.nodemap(&op) !! *.deepmap(&op) } sub METAOP_HYPER_CALL(\list, |args) is implementation-detail { list.deepmap(-> &code { code(|args) }) } sub HYPER(\operator, :$dwim-left, :$dwim-right, |c) is implementation-detail { Hyper.new(operator, :$dwim-left, :$dwim-right).infix(|c) } #line 1 SETTING::src/core.c/Deprecations.rakumod class Deprecation { has str $.file; # file of the code that is deprecated has str $.type; # type of code (sub/method etc.) that is deprecated has str $.package; # package of code that is deprecated has str $.name; # name of code that is deprecated has str $.alternative; # alternative for code that is deprecated has %.callsites; # places where called (file -> line -> count) has Version $.from; # release version from which deprecated has Version $.removed; # release version when will be removed my %DEPRECATIONS; # where we keep our deprecation info method DEPRECATIONS() is raw is implementation-detail { %DEPRECATIONS } multi method WHICH (Deprecation:D: --> ValueObjAt:D) { my $which := nqp::list_s("Deprecation"); nqp::push_s($which,$!file || ""); nqp::push_s($which,$!type || ""); nqp::push_s($which,$!package || ""); nqp::push_s($which,$!name || ""); nqp::box_s( nqp::join("|",$which), ValueObjAt ) } proto method report (|) {*} multi method report (Deprecation:U:) { return Nil unless %DEPRECATIONS; my $message = "Saw {+%DEPRECATIONS} occurrence{ 's' if +%DEPRECATIONS != 1 } of deprecated code.\n"; $message ~= ("=" x 80) ~ "\n"; for %DEPRECATIONS.sort(*.key)>>.value>>.report -> $r { $message ~= $r; $message ~= ("-" x 80) ~ "\n"; } %DEPRECATIONS = (); # reset for new batches if applicable $message.chop; } multi method report (Deprecation:D:) { my $type = $.type ?? "$.type " !! ""; my $name = $.name ?? "$.name " !! ""; my $package = $.package ?? "(from $.package) " !! ""; my $message = $type ~ $name ~ $package ~ "seen at:\n"; for %.callsites.kv -> $file, $lines { $message ~= " $file, line{ 's' if +$lines > 1 } { $lines.keys.sort(*.Int).join(',') }\n"; if $.from or $.removed { $message ~= $.from ?? "Deprecated since v$.from, will be removed" !! "Will be removed"; $message ~= $.removed ?? " with release v$.removed!\n" !! " sometime in the future\n"; } } $message ~= "Please use $.alternative instead.\n"; $message; } } class Rakudo::Deprecations { my %DEPRECATIONS := Deprecation.DEPRECATIONS; my $ver; method DEPRECATED( $alternative, $from?, $removed?, :$up = 1, :$what, :$file, :$line, Bool :$lang-vers ) is implementation-detail { $ver //= $*RAKU.compiler.version; my $version = $lang-vers ?? nqp::getcomp('Raku').language_version !! $ver; # if $lang-vers was given, treat the provided versions as language # versions, rather than compiler versions. Note that we can't # `state` the lang version (I think) because different CompUnits # might be using different versions. my Version $vfrom; my Version $vremoved; $from && nqp::iseq_i($version cmp ($vfrom = Version.new: $from), -1) && return; # not deprecated yet; $vremoved = Version.new($removed) if $removed; my $bt = Backtrace.new; my $deprecated = $bt[ my $index = $bt.next-interesting-index(1, :named, :setting, :reveal) // 0 ]; if $up ~~ Whatever { $index = $_ with $bt.next-interesting-index($index, :noproto); } else { for ^$up -> $level { $index = $_ with $bt.next-interesting-index($index, :noproto, :setting) } } my $callsite = $bt[$index]; # get object, existing or new my $dep = $what ?? Deprecation.new( :name($what), :$alternative, :from($vfrom), :removed($vremoved) ) !! Deprecation.new( file => $deprecated.file, type => $deprecated.subtype.tc, package => try { $deprecated.package.^name } // 'unknown', name => $deprecated.subname, :$alternative, :from($vfrom), :removed($vremoved), ); $dep = %DEPRECATIONS{$dep.WHICH} //= $dep; state $fatal = %*ENV; die $dep.report if $fatal; # update callsite ++$dep.callsites{$file // $callsite.file.IO}{$line // $callsite.line}; } } END { unless %*ENV { if Deprecation.report -> $message { note $message; # q:to/TEXT/ doesn't work in settings note 'Please contact the author to have these occurrences of deprecated code adapted, so that this message will disappear!'; } } } sub DEPRECATED(|c) is hidden-from-backtrace is implementation-detail { Rakudo::Deprecations.DEPRECATED(|c) } #line 1 SETTING::src/core.c/Thread.rakumod # Thread represents an OS-level thread. While it could be used directly, it # is not the preferred way to work in Raku. It's a building block for the # interesting things. my class Thread { # The VM-level thread handle. has Mu $!vm_thread; # Is the thread's lifetime bounded by that of the application, such # that when it exits, so does the thread? has Bool $.app_lifetime; # Thread's (user-defined) name. has Str $.name; my atomicint $started; my atomicint $aborted; my atomicint $completed; my atomicint $joined; my atomicint $yields; my atomicint $highest_id; submethod BUILD( :&code!, Bool() :$!app_lifetime = False, Str() :$!name = "" --> Nil ) { constant THREAD_ERROR = 'Could not create a new Thread: '; CATCH { when X::AdHoc { .payload.starts-with(THREAD_ERROR) ?? X::Exhausted.new( :what, :reason(.payload.substr(THREAD_ERROR.chars)) ).throw !! .rethrow } } # Make sure we have at least called nqp::cpucores once before # we start any thread. This to avoid issues on MacOS Monterey Kernel.cpu-cores-but-one; my $entry := anon sub THREAD-ENTRY() { my $*THREAD = self; CONTROL { default { ++⚛$aborted; my Mu $vm-ex := nqp::getattr(nqp::decont($_), Exception, '$!ex'); nqp::getcomp('Raku').handle-control($vm-ex); } } my $*STACK-ID = Rakudo::Internals.NEXT-ID; code(); ++⚛$completed; } $!vm_thread := nqp::newthread(nqp::getattr($entry, Code, '$!do'), $!app_lifetime ?? 1 !! 0); $highest_id ⚛= nqp::threadid($!vm_thread); } method start(Thread:U: &code, *%adverbs) { Thread.new(:&code, |%adverbs).run() } method run(Thread:D:) { ++⚛$started; nqp::threadrun($!vm_thread); self } method id(Thread:D:) { nqp::p6box_i(nqp::threadid($!vm_thread)); } method finish(Thread:D:) { nqp::threadjoin($!vm_thread); ++⚛$joined; self } method join(Thread:D:) { self.finish } multi method Numeric(Thread:D:) { self.id } multi method Str(Thread:D:) { "Thread<$.id>($.name)" } multi method gist(Thread:D:) { "Thread #$.id" ~ ($!name ne '' ?? " ($!name)" !! '') } method yield(Thread:U: --> Nil) { ++⚛$yields; nqp::threadyield(); } method is-initial-thread(--> Bool) { nqp::hllbool( nqp::iseq_i( nqp::threadid( nqp::if(nqp::isconcrete(self),$!vm_thread,nqp::currentthread) ), nqp::threadid(Rakudo::Internals.INITTHREAD) ) ) } method usage(Thread:U:) is raw { nqp::list_i($started,$aborted,$completed,$joined,$yields,$highest_id) } } Rakudo::Internals.REGISTER-DYNAMIC: '$*THREAD', { my $init_thread := nqp::create(Thread); nqp::bindattr($init_thread, Thread, '$!vm_thread', Rakudo::Internals.INITTHREAD); nqp::bindattr($init_thread, Thread, '$!app_lifetime', False); nqp::bindattr($init_thread, Thread, '$!name', 'Initial thread'); PROCESS::<$THREAD> := $init_thread; } #line 1 SETTING::src/core.c/Lock.rakumod # A reentrant lock mechanism with condition variable support. my class X::Lock::ConditionVariable::New is Exception { method message() { "Cannot directly create a ConditionVariable; use the 'condition' method on a lock" } } my class Lock { class ConditionVariable is repr('ConditionVariable') { method new() { X::Lock::ConditionVariable::New.new.throw } proto method wait(|) {*} multi method wait(--> Nil) { nqp::condwait(self) } multi method wait(&predicate --> Nil) { nqp::condwait(self) until predicate; } method signal() { nqp::condsignalone(self) } method signal_all() { nqp::condsignalall(self) } } method new() { nqp::create(self) } method lock(Lock:D:) { nqp::lock(self) } method unlock(Lock:D:) { nqp::unlock(self) } # use a multi to ensure LEAVE isn't run when bad args are given proto method protect(|) {*} multi method protect(Lock:D: &code) is raw { nqp::lock(self); LEAVE nqp::unlock(self); code() } method condition(Lock:D:) { nqp::getlockcondvar(self, ConditionVariable) } } #line 1 SETTING::src/core.c/Lock/Async.rakumod # An asynchronous lock provides a non-blocking non-reentrant mechanism for # mutual exclusion. The lock method returns a Promise, which will already be # Kept if nothing was holding the lock already, so execution can proceed # immediately. For performance reasons, in this case it returns a singleton # Promise instance. Otherwise, a Promise in planned state will be returned, # and Kept once the lock has been unlocked by its current holder. The lock # and unlock do not need to take place on the same thread; that's why it's not # reentrant. my class X::Lock::Async::NotLocked is Exception { method message() { "Cannot unlock a Lock::Async that is not currently locked" } } my class Lock::Async { # The Holder class is an immutable object. A type object represents an # unheld lock, an instance represents a held lock, and it has a queue of # vows to be kept on unlock. my class Holder { has $!queue; method queue-vow(\v) { my $new-queue := $!queue.DEFINITE ?? nqp::clone($!queue) !! nqp::list(); nqp::push($new-queue, v); nqp::p6bindattrinvres(nqp::create(Holder), Holder, '$!queue', $new-queue) } method waiter-queue-length() { nqp::elems($!queue) } # Assumes it won't be called if there is no queue (SINGLE_HOLDER case # in unlock()) method head-vow() { nqp::atpos($!queue, 0) } # Assumes it won't be called if the queue only had one item in it (to # mantain SINGLE_HOLDER fast path usage) method without-head-vow() { my $new-queue := nqp::clone($!queue); nqp::shift($new-queue); nqp::p6bindattrinvres(nqp::create(Holder), Holder, '$!queue', $new-queue) } } # Base states for Holder my constant NO_HOLDER = Holder; my constant SINGLE_HOLDER = nqp::create(Holder); # The current holder record, with waiters queue, of the lock. has Holder $!holder = Holder; # Singleton Promise to be used when there's no need to wait. my $KEPT-PROMISE := nqp::null; method lock(Lock::Async:D: --> Promise) { loop { my $holder := ⚛$!holder; if $holder.DEFINITE { my $p := Promise.new; my $v := $p.vow; my $holder-update := $holder.queue-vow($v); if nqp::eqaddr(nqp::cas($!holder, $holder, $holder-update),$holder) { return $p; } } elsif nqp::eqaddr(nqp::cas($!holder, NO_HOLDER, SINGLE_HOLDER),NO_HOLDER) { # Successfully acquired and we're the only holder return nqp::ifnull($KEPT-PROMISE,$KEPT-PROMISE := Promise.kept); } } } method unlock(Lock::Async:D: --> Nil) { loop { my $holder := ⚛$!holder; if nqp::eqaddr($holder,SINGLE_HOLDER) { # We're the single holder and there's no wait queue. if nqp::eqaddr(nqp::cas($!holder, SINGLE_HOLDER, NO_HOLDER),SINGLE_HOLDER) { # Successfully released to NO_HOLDER state. return; } } elsif $holder.DEFINITE { my int $queue-length = $holder.waiter-queue-length(); my $v := $holder.head-vow; if $queue-length == 1 { if nqp::eqaddr(nqp::cas($!holder, $holder, SINGLE_HOLDER),$holder) { # Successfully released; keep the head vow, thus # giving the lock to the next waiter. $v.keep(True); return; } } else { my $new-holder := $holder.without-head-vow(); if nqp::eqaddr(nqp::cas($!holder, $holder, $new-holder),$holder) { # Successfully released and installed remaining queue; # keep the head vow which we successfully removed. $v.keep(True); return; } } } else { X::Lock::Async::NotLocked.new.throw } } } proto method protect(|) {*} multi method protect(Lock::Async:D: &code) is raw { my int $acquired = 0; $*AWAITER.await(self.lock()); $acquired = 1; LEAVE self.unlock() if $acquired; code() } # This either runs the code now if we can obtain the lock, releasing the # lock afterwards, or queues the code to run if a recursive use of the # lock is observed. It relies on all users of the lock to use it through # this method only. This is useful for providing back-pressure while also # avoiding code deadlocking on itself by providing a way for it to get run # later on. Returns Nil if the code was run now (maybe after blocking), or # a Promise if it was queued for running later. method protect-or-queue-on-recursion(Lock::Async:D: &code) { my $try-acquire := self.lock(); if $try-acquire { # We could acquire the lock. Run the code right now. self!run-with-updated-recursion-list(&code); Nil } elsif self!on-recursion-list() { # Lock is already held on the stack, so we're recursing. Queue. $try-acquire.then({ self!run-with-updated-recursion-list(&code); }); } else { # Lock is held but by something else. Await its availability. $*AWAITER.await($try-acquire); self!run-with-updated-recursion-list(&code); Nil } } method !on-recursion-list() { my $rec-list := nqp::getlexdyn('$*LOCK-ASYNC-RECURSION-LIST'); nqp::isnull($rec-list) ?? False !! self!search-recursion-list($rec-list) } method !search-recursion-list(IterationBuffer \rec-list) { my int $n = nqp::elems(rec-list); loop (my int $i = 0; $i < $n; ++$i) { return True if nqp::eqaddr(nqp::atpos(rec-list, $i), self); } False } method !run-with-updated-recursion-list(&code) { LEAVE self.unlock(); my $current := nqp::getlexdyn('$*LOCK-ASYNC-RECURSION-LIST'); my $new-held := nqp::isnull($current) ?? nqp::create(IterationBuffer) !! nqp::clone($current); nqp::push($new-held, self); self!run-under-recursion-list($new-held, &code); } method with-lock-hidden-from-recursion-check(&code) { my $current := nqp::getlexdyn('$*LOCK-ASYNC-RECURSION-LIST'); nqp::isnull($current) ?? code() !! self!hidden-in-recursion-list($current, &code) } method !hidden-in-recursion-list(IterationBuffer \current, &code) { my $new-held := nqp::create(IterationBuffer); my int $n = nqp::elems(current); loop (my int $i = 0; $i < $n; ++$i) { my $lock := nqp::atpos(current, $i); nqp::push($new-held, $lock) unless nqp::eqaddr($lock, self); } self!run-under-recursion-list($new-held, &code); } method !run-under-recursion-list(IterationBuffer $*LOCK-ASYNC-RECURSION-LIST, &code) { code() } } #line 1 SETTING::src/core.c/Lock/Soft.rakumod my class X::Lock::Unlock::NoMutex is Exception { method message { "Attempt to unlock mutex not held yet" } } my class X::Lock::Unlock::WrongThread is Exception { method message { "Attempt to unlock mutex by thread not holding it" } } my class X::Lock::ConditionVariable::Duplicate is Exception { method message { "Lock already has a condition variable" } } my class X::Lock::ConditionVariable::NoMutex is Exception { method message { "Can only wait on a condition variable when holding mutex" } } my class X::Lock::ConditionVariable::WrongThread is Exception { method message { "Can only call wait on the thread which holds the mutex" } } my class Lock::Soft { my class ConditionVariable {...} trusts ConditionVariable; my class Node { has $!promise; has Int $!stack-id; has int $!holders; my $KEPT-PROMISE := nqp::null(); method !SET-SELF($kept is raw) { $!stack-id := +$*STACK-ID; $!holders = 1; $!promise := nqp::if($kept, nqp::ifnull($KEPT-PROMISE, ($KEPT-PROMISE := Promise.kept)), Promise.new); self } method new($kept is raw) { nqp::create(self)!SET-SELF($kept) } method acquire { ++$!holders; } method release { die "Too many calls to release: " ~ nqp::abs_i($!holders) if --$!holders < 0; $!holders } method keep-promise { $!promise.keep(True) } } my class ConditionVariable { trusts Lock::Soft; my class CondNode { has $.node; has &.predicate; has $.promise; method !SET-SELF($!node, &!predicate) { $!promise := Promise.new; self } method new($node, &predicate) { nqp::create(self)!SET-SELF($node, &predicate) } } has Lock::Soft $.lock; has Mu $!wait-list; has atomicint $!signals; method !SET-SELF($lock is raw) { $!lock := $lock; $!wait-list := nqp::list(); $!signals = 0; self } method new(Lock::Soft:D $lock) { X::Lock::ConditionVariable::Duplicate.new.throw with nqp::getattr(nqp::decont($lock), Lock::Soft, '$!cond'); nqp::create(self)!SET-SELF($lock) } # Since wait can only be called from within a held mutex there is no need to protect condition variable # structure integrity. method wait(&predicate?) { my $stack-id := +$*STACK-ID; my $queue := nqp::getattr($!lock, Lock::Soft, '$!queue'); X::Lock::ConditionVariable::NoMutex.new.throw unless nqp::elems($queue); # We need to do nothing if the predicate is already true. return if &predicate andthen .(); my $owner := nqp::atpos($queue, 0 ); X::Lock::ConditionVariable::WrongThread.new.throw unless nqp::getattr($owner, Node, '$!stack-id') == $stack-id; $!signals ⚛= 0 unless nqp::elems($!wait-list); my $cnode := CondNode.new($owner, &predicate); nqp::push($!wait-list, $cnode); # This thread will be awaiting for the condition, release the next one in the queue $!lock!Lock::Soft::shift-node(:unlock); $*AWAITER.await: $cnode.promise; } method signal { ++⚛$!signals unless ⚛$!signals < 0; self!release-waiting if nqp::elems($!wait-list) && $!lock!Lock::Soft::try-acquire-lock; } method signal_all { $!signals ⚛= -1; self!release-waiting if nqp::elems($!wait-list) && $!lock!Lock::Soft::try-acquire-lock; } # Return true if a waiting thread has been released. We always release only one awaiting mutex, event if # signall_all has been called. In the latter case we rely upon unlocking of a previously released mutext to # release the next one in the waiting list. And so on until there is any releasable remaining. method !release-waiting { my $waiting := nqp::elems($!wait-list); return False unless $waiting && $!signals; loop (my $i = 0; $i < $waiting; ++$i) { my $cnode := nqp::atpos($!wait-list, $i); if !$cnode.predicate || $cnode.predicate.() { $!wait-list := nqp::splice($!wait-list, nqp::list(), $i, 1); $!lock!Lock::Soft::replace-owner($cnode.node); --⚛$!signals if $!signals > 0; # Release the waiting thread $cnode.promise.keep(True); return True; } } False; } } has $!queue; has ConditionVariable $!cond; method !SET-SELF { $!queue := nqp::list(); self } method new { nqp::create(nqp::what(self))!SET-SELF } method !shift-node(:$unlock) { loop { my $queue := $!queue; nqp::shift(my $updated := nqp::clone($queue)); if nqp::eqaddr(nqp::casattr(self, ::?CLASS, '$!queue', $queue, $updated), $queue) { if $unlock { nqp::if( nqp::elems($updated), nqp::getattr(nqp::atpos($updated,0), Node, '$!promise').keep); } return nqp::atpos($queue, 0); } } } method !replace-owner($node is raw) { loop { my $queue := $!queue; my $updated := nqp::clone($queue); nqp::bindpos($updated, 0, $node); if nqp::eqaddr(nqp::casattr(self, ::?CLASS, '$!queue', $queue, $updated), $queue) { return } } } method !try-acquire-lock { my $queue := $!queue; return 0 if nqp::elems($queue); my $node := Node.new; my $updated := nqp::clone($queue); nqp::push($updated, $node); nqp::eqaddr(nqp::casattr(self, ::?CLASS, '$!queue', $queue, $updated), $queue) } # Note that the meaning of returned promise is different from Lock::Async method lock(--> Nil) { my $stack-id := +$*STACK-ID; # Reduce dynamic lookups by caching my $node-kept := nqp::null(); my $node-unkept := nqp::null(); my $promise; until nqp::defined($promise) { my $queue := $!queue; my $elems := nqp::elems($queue); my $owner := nqp::atpos($queue, 0); if $elems and nqp::getattr($owner, Node, '$!stack-id') == $stack-id { # Recursive lock $owner.acquire; return } my $updated := nqp::clone($queue); nqp::push($updated, nqp::if( $elems, nqp::ifnull($node-unkept, ($node-unkept := Node.new(0))), nqp::ifnull($node-kept, ($node-kept := Node.new(1))))); if nqp::eqaddr(nqp::casattr(self, Lock::Soft, '$!queue', $queue, $updated), $queue) { $promise := nqp::getattr(nqp::if($elems, $node-unkept, $node-kept), Node, '$!promise'); } } # Await for our turn unless we're first on the queue $*AWAITER.await: $promise; } method unlock(--> Nil) { my $stack-id := +$*STACK-ID; my $queue := $!queue; X::Lock::Unlock::NoMutex.new.throw unless nqp::elems($queue); my $owner := nqp::atpos($queue, 0); X::Lock::Unlock::WrongThread.new.throw unless nqp::getattr($owner, Node, '$!stack-id') == $stack-id; unless $owner.release { # We only pull the owner from the list when no condition is to be fulfilled. In the later case the owner # item will be replaced with a waiting thread. unless nqp::defined($!cond) && $!cond!ConditionVariable::release-waiting { self!shift-node(:unlock); } return } } proto method protect(|) {*} multi method protect(::?CLASS:D: &code --> Mu) is raw { self.lock; LEAVE self.unlock; code() } method condition { without ⚛$!cond { nqp::cas($!cond, ConditionVariable, ConditionVariable.new(self)); } $!cond } } #line 1 SETTING::src/core.c/Semaphore.rakumod my class Semaphore is repr('Semaphore') { method new(int $permits) { nqp::box_i($permits, Semaphore); } method acquire() { nqp::semacquire(self); } method try_acquire(--> Bool:D) { nqp::hllbool(nqp::semtryacquire(self)) } method release() { nqp::semrelease(self); } } #line 1 SETTING::src/core.c/Cancellation.rakumod my class Cancellation { has $.cancelled; has $!lock; has @!async_handles; submethod BUILD(:@!async_handles --> Nil) { $!cancelled = False; $!lock = Lock.new; } method cancel() { $!lock.protect({ unless $!cancelled { for @!async_handles { nqp::cancel(nqp::decont($_)); } $!cancelled = True; } }) } } #line 1 SETTING::src/core.c/Awaitable.rakumod # An Awaitable is something we can use the `await` operator on. To support # this, it requires a `get-await-handle` method be implemented, which returns # an `Awaitable::AwaitHandle`. my role Awaitable { method get-await-handle() { ... } } # An Awaitable::Handle implementation is an immutable object that conveys the # status of the requested asynchronous result at the point we obtain the # handle. If the `.already` property is `True`, then there is no need to block # or suspend execution; the `.result` or `.cause` of failure can be used right # away (depending on the value of `.success). Otherwise, the consumer of the # handle should call the `subscribe-awaiter` method with its unblock/resume # handler, and then proceed to block/suspend. In this case, the handler will # be passed two arguments: a `Bool` success, and a result/cause (result if # success is `True`, cause if it's `False`). The `Awaitable::Handle` will # *not* have its success/result/cause updated; this would open the door to # data races (including subtle ones related to read/write ordering), when # the point of the fast-path is to test if we've got a result already with # minimal overhead (and thus minimal concurrency control). my role Awaitable::Handle { has Bool $.already; has Bool $.success; has Mu $.result; has Exception $.cause; method already-success(Mu \result) { nqp::create(self)!ALREADY_SUCCESS(result) } method !ALREADY_SUCCESS(Mu \result) { $!already := $!success := True; $!result := result; self } method already-failure(Mu \cause) { nqp::create(self)!ALREADY_FAILURE(cause) } method !ALREADY_FAILURE(Mu \cause) { $!already := True; $!success := False; $!cause := cause; self } method subscribe-awaiter(&subscriber) { ... } } #line 1 SETTING::src/core.c/Awaiter.rakumod my role Awaiter { method await(Awaitable:D $a) { ... } method await-all(Iterable:D $i) { ... } } my class Awaiter::Blocking does Awaiter { method await(Awaitable:D $a) { my $handle := $a.get-await-handle; if $handle.already { $handle.success ?? $handle.result !! $handle.cause.rethrow } else { my $s = Semaphore.new(0); my $success; my $result; $handle.subscribe-awaiter(-> \success, \result { $success := success; $result := result; $s.release; }); $s.acquire; $success ?? $result !! $result.rethrow } } method await-all(Iterable:D \i) { # Collect results that are already available, and handles where the # results are not yet available together with the matching insertion # indices. my \results = nqp::list(); my \handles = nqp::list(); my \indices = nqp::list_i(); my int $insert = 0; my $saw-slip = False; for i -> $awaitable { unless nqp::istype($awaitable, Awaitable) { die "Can only specify Awaitable objects to await (got a $awaitable.^name())"; } unless nqp::isconcrete($awaitable) { die "Must specify a defined Awaitable to await (got an undefined $awaitable.^name())"; } my $handle := $awaitable.get-await-handle; if $handle.already { if $handle.success { my \result = $handle.result; nqp::bindpos(results, $insert, result); $saw-slip = True if nqp::istype(result, Slip); } else { $handle.cause.rethrow } } else { nqp::push(handles, $handle); nqp::push_i(indices, $insert); } ++$insert; } # See if we have anything that we need to really block on. If so, we # use a lock and condition variable to handle the blocking. The lock # protects writes into the array. my int $num-handles = nqp::elems(handles); if $num-handles { my $exception = Mu; my $l = Lock.new; my $ready = $l.condition(); my int $remaining = $num-handles; loop (my int $i = 0; $i < $num-handles; ++$i) { my $handle := nqp::atpos(handles, $i); my int $insert = nqp::atpos_i(indices, $i); $handle.subscribe-awaiter(-> \success, \result { $l.protect: { if success && $remaining { nqp::bindpos(results, $insert, result); $saw-slip = True if nqp::istype(result, Slip); --$remaining; $ready.signal unless $remaining; } elsif !nqp::isconcrete($exception) { $exception := result; $remaining = 0; $ready.signal; } } }); } $l.protect: { $ready.wait: { $remaining == 0 } } # If we got an exception, throw it. $exception.rethrow if nqp::isconcrete($exception); } my \result-list = nqp::p6bindattrinvres(nqp::create(List), List, '$!reified', results); $saw-slip ?? result-list.map(-> \val { val }).List !! result-list } } PROCESS::<$AWAITER> := Awaiter::Blocking; #line 1 SETTING::src/core.c/Scheduler.rakumod # Schedulers do this role. It mostly serves as an interface for the things # that schedulers must do, as well as a way to factor out some common "sugar" # and infrastructure. my class X::Scheduler::CueInNaNSeconds is Exception { method message(--> Str) { 'Cannot pass NaN as a number of seconds to Scheduler.cue' } } my role Scheduler { has &.uncaught_handler is rw; method handle_uncaught($exception) { my $ch = &!uncaught_handler; if $ch { $ch($exception); } else { # No default handler, so terminate the application. note "Unhandled exception in code scheduled on thread " ~ $*THREAD.id; if Rakudo::Internals.LL-EXCEPTION { note $exception.message; note $exception.backtrace.full; } else { note $exception.gist; } exit(1); } } method cue { ... } method loads() { ... } } #line 1 SETTING::src/core.c/Env.rakumod { my $CWD := nqp::p6box_s(nqp::cwd()); PROCESS::<$CWD> = IO::Path.new($CWD, :$CWD); # need :CWD to prevent looping } #line 1 SETTING::src/core.c/ThreadPoolScheduler.rakumod my class ThreadPoolScheduler does Scheduler { # A concurrent, blocking-on-receive queue. my class Queue is repr('ConcBlockingQueue') { method elems() is raw { nqp::elems(self) } } # Initialize $*PID here, as we need it for the debug message # anyway *and* it appears to have a positive effect on stability # specifically wrt GH #1202. PROCESS::<$PID> := nqp::p6box_i(my int $pid = nqp::getpid); my constant UNLIMITED_THREADS = 9223372036854775807; # 2⁶³-1 # Scheduler defaults controlled by environment variables my $ENV := nqp::getattr(%*ENV,Map,'$!storage'); my int $scheduler-debug; $scheduler-debug = 1 if nqp::atkey($ENV,'RAKUDO_SCHEDULER_DEBUG'); my int $scheduler-debug-status; $scheduler-debug-status = 1 if nqp::atkey($ENV,'RAKUDO_SCHEDULER_DEBUG_STATUS'); sub scheduler-debug($message --> Nil) { if $scheduler-debug { note "[SCHEDULER $pid] $message"; } } # Infrastructure for non-blocking `await` for code running on the # scheduler. my constant THREAD_POOL_PROMPT = Mu.new; my class ContinuationWrapper { has $.cont; method new(Mu \cont) { nqp::p6bindattrinvres(nqp::create(self), ContinuationWrapper, '$!cont', cont) } } class ThreadPoolAwaiter does Awaiter { has $!queue; submethod BUILD(:$queue!) { $!queue := nqp::decont($queue); } sub holding-locks() { nqp::hllbool(nqp::threadlockcount(nqp::currentthread())) } method await(Awaitable:D $a) { holding-locks() || !nqp::isnull(nqp::getlexdyn('$*RAKUDO-AWAIT-BLOCKING')) ?? Awaiter::Blocking.await($a) !! self!do-await($a) } method !do-await(Awaitable:D $a) { my $handle := $a.get-await-handle; if $handle.already { $handle.success ?? $handle.result !! $handle.cause.rethrow } else { my $success; my $result; nqp::continuationcontrol(1, THREAD_POOL_PROMPT, nqp::getattr(-> Mu \c { $handle.subscribe-awaiter(-> \success, \result { $success := success; $result := result; nqp::push($!queue, ContinuationWrapper.new(c)); Nil }); }, Code, '$!do')); $success ?? $result !! $result.rethrow } } method await-all(Iterable:D \i) { holding-locks() || !nqp::isnull(nqp::getlexdyn('$*RAKUDO-AWAIT-BLOCKING')) ?? Awaiter::Blocking.await-all(i) !! self!do-await-all(i) } method !do-await-all(Iterable:D \i) { # Collect results that are already available, and handles where the # results are not yet available together with the matching insertion # indices. my \results = nqp::list(); my \handles = nqp::list(); my \indices = nqp::list_i(); my int $insert = 0; my $saw-slip = False; for i -> $awaitable { unless nqp::istype($awaitable, Awaitable) { die "Can only specify Awaitable objects to await (got a $awaitable.^name())"; } unless nqp::isconcrete($awaitable) { die "Must specify a defined Awaitable to await (got an undefined $awaitable.^name())"; } my $handle := $awaitable.get-await-handle; if $handle.already { if $handle.success { my \result = $handle.result; nqp::bindpos(results, $insert, result); $saw-slip = True if nqp::istype(result, Slip); } else { $handle.cause.rethrow } } else { nqp::push(handles, $handle); nqp::push_i(indices, $insert); } ++$insert; } # See if we have anything that we really need to suspend for. If # so, we need to take great care that the continuation taking is # complete before we try to resume it (completions can happen on # different threads, and so concurrent with us subscribing, not # to mention concurrent with each other wanting to resume). We # use a lock to take care of this, holding the lock until the # continuation has been taken. my int $num-handles = nqp::elems(handles); if $num-handles { my $continuation; my $exception; my $l = Lock.new; $l.lock; { CATCH { # Unlock if we fail here, and let the exception # propagate outwards. $l.unlock(); } my int $remaining = $num-handles; loop (my int $i = 0; $i < $num-handles; ++$i) { my $handle := nqp::atpos(handles, $i); my int $insert = nqp::atpos_i(indices, $i); $handle.subscribe-awaiter(-> \success, \result { my int $resume; $l.protect: { if success && $remaining { nqp::bindpos(results, $insert, result); $saw-slip = True if nqp::istype(result, Slip); --$remaining; $resume = 1 unless $remaining; } elsif !nqp::isconcrete($exception) { $exception := result; $remaining = 0; $resume = 1; } } if $resume { nqp::push($!queue, { $l.lock; # lock gets released as soon as $continuation is initialized $l.unlock; # no need to hold the lock while running the continuation - no one else is gonna take it nqp::continuationinvoke($continuation, nqp::null()) }); } }); } } nqp::continuationcontrol(1, THREAD_POOL_PROMPT, nqp::getattr(-> Mu \c { $continuation := c; $l.unlock; }, Code, '$!do')); # If we got an exception, throw it. $exception.rethrow if nqp::isconcrete($exception); } my \result-list = nqp::p6bindattrinvres(nqp::create(List), List, '$!reified', results); $saw-slip ?? result-list.map(-> \val { val }).List !! result-list } } # There are three kinds of worker: # * General worker threads all pull from the main queue. If they have no # work, they may steal from timer threads. # * Timer worker threads are intended to handle time-based events. They # pull events from the time-sensitive queue, and they will not do any # work stealing so as to be ready and available for timer events. The # time-sensitive queue will only be returned when a queue is requested # with the :hint-time-sensitive named argument. Only one timer worker # will be created on the first request for such a queue; the supervisor # will then monitor the time-sensitive queue length and add more if # needed. # * Affinity worker threads each have their own queue. They are used when # a queue is requested and :hint-affinity is passed. These are useful # for things like Proc::Async and IO::Socket::Async, where events will # be processed using a Supply, which is serial, and so there's no point # at all in contending over the data. Work will not be stolen from an # affinity worker thread. my role Worker { has $.thread; has $!scheduler; # Completed is the number of tasks completed since the last time the # supervisor checked in. has atomicint $.completed; # Total number of tasks completed since creation. has int $.total; # Working is 1 if the worker is currently busy, 0 if not. has int $.working; # Number of times take-completed has returned zero in a row. has int $.times-nothing-completed; # Resets the completed to zero and updates the total. method take-completed() { my atomicint $taken; cas $!completed, -> atomicint $current { $taken = $current; 0 } if $taken == 0 { ++$!times-nothing-completed; } else { $!times-nothing-completed = 0; } $taken } method !run-one(\task --> Nil) { $!working = 1; if nqp::istype(task, ContinuationWrapper) { nqp::continuationinvoke(task.cont, nqp::null()); } else { nqp::continuationreset(THREAD_POOL_PROMPT, nqp::getattr({ CATCH { default { $!scheduler.handle_uncaught($_) } } CONTROL { default { my Mu $vm-ex := nqp::getattr(nqp::decont($_), Exception, '$!ex'); nqp::getcomp('Raku').handle-control($vm-ex); } } # Consider $*STACK-ID as a kind of thread logical ID. my $*STACK-ID := Rakudo::Internals.NEXT-ID; if nqp::istype(task, List) { my Mu $code := nqp::shift(nqp::getattr(task, List, '$!reified')); $code(|task); } else { task.(); } }, Code, '$!do')); } $!working = 0; ++⚛$!completed; ++$!total; } } my class GeneralWorker does Worker { submethod TWEAK(Queue:D :$queue!, :$!scheduler!) { $!thread = Thread.start(:app_lifetime, :name, { my $*AWAITER := ThreadPoolAwaiter.new(:$queue); loop { self!run-one(nqp::shift($queue)); } }); } } my class TimerWorker does Worker { submethod TWEAK(Queue:D :$queue!, :$!scheduler!) { $!thread = Thread.start(:app_lifetime, :name, { my $*AWAITER := ThreadPoolAwaiter.new(:$queue); loop { self!run-one(nqp::shift($queue)); } }); } } my class AffinityWorker does Worker { has Queue $.queue; submethod TWEAK(:$!scheduler!) { my $queue := $!queue := Queue.CREATE; $!thread = Thread.start(:app_lifetime, :name, { my $*AWAITER := ThreadPoolAwaiter.new(:$!queue); loop { self!run-one(nqp::shift($queue)); } }); } } # Initial and maximum threads allowed. has uint $!max_threads; # All of the worker and queue state below is guarded by this lock. has Lock $!state-lock; # The general queue and timer queue, if created. has Queue $!general-queue; has Queue $!timer-queue; # The current lists of workers. Immutable lists; new ones are produced # upon changes. has $!general-workers; has $!timer-workers; has $!affinity-workers; # The supervisor thread, if started. has Thread $!supervisor; method !general-queue() { nqp::if( nqp::isconcrete($!general-queue), $!general-queue, nqp::stmts( $!state-lock.protect( { nqp::unless( nqp::isconcrete($!general-queue), nqp::stmts( # We don't have any workers yet, so start one. ($!general-queue := nqp::create(Queue)), ($!general-workers := first-worker( GeneralWorker.new( queue => $!general-queue, scheduler => self ) )), scheduler-debug("Created initial general worker thread"), self!maybe-start-supervisor ) ) } ), $!general-queue ) ) } method !timer-queue() { nqp::if( nqp::isconcrete($!timer-queue), $!timer-queue, nqp::stmts( $!state-lock.protect( { nqp::unless( nqp::isconcrete($!timer-queue), nqp::stmts( # We don't have any workers yet, so start one. ($!timer-queue := nqp::create(Queue)), ($!timer-workers := first-worker( TimerWorker.new( queue => $!timer-queue, scheduler => self ) )), scheduler-debug("Created initial timer worker thread"), self!maybe-start-supervisor ) ) } ), $!timer-queue ) ) } # set up affinity threshold information my $affinity-add-thresholds := nqp::list_i(0, 1, 5, 10, 20, 50, 100); my int $affinity-max-index = nqp::sub_i(nqp::elems($affinity-add-thresholds),1); my $affinity-max-threshold = nqp::atpos_i($affinity-add-thresholds,$affinity-max-index ); method !affinity-queue() { nqp::stmts( # If there are no affinity workers, start one. nqp::unless( nqp::elems(my $cur-affinity-workers := $!affinity-workers), nqp::stmts( $!state-lock.protect( { nqp::unless( nqp::elems($!affinity-workers), nqp::stmts( # We don't have any affinity workers yet, so start one # and return its queue. ($!affinity-workers := first-worker( AffinityWorker.new( scheduler => self ) )), scheduler-debug("Created initial affinity worker thread"), self!maybe-start-supervisor, (return nqp::atpos($!affinity-workers,0).queue) ) ) } ), ($cur-affinity-workers := $!affinity-workers) # lost race ) ), # Otherwise, see which has the least load (this is inherently racey # and approximate, but enough to help us avoid a busy worker). If we # find an empty queue, return it immediately. (my int $i = -1), nqp::while( nqp::islt_i(++$i,nqp::elems($cur-affinity-workers)), nqp::if( nqp::isconcrete(my $most-free-worker), nqp::stmts( (my $cand := nqp::atpos($cur-affinity-workers,$i)), nqp::unless( nqp::elems(my $queue := $cand.queue), nqp::unless( $cand.working, (return $queue), ), ), nqp::if( nqp::islt_i( nqp::elems($queue), nqp::elems($most-free-worker.queue) ), $most-free-worker := $cand ) ), ($most-free-worker := nqp::atpos($cur-affinity-workers,$i)) ) ), # Otherwise, check if the queue beats the threshold to add another # worker thread. nqp::if( nqp::isle_i( nqp::elems(my $chosen-queue := $most-free-worker.queue), nqp::if( nqp::islt_i( nqp::elems($cur-affinity-workers), $affinity-max-index ), nqp::atpos_i( $affinity-add-thresholds, nqp::elems($cur-affinity-workers) ), $affinity-max-threshold, ) ), # found one that is empty enough $chosen-queue, # need to add another one, unless another thread did already $!state-lock.protect( { nqp::stmts( nqp::if( nqp::isgt_i( nqp::elems($!general-workers) + nqp::elems($!timer-workers) + nqp::elems($!affinity-workers), $!max_threads ), # alas, no way to add more threads nqp::stmts( scheduler-debug("Will not add extra affinity worker; hit $!max_threads thread limit"), (return $chosen-queue) ) ), nqp::if( nqp::isne_i( nqp::elems($cur-affinity-workers), nqp::elems($!affinity-workers) ), # different load found, take this one (return $chosen-queue) ), # ok ok, add new worker ($!affinity-workers := push-worker( $!affinity-workers, (my $new-worker := AffinityWorker.new(scheduler => self)) )), scheduler-debug("Added an affinity worker thread"), $new-worker.queue ) } ) ) ) } # Initializing a worker list with a worker, is straightforward and devoid # of concurrency issues, as we're already in protected code when we do this. sub first-worker(\first) is raw { my $workers := nqp::create(IterationBuffer); nqp::push($workers,first); $workers } # Since the worker lists can be changed during copying, we need to # just take whatever we can get and assume that it may be gone by # the time we get to it. sub push-worker(\workers, \to-push) is raw { my $new-workers := nqp::clone(workers); nqp::push($new-workers,to-push); $new-workers } # The supervisor sits in a loop, mostly sleeping. Each time it wakes up, # it takes stock of the current situation and decides whether or not to # add threads. my constant SUPERVISION_INTERVAL = 1e-2; my constant NUM_SAMPLES = 5; my constant NUM_SAMPLES_NUM = 5e0; my constant EXHAUSTED_RETRY_AFTER = 100; method !maybe-start-supervisor(--> Nil) { unless $!supervisor.DEFINITE { my int $cpu-cores = Kernel.cpu-cores-but-one; $!supervisor = Thread.start(:app_lifetime, :name, { sub add-general-worker(--> Nil) { $!state-lock.protect: { $!general-workers := push-worker( $!general-workers, GeneralWorker.new( queue => $!general-queue, scheduler => self ) ); } scheduler-debug "Added a general worker thread"; } sub add-timer-worker(--> Nil) { $!state-lock.protect: { $!timer-workers := push-worker( $!timer-workers, TimerWorker.new( queue => $!timer-queue, scheduler => self ) ); } scheduler-debug "Added a timer worker thread"; } scheduler-debug "Supervisor started"; my int $last-rusage-time = nqp::time; my int @rusage; nqp::getrusage(@rusage); my int $last-usage = nqp::mul_i( nqp::atpos_i(@rusage,nqp::const::RUSAGE_UTIME_SEC), 1000000 ) + nqp::atpos_i(@rusage,nqp::const::RUSAGE_UTIME_MSEC) + nqp::mul_i( nqp::atpos_i(@rusage,nqp::const::RUSAGE_STIME_SEC), 1000000 ) + nqp::atpos_i(@rusage, nqp::const::RUSAGE_STIME_MSEC); my num @last-utils = 0e0 xx NUM_SAMPLES; # These definitions used to live inside the supervisor loop. # Moving them out of the loop does not improve CPU usage # noticably, but does seem to save about 3M of memory for # every 10 seconds of runtime. Whether this is an actual # leak, or just less churn on garbage collection, remains # unclear until we have profiling options that also work # when multiple threads are running. my int $exhausted; my int $now; my int $rusage-period; my int $current-usage; my int $usage-delta; my num $normalized-delta; my num $per-core; my num $per-core-util; my num $smooth-per-core-util = 0e0; scheduler-debug "Supervisor thinks there are $cpu-cores CPU cores"; loop { CATCH { when X::Exhausted { $exhausted = 1; scheduler-debug .message; scheduler-debug "Refraining from trying to start new threads"; } default { scheduler-debug .gist; } } # Wait until the next time we should check how things # are. nqp::sleep(SUPERVISION_INTERVAL); # Work out the delta of CPU usage since last supervision # and the time period that measurement spans. $now = nqp::time; $rusage-period = $now - $last-rusage-time; $last-rusage-time = $now; nqp::getrusage(@rusage); $current-usage = nqp::mul_i( nqp::atpos_i(@rusage,nqp::const::RUSAGE_UTIME_SEC), 1000000 ) + nqp::atpos_i(@rusage,nqp::const::RUSAGE_UTIME_MSEC) + nqp::mul_i( nqp::atpos_i(@rusage,nqp::const::RUSAGE_STIME_SEC), 1000000 ) + nqp::atpos_i(@rusage,nqp::const::RUSAGE_STIME_MSEC); $usage-delta = $current-usage - $last-usage; $last-usage = $current-usage; # Scale this by the time between rusage calls and turn it # into a per-core utilization percentage. $normalized-delta = nqp::div_n($usage-delta, $rusage-period); $per-core = nqp::div_n($normalized-delta, $cpu-cores); # used to have a "100 *" in the front, but for speed # and mostly memory usage reasons it got constant-folded # into the 1000000 instead. $per-core-util = nqp::div_n($per-core, (10000e0 * NUM_SAMPLES_NUM)); # Since those values are noisy, average the last # NUM_SAMPLES values to get a smoothed value. $smooth-per-core-util -= nqp::shift_n(@last-utils); $smooth-per-core-util += $per-core-util; nqp::push_n(@last-utils,$per-core-util); note "[SCHEDULER $pid] Per-core utilization (approx): $smooth-per-core-util%" if $scheduler-debug-status; # exhausted the system allotment of low level threads if $exhausted { if ++$exhausted > EXHAUSTED_RETRY_AFTER { scheduler-debug "No more system threads"; $exhausted = 0 # for next run of supervisor } } # we can still add threads if necessary else { self!tweak-workers($!general-queue, $!general-workers, &add-general-worker, $cpu-cores, $smooth-per-core-util) if $!general-queue.DEFINITE && nqp::elems($!general-queue); self!tweak-workers($!timer-queue, $!timer-workers, &add-timer-worker, $cpu-cores, $smooth-per-core-util) if $!timer-queue.DEFINITE && nqp::elems($!timer-queue); } # always need to prod affinity workers if nqp::isconcrete($!affinity-workers) && nqp::elems($!affinity-workers) -> int $count { my $worker; my $item; loop (my int $idx = 0; $idx < $count; $idx++) { $worker := nqp::atpos($!affinity-workers, $idx); if $worker.working { $worker.take-completed; # If an affinity worker completed nothing for some time, # steal an item from its queue, moving it to general queue. # This resolves deadlocks in certain cases. if $worker.times-nothing-completed > 10 { scheduler-debug "Stealing queue from affinity worker"; $item := nqp::queuepoll($worker.queue); nqp::push(self!general-queue, $item) unless nqp::isnull($item); } } } } } }); } } # Tweak workers for non-empty queues method !tweak-workers(\queue, \worker-list, &add-worker, $cores, $per-core-util) { # Go through the worker list. If something is not working, then there # is at least one worker free to process things in the queue, so we # don't need to add one. my int $total-completed; my int $total-times-nothing-completed; my int $i = -1; nqp::while( ++$i < nqp::elems(worker-list), nqp::if( (my $worker := nqp::atpos(worker-list,$i)).working, nqp::stmts( ($total-completed += $worker.take-completed), ($total-times-nothing-completed += $worker.times-nothing-completed) ), return ) ); sub heuristic-check-for-deadlock(--> Nil) { my int $average-times-nothing-completed = $total-times-nothing-completed div (nqp::elems(worker-list) || 1); if $average-times-nothing-completed > 20 { scheduler-debug "Heuristic queue progress deadlock situation detected"; add-worker(); } } # Consider adding more worker threads when: # 1. We didn't complete anything since the last supervision. This is # likely because some long-running tasks are holding onto the # workers. We should at least think about adding more (and the # code below will try to determine if that's beneficial). # 2. The number of tasks in the queue is greater than the number of # workers. Such a situation suggests we are under-resourced, and # so liable to fall behind. Consider it like checkout lanes at a # supermarket: if 20 people are queueing and there are only 2 open # checkout lanes, it makes sense to open more, but if there are 20 # people waiting and 30 open checkout lanes, there's little to be # won by opening another one at this point. my int $total-workers = nqp::elems($!general-workers) + nqp::elems($!timer-workers) + nqp::elems($!affinity-workers); if $total-completed == 0 || nqp::elems(queue) > nqp::elems(worker-list) { if $total-workers < $!max_threads { # There's something in the queue and we haven't completed it. # If we are still below the CPU core count, just add a worker. if $total-workers < $cores { add-worker(); } # Otherwise, consider utilization. If it's very little then a # further thread may be needed for deadlock breaking. elsif $per-core-util < 2 { scheduler-debug "Heuristic low utilization deadlock situation detected"; add-worker(); } # Another form of deadlock can happen when one kind of queue # is being processed but another is not. In that case, the # number of iterations since nothing was completed by any # worker will grow. else { heuristic-check-for-deadlock } } else { scheduler-debug "Will not add extra worker; hit $!max_threads thread limit [branch with 0 total completed]"; } } elsif $total-times-nothing-completed > 20*$cores { if $total-workers < $!max_threads { heuristic-check-for-deadlock } else { scheduler-debug "Will not add extra worker; hit $!max_threads thread limit [branch with some total completed]"; } } } method !SET-SELF($initial_threads, $max_threads) { my $default_max = (Kernel.cpu-cores * 8) max 64; with $max_threads // %*ENV { $!max_threads = nqp::istype($_,Whatever) ?? UNLIMITED_THREADS !! nqp::istype($_, Numeric) && $_ == Inf ?? UNLIMITED_THREADS !! nqp::istype($_, Int) ?? ($_ < 0 ?? UNLIMITED_THREADS !! (.Int || $default_max)) !! nqp::istype($_, Str) ?? (.lc eq any ) ?? UNLIMITED_THREADS !! die "Cannot use '$_' as a value for maximum threads" !! die "Cannot use a '" ~ $_.^name ~ "' for maximum threads value" } else { $!max_threads = $default_max; } die "Initial thread pool threads ($initial_threads) must be less than or equal to maximum threads ($!max_threads)" if $initial_threads > $!max_threads; $!general-workers := nqp::create(IterationBuffer); $!timer-workers := nqp::create(IterationBuffer); $!affinity-workers := nqp::create(IterationBuffer); $!state-lock := Lock.new; if $initial_threads > 0 { # We've been asked to make some initial threads; we interpret this # as general workers. $!general-queue := nqp::create(Queue); nqp::push( $!general-workers, GeneralWorker.new(queue => $!general-queue, scheduler => self) ) for ^$initial_threads; scheduler-debug "Created scheduler with $initial_threads initial general workers"; self!maybe-start-supervisor(); } else { scheduler-debug "Created scheduler without initial general workers"; } self } method new(Int:D() :$initial_threads = 0, :$max_threads) { nqp::create(self)!SET-SELF($initial_threads, $max_threads) } method max_threads(ThreadPoolScheduler:D:) { $!max_threads == UNLIMITED_THREADS ?? Inf !! $!max_threads } method queue(Bool :$hint-time-sensitive, :$hint-affinity) { if $hint-affinity { self!affinity-queue() } elsif $hint-time-sensitive { self!timer-queue() } else { self!general-queue() } } # Checks if the value given is Inf, -Inf, or NaN. If NaN, this throws. # if Inf, this returns Nil for ThreadPoolScheduler.cue to return an empty # Cancellation for. If -Inf, returns 0. Otherwise, returns the value. sub validate-seconds(Numeric() $value --> Numeric) { nqp::unless( nqp::istype($value, Num), $value, nqp::if( nqp::iseq_n($value,nqp::inf()), Nil, nqp::if( nqp::iseq_n($value,nqp::neginf()), 0, nqp::if( nqp::isnanorinf($value), X::Scheduler::CueInNaNSeconds.new().throw(), $value ) ) ) ) } sub to-millis(Numeric $value --> int) { nqp::unless( nqp::isconcrete(nqp::decont($value)), nqp::stmts( warn("Minimum timer resolution is 1ms; using that instead of Inf"), 1 ), nqp::if( nqp::isgt_i((my int $proposed = (1000 * $value).Int),0), $proposed, nqp::stmts( warn("Minimum timer resolution is 1ms; using that instead of {1000 * $value}ms"), 1 ) ), ) } sub to-millis-allow-zero(Numeric $value --> int) { nqp::unless( nqp::isconcrete(nqp::decont($value)), nqp::stmts( warn("Minimum timer resolution is 0ms; using that instead of Inf"), 0 ), nqp::if( nqp::isgt_i((my int $proposed = (1000 * $value).Int),0), $proposed # not true == 0 == what we need ) ) } sub wrap-catch(&code, &catch) { -> { CATCH { default { catch($_) } }; code() } } my class TimerCancellation is repr('AsyncTask') { } method !CUE_DELAY_TIMES(&code, int $delay, int $times, %args) { nqp::stmts( (my &run := nqp::if( # set up what we need to run nqp::isnull(my $catch := nqp::atkey(nqp::getattr(%args,Map,'$!storage'),"catch")), &code, wrap-catch(&code, $catch) # wrap any catch handler around code )), Cancellation.new( async_handles => nqp::if( $times, nqp::stmts( # need to run more than once (my @async_handles), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$times), @async_handles.push( nqp::timer(self!timer-queue,&run,$delay,0,TimerCancellation) ) ), @async_handles ), [ # only needs to run once nqp::timer(self!timer-queue,&run,$delay,0,TimerCancellation) ] )) ) } proto method cue(|) {*} multi method cue(&code, :$every!, :$times = 1, *%_) { # these need to exist in this scope my $handle; my $cancellation; sub cancellation() { $cancellation //= Cancellation.new(async_handles => [$handle]) } nqp::if( nqp::isconcrete( nqp::atkey((my $args := nqp::getattr(%_,Map,'$!storage')),"stop") ) && nqp::isconcrete($times) && $times > 1, die("Cannot specify :every, :times and :stop at the same time"), nqp::if( nqp::isconcrete(nqp::atkey($args,"at")) && nqp::isconcrete(nqp::atkey($args,"in")), die("Cannot specify :at and :in at the same time"), nqp::stmts( (my $interval := validate-seconds($every)), # ensure the interval given is sane (my $delay-in-seconds := validate-seconds( # ensure the delay or time given is sane nqp::if( nqp::isnull(my $at := nqp::atkey($args,"at")), nqp::ifnull(nqp::atkey($args,"in"),0), $at - now ) )); nqp::unless( nqp::isconcrete($delay-in-seconds), (return Cancellation.new(async_handles => [])) ), (my int $delay = to-millis-allow-zero($delay-in-seconds)), (my &run := nqp::if( # set up what should run nqp::isnull(my $catch := nqp::atkey($args,"catch")), &code, wrap-catch(&code, $catch) # wrap any catch handler around code )), nqp::stmts( (my int $interval-is-nil = nqp::not_i(nqp::isconcrete($interval))), (my $stopper := nqp::if( ($interval-is-nil || nqp::isgt_i($times,1)), nqp::stmts( # create our own stopper (my int $todo = nqp::add_i(nqp::if($interval-is-nil,1,$times),1)), sub { nqp::not_i(--$todo) } ), nqp::atkey($args,"stop") )), nqp::if( $interval-is-nil, nqp::stmts( (warn "Inf was passed via :every; running the given block only once, immediately"), run(), (return Cancellation.new(async_handles => [])) ) ), nqp::if( nqp::isconcrete($stopper), nqp::stmts( # we have a stopper ($handle := nqp::timer( self!timer-queue, -> { nqp::if($stopper(),cancellation().cancel,run()) }, $delay, to-millis($interval), TimerCancellation )), (return cancellation()) ), nqp::stmts( # we have no stopper ($handle := nqp::timer( self!timer-queue, &run, $delay, to-millis($interval), TimerCancellation )), (return cancellation()) ) ) ) ) ) ) } multi method cue(&code, :$times!, *%_) { nqp::stmts( (my $args := nqp::getattr(%_,Map,'$!storage')), nqp::if( nqp::isconcrete(my $at := nqp::atkey($args,"at")) && nqp::isconcrete(my $in := nqp::atkey($args,"in")), die("Cannot specify :at and :in at the same time"), nqp::stmts( nqp::if( nqp::isconcrete($at) && nqp::not_i(nqp::isconcrete($at := validate-seconds($at))), (return Cancellation.new(async_handles => [])), nqp::if( nqp::isconcrete($in) && nqp::not_i(nqp::isconcrete($in := validate-seconds($in))), (return Cancellation.new(async_handles => [])) ) ), self!CUE_DELAY_TIMES( &code, to-millis(nqp::ifnull( $in, nqp::if(nqp::isnull($at), .001, $at - now) )), $times, %_ ) ) ) ) } multi method cue(&code, :$at!, *%_) { nqp::isconcrete($at) && nqp::isconcrete(nqp::atkey(nqp::getattr(%_,Map,'$!storage'),"in")) ?? die("Cannot specify :at and :in at the same time") !! nqp::isconcrete(my $time := validate-seconds($at)) ?? self!CUE_DELAY_TIMES( &code, to-millis-allow-zero($time - now), 0, %_ ) !! Cancellation.new(async_handles => []) } multi method cue(&code, :$in!, *%_) { nqp::isconcrete(my $delay := validate-seconds($in)) ?? (self!CUE_DELAY_TIMES(&code, to-millis-allow-zero($delay), 0, %_)) !! Cancellation.new(async_handles => []) } multi method cue(&code, :&catch! --> Nil) { nqp::push(self!general-queue, wrap-catch(&code, &catch)) } multi method cue(&code --> Nil) { nqp::push(self!general-queue,&code) } method loads() is raw { my int $loads = 0; $loads = $loads + nqp::elems($!general-queue) if $!general-queue; $loads = $loads + nqp::elems($!timer-queue) if $!timer-queue; my int $i = -1; nqp::while( ++$i < nqp::elems($!affinity-workers), $loads = $loads + nqp::elems(nqp::atpos($!affinity-workers,$i).queue) ); $loads } # Constants indexing into the data array my constant SUPERVISOR = 0; my constant GW = 1; my constant GTQ = 2; my constant GTC = 3; my constant TW = 4; my constant TTQ = 5; my constant TTC = 6; my constant AW = 7; my constant ATQ = 8; my constant ATC = 9; my constant COLUMNS = 10; # calculate number of tasks completed for a worker list sub completed(\workers) is raw { my int $elems = nqp::elems(workers); my int $completed; my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::stmts( (my $w := nqp::atpos(workers,$i)), ($completed = nqp::add_i( $completed, nqp::getattr_i($w,$w.WHAT,'$!total') )) ) ); $completed } proto method usage(|) {*} multi method usage(ThreadPoolScheduler:U:) is raw { nqp::setelems(nqp::list_i,COLUMNS) } multi method usage(ThreadPoolScheduler:D:) is raw { my $data := nqp::setelems(nqp::list_i,COLUMNS); nqp::bindpos_i($data,SUPERVISOR,1) if $!supervisor; if $!general-workers -> \workers { nqp::bindpos_i($data,GW,nqp::elems(workers)); nqp::bindpos_i($data,GTQ,nqp::elems($!general-queue)) if $!general-queue; nqp::bindpos_i($data,GTC,completed(workers)); } if $!timer-workers -> \workers { nqp::bindpos_i($data,TW,nqp::elems(workers)); nqp::bindpos_i($data,TTQ,nqp::elems($!timer-queue)) if $!timer-queue; nqp::bindpos_i($data,TTC,completed(workers)); } if $!affinity-workers -> \workers { my int $elems = nqp::bindpos_i($data,AW,nqp::elems(workers)); my int $completed; my int $queued; my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::stmts( (my $w := nqp::atpos(workers,$i)), ($completed = nqp::add_i( $completed, nqp::getattr_i($w,$w.WHAT,'$!total') )), ($queued = nqp::add_i( $queued, nqp::elems(nqp::getattr($w,$w.WHAT,'$!queue')) )) ) ); nqp::bindpos_i($data,ATQ,$queued); nqp::bindpos_i($data,ATC,$completed); } # the final thing $data } } #line 1 SETTING::src/core.c/CurrentThreadScheduler.rakumod # Scheduler that always does things immediately, on the current thread. my class CurrentThreadScheduler does Scheduler { method handle_uncaught($exception) { $exception.throw } method cue(&code, :$at, :$in, :$every, :$times = 1, :&catch is copy ) { die "Cannot specify :at and :in at the same time" if $at.defined and $in.defined; die "Cannot specify :every and :times at the same time" if $every.defined and $times > 1; die "Cannot specify :every in {self.^name}" if $every; my $delay := nqp::decont($at ?? $at - now !! $in); nqp::if( nqp::istype($delay, Num), nqp::if( nqp::iseq_n($delay, nqp::inf()), (return class { method cancel() {} }), nqp::if( nqp::iseq_n($delay, nqp::neginf()), ($delay := 0), nqp::if( nqp::isnanorinf($delay), X::Scheduler::CueInNaNSeconds.new().throw() ) ) ) ); sleep $delay if $delay; &catch //= (self && self.uncaught_handler) // -> $ex { self.handle_uncaught($ex) }; # Don't set $*STACK-ID here because this scheduler doesn't start a new stack, as ThreadPoolScheduler does. for 1 .. $times { CATCH { default { catch($_) } }; code(); } class { method cancel() {} } } method loads(--> 0) { } } #line 1 SETTING::src/core.c/Promise.rakumod # A promise is a synchronization mechanism for a piece of work that will # produce a single result (keeping the promise) or fail (breaking the # promise). my enum PromiseStatus (:Planned(0), :Kept(1), :Broken(2)); my class X::Promise::Combinator is Exception { has $.combinator; method message() { "Can only use $!combinator to combine defined Promise objects" } } my class X::Promise::CauseOnlyValidOnBroken is Exception { has $.promise; has $.status; method message() { "Can only call cause on a broken promise (status: $.status)" } } my class X::Promise::Vowed is Exception { has $.promise; method message() { "Access denied to keep/break this Promise; already vowed" } } my class X::Promise::Resolved is Exception { has $.promise; method message() { "Cannot keep/break a Promise more than once (status: $!promise.status())"; } } my role X::Promise::Broken { has $.result-backtrace; multi method gist(::?CLASS:D:) { "Tried to get the result of a broken Promise\n" ~ ((try $!result-backtrace ~ "\n") // '') ~ "Original exception:\n" ~ callsame().indent(4) } } my class Promise does Awaitable { has $.scheduler; has $.status; has $!result is default(Nil); has int $!vow_taken; has $!lock; has $!cond; has $!thens; has Mu $!dynamic_context; has Bool $!report-broken-if-sunk; method !SET-SELF($scheduler, $report) { $!scheduler := $scheduler; $!report-broken-if-sunk := nqp::if(nqp::istrue($report),True,False); $!lock := nqp::create(Lock); $!cond := $!lock.condition; $!status := Planned; $!thens := nqp::null; self } submethod new(:$scheduler = $*SCHEDULER, :$report-broken-if-sunk) { if nqp::eqaddr(self,Promise) { nqp::create(self)!SET-SELF($scheduler, $report-broken-if-sunk) } else { my \p = nqp::create(self); p.BUILD(:$scheduler, :$report-broken-if-sunk); p } } submethod BUILD( :$!scheduler = $*SCHEDULER, :$report-broken-if-sunk --> Nil) { $!report-broken-if-sunk := so $report-broken-if-sunk; $!lock := nqp::create(Lock); $!cond := $!lock.condition(); $!status := Planned; $!thens := nqp::null; } # A Vow is used to enable the right to keep/break a promise # to be restricted to a given "owner". Taking the Vow for a Promise # prevents anybody else from getting hold of it. my class Vow { ... } trusts Vow; my class Vow { has $.promise; method keep(Mu \result) { $!promise!Promise::keep(result) } method break(\exception) { $!promise!Promise::break(exception) } } method vow() { nqp::lock($!lock); if $!vow_taken { nqp::unlock($!lock); X::Promise::Vowed.new(promise => self).throw } $!vow_taken = 1; nqp::unlock($!lock); nqp::p6bindattrinvres(nqp::create(Vow), Vow, '$!promise', self); } proto method kept(|) {*} multi method kept(Promise:U:) { my \rv := self.new; rv!keep(True); rv; } multi method kept(Promise:U: Mu \result) { my \rv := self.new; rv!keep(result); rv; } proto method keep(|) {*} multi method keep(Promise:D:) { self.vow.keep(True) } multi method keep(Promise:D: Mu \result) { self.vow.keep(result) } method !keep(Mu \result --> Nil) { $!lock.protect({ X::Promise::Resolved.new(promise => self).throw if $!status != Planned; $!result := result; $!status := Kept; self!schedule_thens(); $!cond.signal_all; }); } proto method broken(|) {*} multi method broken(Promise:U:) { my \rv := self.new; rv!break("Died"); rv; } multi method broken(Promise:U: Mu \exception) { my \rv := self.new; rv!break(exception); rv; } proto method break(|) {*} multi method break(Promise:D:) { self.vow.break("Died") } multi method break(Promise:D: \result) { self.vow.break(result) } method !break(\result --> Nil) { $!lock.protect({ X::Promise::Resolved.new(promise => self).throw if $!status != Planned; $!result := nqp::istype(result, Exception) ?? result !! X::AdHoc.new(payload => result); $!status := Broken; self!schedule_thens(); $!cond.signal_all; }); } method !schedule_thens(--> Nil) { nqp::unless( nqp::isnull($!thens), nqp::while( nqp::elems($!thens), $!scheduler.cue(nqp::shift($!thens), :catch(nqp::shift($!thens))) ) ) } method result(Promise:D:) { # One important missing optimization here is that if the promise is # not yet started, then the work can be done immediately by the # thing that is blocking on it. $!lock.protect: { $!cond.wait: { $!status != Planned } }; if $!status == Kept { $!result } elsif $!status == Broken { ($!result but X::Promise::Broken(Backtrace.new)).rethrow } } multi method Bool(Promise:D:) { $!status != Planned } method cause(Promise:D:) { my $status := $!status; if $status == Broken { $!result } else { X::Promise::CauseOnlyValidOnBroken.new( promise => self, status => $status, ).throw } } method !PLANNED-THEN(\then-promise, \vow, \then-code) { # Push 2 entries to $!thens: something that starts the then code, # and something that handles its exceptions. They will be sent to the # scheduler when this promise is kept or broken. nqp::bindattr(then-promise, Promise, '$!dynamic_context', nqp::ctx()); nqp::push(nqp::ifnull($!thens, ($!thens := nqp::list)), then-code); nqp::push($!thens, -> $ex { vow.break($ex) }); nqp::unlock($!lock); then-promise } method then(Promise:D: &code) { nqp::lock($!lock); if $!status == Broken || $!status == Kept { # Already have the result, start immediately. nqp::unlock($!lock); self.WHAT.start( { code(self) }, :$!scheduler); } else { my $then-p := self.new(:$!scheduler); my $vow := $then-p.vow; self!PLANNED-THEN($then-p, $vow, { my $*PROMISE := $then-p; $vow.keep(code(self)) } ) } } method andthen(Promise:D: &code) { nqp::lock($!lock); if $!status == Broken { nqp::unlock($!lock); self.WHAT.broken($!result) } elsif $!status == Kept { # Already have the result, start immediately. nqp::unlock($!lock); self.WHAT.start( { code(self) }, :$!scheduler); } else { my $then-p := self.new(:$!scheduler); my $vow := $then-p.vow; self!PLANNED-THEN( $then-p, $vow, { $!status == Kept ?? do { my $*PROMISE := $then-p; $vow.keep(code(self)) } !! $vow.break($!result) }) } } method orelse(Promise:D: &code) { nqp::lock($!lock); if $!status == Broken { nqp::unlock($!lock); self.WHAT.start( { code(self) }, :$!scheduler); } elsif $!status == Kept { # Already have the result, start immediately. nqp::unlock($!lock); self.WHAT.kept($!result); } else { my $then-p := self.new(:$!scheduler); my $vow := $then-p.vow; self!PLANNED-THEN( $then-p, $vow, { $!status == Kept ?? $vow.keep($!result) !! do { my $*PROMISE := $then-p; $vow.keep(code(self)) } }) } } my class PromiseAwaitableHandle does Awaitable::Handle { has &!add-subscriber; method not-ready(&add-subscriber) { nqp::create(self)!not-ready(&add-subscriber) } method !not-ready(&add-subscriber) { $!already = False; &!add-subscriber := &add-subscriber; self } method subscribe-awaiter(&subscriber --> Nil) { &!add-subscriber(&subscriber); } } method get-await-handle(--> Awaitable::Handle:D) { if $!status == Broken { PromiseAwaitableHandle.already-failure($!result) } elsif $!status == Kept { PromiseAwaitableHandle.already-success($!result) } else { PromiseAwaitableHandle.not-ready: -> &on-ready { nqp::lock($!lock); if $!status == Broken || $!status == Kept { # Already have the result, call on-ready immediately. nqp::unlock($!lock); on-ready($!status == Kept, $!result) } else { # Push 2 entries to $!thens (only need the first one in # this case; second we push 'cus .then uses it). nqp::push( nqp::ifnull($!thens,($!thens := nqp::list)), { on-ready($!status == Kept, $!result) } ); nqp::push($!thens, Callable); nqp::unlock($!lock); } } } } method sink(--> Nil) { self.then({ $!scheduler.handle_uncaught(.cause) if .status == Broken }) if $!report-broken-if-sunk && $!lock.protect({ nqp::hllbool( nqp::isnull($!thens) || nqp::not_i(nqp::elems($!thens)) ) }); } method start(Promise:U: &code, :&catch, :$scheduler = $*SCHEDULER, :$report-broken-if-sunk, |c) { my $p := self.new(:$scheduler, :$report-broken-if-sunk); nqp::bindattr($p, Promise, '$!dynamic_context', nqp::ctx()); my $vow := $p.vow; $scheduler.cue( { my $*PROMISE := $p; $vow.keep(code(|c)) }, :catch(-> $ex { catch($ex) if &catch; $vow.break($ex); }) ); $p } method in(Promise:U: $seconds, :$scheduler = $*SCHEDULER) { my $p := self.new(:$scheduler); my $vow := $p.vow; $scheduler.cue({ $vow.keep(True) }, :in($seconds)); $p } method at(Promise:U: $at, :$scheduler = $*SCHEDULER) { self.in( $at - now, :$scheduler ) } method anyof(Promise:U: *@p) { self!until_n_kept(@p, 1, 'anyof') } method allof(Promise:U: *@p) { self!until_n_kept(@p, +@p, 'allof') } method !until_n_kept(@promises, Int:D $N, Str $combinator) { my $p := self.new; unless @promises { $p.keep; return $p } X::Promise::Combinator.new(:$combinator).throw unless Rakudo::Internals.ALL_DEFINED_TYPE(@promises, Promise); my int $n = $N; my int $c = $n; my $lock := nqp::create(Lock); my $vow := $p.vow; for @promises -> $cand { $cand.then({ if $lock.protect({ $c = $c - 1 }) == 0 { $vow.keep(True) } }) } $p } multi method Supply(Promise:D:) { Supply.on-demand: -> $s { self.then({ if self.status == Kept { $s.emit(self.result); $s.done(); } else { $s.quit(self.cause); } }); } } } multi sub infix:(Promise:D $a, Promise:D $b --> Bool:D) { nqp::hllbool( nqp::eqaddr($a,$b) || $a.result eqv $b.result ) } #line 1 SETTING::src/core.c/Channel.rakumod # A channel provides a thread-safe way to send a series of values from some # producer(s) to some consumer(s). my class X::Channel::SendOnClosed is Exception { has $.channel; method message() { "Cannot send a message on a closed channel" } } my class X::Channel::ReceiveOnClosed is Exception { has $.channel; method message() { "Cannot receive a message on a closed channel" } } my class Channel does Awaitable { # The queue of events moving through the channel. my class Queue is repr('ConcBlockingQueue') { } has $!queue; # Promise that is triggered when all values are received, or an error is # received and the channel is thus closed. has $!closed_promise; # Closed promise's vow. has $!closed_promise_vow; # Flag for if the channel is closed to senders. has int $!closed; # We use a Supplier to send async notifications that there may be a new # message to read from the channel (there may be many things competing # over them). has $!async-notify; # Magical objects for various ways a channel can end. my class CHANNEL_CLOSE { } my class CHANNEL_FAIL { has $.error } submethod BUILD(--> Nil) { $!queue := nqp::create(Queue); $!closed_promise = Promise.new; $!closed_promise_vow = $!closed_promise.vow; $!async-notify = Supplier.new; } method send(Channel:D: \item --> Nil) { nqp::if( $!closed, X::Channel::SendOnClosed.new(channel => self).throw, nqp::stmts( nqp::push($!queue,nqp::decont(item)), $!async-notify.emit(True) ) ) } method receive(Channel:D:) { my \msg := nqp::shift($!queue); nqp::if( nqp::istype(msg,CHANNEL_CLOSE), nqp::stmts( nqp::push($!queue, msg), # make sure other readers see it X::Channel::ReceiveOnClosed.new(channel => self).throw ), nqp::if( nqp::istype(msg,CHANNEL_FAIL), nqp::stmts( nqp::push($!queue,msg), # make sure other readers see it msg.error.rethrow ), nqp::stmts( self!peek(), # trigger promise if closed msg ) ) ) } method poll(Channel:D:) { nqp::if( nqp::isnull(my \msg := nqp::queuepoll($!queue)), Nil, nqp::if( nqp::istype(msg, CHANNEL_CLOSE), nqp::stmts( nqp::push($!queue, msg), Nil ), nqp::if( nqp::istype(msg, CHANNEL_FAIL), nqp::stmts( nqp::push($!queue, msg), Nil ), nqp::stmts( self!peek(), # trigger promise if closed msg ) ) ) ) } method !peek(Channel:D:) { my \msg := nqp::atpos($!queue, 0); if nqp::isnull(msg) { Nil } else { if nqp::istype(msg, CHANNEL_CLOSE) { try $!closed_promise_vow.keep(Nil); Nil } elsif nqp::istype(msg, CHANNEL_FAIL) { try $!closed_promise_vow.break(msg.error); Nil } else { msg } } } method Capture(Channel:D:) { self.List.Capture } multi method Supply(Channel:D:) { supply { my $closed = False; # Tap the async notification for new values supply. whenever $!async-notify.unsanitized-supply.schedule-on($*SCHEDULER) { done if $closed; my Mu \got = self.poll; if nqp::eqaddr(got, Nil) { if $!closed_promise { $!closed_promise.status == Kept ?? done() !! X::AdHoc.new( payload => $!closed_promise.cause ).throw } } else { emit got; } } # Grab anything that's in the channel and emit it. Note that # it's important to do this after tapping the supply, or a # value sent between us draining it and doing the tap would # not result in a notification, and so we'd not emit it on # the supply. This lost event can then cause a deadlock. We # also limit ourselves to fetching up to the number of items # currently in the channel before we started; any further # ones will result in an async notification. If we don't, and # the code we `emit` to itself synchronously adds things, then # we can end up with the async notifications piling up becuase # the `whenever` above never gets chance to run. Note that we # may be competing over the items currently in the queue, so the # `last if ...` check in this loop is still essential. my int $initial-items = nqp::elems($!queue); while $initial-items-- { done if $closed; my Mu \got = self.poll; last if nqp::eqaddr(got, Nil); emit got; } self!peek(); if $!closed_promise { $!closed_promise.status == Kept ?? done() !! X::AdHoc.new( payload => $!closed_promise.cause ).throw } CLOSE { $closed = True; } } } my class Iterate { ... } trusts Iterate; my class Iterate does Iterator { has $!queue is built(:bind); has $!channel is built(:bind); method pull-one() { my \msg := nqp::shift($!queue); nqp::if( nqp::istype(msg,CHANNEL_CLOSE), nqp::stmts( nqp::push($!queue,msg), # make sure other readers see it IterationEnd ), nqp::if( nqp::istype(msg,CHANNEL_FAIL), nqp::stmts( nqp::push($!queue,msg), # make sure other readers see it msg.error.rethrow ), nqp::stmts( $!channel!Channel::peek(), # trigger promise if closed msg ) ) ) } } method iterator(Channel:D:) { Iterate.new(:$!queue,:channel(self)) } method list(Channel:D:) { List.from-iterator: self.iterator } my class ChannelAwaitableHandle does Awaitable::Handle { has $!channel; has $!closed_promise; has $!async-notify; method not-ready(Channel:D $channel, Promise:D $closed_promise, Supplier:D $async-notify) { nqp::create(self)!not-ready($channel, $closed_promise, $async-notify) } method !not-ready($channel, $closed_promise, $async-notify) { $!already = False; $!channel := $channel; $!closed_promise := $closed_promise; $!async-notify := $async-notify; self } method subscribe-awaiter(&subscriber --> Nil) { # Need some care here to avoid a race. We must tap the notification # supply first, and then do an immediate poll after it, just to be # sure we won't miss notifications between the two. Also, we need # to take some care that we never call subscriber twice. my $notified := False; my $l := Lock.new; my $t; $l.protect: { # Lock ensures $t will be assigned before we run the logic # inside of poll-now, which relies on being able to do # $t.close. $t := $!async-notify.unsanitized-supply.tap: &poll-now; } poll-now(); sub poll-now($discard?) { $l.protect: { unless $notified { my \maybe = $!channel.poll; if maybe === Nil { if $!closed_promise.status == Kept { $notified := True; subscriber(False, X::Channel::ReceiveOnClosed.new(:$!channel)) } elsif $!closed_promise.status == Broken { $notified := True; subscriber(False, $!closed_promise.cause) } } else { $notified := True; subscriber(True, maybe); } $t.close if $notified; } } } } } method get-await-handle(--> Awaitable::Handle:D) { my \maybe = self.poll; if maybe === Nil { if $!closed_promise { ChannelAwaitableHandle.already-failure( $!closed_promise.status == Kept ?? X::Channel::ReceiveOnClosed.new(channel => self) !! $!closed_promise.cause ) } else { ChannelAwaitableHandle.not-ready(self, $!closed_promise, $!async-notify) } } else { ChannelAwaitableHandle.already-success(maybe) } } method close(--> Nil) { $!closed = 1; nqp::push($!queue, CHANNEL_CLOSE); # if $!queue is otherwise empty, make sure that $!closed_promise # learns about the new value self!peek(); $!async-notify.emit(True); } method elems() { ('Cannot determine number of elements on a ' ~ self.^name).Failure } method fail($error is copy) { $!closed = 1; $error = X::AdHoc.new(payload => $error) unless nqp::istype($error, Exception); nqp::push($!queue, CHANNEL_FAIL.new(:$error)); self!peek(); $!async-notify.emit(True); Nil } method closed() { $!closed_promise } } #line 1 SETTING::src/core.c/Supply.rakumod # When we tap a Supply, we get back a Tap object. We close the tap in order # to turn off the flow of values. my class Tap { has &!on-close; submethod BUILD(:&!on-close --> Nil) { } # for subclasses of Tap multi method new(Tap: --> Tap:D) { nqp::create(self) } multi method new(Tap: &on-close --> Tap:D) { nqp::eqaddr(self.WHAT,Tap) ?? nqp::p6bindattrinvres( # we're a real Tap, fast path nqp::create(self),Tap,'&!on-close',&on-close ) !! self.bless(:&on-close) # subclass, use slow path } method close(--> True) { nqp::if( nqp::isconcrete(&!on-close), nqp::if( nqp::istype((my \close-result := &!on-close()),Promise), (await close-result) ) ) } } # The asynchronous dual of the Iterator role; goes inside of a Supply, which # is the asynchronous dual of the Seq class. So just as a Seq wraps around an # Iterator so we don't expose all the internal iterator types to the world, a # Supply wraps about a Tappable so we don't expose all of those. (It may # surprise you that it's a Tappable, not a Tap, given Seq wraps an Iterator, # not an Iterable. Guess that's part of the duality too. Ask your local # category theorist. :-)) my role Tappable { method tap(&emit, &done, &quit, &tap) { ... } method live() { ... } # Taps into a live data source method serial() { ... } # Promises no concurrent emits method sane() { ... } # Matches emit* [done|quit]? grammar } # A few Supply-related exception types. my class X::Supply::Combinator is Exception { has $.combinator; method message() { "Can only use $!combinator to combine defined Supply objects" } } my class X::Supply::Migrate::Needs is Exception { method message() { ".migrate needs Supplies to be emitted" } } my class X::Supply::New is Exception { method message() { "Cannot directly create a Supply. You might want:\n" ~ " - To use a Supplier in order to get a live supply\n" ~ " - To use Supply.on-demand to create an on-demand supply\n" ~ " - To create a Supply using a supply block" } } # A Supply is like an asynchronous Seq. All the methods that you can do on # a Supply go in here. my class Supplier { ... } my class Supplier::Preserving { ... } my class Supply does Awaitable { has Tappable $!tappable; proto method new(|) {*} multi method new(Supply:) { X::Supply::New.new.throw } multi method new(Supply: Tappable $tappable) { nqp::eqaddr(self.WHAT,Supply) ?? nqp::p6bindattrinvres( # we're a real Supply, fast path nqp::create(self),Supply,'$!tappable',$tappable ) !! self.bless(:$tappable) # subclass, use slow path } submethod BUILD(Tappable :$!tappable! --> Nil) { } # for subclasses method Capture(Supply:D:) { self.List.Capture } method live(Supply:D:) { $!tappable.live } method serial(Supply:D:) { $!tappable.serial } method Tappable(--> Tappable) { $!tappable } my \DISCARD = -> $ {}; my \NOP = -> {}; my \DEATH = -> $ex { $ex.throw }; method tap(Supply:D: &emit = DISCARD, :&done = NOP, :&quit = DEATH, :&tap = DISCARD) { $!tappable.tap(&emit, &done, &quit, &tap) } method act(Supply:D: &actor, *%others) { self.sanitize.tap(&actor, |%others) } # continued in src/core.c/Supply-factories.rakumod #line 1 SETTING::src/core.c/Supply-factories.rakumod # continued from src/core.c/Supply.rakumod ## Supply factories ## my class OnDemand does Tappable { has &!producer; has &!closing; has $!scheduler; submethod BUILD(:&!producer!, :&!closing!, :$!scheduler! --> Nil) {} method tap(&emit, &done, &quit, &tap) { my int $closed = 0; my $t = Tap.new: { if &!closing { &!closing() unless $closed++; } } tap($t); my $p = Supplier.new; $p.Supply.tap(&emit, done => { done(); $t.close(); }, quit => -> \ex { quit(ex); $t.close(); }); $!scheduler.cue({ &!producer($p) }, catch => -> \ex { $p.quit(ex) }); $t } method live(--> False) { } method sane(--> False) { } method serial(--> False) { } } method on-demand(Supply:U: &producer, :&closing, :$scheduler = CurrentThreadScheduler) { Supply.new(OnDemand.new(:&producer, :&closing, :$scheduler)).sanitize } method from-list(Supply:U: +@values, :$scheduler = CurrentThreadScheduler) { self.on-demand(-> $p { $p.emit($_) for @values; $p.done(); }, :$scheduler); } my class Interval does Tappable { has $!scheduler; has $!interval; has $!delay; submethod BUILD(:$!scheduler, :$!interval, :$!delay --> Nil) { } method tap(&emit, &, &, &tap) { my $i = 0; my $lock = Lock::Async.new; $lock.protect: { my $cancellation = $!scheduler.cue( { CATCH { $cancellation.cancel if $cancellation } $lock.protect: { emit $i++ }; }, :every($!interval), :in($!delay) ); my $t = Tap.new({ $cancellation.cancel }); tap($t); $t } } method live(--> False) { } method sane(--> True) { } method serial(--> True) { } } method interval(Supply:U: $interval, $delay = 0, :$scheduler = $*SCHEDULER) { Supply.new(Interval.new(:$interval, :$delay, :$scheduler)); } ## ## Simple operations are those that operate on a single Supply, carry its ## liveness, and are always serial. We implement the directly as they are ## common and fairly "hot path". ## my role SimpleOpTappable does Tappable { has $!source; method live() { $!source.live } method sane(--> True) { } method serial(--> True) { } method !cleanup(int $cleaned-up is rw, $source-tap) { if $source-tap && !$cleaned-up { $cleaned-up = 1; $source-tap.close; } } } my class Serialize does SimpleOpTappable { submethod BUILD(:$!source! --> Nil) { } method tap(&emit, &done, &quit, &tap) { my $lock = Lock::Async.new; my int $cleaned-up = 0; my $source-tap; my $t; $!source.tap( tap => { $source-tap = $_; $t = Tap.new({ self!cleanup($cleaned-up, $source-tap) }); tap($t); }, -> \value{ $lock.protect-or-queue-on-recursion: { emit(value); } }, done => -> { $lock.protect-or-queue-on-recursion: { done(); self!cleanup($cleaned-up, $source-tap); } }, quit => -> $ex { $lock.protect-or-queue-on-recursion: { quit($ex); self!cleanup($cleaned-up, $source-tap); } }); $t } } method serialize(Supply:D:) { $!tappable.serial ?? self !! Supply.new(Serialize.new(source => self)) } my class Sanitize does SimpleOpTappable { submethod BUILD(:$!source! --> Nil) { } method tap(&emit, &done, &quit, &tap) { my int $cleaned-up = 0; my int $finished = 0; my $source-tap; my $t; $!source.tap( tap => { $source-tap = $_; $t = Tap.new({ self!cleanup($cleaned-up, $source-tap) }); tap($t); }, -> \value{ emit(value) unless $finished; }, done => -> { unless $finished { $finished = 1; done(); self!cleanup($cleaned-up, $source-tap); } }, quit => -> $ex { unless $finished { $finished = 1; quit($ex); self!cleanup($cleaned-up, $source-tap); } }); $t } } method sanitize() { $!tappable.sane ?? self !! Supply.new(Sanitize.new(source => self.serialize)) } my class OnClose does SimpleOpTappable { has &!on-close; submethod BUILD(:$!source!, :&!on-close! --> Nil) { } method tap(&emit, &done, &quit, &tap) { my int $cleaned-up = 0; my $t; $!source.tap: &emit, :&done, :&quit, tap => -> $source-tap { $t = Tap.new({ &!on-close(); self!cleanup($cleaned-up, $source-tap) }); tap($t); } $t } } method on-close(Supply:D: &on-close) { Supply.new(OnClose.new(source => self, :&on-close)) } my class MapSupply does SimpleOpTappable { has &!mapper; submethod BUILD(:$!source!, :&!mapper! --> Nil) { } method tap(&emit, &done, &quit, &tap) { my int $cleaned-up = 0; my $source-tap; my $t; $!source.tap( tap => { $source-tap = $_; $t = Tap.new({ self!cleanup($cleaned-up, $source-tap) }); tap($t); }, -> \value { my \result = try &!mapper(value); if $! { quit($!); self!cleanup($cleaned-up, $source-tap); } else { emit(result) } }, done => -> { done(); self!cleanup($cleaned-up, $source-tap); }, quit => -> $ex { quit($ex); self!cleanup($cleaned-up, $source-tap); }); $t } } method map(Supply:D: &mapper) { Supply.new(MapSupply.new(source => self.sanitize, :&mapper)) } my class Grep does SimpleOpTappable { has Mu $!test; submethod BUILD(:$!source!, Mu :$!test! --> Nil) { } method tap(&emit, &done, &quit, &tap) { my int $cleaned-up = 0; my $source-tap; my $t; $!source.tap( tap => { $source-tap = $_; $t = Tap.new({ self!cleanup($cleaned-up, $source-tap) }); tap($t); }, -> \value { if try $!test.ACCEPTS(value) { emit(value); } elsif $! { quit($!); self!cleanup($cleaned-up, $source-tap); } }, done => -> { done(); self!cleanup($cleaned-up, $source-tap); }, quit => -> $ex { quit($ex); self!cleanup($cleaned-up, $source-tap); }); $t } } method grep(Supply:D: Mu $test) { Supply.new(Grep.new(source => self.sanitize, :$test)) } method first(Supply:D: :$end, |c) { c.list ?? $end ?? self.grep(|c).tail !! self.grep(|c).head !! $end ?? self.tail !! self.head } my class ScheduleOn does SimpleOpTappable { has $!scheduler; submethod BUILD(:$!source!, :$!scheduler! --> Nil) { } method tap(&emit, &done, &quit, &tap) { my int $cleaned-up = 0; my $source-tap; my $t; $!source.tap( tap => { $source-tap = $_; $t = Tap.new({ self!cleanup($cleaned-up, $source-tap) }); tap($t); }, -> \value { $!scheduler.cue: { emit(value) } }, done => -> { $!scheduler.cue: { done(); self!cleanup($cleaned-up, $source-tap); } }, quit => -> $ex { $!scheduler.cue: { quit($ex); self!cleanup($cleaned-up, $source-tap); } }); $t } } method schedule-on(Supply:D: Scheduler $scheduler) { Supply.new(ScheduleOn.new(source => self.sanitize, :$scheduler)) } my class Start does SimpleOpTappable { has $!value; has &!startee; submethod BUILD(:$!value, :&!startee --> Nil) { } method tap(&emit, &done, &quit, &tap) { my int $closed = 0; my $t = Tap.new({ $closed = 1 }); tap($t); Promise.start({ &!startee($!value) }).then({ unless $closed { if .status == Kept { emit(.result); done(); } else { quit(.cause); } } }); $t } } method start(Supply:D: &startee) { self.map: -> \value { Supply.new(Start.new(:value(value), :&startee)) } } my class Stable does SimpleOpTappable { has $!time; has $!scheduler; submethod BUILD(:$!source!, :$!time!, :$!scheduler! --> Nil) { } method tap(&emit, &done, &quit, &tap) { my int $cleaned-up = 0; my $lock = Lock::Async.new; my $last_cancellation; my $source-tap; my $t; $!source.tap( tap => { $source-tap = $_; $t = Tap.new({ self!cleanup($cleaned-up, $source-tap) }); tap($t); }, -> \value { $lock.protect: { if $last_cancellation { $last_cancellation.cancel; } $last_cancellation = $!scheduler.cue( :in($!time), { $lock.protect: { $last_cancellation = Nil; } try { CATCH { default { quit($_); self!cleanup($cleaned-up, $source-tap); } } emit(value); } }); } }, done => -> { done(); self!cleanup($cleaned-up, $source-tap); }, quit => -> $ex { quit($ex); self!cleanup($cleaned-up, $source-tap); }); $t } } method stable(Supply:D: $time, :$scheduler = $*SCHEDULER) { $time ?? Supply.new( Stable.new(source => self.sanitize, :$time, :$scheduler) ) !! self } my class Delayed does SimpleOpTappable { has $!time; has $!scheduler; submethod BUILD(:$!source!, :$!time, :$!scheduler! --> Nil) { } method tap(&emit, &done, &quit, &tap) { my int $cleaned-up = 0; my $source-tap; my $t; $!source.tap( tap => { $source-tap = $_; $t = Tap.new({ self!cleanup($cleaned-up, $source-tap) }); tap($t); }, -> \value { $!scheduler.cue: { emit(value) }, :in($!time) }, done => -> { $!scheduler.cue: { done(); self!cleanup($cleaned-up, $source-tap); }, :in($!time) }, quit => -> $ex { $!scheduler.cue: { quit($ex); self!cleanup($cleaned-up, $source-tap); }, :in($!time) }); $t } } method delayed(Supply:D: $time, :$scheduler = $*SCHEDULER) { $time ?? Supply.new(Delayed.new(source => self.sanitize,:$time,:$scheduler)) !! self # nothing to do } ## ## A bunch of the more complex combinators, implemented as supply blocks ## method do(Supply:D: &side-effect) { supply { whenever self -> \value { side-effect(value); emit(value); } } } method flat(Supply:D:) { supply { whenever self -> \inner { whenever inner -> \value { emit value; } } } } method merge(*@s) { @s.unshift(self) if self.DEFINITE; # add if instance method return supply { } unless +@s; # nothing to be done X::Supply::Combinator.new( combinator => 'merge' ).throw unless Rakudo::Internals.ALL_DEFINED_TYPE(@s,Supply); return @s[0].sanitize if +@s == 1; # nothing to be done supply { for @s { whenever $_ -> \value { emit(value) } } } } method reduce(Supply:D: &with) { supply { my $reduced := nqp::null; whenever self -> \value { $reduced := nqp::isnull($reduced) ?? value !! with($reduced, value); LAST { emit nqp::ifnull($reduced,Nil); } } } } method produce(Supply:D: &with) { supply { my $reduced := nqp::null; whenever self -> \value { emit $reduced := nqp::isnull($reduced) ?? value !! with($reduced, value); } } } method migrate(Supply:D:) { supply { my $current; whenever self -> \inner { X::Supply::Migrate::Needs.new.throw unless nqp::istype(inner, Supply); $current.close if $current; $current = do whenever inner -> \value { emit(value); } } } } proto method classify(|) {*} multi method classify(Supply:D: &mapper ) { self!classify(&mapper); } multi method classify(Supply:D: %mapper ) { self!classify({ %mapper{$^a} }); } multi method classify(Supply:D: @mapper ) { self!classify({ @mapper[$^a] }); } proto method categorize (|) {*} multi method categorize(Supply:D: &mapper ) { self!classify(&mapper, :multi); } multi method categorize(Supply:D: %mapper ) { self!classify({ %mapper{$^a} }, :multi); } multi method categorize(Supply:D: @mapper ) { self!classify({ @mapper[$^a] }, :multi); } method !classify(&mapper, :$multi) { supply { my %mapping; sub find-target($key) { %mapping{ $key.WHICH } //= do { my $p = Supplier::Preserving.new; emit($key => $p.Supply); $p }; } whenever self -> \value { if $multi { for @(mapper(value)) -> $key { find-target($key).emit(value); } } else { find-target(mapper(value)).emit(value); } LAST { %mapping.values>>.done; } } } } # comb the supply for characters proto method comb(|) {*} multi method comb(Supply:D:) { supply { whenever self -> str $str { for ^nqp::chars($str) -> int $i { emit nqp::box_s(nqp::substr($str,$i,1),Str); } } } } # comb the supply for N characters at a time multi method comb(Supply:D: Int:D $the-batch) { $the-batch <= 1 ?? self.comb !! supply { my str $str; my int $batch = $the-batch; whenever self -> str $val { $str = nqp::concat($str,$val); my int $i; my int $times = nqp::chars($str) div $batch; nqp::while( $times--, nqp::stmts( emit(nqp::box_s(nqp::substr($str,$i,$batch),Str)), ($i = $i + $batch) ) ); $str = nqp::substr($str,$i); LAST { emit $str if nqp::chars($str) } } } } # comb the supply for a Str needle multi method comb(Supply:D: Str:D $the-needle) { $the-needle ?? supply { my str $str; my str $needle = $the-needle; my int $len = nqp::chars($needle); whenever self -> str $val { $str = nqp::concat($str,$val); my int $i; my int $pos; nqp::while( nqp::isgt_i(($i = nqp::index($str,$needle,$pos)),-1), nqp::stmts( emit($the-needle), ($pos = $i + $len) ) ); $str = nqp::substr($str,$pos); } } !! self.comb } # Specifying :match forces a collect of all strings first multi method comb(Supply:D: Regex:D $matcher, :$match!, |c) { $match ?? supply { my $parts := nqp::list_s; whenever self -> str $val { nqp::push_s($parts,$val); LAST { emit $_ for nqp::join('',$parts).comb($matcher, :match, |c); } } } !! self.comb($matcher, |c) } # comb the supply for a Regex needle multi method comb(Supply:D: Regex:D $matcher) { supply { my str $str; whenever self -> str $val { $str = nqp::concat($str,$val); my @matches = $str.comb($matcher, :match); emit .Str for @matches; $str = nqp::substr($str,@matches.tail.pos) if @matches; } } } # comb the supply for a Str needle for a max number of time multi method comb(Supply:D: \the-thing, \the-limit) { self.comb(the-thing).head(the-limit) } # split the supply on the needle and adverbs multi method split(Supply:D: \needle) { supply { my $str = ""; # prevent warning on first batch my @matches; whenever self -> \value { done unless @matches = ($str ~ value).split(needle, |%_); $str = @matches.pop.Str; # keep last for next batch emit .Str for @matches; LAST { emit $str } } } } # split the supply on the needle, limit and adverbs multi method split(Supply:D: \needle, \the-limit) { self.split(needle, |%_).head(the-limit) } # encode chunks with the given encoding method encode(Supply:D: $encoding = "utf8") { supply { whenever self -> \val { emit val.encode($encoding); } } } # decode chunks with the given encoding method decode(Supply:D: $encoding = "utf8") { supply { my str $str; whenever self -> \val { my str $decoded = nqp::concat($str,val.decode($encoding)); if nqp::chars($decoded) > 1 { emit nqp::box_s( nqp::substr($decoded,0,nqp::chars($decoded) - 1), Str ); $str = nqp::substr($decoded,nqp::chars($decoded) - 1); } else { $str = $decoded; } LAST { emit nqp::box_s($str,Str) if nqp::chars($str) } } } } # continued in src/core.c/Supply-coercers.rakumod #line 1 SETTING::src/core.c/Supply-coercers.rakumod # continued from src/core.c/Supply-factories.rakumod ## ## Coercions ## multi method Supply(Supply:D:) { self } method Channel(Supply:D:) { my $c = Channel.new(); self.sanitize.tap: -> \val { $c.send(val) }, done => { $c.close }, quit => -> $ex { $c.fail($ex) }; $c } my class SupplyIterator does Iterator { my class ConcQueue is repr('ConcBlockingQueue') { } has $!queue; has $!exception; method TWEAK(:$supply) { $!queue := nqp::create(ConcQueue); $!exception := Nil; $supply.tap: { nqp::push($!queue, $_) }, done => -> { nqp::push($!queue, ConcQueue); # Sentinel value. }, quit => { $!exception := $_; nqp::push($!queue, ConcQueue); # Sentinel value. }; self } method pull-one() is raw { nqp::eqaddr((my $got := nqp::shift($!queue)),ConcQueue) ?? nqp::isconcrete($!exception) ?? $!exception.rethrow !! IterationEnd !! $got } method push-all(\target --> IterationEnd) { nqp::until( nqp::eqaddr((my $got := nqp::shift($!queue)),ConcQueue), target.push($got) ); $!exception.rethrow if nqp::isconcrete($!exception); } # method is-lazy(--> Bool:D) { ... } } multi method iterator(Supply:D:) { SupplyIterator.new: supply => self } multi method list(Supply:D:) { List.from-iterator: self.iterator } method Seq(Supply:D:) { Seq.new: self.iterator } method Promise(Supply:D:) { my $p = Promise.new; my $v = $p.vow; my $final := Nil; my $t = self.tap: -> \val { $final := val }, done => { $v.keep($final) }, quit => -> \ex { $v.break(ex) }; $p } method wait(Supply:D:) { await self.Promise } my class SupplyAwaitableHandle does Awaitable::Handle { has $!supply; method not-ready(Supply:D \supply) { nqp::create(self)!not-ready(supply) } method !not-ready(\supply) { $!already = False; $!supply := supply; self } method subscribe-awaiter(&subscriber --> Nil) { my $final := Nil; $!supply.tap: -> \val { $final := val }, done => { subscriber(True, $final) }, quit => -> \ex { subscriber(False, ex) }; } } method get-await-handle(--> Awaitable::Handle) { SupplyAwaitableHandle.not-ready(self) } multi method unique(Supply:D: :&as, :&with, :$expires!) { $expires ?? supply { if &with and !(&with === &[===]) { my @seen; # really Mu, but doesn't work in settings my Mu $target; if &as { whenever self -> \val { my $now := now; $target = &as(val); my $index = @seen.first({&with($target,$_[0])},:k); with $index { if $now > @seen[$index][1] { # expired @seen[$index][1] = $now+$expires; emit(val); } } else { @seen.push: [$target, $now+$expires]; emit(val); } } } else { whenever self -> \val { my $now := now; my $index = @seen.first({&with(val,$_[0])},:k); with $index { if $now > @seen[$index][1] { # expired @seen[$index][1] = $now+$expires; emit(val); } } else { @seen.push: [val, $now+$expires]; emit(val); } } } } else { my $seen := nqp::hash(); my str $target; if &as { whenever self -> \val { my $now := now; $target = nqp::unbox_s(&as(val).WHICH); if nqp::not_i(nqp::existskey($seen,$target)) || $now > nqp::atkey($seen,$target) { #expired emit(val); nqp::bindkey($seen,$target,$now+$expires); } } } else { whenever self -> \val { my $now := now; $target = nqp::unbox_s(val.WHICH); if nqp::not_i(nqp::existskey($seen,$target)) || $now > nqp::atkey($seen,$target) { #expired emit(val); nqp::bindkey($seen,$target,$now+$expires); } } } } } !! self.unique(:&as, :&with) } multi method unique(Supply:D: :&as, :&with) { supply { if &with and !(&with === &[===]) { my $seen := nqp::create(IterationBuffer); my Mu $target; if &as { whenever self -> \val { emit(val) unless seen($seen, as(val), &with); } } else { whenever self -> \val { emit(val) unless seen($seen, val, &with); } } } else { my $seen := nqp::hash(); my $which; if &as { whenever self -> \val { $which := as(val).WHICH; unless nqp::existskey($seen, $which) { nqp::bindkey($seen, $which, 1); emit(val); } } } else { whenever self -> \val { $which := val.WHICH; unless nqp::existskey($seen, $which) { nqp::bindkey($seen, $which, 1); emit(val); } } } } } } multi method squish(Supply:D:) { supply { my $last := nqp::null; my $which; whenever self -> \val { if nqp::isnull($last) { emit val; $last := val.WHICH; } elsif $last ne ($which := val.WHICH) { emit val; $last := $which; } } } } multi method squish(Supply:D: :&as!, :&with!) { supply { my $target; my $last := nqp::null; whenever self -> \val { $target := as(val); if nqp::isnull($last) { emit val; } else { emit val unless with($last, $target); } $last := $target; } } } multi method squish(Supply:D: :&as!) { supply { my $target; my $last := nqp::null; my $which; whenever self -> \val { $target := as(val); if nqp::isnull($last) { emit val; $last := $target.WHICH; } elsif $last ne ($which := $target.WHICH) { emit val; $last := $which; } } } } multi method squish(Supply:D: :&with!) { supply { my $last := nqp::null; whenever self -> \val { emit val if nqp::isnull($last) || nqp::not_i(with($last, val)); $last := val; } } } sub seen(IterationBuffer:D \seen, \value, &with) { my int $i = -1; my int $elems = nqp::elems(seen); return 1 if with(value, nqp::atpos(seen,$i)) while ++$i < $elems; # not seen nqp::push(seen, value); 0 } multi method repeated(Supply:D:) { supply { my $seen := nqp::hash; my $which; whenever self -> \val { nqp::existskey($seen,($which := val.WHICH)) ?? emit(val) !! nqp::bindkey($seen,$which,1) } } } multi method repeated(Supply:D: :&as!, :&with!) { supply { my $seen := nqp::create(IterationBuffer); whenever self -> \val { emit(val) if seen($seen, as(val), &with); } } } multi method repeated(Supply:D: :&as!) { supply { my $seen := nqp::hash; my $which; whenever self -> \val { nqp::existskey($seen,($which := as(val).WHICH)) ?? emit(val) !! nqp::bindkey($seen,$which,1) } } } multi method repeated(Supply:D: :&with!) { supply { my $seen := nqp::create(IterationBuffer); whenever self -> \val { emit(val) if seen($seen, val, &with); } } } multi method rotor(Supply:D: Int:D $batch, :$partial) { self.rotor(($batch,), :$partial) } multi method rotor(Supply:D: *@cycle, :$partial) { my @c := @cycle.is-lazy ?? @cycle !! (@cycle xx *).flat.cache; supply { my Int $elems; my Int $gap; my int $to-skip; my int $skip; my \c = @c.iterator; sub next-batch(--> Nil) { given c.pull-one { when Pair { $elems = +.key; $gap = +.value; $to-skip = $gap > 0 ?? $gap !! 0; } default { $elems = +$_; $gap = 0; $to-skip = 0; } } } next-batch; my @batched; sub flush(--> Nil) { emit( @batched.splice(0, +@batched, @batched[* + $gap .. *]) ); $skip = $to-skip; } whenever self -> \val { @batched.push: val unless $skip && $skip--; if @batched.elems == $elems { flush; next-batch; } LAST { flush if @batched and $partial; } } } } method batch(Supply:D: Int(Cool) :$elems = 0, :$seconds, :$emit-timed --> Supply:D) { supply { my int $max = $elems >= 0 ?? $elems !! 0; my $batched := nqp::list; sub flush(--> Nil) { emit($batched); $batched := nqp::list; } sub final-flush(--> Nil) { emit($batched) if nqp::elems($batched); } if $seconds { if $emit-timed { my $timer = Supply.interval($seconds); whenever $timer -> \tick { flush if nqp::elems($batched); LAST { final-flush; } } if $max > 0 { whenever self -> \val { nqp::push($batched,val); flush if nqp::iseq_i(nqp::elems($batched),$max); } } } else { # no emit-timed my int $msecs = ($seconds * 1000).Int; my int $last_time = nqp::div_i(nqp::mul_i(nqp::time,1000000),$msecs); if $max > 0 { whenever self -> \val { my int $this_time = nqp::div_i(nqp::time,nqp::mul_i($msecs,1000000)); if $this_time != $last_time { flush if nqp::elems($batched); $last_time = $this_time; nqp::push($batched,val); } else { nqp::push($batched,val); flush if nqp::iseq_i(nqp::elems($batched),$max); } LAST { final-flush; } } } else { # no max and $seconds whenever self -> \val { my int $this_time = nqp::div_i(nqp::time,nqp::mul_i($msecs,1000000)); if $this_time != $last_time { flush if nqp::elems($batched); $last_time = $this_time; } nqp::push($batched,val); LAST { final-flush; } } } } } else { # just $elems whenever self -> \val { nqp::push($batched,val); flush if nqp::isge_i(nqp::elems($batched),$max); LAST { final-flush; } } } } } proto method lines(|) {*} # optional chomping lines from a Supply multi method lines(Supply:D: :$chomp! ) { $chomp ?? self.lines # need to chomp !! supply { # no chomping wanted my str $str; my int $left; my int $pos; my int $nextpos; whenever self -> str $val { $str = nqp::concat($str,$val); $pos = 0; while ($left = nqp::chars($str) - $pos) > 0 { $nextpos = nqp::findcclass( nqp::const::CCLASS_NEWLINE,$str,$pos,$left); last if $nextpos >= nqp::chars($str) # no line delimiter or nqp::eqat($str,"\r",$nextpos) # broken CRLF? && $nextpos == nqp::chars($str) - 1; # yes! emit nqp::p6box_s(nqp::substr($str,$pos,$nextpos - $pos + 1)); $pos = $nextpos + 1; } $str = nqp::substr($str,$pos); LAST { emit nqp::p6box_s($str) if nqp::chars($str); } } } } # chomping lines from a Supply multi method lines(Supply:D:) { supply { my str $str; my int $pos; my int $left; my int $nextpos; whenever self -> str $val { $str = nqp::concat($str,$val); $pos = 0; while ($left = nqp::chars($str) - $pos) > 0 { $nextpos = nqp::findcclass( nqp::const::CCLASS_NEWLINE,$str,$pos,$left); last if $nextpos >= nqp::chars($str) # no line delimiter or nqp::eqat($str,"\r",$nextpos) # broken CRLF? && $nextpos == nqp::chars($str) - 1; # yes! emit nqp::p6box_s(nqp::substr($str,$pos,$nextpos - $pos)); $pos = $nextpos + 1; } $str = nqp::substr($str,$pos); LAST { emit nqp::p6box_s(nqp::substr($str,0, nqp::chars($str) - nqp::iscclass( # skip whitespace at end nqp::const::CCLASS_NEWLINE,$str,nqp::chars($str) - 1) )) if nqp::chars($str); } } } } method words(Supply:D:) { supply { my str $str; my int $left; my int $pos; my int $nextpos; whenever self -> str $val { $str = nqp::concat($str,$val); $pos = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE,$str,0,nqp::chars($str)); while ($left = nqp::chars($str) - $pos) > 0 { $nextpos = nqp::findcclass( nqp::const::CCLASS_WHITESPACE,$str,$pos,$left); last unless $left = nqp::chars($str) - $nextpos; # broken word emit nqp::p6box_s(nqp::substr($str,$pos,$nextpos - $pos)); $pos = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE,$str,$nextpos,$left); } $str = nqp::substr($str,$pos); LAST { emit nqp::p6box_s($str) if nqp::chars($str); } } } } multi method elems(Supply:D:) { supply { my int $elems; whenever self { emit ++$elems } } } multi method elems(Supply:D: $seconds ) { supply { my $last-time := nqp::div_i(nqp::time(),1000000000) div $seconds; my $this-time; my int $elems; my int $last-elems; whenever self { $last-elems = ++$elems; $this-time := nqp::div_i(nqp::time(),1000000000) div $seconds; if $this-time != $last-time { emit $elems; $last-time := $this-time; } LAST emit $elems if $elems != $last-elems; } } } multi method head(Supply:D:) { supply { whenever self -> \val { emit val; done } } } multi method head(Supply:D: Callable:D $limit) { (my int $lose = -$limit(0)) <= 0 ?? self !! supply { my $values := nqp::list; whenever self -> \val { nqp::push($values,val); LAST { nqp::while( nqp::elems($values) > $lose, (emit nqp::shift($values)) ); } } } } multi method head(Supply:D: \limit) { nqp::istype(limit,Whatever) || limit == Inf ?? self !! limit <= 0 ?? supply { } !! supply { my int $todo = limit.Int; whenever self -> \val { emit(val); done unless --$todo; } } } multi method tail(Supply:D:) { supply { my $last; whenever self -> \val { $last := val; LAST emit $last; } } } multi method tail(Supply:D: Callable:D $limit) { self.skip(-$limit(0)) } multi method tail(Supply:D: \limit) { nqp::istype(limit,Whatever) || limit == Inf ?? self !! limit <= 0 ?? supply { whenever self -> \val { } } !! (my int $size = limit.Int) == 1 ?? self.tail !! supply { my $lastn := nqp::list; my int $index = 0; nqp::setelems($lastn,$size); # presize list nqp::setelems($lastn,0); whenever self -> \val { nqp::bindpos($lastn,$index,val); $index = ($index + 1) % $size; LAST { my int $todo = nqp::elems($lastn); $index = 0 # start from beginning if $todo < $size; # if not a full set while $todo { emit nqp::atpos($lastn,$index); $index = ($index + 1) % $size; $todo = $todo - 1; } } } } } method skip(Supply:D: Int(Cool) $number = 1) { supply { my int $size = $number + 1; my int $skipping = $size > 1; whenever self { .emit unless $skipping && ($skipping = --$size) } } } method min(Supply:D: &by = &infix:) { my &cmp = &by.arity == 2 ?? &by !! { by($^a) cmp by($^b) } supply { my $min; whenever self -> \val { if val.defined and !$min.defined || cmp(val,$min) < 0 { emit( $min := val ); } } } } method max(Supply:D: &by = &infix:) { my &cmp = &by.arity == 2 ?? &by !! { by($^a) cmp by($^b) } supply { my $max; whenever self -> \val { if val.defined and !$max.defined || cmp(val,$max) > 0 { emit( $max = val ); } } } } method minmax(Supply:D: &by = &infix:) { my &cmp = &by.arity == 2 ?? &by !! { by($^a) cmp by($^b) } supply { my $min; my $max; whenever self -> \val { if nqp::istype(val,Failure) { val.throw; # XXX or just ignore ??? } elsif val.defined { if !$min.defined { emit( Range.new($min = val, $max = val) ); } elsif cmp(val,$min) < 0 { emit( Range.new( $min = val, $max ) ); } elsif cmp(val,$max) > 0 { emit( Range.new( $min, $max = val ) ); } } } } } method grab(Supply:D: &when_done) { supply { my $seen := nqp::create(IterationBuffer); whenever self -> \val { nqp::push($seen,val); LAST { emit($_) for when_done($seen.List); } } } } method rotate(Supply:D: Int(Cool) $rotate = 1) { # potentially ok if $rotate > 0 { my $rotated := nqp::create(IterationBuffer); supply { whenever self -> \val { nqp::elems($rotated) < $rotate ?? nqp::push($rotated,val) !! emit(val); LAST { # not enough elems found to rotate, adapt rotation if nqp::elems($rotated) < $rotate { emit($_) for $rotated.List.rotate($rotate); } # produce the rotated values at the end else { emit(nqp::shift($rotated)) while nqp::elems($rotated); } } } } } # must first grab all elsif $rotate < 0 { self.grab: *.rotate($rotate) } # no need to change anything else { self } } method reverse(Supply:D:) { self.grab: *.reverse } multi method sort(Supply:D:) { self.grab: *.sort } multi method sort(Supply:D: &by) { self.grab: *.sort(&by) } multi method collate(Supply:D:) { self.grab: *.collate } method zip(**@s, :&with) { @s.unshift(self) if self.DEFINITE; # add if instance method return supply { } unless +@s; # nothing to be done X::Supply::Combinator.new( combinator => 'zip' ).throw unless Rakudo::Internals.ALL_DEFINED_TYPE(@s,Supply); return @s[0] if +@s == 1; # nothing to be done supply { my @values = nqp::create(Array) xx +@s; my Int @counts = 0 xx +@s; my $watermark = Inf; for @s.kv -> $index, $supply { if &with { whenever $supply -> \val { @values[$index].push(val); @counts[$index]++; emit( [[&with]] @values.map(*.shift) ) if all(@values); done if all(@counts) >= $watermark; LAST { $watermark min= @counts[$index]; done if all(@counts) >= $watermark; } } } else { whenever $supply -> \val { @values[$index].push(val); @counts[$index]++; emit( $(@values.map(*.shift).list.eager) ) if all(@values); done if all(@counts) >= $watermark; LAST { $watermark min= @counts[$index]; done if all(@counts) >= $watermark; } } } } } } method zip-latest(**@s, :&with, :$initial ) { @s.unshift(self) if self.DEFINITE; # add if instance method return supply { } unless +@s; # nothing to do. X::Supply::Combinator.new( combinator => 'zip-latest' ).throw unless Rakudo::Internals.ALL_DEFINED_TYPE(@s,Supply); return @s[0] if +@s == 1; # nothing to do. supply { my @values; my $uninitialised = +@s; # how many supplies have yet to emit until we # can start emitting, too? if $initial { @values = @$initial; $uninitialised = 0 max $uninitialised - @$initial; } for @s.kv -> $index, $supply { if &with { whenever $supply -> \val { --$uninitialised if $uninitialised > 0 && not @values.EXISTS-POS($index); @values[$index] = val; emit( [[&with]] @values ) unless $uninitialised; } } else { whenever $supply -> \val { --$uninitialised if $uninitialised > 0 && not @values.EXISTS-POS($index); @values[$index] = val; emit( @values.List.item ) unless $uninitialised; } } } } } proto method throttle(|) {*} multi method throttle(Supply:D: Int() $elems, Real() $seconds, Real() $delay = 0, :$scheduler = $*SCHEDULER, :$control, :$status, :$bleed, :$vent-at, ) { my $timer = Supply.interval($seconds,$delay,:$scheduler); my int $limit = $elems; my int $vent = $vent-at if $bleed; supply { my @buffer; my int $allowed = $limit; my int $emitted; my int $bled; my int $done; sub emit-status($id --> Nil) { $status.emit( { :$allowed, :$bled, :buffered(+@buffer), :$emitted, :$id, :$limit, :$vent-at } ); } whenever $timer -> \tick { if +@buffer -> \buffered { my int $todo = buffered > $limit ?? $limit !! buffered; emit(@buffer.shift) for ^$todo; $emitted = $emitted + $todo; $allowed = $limit - $todo; } else { $allowed = $limit; } if $done && !@buffer { done; } } whenever self -> \val { if $allowed { emit(val); $emitted = $emitted + 1; $allowed = $allowed - 1; } elsif $vent && +@buffer >= $vent { $bleed.emit(val); } else { @buffer.push(val); } LAST { if $status { emit-status("done"); $status.done; } if $bleed && @buffer { $bleed.emit(@buffer.shift) while @buffer; $bleed.done; } $done = 1; } } if $control { whenever $control -> \val { my str $type; my str $value; Rakudo::Internals.KEY_COLON_VALUE(val,$type,$value); if $type eq 'limit' { my int $extra = $value - $limit; $allowed = $extra > 0 || $allowed + $extra >= 0 ?? $allowed + $extra !! 0; $limit = $value; } elsif $type eq 'bleed' && $bleed { my int $todo = $value min +@buffer; $bleed.emit(@buffer.shift) for ^$todo; $bled = $bled + $todo; } elsif $type eq 'status' && $status { emit-status($value); } elsif $type eq 'vent-at' && $bleed { $vent = $value; if $vent && +@buffer > $vent { $bleed.emit(@buffer.shift) until !@buffer || +@buffer == $vent; } } } } } } multi method throttle(Supply:D: Int() $elems, Callable:D $process, Real() $delay = 0, :$scheduler = $*SCHEDULER, :$control, :$status, :$bleed, :$vent-at, ) { sleep $delay if $delay; my @buffer; my int $limit = $elems; my int $allowed = $limit; my int $running; my int $emitted; my int $bled; my int $done; my int $vent = $vent-at if $bleed; my $ready = Supplier::Preserving.new; sub start-process(\val --> Nil) { my $p = Promise.start( $process, :$scheduler, val ); $running = $running + 1; $allowed = $allowed - 1; $p.then: { $ready.emit($p) }; } sub emit-status($id --> Nil) { $status.emit( { :$allowed, :$bled, :buffered(+@buffer), :$emitted, :$id, :$limit, :$running } ); } supply { whenever $ready.Supply -> \val { # when a process is ready $running = $running - 1; $allowed = $allowed + 1; emit(val); $emitted = $emitted + 1; start-process(@buffer.shift) if $allowed > 0 && @buffer; if $done && !$running { $control.done if $control; if $status { emit-status("done"); $status.done; } if $bleed && @buffer { $bleed.emit(@buffer.shift) while @buffer; $bleed.done; } done; } } if $control { whenever $control -> \val { my str $type; my str $value; Rakudo::Internals.KEY_COLON_VALUE(val,$type,$value); if $type eq 'limit' { $allowed = $allowed + $value - $limit; $limit = $value; start-process(@buffer.shift) while $allowed > 0 && @buffer; } elsif $type eq 'bleed' && $bleed { my int $todo = $value min +@buffer; $bleed.emit(@buffer.shift) for ^$todo; $bled = $bled + $todo; } elsif $type eq 'status' && $status { emit-status($value); } elsif $type eq 'vent-at' && $bleed { $vent = $value; if $vent && +@buffer > $vent { $bleed.emit(@buffer.shift) until !@buffer || +@buffer == $vent; } } } } whenever self -> \val { $allowed > 0 ?? start-process(val) !! $vent && $vent == +@buffer ?? $bleed.emit(val) !! @buffer.push(val); LAST { $done = 1 } } } } method share(Supply:D:) { my $sup = Supplier.new; self.tap: -> \msg { $sup.emit(msg) }, done => -> { $sup.done() }, quit => -> \ex { $sup.quit(ex) } $sup.Supply } } #line 1 SETTING::src/core.c/Supplier.rakumod # A Supplier is a convenient way to create a live Supply. The publisher can # be used to emit/done/quit. The Supply objects obtained from it will tap into # the same live Supply. my class Supplier { my class TapList does Tappable { my class TapListEntry { has &.emit; has &.done; has &.quit; } # Lock serializes updates to tappers. has Lock $!lock = Lock.new; # An immutable list of tappers. Always replaced on change, never # mutated in-place ==> thread safe together with lock (and only # need lock on modification). has Mu $!tappers; # Avoid auto-vivification of the Scalar container for the $!tappers # attribute when reading. That could lead to a Mu overwriting the list # in $!tappers after we've already set $added to True in method tap. submethod BUILD() { $!tappers := Mu; } method tap(&emit, &done, &quit, &tap) { my $tle := TapListEntry.new(:&emit, :&done, :&quit); # Since we run `tap` before adding, there's a small chance of # a tap removal attempt happening for the add attempt. We use # these two flags to handle that case. This is safe since we # only ever access them under lock. my $added := False; my $removed := False; my $t = Tap.new({ $!lock.protect({ if $added { my Mu $update := nqp::list(); for nqp::hllize($!tappers) -> \entry { nqp::push($update, entry) unless entry =:= $tle; } $!tappers := $update; } $removed := True; }); }); tap($t); $!lock.protect({ unless $removed { my Mu $update := nqp::isconcrete($!tappers) ?? nqp::clone($!tappers) !! nqp::list(); nqp::push($update, $tle); $!tappers := $update; } $added := True; }); $t } method emit(Mu \value --> Nil) { nqp::if( nqp::isconcrete(my $snapshot := $!tappers) && (my int $n = nqp::elems($snapshot)), nqp::if( # at least one tap nqp::isgt_i($n,1), nqp::stmts( # multiple taps (my int $i = -1), nqp::while( nqp::islt_i(++$i,$n), nqp::atpos($snapshot,$i).emit()(value) ) ), nqp::atpos($snapshot,0).emit()(value) # only one tap ) ) } method done(--> Nil) { my $snapshot := $!tappers; if nqp::isconcrete($snapshot) { my int $n = nqp::elems($snapshot); loop (my int $i = 0; $i < $n; $i = $i + 1) { nqp::atpos($snapshot, $i).done()(); } } } method quit($ex --> Nil) { my $snapshot := $!tappers; if nqp::isconcrete($snapshot) { my int $n = nqp::elems($snapshot); loop (my int $i = 0; $i < $n; $i = $i + 1) { nqp::atpos($snapshot, $i).quit()($ex); } } } method live(--> True) { } method serial(--> False) { } method sane(--> False) { } } has $!taplist; method new() { self.bless(taplist => TapList.new) } submethod BUILD(:$!taplist! --> Nil) { } method emit(Supplier:D: Mu \value --> Nil) { $!taplist.emit(value); } method done(Supplier:D: --> Nil) { $!taplist.done(); } proto method quit($) {*} multi method quit(Supplier:D: Exception $ex) { $!taplist.quit($ex); } multi method quit(Supplier:D: Str() $message) { $!taplist.quit(X::AdHoc.new(payload => $message)); } method Supply(Supplier:D:) { Supply.new($!taplist).sanitize } method unsanitized-supply(Supplier:D:) { Supply.new($!taplist) } } # A preserving supplier holds on to emitted values and state when nobody is # tapping. As soon as there a tap is made, any preserved events will be # immediately sent to that tapper. my class Supplier::Preserving is Supplier { my class PreservingTapList does Tappable { my class TapListEntry { has &.emit; has &.done; has &.quit; } # Lock serializes updates to tappers. has Lock $!lock = Lock.new; # An immutable list of tappers. Always replaced on change, never # mutated in-place ==> thread safe together with lock (and only # need lock on modification). has Mu $!tappers; # Events to reply, whether the replay was done, and a lock to protect # updates to these. has @!replay; has int $!replay-done; has $!replay-lock = Lock.new; method tap(&emit, &done, &quit, &tap) { my $tle := TapListEntry.new(:&emit, :&done, :&quit); my int $replay = 0; # Since we run `tap` before adding, there's a small chance of # a tap removal attempt happening for the add attempt. We use # these two flags to handle that case. This is safe since we # only ever access them under lock. my $added := False; my $removed := False; my $t = Tap.new({ $!lock.protect({ if $added { my Mu $update := nqp::list(); for nqp::hllize($!tappers) -> \entry { nqp::push($update, entry) unless entry =:= $tle; } $!replay-done = 0 if nqp::elems($update) == 0; $!tappers := $update; } $removed := True; }); }); tap($t); $!lock.protect({ unless $removed { my Mu $update := nqp::isconcrete($!tappers) ?? nqp::clone($!tappers) !! nqp::list(); nqp::push($update, $tle); $replay = 1 if nqp::elems($update) == 1; self!replay($tle) if $replay; $!tappers := $update; } $added := True; }); $t } method emit(\value --> Nil) { loop { my int $sent = 0; my $snapshot := $!tappers; if nqp::isconcrete($snapshot) { $sent = nqp::elems($snapshot); loop (my int $i = 0; $i < $sent; $i = $i + 1) { nqp::atpos($snapshot, $i).emit()(value); } } return if $sent; return if self!add-replay({ $_.emit()(value) }); } } method done(--> Nil) { loop { my int $sent = 0; my $snapshot := $!tappers; if nqp::isconcrete($snapshot) { $sent = nqp::elems($snapshot); loop (my int $i = 0; $i < $sent; $i = $i + 1) { nqp::atpos($snapshot, $i).done()(); } } return if $sent; return if self!add-replay({ $_.done()() }); } } method quit($ex --> Nil) { loop { my int $sent = 0; my $snapshot := $!tappers; if nqp::isconcrete($snapshot) { $sent = nqp::elems($snapshot); loop (my int $i = 0; $i < $sent; $i = $i + 1) { nqp::atpos($snapshot, $i).quit()($ex); } } return if $sent; return if self!add-replay({ $_.quit()($ex) }); } } method !add-replay(&replay --> Bool) { $!replay-lock.protect: { if $!replay-done { False } else { @!replay.push(&replay); True } } } method !replay($tle) { $!replay-lock.protect: { while @!replay.shift -> $rep { $rep($tle); } $!replay-done = 1; } } method live(--> True) { } method serial(--> False) { } method sane(--> False) { } } method new() { self.bless(taplist => PreservingTapList.new) } } #line 1 SETTING::src/core.c/Rakudo/Supply.rakumod class Rakudo::Supply { my constant ADD_WHENEVER_PROMPT = Mu.new; class CachedAwaitHandle does Awaitable { has $.get-await-handle; } class BlockAddWheneverAwaiter does Awaiter { has $!continuations; method await(Awaitable:D $a) { my $handle := $a.get-await-handle; if $handle.already { $handle.success ?? $handle.result !! $handle.cause.rethrow } else { my $reawaitable := CachedAwaitHandle.new(get-await-handle => $handle); $!continuations := nqp::list() unless nqp::isconcrete($!continuations); nqp::continuationcontrol(0, ADD_WHENEVER_PROMPT, nqp::getattr(-> Mu \c { nqp::push($!continuations, -> $delegate-awaiter { nqp::continuationinvoke(c, nqp::getattr({ $delegate-awaiter.await($reawaitable); }, Code, '$!do')); }); }, Code, '$!do')); } } method await-all(Iterable:D \i) { $!continuations := nqp::list() unless nqp::isconcrete($!continuations); nqp::continuationcontrol(0, ADD_WHENEVER_PROMPT, nqp::getattr(-> Mu \c { nqp::push($!continuations, -> $delegate-awaiter { nqp::continuationinvoke(c, nqp::getattr({ $delegate-awaiter.await-all(i); }, Code, '$!do')); }); }, Code, '$!do')); } method take-all() { if nqp::isconcrete($!continuations) { my \result = $!continuations; $!continuations := Mu; result } else { Empty } } } class BlockState { has &.emit; has &.done; has &.quit; has @.close-phasers; has $.active; has $!lock; has %!active-taps; has $.run-async-lock; has $.awaiter; method new(:&emit!, :&done!, :&quit!) { self.CREATE!SET-SELF(&emit, &done, &quit) } method !SET-SELF(&emit, &done, &quit) { &!emit := &emit; &!done := &done; &!quit := &quit; $!active = 1; $!lock := Lock.new; $!run-async-lock := Lock::Async.new; $!awaiter := BlockAddWheneverAwaiter.CREATE; self } method decrement-active() { $!lock.protect: { --$!active } } method get-and-zero-active() { $!lock.protect: { my $result = $!active; $!active = 0; $result } } method add-active-tap($tap --> Nil) { $!lock.protect: { ++$!active; %!active-taps{nqp::objectid($tap)} = $tap; } } method delete-active-tap($tap --> Nil) { $!lock.protect: { %!active-taps{nqp::objectid($tap)}:delete; } } method teardown(--> Nil) { $!lock.protect: { my $to-close := nqp::create(IterationBuffer); %!active-taps.values.iterator.push-all($to-close); %!active-taps = (); $!active = 0; my int $n = nqp::elems($to-close); loop (my int $i = 0; $i < $n; $i++) { nqp::atpos($to-close, $i).close(); } my @close-phasers := @!close-phasers; while @close-phasers { @close-phasers.pop()(); } } } method run-emit(--> Nil) { if $!active { my \ex := nqp::exception(); my $emit-handler := &!emit; $emit-handler(nqp::getpayload(ex)) if $emit-handler.DEFINITE; nqp::resume(ex) } } method run-done(--> Nil) { self.get-and-zero-active(); self.teardown(); my $done-handler := &!done; $done-handler() if $done-handler.DEFINITE; } method run-last(Tap $tap, &code --> Nil) { self.delete-active-tap($tap); self.decrement-active(); $tap.close(); &code.fire_if_phasers("LAST"); $!lock.protect: { if $!active == 0 { self.teardown(); my $done-handler := &!done; $done-handler() if $done-handler.DEFINITE; } } } method run-catch(--> Nil) { my \ex = EXCEPTION(nqp::exception()); self.get-and-zero-active(); self.teardown(); my $quit-handler = &!quit; $quit-handler(ex) if $quit-handler; } } class BlockTappable does Tappable { has &!block; submethod BUILD(:&!block --> Nil) { } method tap(&emit, &done, &quit, &tap) { # Create state for this tapping. my $state := Rakudo::Supply::BlockState.new(:&emit, :&done, :&quit); # Placed here so it can close over $state, but we only need to # closure-clone it once per Supply block, not once per whenever. sub add-whenever($supply, &whenever-block) { my $tap; $state.run-async-lock.with-lock-hidden-from-recursion-check: { my $*AWAITER := $state.awaiter; nqp::continuationreset(ADD_WHENEVER_PROMPT, nqp::getattr({ $supply.tap( tap => { $tap := $_; $state.add-active-tap($tap); }, -> \value { self!run-supply-code(&whenever-block, value, $state, &add-whenever, $tap) }, done => { $state.delete-active-tap($tap); my @phasers := &whenever-block.phasers('LAST'); if @phasers { self!run-supply-code({ .() for @phasers }, Nil, $state, &add-whenever, $tap) } $tap.close; self!deactivate-one($state); }, quit => -> \ex { $state.delete-active-tap($tap); my $handled := False; self!run-supply-code({ my $phaser := &whenever-block.phasers('QUIT')[0]; if $phaser.DEFINITE { $handled := $phaser(ex) === Nil; } if !$handled && $state.get-and-zero-active() { $state.quit().(ex) if $state.quit; $state.teardown(); } }, Nil, $state, &add-whenever, $tap); if $handled { $tap.close; self!deactivate-one($state); } }); }, Code, '$!do')); } $tap } # Stash away any CLOSE phasers. if nqp::istype(&!block, Block) { $state.close-phasers.append(&!block.phasers('CLOSE')); } # Create and pass on tap; when closed, tear down the state and all # of our subscriptions. my $t := Tap.new(-> { $state.teardown() }); tap($t); # Run the Supply block, then decrease active count afterwards (it # counts as an active runner). self!run-supply-code: { &!block(); self!deactivate-one-internal($state) }, Nil, $state, &add-whenever, $t; # Evaluate to the Tap. $t } method !run-supply-code(&code, \value, BlockState $state, &add-whenever, $tap) { my @run-after; my $queued := $state.run-async-lock.protect-or-queue-on-recursion: { my &*ADD-WHENEVER := &add-whenever; $state.active > 0 and nqp::handle(code(value), 'EMIT', $state.run-emit(), 'DONE', $state.run-done(), 'CATCH', $state.run-catch(), 'LAST', $state.run-last($tap, &code), 'NEXT', 0); @run-after = $state.awaiter.take-all; } if $queued.defined { $queued.then({ self!run-add-whenever-awaits(@run-after) }); } else { self!run-add-whenever-awaits(@run-after); } } method !run-add-whenever-awaits(@run-after --> Nil) { if @run-after { my $nested-awaiter := BlockAddWheneverAwaiter.CREATE; my $delegate-awaiter := $*AWAITER; while @run-after.elems { my $*AWAITER := $nested-awaiter; nqp::continuationreset(ADD_WHENEVER_PROMPT, nqp::getattr({ @run-after.shift()($delegate-awaiter); }, Code, '$!do')); @run-after.append($nested-awaiter.take-all); } } } method !deactivate-one(BlockState $state) { $state.run-async-lock.protect-or-queue-on-recursion: { self!deactivate-one-internal($state) }; } method !deactivate-one-internal(BlockState $state) { if $state.decrement-active() == 0 { my $done-handler := $state.done; $done-handler() if $done-handler; $state.teardown(); } } method live(--> False) { } method sane(--> True) { } method serial(--> True) { } } class OneWheneverState { has &.emit; has &.done; has &.quit; has @.close-phasers; has $.tap is rw; has $.active; method new(:&emit!, :&done!, :&quit!) { self.CREATE!SET-SELF(&emit, &done, &quit) } method !SET-SELF(&emit, &done, &quit) { &!emit := &emit; &!done := &done; &!quit := &quit; $!active = 1; self } method teardown(--> Nil) { $!active = 0; $!tap.close if $!tap; my @close-phasers := @!close-phasers; while @close-phasers { @close-phasers.pop()(); } } method run-emit(--> Nil) { if $!active { my \ex := nqp::exception(); my $emit-handler := &!emit; $emit-handler(nqp::getpayload(ex)) if $emit-handler.DEFINITE; nqp::resume(ex) } } method run-done(--> Nil) { if $!active { self.teardown(); my $done-handler := &!done; $done-handler() if $done-handler.DEFINITE; } } method run-last(&code, --> Nil) { &code.fire_if_phasers("LAST"); self.run-done; } method run-catch(--> Nil) { if $!active { my \ex = EXCEPTION(nqp::exception()); self.teardown(); my $quit-handler = &!quit; $quit-handler(ex) if $quit-handler; } } } class OneWheneverTappable does Tappable { has &!block; submethod BUILD(:&!block --> Nil) { } method tap(&emit, &done, &quit, &tap) { # Create state for this tapping. my $state := Rakudo::Supply::OneWheneverState.new(:&emit, :&done, :&quit); # We only expcet one whenever; detect getting a second and complain. my $*WHENEVER-SUPPLY-TO-ADD := Nil; my &*WHENEVER-BLOCK-TO-ADD := Nil; sub add-whenever(\the-supply, \the-whenever-block) { if $*WHENEVER-SUPPLY-TO-ADD =:= Nil { $*WHENEVER-SUPPLY-TO-ADD := the-supply; &*WHENEVER-BLOCK-TO-ADD := the-whenever-block; } else { die "Single whenever block special case tried to add second whenever"; } } # Stash away any CLOSE phasers. if nqp::istype(&!block, Block) { $state.close-phasers.append(&!block.phasers('CLOSE')); } # Create and pass on tap; when closed, tear down the state and all # of our subscriptions. my $t := Tap.new(-> { $state.teardown() }); tap($t); # Run the Supply block. Only proceed if it didn't send done/quit. self!run-supply-code: { &!block() }, Nil, $state, &add-whenever; if $state.active { # If we didn't get a whenever, something is badly wrong. if $*WHENEVER-SUPPLY-TO-ADD =:= Nil { die "Single whenever block special case did not get a whenever block"; } # Otherwise, we can now tap that whenever block. Since it is the # only one, and we know from compile-time analysis it is the last # thing in the block, then it's safe to do it now the block is # completed and without any concurrency control. However, we do # call .sanitize just in case, to ensure that we have a serial and # protocol-following Supply. That is enough. my $supply := $*WHENEVER-SUPPLY-TO-ADD.sanitize; my &whenever-block := &*WHENEVER-BLOCK-TO-ADD; my $tap; $supply.tap( tap => { $tap := $_; $state.tap = $tap; }, -> \value { self!run-supply-code(&whenever-block, value, $state, &add-whenever) }, done => { my @phasers := &whenever-block.phasers('LAST'); if @phasers { self!run-supply-code({ .() for @phasers }, Nil, $state, &add-whenever) } $tap.close; $state.run-done(); }, quit => -> \ex { my $handled := False; self!run-supply-code({ my $phaser := &whenever-block.phasers('QUIT')[0]; if $phaser.DEFINITE { $handled := $phaser(ex) === Nil; } if !$handled { $state.quit().(ex) if $state.quit; $state.teardown(); } }, Nil, $state, &add-whenever); if $handled { $tap.close; $state.run-done(); } }); } # Evaluate to the Tap. $t } method !run-supply-code(&code, \value, OneWheneverState $state, &add-whenever) { my &*ADD-WHENEVER := &add-whenever; { $state.active > 0 and nqp::handle(code(value), 'EMIT', $state.run-emit(), 'DONE', $state.run-done(), 'CATCH', $state.run-catch(), 'LAST', $state.run-last(&code), 'NEXT', 0); }(); # XXX Workaround for optimizer bug } method live(--> False) { } method sane(--> True) { } method serial(--> True) { } } class OneEmitTappable does Tappable { has &!block; submethod BUILD(:&!block! --> Nil) {} method tap(&emit, &done, &quit, &tap) { my $t := Tap.new; tap($t); try { CATCH { default { quit($_); } } emit(&!block()); done(); } $t } method live(--> False) { } method sane(--> True) { } method serial(--> True) { } } } sub SUPPLY(&block) is implementation-detail { Supply.new(Rakudo::Supply::BlockTappable.new(:&block)) } sub WHENEVER(Supply() $supply, &block) is implementation-detail { my \adder := nqp::getlexdyn('&*ADD-WHENEVER'); nqp::isnull(adder) ?? X::WheneverOutOfScope.new.throw !! adder.($supply, &block) } sub REACT(&block) is implementation-detail { my $s := SUPPLY(&block); my $p := Promise.new; $s.tap( { warn "Useless use of emit in react" }, done => { $p.keep(Nil) }, quit => { $p.break($_) }); $*AWAITER.await($p); } sub SUPPLY-ONE-EMIT(&block) is implementation-detail { Supply.new(Rakudo::Supply::OneEmitTappable.new(:&block)) } sub SUPPLY-ONE-WHENEVER(&block) is implementation-detail { Supply.new(Rakudo::Supply::OneWheneverTappable.new(:&block)) } sub REACT-ONE-WHENEVER(&block) is implementation-detail { my $s := SUPPLY-ONE-WHENEVER(&block); my $p := Promise.new; $s.tap( { warn "Useless use of emit in react" }, done => { $p.keep(Nil) }, quit => { $p.break($_) }); $*AWAITER.await($p); } #line 1 SETTING::src/core.c/asyncops.rakumod # Waits for a promise to be kept or a channel to be able to receive a value # and, once it can, unwraps or returns the result. Under Raku 6.c, await will # really block the calling thread. In 6.d, if the thread is on the thread pool # then a continuation will be taken, and the thread is freed up. my role X::Await::Died { has $.await-backtrace; multi method gist(::?CLASS:D:) { "An operation first awaited:\n" ~ ((try $!await-backtrace ~ "\n") // '') ~ "Died with the exception:\n" ~ callsame().indent(4) } } proto sub await(|) {*} multi sub await() { die "Must specify a Promise or Channel to await on (got an empty list)"; } multi sub await(Any:U $x) { die "Must specify a defined Promise, Channel, or Supply to await on (got an undefined $x.^name())"; } multi sub await(Any:D $x) { die "Must specify a Promise, Channel, or Supply to await on (got a $x.^name())"; } multi sub await(Promise:D $p) { CATCH { unless nqp::istype($_, X::Await::Died) { ($_ but X::Await::Died(Backtrace.new(5))).rethrow } } my $*RAKUDO-AWAIT-BLOCKING := True; $*AWAITER.await($p) } multi sub await(Channel:D $c) { CATCH { unless nqp::istype($_, X::Await::Died) { ($_ but X::Await::Died(Backtrace.new(5))).rethrow } } my $*RAKUDO-AWAIT-BLOCKING := True; $*AWAITER.await($c) } multi sub await(Supply:D $s) { CATCH { unless nqp::istype($_, X::Await::Died) { ($_ but X::Await::Died(Backtrace.new(5))).rethrow } } my $*RAKUDO-AWAIT-BLOCKING := True; $*AWAITER.await($s) } multi sub await(Iterable:D $i) { eager $i.eager.map({ await $_ }) } multi sub await(*@awaitables) { eager @awaitables.eager.map({await $_}) } #line 1 SETTING::src/core.c/IO/Socket.rakumod my role IO::Socket { has $!PIO; has Str $.encoding = 'utf8'; has $.nl-in is rw = ["\n", "\r\n"]; has Str:D $.nl-out is rw = "\n"; has Encoding::Decoder $!decoder; has Encoding::Encoder $!encoder; method !ensure-coders(--> Nil) { unless $!decoder.DEFINITE { my $encoding = Encoding::Registry.find($!encoding); $!decoder := $encoding.decoder(); $!decoder.set-line-separators($!nl-in.list); $!encoder := $encoding.encoder(); } } # The if bin is true, will return Buf, Str otherwise method recv(Cool $limit? is copy, :$bin) { fail('Socket not available') unless $!PIO; $limit = 65535 if !$limit.DEFINITE || $limit === Inf; if $bin { nqp::readfh($!PIO, nqp::create(buf8.^pun), $limit) } else { self!ensure-coders(); my $result = $!decoder.consume-exactly-chars($limit); without $result { $!decoder.add-bytes(nqp::readfh($!PIO, nqp::create(buf8.^pun), 65535)); $result = $!decoder.consume-exactly-chars($limit); without $result { $result = $!decoder.consume-all-chars(); } } $result } } method read(IO::Socket:D: Int(Cool) $bufsize) { fail('Socket not available') unless $!PIO; my int $toread = $bufsize; my $res := nqp::readfh($!PIO,nqp::create(buf8.^pun),$toread); while nqp::elems($res) < $toread { my $buf := nqp::readfh($!PIO,nqp::create(buf8.^pun),$toread - nqp::elems($res)); nqp::elems($buf) ?? $res.append($buf) !! return $res } $res } method nl-in is rw { Proxy.new( FETCH => { $!nl-in }, STORE => -> $, $nl-in { $!nl-in = $nl-in; with $!decoder { .set-line-separators($!nl-in.list); } $nl-in } ) } method get() { self!ensure-coders(); my Str $line = $!decoder.consume-line-chars(:chomp); if $line.DEFINITE { $line } else { loop { my $read = nqp::readfh($!PIO, nqp::create(buf8.^pun), 65535); $!decoder.add-bytes($read); $line = $!decoder.consume-line-chars(:chomp); last if $line.DEFINITE; if $read == 0 { $line = $!decoder.consume-line-chars(:chomp, :eof) unless $!decoder.is-empty; last; } } $line.DEFINITE ?? $line !! Nil } } method lines() { gather while (my $line = self.get()).DEFINITE { take $line; } } method print(Str(Cool) $string --> True) { self!ensure-coders(); self.write($!encoder.encode-chars($string)); } method put(Str(Cool) $string --> True) { self.print($string ~ $!nl-out); } method write(Blob:D $buf --> True) { fail('Socket not available') unless $!PIO; nqp::writefh($!PIO, nqp::decont($buf)); } method close(--> True) { fail("Not connected!") unless $!PIO; nqp::closefh($!PIO); $!PIO := nqp::null; } method native-descriptor(::?CLASS:D:) { nqp::filenofh($!PIO) } } #line 1 SETTING::src/core.c/IO/Socket/INET.rakumod my class IO::Socket::INET does IO::Socket { my module PIO { constant MIN_PORT = 0; constant MAX_PORT = 65_535; # RFC 793: TCP/UDP port limit } has ProtocolFamily:D $.family = PF_UNSPEC; has SocketType:D $.type = SOCK_STREAM; has ProtocolType:D $.proto = PROTO_TCP; has Str $.host; has Int $.port; has Str $.localhost; has Int $.localport; has Int $.backlog; has Bool $.listening; # XXX: this could be a bit smarter about how it deals with unspecified # families... my sub split-host-port(:$host is copy, :$port is copy, :$family) { if ($host) { my ($split-host, $split-port) = $family == PF_INET6 ?? v6-split($host) !! v4-split($host); if $split-port { $host = $split-host.Str; $port //= $split-port.Int } } fail "Invalid port $port.gist(). Must be {PIO::MIN_PORT}..{PIO::MAX_PORT}" unless $port.defined and PIO::MIN_PORT <= $port <= PIO::MAX_PORT; return ($host, $port); } my sub v4-split($uri) { return $uri.split(':', 2); } my sub v6-split($uri) { my ($host, $port) = ($uri ~~ /^'[' (.+) ']' \: (\d+)$/)[0,1]; return $host ?? ($host, $port) !! $uri; } # Create new socket that listens on $localhost:$localport multi method new( Bool :$listen! where .so, Str :$localhost is copy, Int :$localport is copy, ProtocolFamily:D(Int:D) :$family = PF_UNSPEC, *%rest, --> IO::Socket::INET:D) { ($localhost, $localport) = ( split-host-port :host($localhost), :port($localport), :$family orelse fail $_) unless $family == PF_UNIX; self.bless( :$localhost, :$localport, :$family, :listening($listen), |%rest, )!initialize(nqp::socket(10)) } # Open new connection to socket on $host:$port multi method new( Str:D :$host! is copy, Int :$port is copy, ProtocolFamily:D(Int:D) :$family = PF_UNSPEC, *%rest, --> IO::Socket::INET:D) { ($host, $port) = split-host-port( :$host, :$port, :$family, ) unless $family == PF_UNIX; self.bless( :$host, :$port, :$family, |%rest, )!initialize(nqp::socket(0)) } # Fail if no valid parameters are passed multi method new() { fail "Nothing given for new socket to connect or bind to. " ~ "Invalid arguments to .new?"; } method !initialize(Mu $PIO is raw) { CATCH { nqp::closefh($PIO) } # Quoting perl's SIO::INET: # If Listen is defined then a listen socket is created, else if the socket type, # which is derived from the protocol, is SOCK_STREAM then connect() is called. if $!listening || $!localhost || $!localport { nqp::bindsock($PIO, nqp::unbox_s($!localhost || "0.0.0.0"), nqp::unbox_i($!localport || 0), nqp::decont_i($!family), nqp::unbox_i($!backlog || 128)); } if $!listening { $!localport = nqp::getport($PIO) unless $!localport || ($!family == PF_UNIX); } elsif $!type == SOCK_STREAM { nqp::connect($PIO, nqp::unbox_s($!host), nqp::unbox_i($!port), nqp::decont_i($!family)); } nqp::bindattr(self, $?CLASS, '$!PIO', $PIO); self; } method connect(IO::Socket::INET:U: Str() $host, Int() $port, ProtocolFamily:D :$family = PF_UNSPEC) { self.new(:$host, :$port, :family($family.value)) } method listen(IO::Socket::INET:U: Str() $localhost, Int() $localport, ProtocolFamily:D :$family = PF_UNSPEC) { self.new(:$localhost, :$localport, :family($family.value), :listen) } method accept() { # A solution as proposed by moritz my $new_sock := $?CLASS.bless(:$!family, :$!proto, :$!type, :$!nl-in); nqp::bindattr($new_sock, $?CLASS, '$!PIO', nqp::accept(nqp::getattr(self, $?CLASS, '$!PIO')) ); return $new_sock; } } #line 1 SETTING::src/core.c/IO/Socket/Async.rakumod my class IO::Socket::Async { my class SocketCancellation is repr('AsyncTask') { } has $!VMIO; has int $!udp; has $.enc; has $!encoder; has $!close-promise; has $!close-vow; subset Port-Number of Int where { !defined($_) or $_ ~~ ^65536 }; has Str $.peer-host; has Port-Number $.peer-port; has Str $.socket-host; has Port-Number $.socket-port; method new() { die "Cannot create an asynchronous socket directly; please use\n" ~ "IO::Socket::Async.connect, IO::Socket::Async.listen,\n" ~ "IO::Socket::Async.udp, or IO::Socket::Async.udp-bind"; } method print(IO::Socket::Async:D: Str() $str, :$scheduler = $*SCHEDULER) { self.write($!encoder.encode-chars($str)) } method write(IO::Socket::Async:D: Blob $b, :$scheduler = $*SCHEDULER) { my $p := Promise.new; my $v := $p.vow; nqp::asyncwritebytes( $!VMIO, $scheduler.queue, -> Mu \bytes, Mu \err { if err { $v.break(err); } else { $v.keep(bytes); } }, nqp::decont($b), SocketCancellation); $p } my class Datagram { has $.data; has str $.hostname; has int $.port; method decode(|c) { $!data ~~ Str ?? X::AdHoc.new( payload => "Cannot decode a datagram with Str data").throw !! self.clone(data => $!data.decode(|c)) } method encode(|c) { $!data ~~ Blob ?? X::AdHoc.new( payload => "Cannot encode a datagram with Blob data" ).throw !! self.clone(data => $!data.encode(|c)) } } my class SocketReaderTappable does Tappable { has $!VMIO; has $!scheduler; has $!buf; has $!close-promise; has $!udp; method new(Mu :$VMIO!, :$scheduler!, :$buf!, :$close-promise!, :$udp!) { self.CREATE!SET-SELF($VMIO, $scheduler, $buf, $close-promise, $udp) } method !SET-SELF(Mu $!VMIO, $!scheduler, $!buf, $!close-promise, $!udp) { self } method tap(&emit, &done, &quit, &tap) { my $buffer := nqp::list(); my int $buffer-start-seq = 0; my int $done-target = -1; my int $finished = 0; sub emit-events() { until nqp::elems($buffer) == 0 || nqp::isnull(nqp::atpos($buffer, 0)) { emit(nqp::shift($buffer)); $buffer-start-seq = $buffer-start-seq + 1; } if $buffer-start-seq == $done-target { done(); $finished = 1; } } my $lock = Lock::Async.new; my $tap; $lock.protect: { my $cancellation := nqp::asyncreadbytes(nqp::decont($!VMIO), $!scheduler.queue(:hint-affinity), -> Mu \seq, Mu \data, Mu \err, Mu \hostname = Str, Mu \port = Int { $lock.protect: { unless $finished { if err { quit(X::AdHoc.new(payload => err)); $finished = 1; } elsif nqp::isconcrete(data) { my int $insert-pos = seq - $buffer-start-seq; if $!udp && nqp::isconcrete(hostname) && nqp::isconcrete(port) { nqp::bindpos($buffer, $insert-pos, Datagram.new( data => data, hostname => hostname, port => port )); } else { nqp::bindpos($buffer, $insert-pos, data); } emit-events(); } else { $done-target = seq; emit-events(); } } } }, nqp::decont($!buf), SocketCancellation); $tap := Tap.new({ nqp::cancel($cancellation) }); tap($tap); } $!close-promise.then: { $lock.protect: { unless $finished { done(); $finished = 1; } } } $tap } method live(--> False) { } method sane(--> True) { } method serial(--> True) { } } multi method Supply(IO::Socket::Async:D: :$bin, :$buf = nqp::create(buf8.^pun), :$datagram, :$enc, :$scheduler = $*SCHEDULER) { if $bin { Supply.new: SocketReaderTappable.new: :$!VMIO, :$scheduler, :$buf, :$!close-promise, udp => $!udp && $datagram } else { my $bin-supply = self.Supply(:bin, :$datagram); if $!udp { supply { whenever $bin-supply { emit .decode($enc // $!enc); } } } else { Rakudo::Internals.BYTE_SUPPLY_DECODER($bin-supply, $enc // $!enc) } } } method close(IO::Socket::Async:D: --> True) { nqp::closefh($!VMIO); try $!close-vow.keep(True); } method connect(IO::Socket::Async:U: Str() $host, Int() $port where Port-Number, :$enc = 'utf-8', :$scheduler = $*SCHEDULER) { my $p = Promise.new; my $v = $p.vow; my $encoding = Encoding::Registry.find($enc); nqp::asyncconnect( $scheduler.queue, -> Mu \socket, Mu \err, Mu \peer-host, Mu \peer-port, Mu \socket-host, Mu \socket-port { if err { $v.break(err); } else { my $client_socket := nqp::create(self); nqp::bindattr($client_socket, IO::Socket::Async, '$!VMIO', socket); nqp::bindattr($client_socket, IO::Socket::Async, '$!enc', $encoding.name); nqp::bindattr($client_socket, IO::Socket::Async, '$!encoder', $encoding.encoder()); nqp::bindattr($client_socket, IO::Socket::Async, '$!peer-host', peer-host); nqp::bindattr($client_socket, IO::Socket::Async, '$!peer-port', peer-port); nqp::bindattr($client_socket, IO::Socket::Async, '$!socket-host', socket-host); nqp::bindattr($client_socket, IO::Socket::Async, '$!socket-port', socket-port); setup-close($client_socket); $v.keep($client_socket); } }, $host, $port, SocketCancellation); $p } method connect-path(IO::Socket::Async:U: Str() $path, :$enc = 'utf-8', :$scheduler = $*SCHEDULER) { my $p = Promise.new; my $v = $p.vow; my $encoding = Encoding::Registry.find($enc); nqp::dispatch('boot-syscall', 'async-unix-connect', $scheduler.queue, -> Mu \socket, Mu \err, Mu \peer-host, Mu \peer-port, Mu \socket-host, Mu \socket-port { if err { $v.break(err); } else { my $client_socket := nqp::create(self); nqp::bindattr($client_socket, IO::Socket::Async, '$!VMIO', socket); nqp::bindattr($client_socket, IO::Socket::Async, '$!enc', $encoding.name); nqp::bindattr($client_socket, IO::Socket::Async, '$!encoder', $encoding.encoder()); nqp::bindattr($client_socket, IO::Socket::Async, '$!peer-host', peer-host); nqp::bindattr($client_socket, IO::Socket::Async, '$!peer-port', peer-port); nqp::bindattr($client_socket, IO::Socket::Async, '$!socket-host', socket-host); nqp::bindattr($client_socket, IO::Socket::Async, '$!socket-port', socket-port); setup-close($client_socket); $v.keep($client_socket); } }, nqp::unbox_s($path), SocketCancellation); $p } class ListenSocket is Tap { has Promise $!VMIO-tobe is built; has Promise $.socket-host is built; has Promise $.socket-port is built; method new(&on-close, Promise :$VMIO-tobe, Promise :$socket-host, Promise :$socket-port) { self.bless(:&on-close, :$VMIO-tobe, :$socket-host, :$socket-port); } method native-descriptor(--> Int) { nqp::filenofh(await $!VMIO-tobe) } } my class SocketListenerTappable does Tappable { has $!host; has $!port; has $!backlog; has $!encoding; has $!scheduler; method new(:$host!, :$port!, :$backlog!, :$encoding!, :$scheduler!) { self.CREATE!SET-SELF($host, $port, $backlog, $encoding, $scheduler) } method !SET-SELF($!host, $!port, $!backlog, $!encoding, $!scheduler) { self } method tap(&emit, &done, &quit, &tap) { my $lock := Lock::Async.new; my $tap; my int $finished = 0; my Promise $VMIO-tobe .= new; my Promise $socket-host .= new; my Promise $socket-port .= new; my $VMIO-vow = $VMIO-tobe.vow; my $host-vow = $socket-host.vow; my $port-vow = $socket-port.vow; $lock.protect: { CATCH { default { tap($tap = ListenSocket.new({ Nil }, :$VMIO-tobe, :$socket-host, :$socket-port)) unless $tap; quit($_); } } my $cancellation := nqp::asynclisten( $!scheduler.queue(:hint-affinity), -> Mu \client-socket, Mu \err, Mu \peer-host, Mu \peer-port, Mu \server-socket, Mu \socket-host, Mu \socket-port { $lock.protect: { if $finished { # do nothing } elsif err { my $exc = X::AdHoc.new(payload => err); quit($exc); $host-vow.break($exc) unless $host-vow.promise; $port-vow.break($exc) unless $port-vow.promise; $finished = 1; } elsif client-socket { my $client_socket := nqp::create(IO::Socket::Async); nqp::bindattr($client_socket, IO::Socket::Async, '$!VMIO', client-socket); nqp::bindattr($client_socket, IO::Socket::Async, '$!enc', $!encoding.name); nqp::bindattr($client_socket, IO::Socket::Async, '$!encoder', $!encoding.encoder()); nqp::bindattr($client_socket, IO::Socket::Async, '$!peer-host', peer-host); nqp::bindattr($client_socket, IO::Socket::Async, '$!peer-port', peer-port); nqp::bindattr($client_socket, IO::Socket::Async, '$!socket-host', socket-host); nqp::bindattr($client_socket, IO::Socket::Async, '$!socket-port', socket-port); setup-close($client_socket); emit($client_socket); } elsif server-socket { $VMIO-vow.keep(server-socket); $host-vow.keep(~socket-host); $port-vow.keep(+socket-port); } } }, $!host, $!port, $!backlog, SocketCancellation); $tap = ListenSocket.new: { my $p = Promise.new; my $v = $p.vow; nqp::cancelnotify($cancellation, $!scheduler.queue, { $v.keep(True); }); $p }, :$VMIO-tobe, :$socket-host, :$socket-port; tap($tap); } $tap } method live(--> False) { } method sane(--> True) { } method serial(--> True) { } } method listen(IO::Socket::Async:U: Str() $host, Int() $port where Port-Number, Int() $backlog = 128, :$enc = 'utf-8', :$scheduler = $*SCHEDULER) { my $encoding = Encoding::Registry.find($enc); Supply.new: SocketListenerTappable.new: :$host, :$port, :$backlog, :$encoding, :$scheduler } my class SocketUnixListenerTappable does Tappable { has $!path; has $!backlog; has $!encoding; has $!scheduler; method new(:$path!, :$backlog!, :$encoding!, :$scheduler!) { self.CREATE!SET-SELF($path, $backlog, $encoding, $scheduler) } method !SET-SELF($!path, $!backlog, $!encoding, $!scheduler) { self } method tap(&emit, &done, &quit, &tap) { my $lock := Lock::Async.new; my $tap; my int $finished = 0; my Promise $VMIO-tobe .= new; my Promise $socket-host .= new; my Promise $socket-port .= new; my $VMIO-vow = $VMIO-tobe.vow; my $host-vow = $socket-host.vow; my $port-vow = $socket-port.vow; $lock.protect: { CATCH { default { tap($tap = ListenSocket.new({ Nil }, :$VMIO-tobe, :$socket-host, :$socket-port)) unless $tap; quit($_); } } my $cancellation := nqp::dispatch('boot-syscall', 'async-unix-listen', $!scheduler.queue(:hint-affinity), -> Mu \client-socket, Mu \err, Mu \peer-host, Mu \peer-port, Mu \server-socket, Mu \socket-host, Mu \socket-port { $lock.protect: { if $finished { # do nothing } elsif err { my $exc = X::AdHoc.new(payload => err); quit($exc); $host-vow.break($exc) unless $host-vow.promise; $port-vow.break($exc) unless $port-vow.promise; $finished = 1; } elsif client-socket { my $client_socket := nqp::create(IO::Socket::Async); nqp::bindattr($client_socket, IO::Socket::Async, '$!VMIO', client-socket); nqp::bindattr($client_socket, IO::Socket::Async, '$!enc', $!encoding.name); nqp::bindattr($client_socket, IO::Socket::Async, '$!encoder', $!encoding.encoder()); nqp::bindattr($client_socket, IO::Socket::Async, '$!peer-host', peer-host); nqp::bindattr($client_socket, IO::Socket::Async, '$!peer-port', peer-port); nqp::bindattr($client_socket, IO::Socket::Async, '$!socket-host', socket-host); nqp::bindattr($client_socket, IO::Socket::Async, '$!socket-port', socket-port); setup-close($client_socket); emit($client_socket); } elsif server-socket { $VMIO-vow.keep(server-socket); $host-vow.keep(~socket-host); $port-vow.keep(+socket-port); } } }, nqp::unbox_s($!path), nqp::unbox_i($!backlog), SocketCancellation); $tap = ListenSocket.new: { my $p = Promise.new; my $v = $p.vow; nqp::cancelnotify($cancellation, $!scheduler.queue, { $v.keep(True); }); $p }, :$VMIO-tobe, :$socket-host, :$socket-port; tap($tap); } $tap } method live(--> False) { } method sane(--> True) { } method serial(--> True) { } } method listen-path(IO::Socket::Async:U: Str() $path, Int() $backlog = 128, :$enc = 'utf-8', :$scheduler = $*SCHEDULER) { my $encoding = Encoding::Registry.find($enc); Supply.new: SocketUnixListenerTappable.new: :$path, :$backlog, :$encoding, :$scheduler } method native-descriptor(--> Int) { nqp::filenofh($!VMIO) } sub setup-close(\socket --> Nil) { my $p := Promise.new; nqp::bindattr(socket, IO::Socket::Async, '$!close-promise', $p); nqp::bindattr(socket, IO::Socket::Async, '$!close-vow', $p.vow); } method udp(IO::Socket::Async:U: :$broadcast, :$enc = 'utf-8', :$scheduler = $*SCHEDULER) { my $p = Promise.new; my $encoding = Encoding::Registry.find($enc); nqp::asyncudp( $scheduler.queue, -> Mu \socket, Mu \err { if err { $p.break(err); } else { my $client_socket := nqp::create(self); nqp::bindattr($client_socket, IO::Socket::Async, '$!VMIO', socket); nqp::bindattr_i($client_socket, IO::Socket::Async, '$!udp', 1); nqp::bindattr($client_socket, IO::Socket::Async, '$!enc', $encoding.name); nqp::bindattr($client_socket, IO::Socket::Async, '$!encoder', $encoding.encoder()); setup-close($client_socket); $p.keep($client_socket); } }, nqp::null_s(), 0, $broadcast ?? 1 !! 0, SocketCancellation); await $p } method bind-udp(IO::Socket::Async:U: Str() $host, Int() $port where Port-Number, :$broadcast, :$enc = 'utf-8', :$scheduler = $*SCHEDULER) { my $p = Promise.new; my $encoding = Encoding::Registry.find($enc); nqp::asyncudp( $scheduler.queue(:hint-affinity), -> Mu \socket, Mu \err { if err { $p.break(err); } else { my $client_socket := nqp::create(self); nqp::bindattr($client_socket, IO::Socket::Async, '$!VMIO', socket); nqp::bindattr_i($client_socket, IO::Socket::Async, '$!udp', 1); nqp::bindattr($client_socket, IO::Socket::Async, '$!enc', $encoding.name); nqp::bindattr($client_socket, IO::Socket::Async, '$!encoder', $encoding.encoder()); setup-close($client_socket); $p.keep($client_socket); } }, nqp::unbox_s($host), nqp::unbox_i($port), $broadcast ?? 1 !! 0, SocketCancellation); await $p } method print-to(IO::Socket::Async:D: Str() $host, Int() $port where Port-Number, Str() $str, :$scheduler = $*SCHEDULER) { self.write-to($host, $port, $!encoder.encode-chars($str), :$scheduler) } method write-to(IO::Socket::Async:D: Str() $host, Int() $port where Port-Number, Blob $b, :$scheduler = $*SCHEDULER) { my $p = Promise.new; my $v = $p.vow; nqp::asyncwritebytesto( $!VMIO, $scheduler.queue, -> Mu \bytes, Mu \err { if err { $v.break(err); } else { $v.keep(bytes); } }, nqp::decont($b), SocketCancellation, nqp::unbox_s($host), nqp::unbox_i($port)); $p } } #line 1 SETTING::src/core.c/Proc.rakumod # Proc is a wrapper around Proc::Async, providing a synchronous API atop of # the asynchronous API. my class Proc { has IO::Pipe $.in; has IO::Pipe $.out; has IO::Pipe $.err; has $.exitcode is default(Nil); has $.signal; has $.pid is default(Nil); has @.command; has Proc::Async $!proc; has Bool $!w; has @!pre-spawn; has @!post-spawn; has $!active-handles = 0; has &!start-stdout; has &!start-stderr; has $!finished; submethod BUILD(:$in = '-', :$out = '-', :$err = '-', :$exitcode, Bool :$bin, Bool :$chomp = True, Bool :$merge, :$command, Str :$enc, Str:D :$nl = "\n", :$signal --> Nil) { @!command := $command.List if $command; if nqp::istype($in, IO::Handle) && $in.DEFINITE { @!pre-spawn.push({ $!proc.bind-stdin($in) }); } elsif $in === True { my $cur-promise = Promise.new; $cur-promise.keep(True); $!in = IO::Pipe.new(:proc(self), :$chomp, :$enc, :$bin, nl-out => $nl, :on-write(-> $blob { $cur-promise .= then({ await $!proc.write($blob) }); }), :on-close({ $cur-promise .= then({ $!proc.close-stdin; }); self!await-if-last-handle })); ++$!active-handles; $!w := True; } elsif nqp::istype($in, Str) && $in eq '-' { # Inherit; nothing to do } else { $!w := True; @!post-spawn.push({ $!proc.close-stdin }); } if $merge { if nqp::istype($out, IO::Handle) && $out.DEFINITE { @!pre-spawn.push({ $!proc.stdout(:bin).merge($!proc.stderr(:bin)).act: { $out.write($_) } }); } else { my $chan = Channel.new; $!out = IO::Pipe.new(:proc(self), :$chomp, :$enc, :$bin, nl-in => $nl, :on-read({ (try $chan.receive) // buf8 }), :on-close({ self!await-if-last-handle })); ++$!active-handles; @!pre-spawn.push({ $!proc.stdout(:bin).merge($!proc.stderr(:bin)).act: { $chan.send($_) }, done => { $chan.close }, quit => { $chan.fail($_) } }); } } else { if $out === True { my $chan; my $stdout-supply; &!start-stdout = { $chan = $stdout-supply.Channel; &!start-stdout = Nil; } $!out = IO::Pipe.new(:proc(self), :$chomp, :$enc, :$bin, nl-in => $nl, :on-read({ &!start-stdout() if &!start-stdout; (try $chan.receive) // buf8 }), :on-close({ $chan //= $stdout-supply.Channel; # If we never read self!await-if-last-handle }), :on-native-descriptor({ $!active-handles--; &!start-stdout = Nil; await $stdout-supply.native-descriptor })); ++$!active-handles; @!pre-spawn.push({ $stdout-supply = $!proc.stdout(:bin) }); } elsif nqp::istype($out, IO::Handle) && $out.DEFINITE { @!pre-spawn.push({ $!proc.bind-stdout($out) }); } elsif nqp::istype($out, Str) && $out eq '-' { # Inherit; nothing to do } else { @!pre-spawn.push({ $!proc.stdout(:bin).tap: -> $ { }, quit => -> $ { } }); } if $err === True { my $chan; my $stderr-supply; &!start-stderr = { $chan = $stderr-supply.Channel; &!start-stderr = Nil; } $!err = IO::Pipe.new(:proc(self), :$chomp, :$enc, :$bin, nl-in => $nl, :on-read({ &!start-stderr() if &!start-stderr; (try $chan.receive) // buf8 }), :on-close({ $chan //= $stderr-supply.Channel; # If we never read self!await-if-last-handle }), :on-native-descriptor({ &!start-stderr = Nil; $!active-handles--; await $stderr-supply.native-descriptor })); ++$!active-handles; @!pre-spawn.push({ $stderr-supply = $!proc.stderr(:bin); }); } elsif nqp::istype($err, IO::Handle) && $err.DEFINITE { @!pre-spawn.push({ $!proc.bind-stderr($err) }); } elsif nqp::istype($err, Str) && $err eq '-' { # Inherit; nothing to do } else { @!pre-spawn.push({ $!proc.stderr(:bin).tap: -> $ { }, quit => -> $ { } }); } } if nqp::istype($exitcode, Int) && $exitcode.DEFINITE { $!exitcode = $exitcode; } if nqp::istype($signal, Int) && $signal.DEFINITE { $!signal = $signal; } } method !await-if-last-handle() { self!wait-for-finish unless --$!active-handles; self } method !wait-for-finish { CATCH { default { self!set-status(0x100) } } &!start-stdout() if &!start-stdout; &!start-stderr() if &!start-stderr; self!set-status(await($!finished)!status) if nqp::istype($!exitcode,Nil); } method spawn(*@args where .so, :$cwd = $*CWD, :$env, :$arg0, :$win-verbatim-args = False --> Bool:D) { @!command := @args.List; self!spawn-internal(@args, $cwd, $env, :$arg0, :$win-verbatim-args) } method shell($cmd, :$cwd = $*CWD, :$env --> Bool:D) { @!command := $cmd.List; my @args := Rakudo::Internals.IS-WIN ?? (%*ENV, '/c', $cmd) !! ('/bin/sh', '-c', $cmd); self!spawn-internal(@args, $cwd, $env, :win-verbatim-args) } method !spawn-internal(@args, $cwd, $env, :$arg0, :$win-verbatim-args --> Bool:D) { my %ENV := $env ?? $env.hash !! %*ENV; $!proc := Proc::Async.new(|@args, :$!w, :$arg0, :$win-verbatim-args); .() for @!pre-spawn; $!finished = $!proc.start(:$cwd, :%ENV, scheduler => $PROCESS::SCHEDULER); my $is-spawned := do { CATCH { default { self!set-status(0x100) } } $!pid = await $!proc.ready; True } // False; .() for @!post-spawn; self!wait-for-finish unless $!out || $!err || $!in; $is-spawned } method !set-status($new_status) { $!exitcode = $new_status +> 8; $!signal = $new_status +& 0xFF; } method !status() { self!wait-for-finish; ($!exitcode +< 8) +| $!signal } multi method Numeric(Proc:D:) { self!wait-for-finish; $!exitcode } multi method Bool(Proc:D:) { self!wait-for-finish; $!exitcode == 0 && $!signal == 0 } method exitcode { self!wait-for-finish; $!exitcode } method signal { self!wait-for-finish; $!signal } method sink(--> Nil) { self!wait-for-finish; X::Proc::Unsuccessful.new(:proc(self)).throw if $!exitcode > 0 || $!signal > 0; } } proto sub run(|) {*} multi sub run(*@args where .so, :$in = '-', :$out = '-', :$err = '-', Bool :$bin, Bool :$chomp = True, Bool :$merge, Str :$enc, Str:D :$nl = "\n", :$cwd = $*CWD, :$env, :$arg0, :$win-verbatim-args = False) { my $proc := Proc.new(:$in, :$out, :$err, :$bin, :$chomp, :$merge, :$enc, :$nl); $proc.spawn(@args, :$cwd, :$env, :$arg0, :$win-verbatim-args); $proc } proto sub shell($, *%) {*} multi sub shell($cmd, :$in = '-', :$out = '-', :$err = '-', Bool :$bin, Bool :$chomp = True, Bool :$merge, Str :$enc, Str:D :$nl = "\n", :$cwd = $*CWD, :$env) { my $proc := Proc.new(:$in, :$out, :$err, :$bin, :$chomp, :$merge, :$enc, :$nl); $proc.shell($cmd, :$cwd, :$env); $proc } sub QX($cmd, :$cwd = $*CWD, :$env) is implementation-detail { my $proc := Proc.new(:out); $proc.shell($cmd, :$cwd, :$env); $proc.out.slurp(:close) // "Unable to read from '$cmd'".Failure } #line 1 SETTING::src/core.c/signals.rakumod my role Signal::Signally { multi method CALL-ME(Int() $signum) { $signum ?? (nextsame) !! self } } my enum Signal does Signal::Signally ( BEGIN |do { my $res := nqp::list; my $signals := nqp::clone(nqp::getsignals); nqp::while( nqp::elems($signals), nqp::push( $res, Pair.new(nqp::shift($signals), nqp::abs_i(nqp::shift($signals))) ) ); $res }); proto sub signal(|) {*} multi sub signal(*@signals, :$scheduler = $*SCHEDULER) { if @signals.grep( { !nqp::istype($_,Signal) } ) -> @invalid { die "Found invalid signals: @invalid.join(', ')" } # 0: Signal not supported by host, Negative: Signal not supported by backend sub unsupported($desc, $name, @sigs --> Nil) { warn "The following signals are not supported on this $desc ($name): @sigs.join(', ')"; } my %vm-sigs := Rakudo::Internals.VM-SIGNALS; my ( @valid, @host-unsupported, @vm-unsupported ); for @signals.unique { $_ ?? 0 < %vm-sigs{$_} ?? @valid.push($_) !! @vm-unsupported.push($_) !! @host-unsupported.push($_) } if @host-unsupported -> @s { unsupported 'system', $*KERNEL.name, @s } if @vm-unsupported -> @s { unsupported 'backend', $*VM\ .name, @s } my class SignalCancellation is repr('AsyncTask') { } Supply.merge( @valid.map(-> $signal { class SignalTappable does Tappable { has $!scheduler; has $!signal; submethod BUILD(:$!scheduler, :$!signal) { } method tap(&emit, &, &, &tap) { my $queue := $!scheduler.queue(:hint-time-sensitive); my $setup-semaphore := Semaphore.new(0); my $cancellation := nqp::signal( $queue, -> { $setup-semaphore.release }, $queue, -> $signum { emit(Signal($signum)) }, nqp::unbox_i($!signal), SignalCancellation); $setup-semaphore.acquire; my $t = Tap.new({ nqp::cancel($cancellation) }); tap($t); $t; } method live(--> False) { } method sane(--> False) { } method serial(--> False) { } } Supply.new(SignalTappable.new(:$scheduler, :$signal)); }) ); } #line 1 SETTING::src/core.c/Proc/Async.rakumod my role X::Proc::Async is Exception { has Proc::Async $.proc; } my class X::Proc::Async::TapBeforeSpawn does X::Proc::Async { has $.handle; method message() { "To avoid data races, you must tap $!handle before running the process" } } my class X::Proc::Async::SupplyOrStd does X::Proc::Async { method message() { "Using .Supply on a Proc::Async implies merging stdout and stderr; .stdout " ~ "and .stderr cannot therefore be used in combination with it" } } my class X::Proc::Async::BindOrUse does X::Proc::Async { has $.handle; has $.use; method message() { "Cannot both bind $.handle to a handle and also $.use" } } my class X::Proc::Async::CharsOrBytes does X::Proc::Async { has $.handle; method message() { "Can only tap one of chars or bytes supply for $!handle" } } my class X::Proc::Async::AlreadyStarted does X::Proc::Async { method message() { "Process has already been started" } } my class X::Proc::Async::MustBeStarted does X::Proc::Async { has $.method; method message() { "Process must be started first before calling '$!method'" } } my class X::Proc::Async::OpenForWriting does X::Proc::Async { has $.method; method message() { "Process must be opened for writing with :w to call '$!method'" } } my class Proc::Async { # An asynchronous process output pipe is a Supply that also can provide # the native descriptor of the underlying pipe. class Pipe is Supply { my class PermitOnTap does Tappable { has Tappable $.delegate is built(:bind); has &.on-tap is built(:bind); method tap(|c) { &!on-tap(); $!delegate.tap(|c) } method live() { self.delegate.live } method serial() { self.delegate.serial } method sane() { self.delegate.sane } } has Promise $.native-descriptor is built(:bind); has &!on-nd-used is built(:bind); method native-descriptor() { &!on-nd-used(); $!native-descriptor } method new($delegate, $native-descriptor, &on-tap, &on-nd-used) { self.bless( tappable => PermitOnTap.bless(:$delegate, :&on-tap), :$native-descriptor, :&on-nd-used ) } } my class ProcessCancellation is repr('AsyncTask') { } my enum CharsOrBytes ( :Bytes(0), :Chars(1) ); has $!ready_promise = Promise.new; has $!ready_vow = $!ready_promise.vow; has $!handle_available_promise = Promise.new; has $!stdout_descriptor_vow; has $!stderr_descriptor_vow; has $!stdout_descriptor_used = Promise.new; has $!stderr_descriptor_used = Promise.new; has $.path; # XXX TODO deprecated on 2018-11-04 has @.args; # XXX TODO deprecated on 2018-11-04 has @.command is List; has $.w; has $.enc = 'utf8'; has $.translate-nl = True; has $.arg0; has $.win-verbatim-args = False; has Bool $.started = False; has $!stdout_supply; has CharsOrBytes $!stdout_type; has $!stderr_supply; has CharsOrBytes $!stderr_type; has $!merge_supply; has CharsOrBytes $!merge_type; has $!stdin-fd; has $!stdin-fd-close; has $!stdout-fd; has $!stderr-fd; has $!process_handle; has $!exit_promise; has @!promises; has $!encoder; has @!close-after-exit; proto method new(|) {*} multi method new(*@args where .so) { # XXX TODO .args and .path deprecated on 2018-11-04 to be # replaced by .command https://github.com/rakudo/rakudo/issues/2444 my @command := @args.List; my $path = @args.shift; self.bless(:$path, :@args, :@command, |%_) } submethod TWEAK(--> Nil) { $!encoder := Encoding::Registry.find($!enc).encoder(:$!translate-nl); $!arg0 //= $!path; @!args.unshift: $!arg0; } method !pipe-cbs(\channel) { -> { $!handle_available_promise.then({ nqp::permit($!process_handle, channel, -1) }) }, -> { (channel == 1 ?? $!stdout_descriptor_used !! $!stderr_descriptor_used).keep(True) } } method !pipe(\what, \the-supply, \type, \value, \fd-vow, \permit-channel) { X::Proc::Async::TapBeforeSpawn.new(handle => what, :proc(self)).throw if $!started; X::Proc::Async::CharsOrBytes.new(handle => what, :proc(self)).throw if the-supply and type != value; type = value; the-supply //= Supplier::Preserving.new; if nqp::isrwcont(fd-vow) { my $native-descriptor := Promise.new; fd-vow = $native-descriptor.vow; Pipe.new(the-supply.Supply.Tappable, $native-descriptor, |self!pipe-cbs(permit-channel)) } else { the-supply.Supply } } method !wrap-decoder(Supply:D $bin-supply, $enc, \fd-vow, \permit-channel, :$translate-nl) { my \sup = Rakudo::Internals.BYTE_SUPPLY_DECODER($bin-supply, $enc // $!enc, :translate-nl($translate-nl // $!translate-nl)); if nqp::isrwcont(fd-vow) { my $native-descriptor := Promise.new; fd-vow = $native-descriptor.vow; Pipe.new(sup.Supply.Tappable, $native-descriptor, |self!pipe-cbs(permit-channel)) } else { sup } } proto method stdout(|) {*} multi method stdout(Proc::Async:D: :$bin!) { $!merge_supply ?? X::Proc::Async::SupplyOrStd.new.throw !! $!stdout-fd ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the stdout Supply')).throw !! $bin ?? self!pipe('stdout', $!stdout_supply, $!stdout_type, Bytes, $!stdout_descriptor_vow, 1) !! self.stdout(|%_) } multi method stdout(Proc::Async:D: :$enc, :$translate-nl) { $!merge_supply ?? X::Proc::Async::SupplyOrStd.new.throw !! $!stdout-fd ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the stdout Supply')).throw !! self!wrap-decoder: self!pipe('stdout',$!stdout_supply,$!stdout_type,Chars,Nil,1), $enc, $!stdout_descriptor_vow, 1, :$translate-nl } proto method stderr(|) {*} multi method stderr(Proc::Async:D: :$bin!) { $!merge_supply ?? X::Proc::Async::SupplyOrStd.new.throw !! $!stderr-fd ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the stderr Supply')).throw !! $bin ?? self!pipe('stderr', $!stderr_supply, $!stderr_type, Bytes, $!stderr_descriptor_vow, 2) !! self.stderr(|%_) } multi method stderr(Proc::Async:D: :$enc, :$translate-nl) { $!merge_supply ?? X::Proc::Async::SupplyOrStd.new.throw !! $!stderr-fd ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the stderr Supply')).throw !! self!wrap-decoder: self!pipe('stderr',$!stderr_supply,$!stderr_type,Chars,Nil,2), $enc, $!stderr_descriptor_vow, 2, :$translate-nl } proto method Supply(|) {*} multi method Supply(Proc::Async:D: :$bin!) { $!stdout_supply || $!stderr_supply ?? X::Proc::Async::SupplyOrStd.new.throw !! $!stdout-fd ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the output Supply')).throw !! $!stderr-fd ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the output Supply')).throw !! $bin ?? self!pipe('merge',$!merge_supply,$!merge_type,Bytes,Nil,0) !! self.Supply(|%_) } multi method Supply(Proc::Async:D: :$enc, :$translate-nl) { $!stdout_supply || $!stderr_supply ?? X::Proc::Async::SupplyOrStd.new.throw !! $!stdout-fd ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the output Supply')).throw !! $!stderr-fd ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the output Supply')).throw !! self!wrap-decoder: self!pipe('merge',$!merge_supply,$!merge_type,Chars,Nil,0), $enc, Nil, 0, :$translate-nl } proto method bind-stdin($) {*} multi method bind-stdin(IO::Handle:D $handle --> Nil) { X::Proc::Async::BindOrUse.new(:handle, :use('use :w')).throw if $!w; $!stdin-fd := $handle.native-descriptor; @!close-after-exit.push($handle) if nqp::istype($handle,IO::Pipe); } multi method bind-stdin(Proc::Async::Pipe:D $pipe --> Nil) { if $!w { X::Proc::Async::BindOrUse.new(:handle, :use('use :w')).throw } $!stdin-fd := $pipe.native-descriptor; $!stdin-fd-close := True; } method bind-stdout(IO::Handle:D $handle --> Nil) { $!stdout_supply ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the stdout Supply')).throw !! $!merge_supply ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the output Supply')).throw !! ($!stdout-fd := $handle.native-descriptor); } method bind-stderr(IO::Handle:D $handle --> Nil) { $!stderr_supply ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the stderr Supply')).throw !! $!merge_supply ?? X::Proc::Async::BindOrUse.new(:handle, :use('get the output Supply')).throw !! ($!stderr-fd := $handle.native-descriptor); } method ready(--> Promise) { $!ready_promise } method pid(--> Promise) { $!ready_promise } method !capture(\callbacks,\std,\the-supply --> Promise) { my $promise := Promise.new; my $vow := $promise.vow; my $ss := Rakudo::Internals::SupplySequencer.new( on-data-ready => -> \data { the-supply.emit(data) }, on-completed => -> { the-supply.done(); $vow.keep(the-supply) }, on-error => -> \err { the-supply.quit(err); $vow.keep((the-supply,err)) }); nqp::bindkey(callbacks, std ~ '_bytes' , -> Mu \seq, Mu \data, Mu \err { $ss.process(seq, data, err) }); $promise } method !win-quote-CommandLineToArgvW(*@args) { my @quoted_args; for @args -> $arg { if !$arg.contains(' ') && !$arg.contains('"') && !$arg.contains('\t') && !$arg.contains('\n') && !$arg.contains('\v') { @quoted_args.push: $arg; } else { my $quoted_arg = $arg; $quoted_arg ~~ s:g/ ( \\* ) \" /$0$0\\\"/; $quoted_arg ~~ s/ ( \\+ ) $ /$0$0/; @quoted_args.push: '"' ~ $quoted_arg ~ '"'; } } @quoted_args.join: ' ' } method start(Proc::Async:D: :$scheduler = $*SCHEDULER, :$ENV, :$cwd = $*CWD --> Promise) { sub actually-start() { nqp::istype($!stdin-fd,Promise) ?? start { await $!stdin-fd.then({ $!stdin-fd := .result }); await self!start-internal($scheduler, $ENV, $cwd); } !! self!start-internal($scheduler, $ENV, $cwd) } if nqp::eqaddr(cas($!started, False, True),False) { actually-start } elsif $!started { X::Proc::Async::AlreadyStarted.new(proc => self).throw } } method !start-internal($scheduler, $ENV, $cwd --> Promise) { my %ENV := $ENV ?? $ENV.hash !! %*ENV; my @quoted-args; if Rakudo::Internals.IS-WIN { @quoted-args.push( $!win-verbatim-args ?? @!args.join(' ') !! self!win-quote-CommandLineToArgvW(@!args)); } else { @quoted-args := @!args; } $!exit_promise := Promise.new; my Mu $callbacks := nqp::hash(); nqp::bindkey($callbacks, 'done', -> Mu \status { $!exit_promise.keep(Proc.new( :exitcode(status +> 8), :signal(status +& 0xFF), :command( @!command ), )) }); nqp::bindkey($callbacks, 'ready', -> Mu \handles = Nil, $pid = 0 { if nqp::isconcrete(handles) { nqp::atpos_i(handles,0) < 0 ?? .break("STDOUT descriptor not available") !! .keep(nqp::atpos_i(handles,0)) with $!stdout_descriptor_vow; nqp::atpos_i(handles,1) < 0 ?? .break("STDERR descriptor not available") !! .keep(nqp::atpos_i(handles,1)) with $!stderr_descriptor_vow; } $!ready_vow.keep($pid); }); nqp::bindkey($callbacks, 'error', -> Mu \err { my $error := X::OS.new(os-error => err); $!exit_promise.break($error); $!ready_vow.break($error); }); @!promises.push(Promise.anyof( self!capture($callbacks,'stdout',$!stdout_supply), $!stdout_descriptor_used )) if $!stdout_supply; @!promises.push(Promise.anyof( self!capture($callbacks,'stderr',$!stderr_supply), $!stderr_descriptor_used )) if $!stderr_supply; @!promises.push( self!capture($callbacks,'merge',$!merge_supply) ) if $!merge_supply; nqp::bindkey($callbacks, 'buf_type', nqp::create(buf8.^pun)); nqp::bindkey($callbacks, 'write', True) if $.w; nqp::bindkey($callbacks, 'stdin_fd', $!stdin-fd) if $!stdin-fd.DEFINITE; nqp::bindkey($callbacks, 'stdin_fd_close', True) if $!stdin-fd-close; nqp::bindkey($callbacks, 'stdout_fd', $!stdout-fd) if $!stdout-fd.DEFINITE; nqp::bindkey($callbacks, 'stderr_fd', $!stderr-fd) if $!stderr-fd.DEFINITE; $!process_handle := nqp::spawnprocasync($scheduler.queue(:hint-affinity), $!path.Str, CLONE-LIST-DECONTAINERIZED(@quoted-args), $cwd.Str, CLONE-HASH-DECONTAINERIZED(%ENV), $callbacks, ); $!handle_available_promise.keep(True); nqp::permit($!process_handle, 0, -1) if $!merge_supply; Promise.allof( $!exit_promise, @!promises ).then({ .close for @!close-after-exit; $!exit_promise.status == Broken ?? $!exit_promise.cause.throw !! $!exit_promise.result }) } method print(Proc::Async:D: Str() $str, :$scheduler = $*SCHEDULER) { $!w ?? $!started ?? self.write($!encoder.encode-chars($str)) !! X::Proc::Async::MustBeStarted.new(:method, :proc(self)).throw !! X::Proc::Async::OpenForWriting.new(:method, :proc(self)).throw } method put(Proc::Async:D: \x, |c) { $!w ?? $!started ?? self.print( x.join ~ "\n", |c ) !! X::Proc::Async::MustBeStarted.new(:method,:proc(self)).throw !! X::Proc::Async::OpenForWriting.new( :method,:proc(self)).throw } method say(Proc::Async:D: \x, |c) { $!w ?? $!started ?? self.print( x.gist ~ "\n", |c ) !! X::Proc::Async::MustBeStarted.new(:method,:proc(self)).throw !! X::Proc::Async::OpenForWriting.new( :method,:proc(self)).throw } method write(Proc::Async:D: Blob:D $b, :$scheduler = $*SCHEDULER) { if $!w && $!started { my $p := Promise.new; my $v := $p.vow; nqp::asyncwritebytes( $!process_handle, $scheduler.queue, -> Mu \bytes, Mu \err { err ?? $v.break(err) !! $v.keep(bytes) }, nqp::decont($b), ProcessCancellation ); $p } else { $!w ?? X::Proc::Async::MustBeStarted.new(:method, :proc(self)).throw !! X::Proc::Async::OpenForWriting.new(:method, :proc(self)).throw; } } method close-stdin(Proc::Async:D: --> True) { $!w ?? $!started ?? nqp::closefh($!process_handle) !! X::Proc::Async::MustBeStarted.new(:method, :proc(self)).throw !! X::Proc::Async::OpenForWriting.new(:method, :proc(self)).throw } # Note: some of the duplicated code in methods could be moved to # proto, but at the moment (2017-06-02) that makes the call 24% slower proto method kill(|) {*} multi method kill(Proc::Async:D: Signal:D \signal = SIGHUP) { $!started ?? nqp::killprocasync($!process_handle, signal.value) !! X::Proc::Async::MustBeStarted.new(:method, :proc(self)).throw } multi method kill(Proc::Async:D: Int:D \signal) { $!started ?? nqp::killprocasync($!process_handle, signal) !! X::Proc::Async::MustBeStarted.new(:method, :proc(self)).throw } multi method kill(Proc::Async:D: Str:D \signal) { $!started ?? nqp::killprocasync($!process_handle, $*KERNEL.signal: signal) !! X::Proc::Async::MustBeStarted.new(:method, :proc(self)).throw } } #line 1 SETTING::src/core.c/Systemic.rakumod role Systemic { has Str $.name is built(:bind) = 'unknown'; has Str $.auth is built(:bind) = 'unknown'; has Version $.version is built(:bind); has Blob $.signature; has Str $.desc; multi method gist(Systemic:D: --> Str:D) { $!version ?? "$.name ($.version)" !! $.name } method Str(--> Str:D) { $.name } } #line 1 SETTING::src/core.c/VM.rakumod class VM does Systemic { has $.config is built(:bind) = nqp::backendconfig; has $.prefix is built(:bind) = $!config; has $.precomp-ext is built(:bind) = "moarvm"; has $.precomp-target is built(:bind) = "mbc"; submethod TWEAK(--> Nil) { # https://github.com/rakudo/rakudo/issues/3436 nqp::bind($!name,'moar'); nqp::bind($!desc,'Short for "Metamodel On A Runtime", MoarVM is a modern virtual machine built for the Rakudo compiler and the NQP Compiler Toolchain.'); nqp::bind($!auth,'The MoarVM Team'); nqp::bind($!version,Version.new($!config // "unknown")); # add new backends here please } method platform-library-name(IO::Path $library, Version :$version) { my int $is-win = Rakudo::Internals.IS-WIN; my int $is-darwin = self.osname eq 'darwin'; my $basename = $library.basename; my int $full-path = $library ne $basename; my $dirname = $library.dirname; # OS X needs version before extension $basename ~= ".$version" if $is-darwin && $version.defined; my $dll = self.config; my $platform-name = sprintf($dll, $basename); $platform-name ~= '.' ~ $version if $version.defined and nqp::iseq_i(nqp::add_i($is-darwin,$is-win),0); $full-path ?? $dirname.IO.add($platform-name).absolute !! $platform-name.IO } proto method osname(|) {*} multi method osname(VM:U:) { nqp::lc(nqp::atkey(nqp::backendconfig,'osname')) } multi method osname(VM:D:) { nqp::lc($!config) } method request-garbage-collection(--> Nil) { nqp::force_gc } } Rakudo::Internals.REGISTER-DYNAMIC: '$*VM', { PROCESS::<$VM> := VM.new; } #line 1 SETTING::src/core.c/Distro.rakumod # The Distro class and its methods, underlying $*DISTRO, are a work in progress. # It is very hard to capture data about a changing universe in a stable API. # If you find errors for your hardware or OS distribution, please report them # with the values that you expected and how to get them in your situation. class Distro does Systemic { has Str $.release is built(:bind); has Bool $.is-win is built(False); has Str $.path-sep is built(:bind); submethod TWEAK (--> Nil) { # https://github.com/rakudo/rakudo/issues/3436 nqp::bind($!name,$!name.lc.trans(" " => "")); # lowercase spaceless $!is-win := so $!name eq any ; } # This is a temporary migration method needed for installation method cur-sep() { "," } } # set up $*DISTRO Rakudo::Internals.REGISTER-DYNAMIC: '$*DISTRO', { my $config := VM.new.config; my $name := $config; my $version := $config; my $path-sep := $name eq 'MSWin32' ?? ';' !! ':'; my Str $release := "unknown"; my Str $auth := "unknown"; my Str $desc := "unknown"; # helper sub to convert key:value lines into a hash sub kv2Map(Str:D $text, str $delimiter --> Map:D) { my $hash := nqp::hash; for $text.lines -> str $line { my $parts := nqp::split($delimiter,$line); if nqp::elems($parts) > 1 { nqp::bindkey( $hash, nqp::shift($parts), nqp::hllize( nqp::elems($parts) == 2 ?? nqp::shift($parts) !! nqp::join($delimiter,$parts) ).trim ); } } nqp::p6bindattrinvres(nqp::create(Map),Map,'$!storage',$hash) } # darwin specific info if $name eq 'darwin' { my $lookup := kv2Map(shell("sw_vers", :out, :err).out.slurp(:close),':'); $name := $_ with $lookup; $version := $_ with $lookup; $release := $_ with $lookup; $auth := 'Apple Inc.'; # presumably my constant $names = nqp::hash( '10.0', 'Cheetah', '10.1', 'Puma', '10.2', 'Jaguar', '10.3', 'Panther', '10.4', 'Tiger', '10.5', 'Leopard', '10.6', 'Snow Leopard', '10.7', 'Lion', '10.8', 'Mountain Lion', '10.9', 'Mavericks', '10.10', 'Yosemite', '10.11', 'El Capitan', '10.12', 'Sierra', '10.13', 'High Sierra', '10.14', 'Mojave', '10.15', 'Catalina', '11', 'Big Sur', '12', 'Monterey', '13', 'Ventura', '14', 'Sonoma', ); my ($major, $minor) = $version.split(".").head(2); my str $key = $major eq '10' ?? "$major.$minor" !! $major; if nqp::atkey($names,$key) -> $nick { $desc := $nick; } } elsif Rakudo::Internals.FILETEST-E('/etc/os-release') { my $lookup := kv2Map('/etc/os-release'.IO.slurp.subst(:g,'"'),'='); $name := $_ with $lookup; $auth := $_ with $lookup; $version := $_ with $lookup; $release := $_ with $lookup; $desc := $_ with $lookup; } elsif $name eq 'linux' { my $lookup := kv2Map(shell(, :out, :err).out.slurp(:close),":"); $auth := $_ with $lookup<<"DISTRIBUTOR ID">>; $desc := $_ with $lookup; $release := $_ with $lookup; } $version := $version.Version; # make sure it is a Version PROCESS::<$DISTRO> := Distro.new(:$name, :$version, :$release, :$auth, :$path-sep, :$desc); } #line 1 SETTING::src/core.c/Kernel.rakumod # The Kernel class and its methods, underlying $*KERNEL, are a work in progress. # It is very hard to capture data about a changing universe in a stable API. # If you find errors for your hardware or OS distribution, please report them # with the values that you expected and how to get them in your situation. class Kernel does Systemic { has Str $!release is built(:bind); has Str $!hardware is built(:bind); has Str $!arch is built(:bind); has Int $!bits is built(:bind); has $!uname; method !uname { $!uname ?? $!uname !! ($!uname := nqp::uname()) } method !uname-s(--> Str:D) { nqp::atpos_s(self!uname, nqp::const::UNAME_SYSNAME) } method !uname-r(--> Str:D) { nqp::atpos_s(self!uname, nqp::const::UNAME_RELEASE) } method !uname-v(--> Str:D) { nqp::atpos_s(self!uname, nqp::const::UNAME_VERSION) } method !uname-m(--> Str:D) { nqp::atpos_s(self!uname, nqp::const::UNAME_MACHINE) } method !uname-p(--> Str:D) { # TODO: find a way to get this without shelling out try shell("uname -p", :out, :!err).out.slurp(:close).chomp; } method name(--> Str:D) { $!name eq 'unknown' ?? self!name($*DISTRO.name) !! $!name } method !name(Str:D \distro --> Str:D) { # https://github.com/rakudo/rakudo/issues/3436 nqp::bind($!name,distro eq 'mswin32' ?? 'win32' !! distro eq 'browser' ?? 'browser' !! self!uname-s.lc ) } method version(--> Version:D) { $!version ?? $!version # https://github.com/rakudo/rakudo/issues/3436 !! nqp::bind($!version,self!uname-v.Version) } method release(--> Str:D) { # somewhat counter-intuitively the UNAME_RELEASE is what # most people think of the kernel version $!release ?? $!release !! ($!release := self!uname-r) } method hardware(--> Str:D) { $!hardware ?? $!hardware !! ($!hardware := self!uname-m) } method arch { $!arch ?? $!arch !! self!arch($*DISTRO.name) } method !arch(Str:D \distro --> Str:D) { $!arch := distro eq 'raspbian' ?? self!uname-m !! distro eq 'browser' ?? self!uname-m !! self!uname-p } method archname(--> Str:D) { self.hardware ~ '-' ~ self.name } method bits(--> Int:D) { $!bits ?? $!bits # naive approach !! ($!bits := $.hardware ~~ m/ _64 | w | amd64 | arm64 / ?? 64 !! 32); } method hostname(--> Str:D) { nqp::p6box_s(nqp::gethostname) } has @!signals; # Signal has $!signals-setup-lock = Lock.new; has $!signals-setup = False; method signals (Kernel:D:) { unless $!signals-setup { $!signals-setup-lock.protect: { unless $!signals-setup { my \arr = nqp::list(Nil); my int $els = nqp::add_i(Signal.enums.values.max, 1); my int $i = 1; nqp::while( nqp::islt_i($i, $els), nqp::bindpos(arr, $i, Signal($i) // Nil), ++$i ); @!signals = |arr; $!signals-setup = True; } } } @!signals } has %!signals-by-Str; has $!signals-by-Str-setup = False; proto method signal (|) {*} multi method signal(Kernel:D: Str:D $signal --> Int:D) { unless $!signals-by-Str-setup { $!signals-setup-lock.protect: { unless $!signals-by-Str-setup { my int $els = @.signals.elems; my int $i = -1; nqp::while( nqp::isgt_i($els,++$i), ($_ := @!signals.AT-POS($i)).defined && %!signals-by-Str.ASSIGN-KEY(.Str, nqp::decont($i)) ); $!signals-by-Str-setup := True; } } } %!signals-by-Str{$signal} // %!signals-by-Str{"SIG$signal"} // Int; } multi method signal(Kernel:D: Signal:D \signal --> Int:D) { signal.value } multi method signal(Kernel:D: Int:D \signal --> Int:D) { signal } method cpu-cores(--> Int) { nqp::cpucores } my $cpu-cores-but-one := nqp::null; method cpu-cores-but-one() is implementation-detail { nqp::ifnull( $cpu-cores-but-one, $cpu-cores-but-one := max nqp::cpucores() - 1, 1 ) } method cpu-usage(--> Int) is raw { my int @rusage; nqp::getrusage(@rusage); nqp::atpos_i(@rusage, nqp::const::RUSAGE_UTIME_SEC) * 1000000 + nqp::atpos_i(@rusage, nqp::const::RUSAGE_UTIME_MSEC) + nqp::atpos_i(@rusage, nqp::const::RUSAGE_STIME_SEC) * 1000000 + nqp::atpos_i(@rusage, nqp::const::RUSAGE_STIME_MSEC) } method free-memory(--> Int) { nqp::freemem() } my $total-mem := nqp::null(); method total-memory(--> Int) { nqp::ifnull( $total-mem, nqp::bind($total-mem,nqp::p6box_i(nqp::totalmem())) ) } my $endian := nqp::null; method endian(--> Endian:D) { nqp::ifnull( $endian, nqp::bind($endian,nqp::if( blob8.new(0,1).read-int16(0) == 1, # hacky way to find out BigEndian, LittleEndian )) ) } } Rakudo::Internals.REGISTER-DYNAMIC: '$*KERNEL', { PROCESS::<$KERNEL> := Kernel.new; } #line 1 SETTING::src/core.c/Compiler.rakumod class Compiler does Systemic { my constant $compiler = nqp::getcomp("Raku"); my constant $config = nqp::gethllsym('default','SysConfig').rakudo-build-config; my constant $compilation-id = nqp::box_s( nqp::sha1($*W.handle.Str ~ nqp::atkey($config,'source-digest')),Str ); my constant $backend = $compiler.backend; my constant $name = $backend.name; # XXX Various issues with this stuff on JVM has $.id is built(:bind) = $compilation-id; has $.release is built(:bind) = BEGIN nqp::ifnull(nqp::atkey($config,'release-number'),""); has $.codename is built(:bind) = BEGIN nqp::ifnull(nqp::atkey($config,'codename'),""); submethod TWEAK(--> Nil) { # https://github.com/rakudo/rakudo/issues/3436 nqp::bind($!name,'rakudo'); nqp::bind($!auth,'Yet Another Society'); # looks like: 2018.01-50-g8afd791c1 nqp::bind($!version,BEGIN Version.new( nqp::box_s(nqp::atkey($config,'version'),Str) )) unless $!version; } method backend() { $name } proto method id(|) {*} multi method id(Compiler:U:) { $compilation-id } multi method id(Compiler:D:) { $!id } method verbose-config(:$say) { my $items := nqp::list_s; nqp::push_s($items,$name ~ '::' ~ .key ~ '=' ~ .value) for BEGIN $backend.config; my $language := BEGIN $compiler.language; nqp::push_s($items,$language ~ '::' ~ .key ~ '=' ~ .value) for BEGIN $compiler.config; nqp::push_s( $items, 'repo::chain=' ~ (try $*REPO.repo-chain.map( *.gist ).join(" ")) // '' ); my $distro := $*DISTRO; nqp::push_s($items,"distro::$_=" ~ ($distro."$_"() // '')) for ; my $kernel := $*KERNEL; nqp::push_s($items,"kernel::$_=" ~ ($kernel."$_"() // '')) for ; try { require System::Info; my $sysinfo := System::Info.new; nqp::push_s($items,"sysinfo::{ .name }={ $sysinfo.$_ // '' }") for $sysinfo.^methods.grep: { .count == 1 && .name ne 'new' }; } my $string := nqp::join("\n",Rakudo::Sorting.MERGESORT-str($items)); if $say { nqp::say($string); Nil } else { my %config; my $clone := nqp::clone($items); while $clone { my ($main,$key,$value) = nqp::shift_s($clone).split(<:: =>); %config.AT-KEY($main).AT-KEY($key) = $value } %config but role { has $!string = $string; proto method Str() { $!string } proto method gist() { $!string } } } } method supports-op(str $name) { $compiler.supports-op($name) } } #line 1 SETTING::src/core.c/Raku.rakumod class Raku does Systemic { has Compiler $.compiler is built(:bind) = Compiler.new; submethod TWEAK(--> Nil) { # https://github.com/rakudo/rakudo/issues/3436 nqp::bind($!name,'Raku'); nqp::bind($!auth,'The Perl Foundation'); nqp::bind($!version,nqp::getcomp('Raku').language_version.Version); } method VMnames { } method DISTROnames { } method KERNELnames { } my $version-cache := nqp::hash; my $version-cache-lock := Lock.new; method version { $version-cache-lock.protect: { my $comp-ver := nqp::getcomp('Raku').language_version(); nqp::existskey($version-cache,$comp-ver) ?? nqp::atkey($version-cache,$comp-ver) !! nqp::bindkey($version-cache,$comp-ver,Version.new($comp-ver)) } } } class Perl is Raku { } # indeed :-) #line 1 SETTING::src/core.c/Rakudo/Internals/JSON.rakumod my class JSONException is Exception { has $.text; method message { 'Invalid JSON: ' ~ $!text } } # A slightly modified version of https://github.com/timo/json_fast/blob/5ce76c039dc143fa9a068f1dfa47b42e58046821/lib/JSON/Fast.pm6 # Key differences: # - to-json stringifies Version objects # - Removes $*JSON_NAN_INF_SUPPORT and the Falsey code path(s) that use it # - Custom code for stringifying some exception related things my class Rakudo::Internals::JSON { my multi sub to-surrogate-pair(Int $ord) { my int $base = $ord - 0x10000; my int $top = $base +& 0b1_1111_1111_1100_0000_0000 +> 10; my int $bottom = $base +& 0b11_1111_1111; Q/\u/ ~ (0xD800 + $top).base(16) ~ Q/\u/ ~ (0xDC00 + $bottom).base(16); } my multi sub to-surrogate-pair(Str $input) { to-surrogate-pair(nqp::ordat($input, 0)); } my $tab := nqp::list_i(92,116); # \t my $lf := nqp::list_i(92,110); # \n my $cr := nqp::list_i(92,114); # \r my $qq := nqp::list_i(92, 34); # \" my $bs := nqp::list_i(92, 92); # \\ # Convert string to decomposed codepoints. Run over that integer array # and inject whatever is necessary, don't do anything if simple ascii. # Then convert back to string and return that. sub str-escape(\text) { my $codes := text.NFD; my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems($codes)), nqp::if( nqp::isle_i((my int $code = nqp::atpos_u($codes,$i)),92) || nqp::isge_i($code,128), nqp::if( # not ascii nqp::isle_i($code,31), nqp::if( # control nqp::iseq_i($code,10), nqp::splice($codes,$lf,$i++,1), # \n nqp::if( nqp::iseq_i($code,13), nqp::splice($codes,$cr,$i++,1), # \r nqp::if( nqp::iseq_i($code,9), nqp::splice($codes,$tab,$i++,1), # \t nqp::stmts( # other control nqp::splice($codes,$code.fmt(Q/\u%04x/).NFD,$i,1), ($i = nqp::add_i($i,5)) ) ) ) ), nqp::if( # not control nqp::iseq_i($code,34), nqp::splice($codes,$qq,$i++,1), # " nqp::if( nqp::iseq_i($code,92), nqp::splice($codes,$bs,$i++,1), # \ nqp::if( nqp::isge_i($code,0x10000), nqp::stmts( # surrogates nqp::splice( $codes, (my $surrogate := to-surrogate-pair($code.chr).NFD), $i, 1 ), ($i = nqp::sub_i(nqp::add_i($i,nqp::elems($surrogate)),1)) ) ) ) ) ) ) ); nqp::strfromcodes($codes) } method to-json( \obj, Bool :$pretty = False, Int :$level = 0, int :$spacing = 2, Bool :$sorted-keys = False, ) { my $out := nqp::list_s; # cannot use str @out because of JVM my str $spaces = ' ' x $spacing; my str $comma = ",\n" ~ $spaces x $level; #-- helper subs from here, with visibility to the above lexicals sub pretty-positional(\positional --> Nil) { $comma = nqp::concat($comma,$spaces); nqp::push_s($out,'['); nqp::push_s($out,nqp::substr($comma,1)); for positional.list { jsonify($_); nqp::push_s($out,$comma); } nqp::pop_s($out); # lose last comma $comma = nqp::substr($comma,0,nqp::sub_i(nqp::chars($comma),$spacing)); nqp::push_s($out,nqp::substr($comma,1)); nqp::push_s($out,']'); } sub pretty-associative(\associative --> Nil) { $comma = nqp::concat($comma,$spaces); nqp::push_s($out,'{'); nqp::push_s($out,nqp::substr($comma,1)); my \pairs := $sorted-keys ?? associative.sort(*.key) !! associative.list; for pairs { jsonify(.key); nqp::push_s($out,": "); jsonify(.value); nqp::push_s($out,$comma); } nqp::pop_s($out); # lose last comma $comma = nqp::substr($comma,0,nqp::sub_i(nqp::chars($comma),$spacing)); nqp::push_s($out,nqp::substr($comma,1)); nqp::push_s($out,'}'); } sub unpretty-positional(\positional --> Nil) { nqp::push_s($out,'['); my int $before = nqp::elems($out); for positional.list { jsonify($_); nqp::push_s($out,","); } nqp::pop_s($out) if nqp::elems($out) > $before; # lose last comma nqp::push_s($out,']'); } sub unpretty-associative(\associative --> Nil) { nqp::push_s($out,'{'); my \pairs := $sorted-keys ?? associative.sort(*.key) !! associative.list; my int $before = nqp::elems($out); for pairs { jsonify(.key); nqp::push_s($out,":"); jsonify(.value); nqp::push_s($out,","); } nqp::pop_s($out) if nqp::elems($out) > $before; # lose last comma nqp::push_s($out,'}'); } sub jsonify(\obj --> Nil) { with obj { # basic ones when Bool { nqp::push_s($out,obj ?? "true" !! "false"); } when IntStr { jsonify(.Int); } when RatStr { jsonify(.Rat); } when NumStr { jsonify(.Num); } when Str { nqp::push_s($out,'"'); nqp::push_s($out,str-escape(obj)); nqp::push_s($out,'"'); } # numeric ones when Int { nqp::push_s($out,.Str); } when Rat { nqp::push_s($out,.contains(".") ?? $_ !! "$_.0") given .Str; } when FatRat { nqp::push_s($out,.contains(".") ?? $_ !! "$_.0") given .Str; } when Num { if nqp::isnanorinf($_) { nqp::push_s( $out, $*JSON_NAN_INF_SUPPORT ?? obj.Str !! "null" ); } else { nqp::push_s($out,.contains("e") ?? $_ !! $_ ~ "e0") given .Str; } } # iterating ones when Seq { jsonify(.cache); } when Positional { $pretty ?? pretty-positional($_) !! unpretty-positional($_); } when Associative { $pretty ?? pretty-associative($_) !! unpretty-associative($_); } # rarer ones when Dateish { nqp::push_s($out,qq/"$_"/); } when Instant { nqp::push_s($out,qq/"{.DateTime}"/) } when Version { jsonify(.Str) } # also handle exceptions here when Exception { jsonify(obj.^name => Hash.new( (message => nqp::can(obj,"message") ?? obj.message !! Nil ), obj.^attributes.grep(*.has_accessor).map: { with .name.substr(2) -> $attr { $attr => ( (.defined and not $_ ~~ Real|Positional|Associative) ?? .Str !! $_ ) given obj."$attr"() } } )); } # huh, what? default { jsonify( { 0 => 'null' } ); } } else { nqp::push_s($out,'null'); } } #-- do the actual work jsonify(obj); nqp::join("",$out) } # there's a new version of from-json and friends that's a lot faster, # but it relies on the existence of the Uni type. # It doesn't exist on jvm, unfortunately. my $ws := nqp::list_i; nqp::bindpos_i($ws, 9, 1); # \t nqp::bindpos_i($ws, 10, 1); # \n nqp::bindpos_i($ws, 13, 1); # \r nqp::bindpos_i($ws, 32, 1); # space nqp::push_i($ws, 0); # allow for -1 as value my sub nom-ws(str $text, int $pos is rw --> Nil) { nqp::while( nqp::atpos_i($ws, nqp::ordat($text, $pos)), $pos = nqp::add_i($pos, 1) ) } my $hexdigits := nqp::list; nqp::bindpos($hexdigits, 48, 0); # 0 nqp::bindpos($hexdigits, 49, 1); # 1 nqp::bindpos($hexdigits, 50, 2); # 2 nqp::bindpos($hexdigits, 51, 3); # 3 nqp::bindpos($hexdigits, 52, 4); # 4 nqp::bindpos($hexdigits, 53, 5); # 5 nqp::bindpos($hexdigits, 54, 6); # 6 nqp::bindpos($hexdigits, 55, 7); # 7 nqp::bindpos($hexdigits, 56, 8); # 8 nqp::bindpos($hexdigits, 57, 9); # 9 nqp::bindpos($hexdigits, 65, 10); # A nqp::bindpos($hexdigits, 66, 11); # B nqp::bindpos($hexdigits, 67, 12); # C nqp::bindpos($hexdigits, 68, 13); # D nqp::bindpos($hexdigits, 69, 14); # E nqp::bindpos($hexdigits, 70, 15); # F nqp::bindpos($hexdigits, 97, 10); # a nqp::bindpos($hexdigits, 98, 11); # b nqp::bindpos($hexdigits, 99, 12); # c nqp::bindpos($hexdigits, 100, 13); # d nqp::bindpos($hexdigits, 101, 14); # e nqp::bindpos($hexdigits, 102, 15); # f my $escapees := nqp::list_i; nqp::bindpos_i($escapees, 34, 34); # " nqp::bindpos_i($escapees, 47, 47); # / nqp::bindpos_i($escapees, 92, 92); # \ nqp::bindpos_i($escapees, 98, 8); # b nqp::bindpos_i($escapees, 102, 12); # f nqp::bindpos_i($escapees, 110, 10); # n nqp::bindpos_i($escapees, 114, 13); # r nqp::bindpos_i($escapees, 116, 9); # t my sub parse-string(str $text, int $pos is rw) { nqp::if( nqp::eqat($text, '"', nqp::sub_i($pos,1)) # starts with clean " && nqp::eqat($text, '"', # ends with clean " (my int $end = nqp::findnotcclass(nqp::const::CCLASS_WORD, $text, $pos, nqp::sub_i(nqp::chars($text),$pos))) ), nqp::stmts( (my $string := nqp::substr($text, $pos, nqp::sub_i($end, $pos))), ($pos = nqp::add_i($end,1)), $string ), parse-string-slow($text, $pos) ) } # Slower parsing of string if the string does not exist of 0 or more # alphanumeric characters my sub parse-string-slow(str $text, int $pos is rw) { my int $start = nqp::sub_i($pos,1); # include starter in string nqp::until( nqp::iseq_i((my $end := nqp::index($text, '"', $pos)), -1), nqp::stmts( ($pos = $end + 1), (my int $index = 1), nqp::while( nqp::eqat($text, '\\', nqp::sub_i($end, $index)), ($index = nqp::add_i($index, 1)) ), nqp::if( nqp::bitand_i($index, 1), (return unjsonify-string( # preceded by an even number of \ nqp::strtocodes( nqp::substr($text, $start, $end - $start), nqp::const::NORMALIZE_NFD, nqp::create(NFD) ), $pos )) ) ) ); die "unexpected end of input in string"; } # convert a sequence of Uni elements into a string, with the initial # quoter as the first element. my sub unjsonify-string(Uni:D \codes, int $pos) { nqp::shift_i(codes); # lose the " without any decoration # fetch a single codepoint from the next 4 Uni elements my sub fetch-codepoint() { my int $codepoint = 0; my int $times = 5; nqp::while( ($times = nqp::sub_i($times, 1)), nqp::if( nqp::elems(codes), nqp::if( nqp::iseq_i( (my uint32 $ordinal = nqp::shift_i(codes)), 48 # 0 ), ($codepoint = nqp::mul_i($codepoint, 16)), nqp::if( (my int $adder = nqp::atpos($hexdigits, $ordinal)), ($codepoint = nqp::add_i( nqp::mul_i($codepoint, 16), $adder )), (die "invalid hexadecimal char { nqp::chr($ordinal).perl } in \\u sequence at $pos") ) ), (die "incomplete \\u sequence in string near $pos") ) ); $codepoint } my $output := nqp::create(Uni); nqp::while( nqp::elems(codes), nqp::if( nqp::iseq_i( (my uint32 $ordinal = nqp::shift_i(codes)), 92 # \ ), nqp::if( # haz an escape nqp::iseq_i(($ordinal = nqp::shift_i(codes)), 117), # u nqp::stmts( # has a \u escape nqp::if( nqp::isge_i((my int $codepoint = fetch-codepoint), 0xD800) && nqp::islt_i($codepoint, 0xE000), nqp::if( # high surrogate nqp::iseq_i(nqp::atpos_i(codes, 0), 92) # \ && nqp::iseq_i(nqp::atpos_i(codes, 1), 117), # u nqp::stmts( # low surrogate nqp::shift_i(codes), # get rid of \ nqp::shift_i(codes), # get rid of u nqp::if( nqp::isge_i((my int $low = fetch-codepoint), 0xDC00), ($codepoint = nqp::add_i( # got low surrogate nqp::add_i( # transmogrify nqp::mul_i(nqp::sub_i($codepoint, 0xD800), 0x400), 0x10000 # with ), # low surrogate nqp::sub_i($low, 0xDC00) )), (die "improper low surrogate \\u$low.base(16) for high surrogate \\u$codepoint.base(16) near $pos") ) ), (die "missing low surrogate for high surrogate \\u$codepoint.base(16) near $pos") ) ), nqp::push_i($output, $codepoint) ), nqp::if( # other escapes? ($codepoint = nqp::atpos_i($escapees, $ordinal)), nqp::push_i($output, $codepoint), # recognized escape (die "unknown escape code found '\\{ # huh? nqp::chr($ordinal) }' found near $pos") ) ), nqp::if( # not an escape nqp::iseq_i($ordinal, 9) || nqp::iseq_i($ordinal, 10), # \t \n (die "this kind of whitespace is not allowed in a string: '{ nqp::chr($ordinal).perl }' near $pos"), nqp::push_i($output, $ordinal) # ok codepoint ) ) ); nqp::strfromcodes($output) } my sub parse-numeric(str $text, int $pos is rw) { my int $start = nqp::sub_i($pos,1); my int $end = nqp::findnotcclass(nqp::const::CCLASS_NUMERIC, $text, $pos, nqp::sub_i(nqp::chars($text),$pos)); nqp::if( nqp::iseq_i(nqp::ordat($text, $end), 46), # . nqp::stmts( ($pos = nqp::add_i($end,1)), ($end = nqp::findnotcclass(nqp::const::CCLASS_NUMERIC, $text, $pos, nqp::sub_i(nqp::chars($text),$pos)) ) ) ); nqp::if( nqp::iseq_i((my int $ordinal = nqp::ordat($text, $end)), 101) # e || nqp::iseq_i($ordinal, 69), # E nqp::stmts( ($pos = nqp::add_i($end,1)), ($pos = nqp::add_i($pos, nqp::eqat($text, '-', $pos) || nqp::eqat($text, '+', $pos) )), ($end = nqp::findnotcclass(nqp::const::CCLASS_NUMERIC, $text, $pos, nqp::sub_i(nqp::chars($text),$pos)) ) ) ); my $result := nqp::substr($text, $start, nqp::sub_i($end,$start)).Numeric; nqp::if( nqp::istype($result, Failure), nqp::stmts( $result.Bool, # handle Failure (die "at $pos: invalid number token $text.substr($start,$end - $start)") ), nqp::stmts( ($pos = $end), $result ) ) } my sub parse-obj(str $text, int $pos is rw) { my %result; my $hash := nqp::ifnull( nqp::getattr(%result,Map,'$!storage'), nqp::bindattr(%result,Map,'$!storage',nqp::hash) ); nom-ws($text, $pos); my int $ordinal = nqp::ordat($text, $pos); nqp::if( nqp::iseq_i($ordinal, 125), # } { nqp::stmts( ($pos = nqp::add_i($pos,1)), %result ), nqp::stmts( my $descriptor := nqp::getattr(%result,Hash,'$!descriptor'); nqp::stmts( # this level is needed for some reason nqp::while( 1, nqp::stmts( nqp::if( nqp::iseq_i($ordinal, 34), # " (my $key := parse-string($text, $pos = nqp::add_i($pos,1))), (die nqp::if( nqp::iseq_i($pos, nqp::chars($text)), "at end of input: expected a quoted string for an object key", "at $pos: json requires object keys to be strings" )) ), nom-ws($text, $pos), nqp::if( nqp::iseq_i(nqp::ordat($text, $pos), 58), # : ($pos = nqp::add_i($pos, 1)), (die "expected to see a ':' after an object key") ), nom-ws($text, $pos), nqp::bindkey($hash, $key, nqp::p6scalarwithvalue($descriptor, parse-thing($text, $pos))), nom-ws($text, $pos), ($ordinal = nqp::ordat($text, $pos)), nqp::if( nqp::iseq_i($ordinal, 125), # } { nqp::stmts( ($pos = nqp::add_i($pos,1)), (return %result) ), nqp::unless( nqp::iseq_i($ordinal, 44), # , (die nqp::if( nqp::iseq_i($pos, nqp::chars($text)), "at end of input: unexpected end of object.", "unexpected '{ nqp::substr($text, $pos, 1) }' in an object at $pos" )) ) ), nom-ws($text, $pos = nqp::add_i($pos,1)), ($ordinal = nqp::ordat($text, $pos)), ) ) ) ) ) } my sub parse-array(str $text, int $pos is rw) { my @result; nqp::bindattr(@result, List, '$!reified', my $buffer := nqp::create(IterationBuffer)); nom-ws($text, $pos); nqp::if( nqp::eqat($text, ']', $pos), nqp::stmts( ($pos = nqp::add_i($pos,1)), @result ), nqp::stmts( (my $descriptor := nqp::getattr(@result, Array, '$!descriptor')), nqp::while( 1, nqp::stmts( (my $thing := parse-thing($text, $pos)), nom-ws($text, $pos), (my int $partitioner = nqp::ordat($text, $pos)), nqp::if( nqp::iseq_i($partitioner,93), # ] nqp::stmts( nqp::push($buffer,nqp::p6scalarwithvalue($descriptor,$thing)), ($pos = nqp::add_i($pos,1)), (return @result) ), nqp::if( nqp::iseq_i($partitioner,44), # , nqp::stmts( nqp::push($buffer,nqp::p6scalarwithvalue($descriptor,$thing)), ($pos = nqp::add_i($pos,1)) ), (die "at $pos, unexpected partitioner '{ nqp::substr($text,$pos,1) }' inside list of things in an array") ) ) ) ) ) ) } my sub parse-thing(str $text, int $pos is rw) { nom-ws($text, $pos); my int $ordinal = nqp::ordat($text, $pos); if nqp::iseq_i($ordinal,34) { # " parse-string($text, $pos = $pos + 1) } elsif nqp::iseq_i($ordinal,91) { # [ parse-array($text, $pos = $pos + 1) } elsif nqp::iseq_i($ordinal,123) { # { parse-obj($text, $pos = $pos + 1) } elsif nqp::iscclass(nqp::const::CCLASS_NUMERIC, $text, $pos) || nqp::iseq_i($ordinal,45) { # - parse-numeric($text, $pos = $pos + 1) } elsif nqp::iseq_i($ordinal,116) && nqp::eqat($text,'true',$pos) { $pos = $pos + 4; True } elsif nqp::iseq_i($ordinal,102) && nqp::eqat($text,'false',$pos) { $pos = $pos + 5; False } elsif nqp::iseq_i($ordinal,110) && nqp::eqat($text,'null',$pos) { $pos = $pos + 4; Any } else { die "at $pos: expected a json object, but got '{ nqp::substr($text, $pos, 8).perl }'"; } } method from-json(Str() $text) { CATCH { when X::AdHoc { die JSONException.new(:text($_)) } } my str $ntext = $text; my int $length = $text.chars; my int $pos = 0; my $result := parse-thing($text, $pos); try nom-ws($text, $pos); if $pos != nqp::chars($text) { die "additional text after the end of the document: { substr($text, $pos).raku }"; } $result } } #line 1 SETTING::src/core.c/Distribution.rakumod # API to obtain the data of any addressable content role Distribution { # `meta` provides an API to the meta data in META6 spec (s22) # - A Distribution may be represented internally by some other # spec (such as using the file system itself for prereqs), as # long as it can also be represented as the META6 hash format method meta(--> Hash) { ... } # `content($content-id)` provides an API to the data itself # - Use `.meta` to determine the $address of a specific $content-id # - IO::Handle is meant to be a data stream that may or may not be available; for now # it would return an IO::Handle and have `.open(:bin).slurp` called on it. So if # a socket wants to handle this role currently it would have to wrap `open` or `.slurp` # to handle any protocol negotiation as well as probably saving the data to a tmpfile and # return an IO::Handle to that method content($content-id --> IO::Handle) { ... } } #line 1 SETTING::src/core.c/Distribution/Locally.rakumod role Distribution::Locally does Distribution { has IO::Path $.prefix is built(:bind); method content(Str:D $address --> IO::Handle:D) { my $handle := IO::Handle.new: path => IO::Path.new: $.meta{$address} // $address, CWD => $!prefix.absolute // $*CWD.absolute; $handle // $handle.throw; } } #line 1 SETTING::src/core.c/Distribution/Hash.rakumod class Distribution::Hash does Distribution::Locally { has $.meta is built(:bind); method new($meta, :$prefix) { self.bless(:$meta, :$prefix) } multi method raku(Distribution::Hash:D:) { self.^name ~ ".new($!meta.raku(), prefix => $!prefix.raku())"; } } #line 1 SETTING::src/core.c/Distribution/Path.rakumod class Distribution::Path does Distribution::Locally { has %.meta is built(False); has IO::Path $.meta-file; method new( IO::Path:D $prefix, IO::Path:D :$meta-file = $prefix.add('META6.json'), ) { self.bless(:$prefix, :$meta-file) } sub forward-slash(Str() $path) { $path.subst('\\', '/', :g) } submethod TWEAK(--> Nil) { die "No meta file located at $!meta-file.path()" unless $!meta-file.e; %!meta := Rakudo::Internals::JSON.from-json($!meta-file.slurp); %!meta := my %files; # set up scripts in bin from file system (not in META) for Rakudo::Internals.DIR-RECURSE($!prefix.add('bin').absolute) { my $io := .IO; my $script := forward-slash $io.is-relative ?? $io !! $io.relative($!prefix); %files{$script} := $script; } # Set up resources if %!meta -> @resources { my $resources-dir := $!prefix.add('resources'); for @resources { if nqp::can($_,'chars') && .chars { # set up path on filesystem, allowing for external libraries my $path := ( .starts-with('libraries/') # 10 chars ?? $resources-dir # | .add('libraries') # v .add($*VM.platform-library-name(.substr(10).IO)) !! $resources-dir.add($_) ).relative($!prefix); # set up the key by which the resource can be reached my $io := .IO; my $key := "resources/" ~ ($io.is-relative ?? $_ !! $io.relative($!prefix) ); %files{forward-slash($key)} := forward-slash($path); } } } } method meta(Distribution::Path:D:) { %!meta.item } method raku(--> Str:D) { self.^name ~ ".new($!prefix.raku(), meta-file => $!meta-file.raku())" } } #line 1 SETTING::src/core.c/Distribution/Resource.rakumod class Distribution::Resource { has $.repo; has $.repo-name; has $.dist-id; has $.key; # NOTE: IO() **MUST** be determined at runtime. The result must not make # it into a precomp file. See also commits 67906e4 and d4d6a99 method IO() { my $repo := self.repo-name ?? CompUnit::RepositoryRegistry.repository-for-name(self.repo-name) !! CompUnit::RepositoryRegistry.repository-for-spec(self.repo); $repo.resource(self.dist-id, "resources/$.key") } method platform-library-name() { my $library = self.IO; # already a full name? ($library ~~ /\.<.alpha>+$/ or $library ~~ /\.so(\.<.digit>+)+$/) ?? $library !! $*VM.platform-library-name($library) } # delegate appropriate IO::Path methods to the resource IO::Path object multi method Str(::?CLASS:D: |c) { self.IO.Str(|c) } multi method gist(::?CLASS:D: |c) { self.IO.gist(|c) } multi method raku(::?CLASS:D: |c) { self.IO.raku(|c) } method absolute(|c) { self.IO.absolute(|c) } method is-absolute(|c) { self.IO.is-absolute(|c) } method relative(|c) { self.IO.relative(|c) } method is-relative(|c) { self.IO.is-relative(|c) } method parts(|c) { self.IO.parts(|c) } method volume(|c) { self.IO.volume(|c) } method dirname(|c) { self.IO.dirname(|c) } method basename(|c) { self.IO.basename(|c) } method extension(|c) { self.IO.extension(|c) } method open(|c) { self.IO.open(|c) } method resolve(|c) { self.IO.resolve(|c) } method slurp(|c) { self.IO.slurp(|c) } method lines(|c) { self.IO.lines(|c) } method comb(|c) { self.IO.comb(|c) } method split(|c) { self.IO.split(|c) } method words(|c) { self.IO.words(|c) } method copy(|c) { self.IO.copy(|c) } } #line 1 SETTING::src/core.c/Distribution/Resources.rakumod role CompUnit::Repository { ... } class Distribution::Resources does Associative { has Str $.dist-id is built(False); has Str $.repo is built(False); has Str $.repo-name is built(False); proto method BUILD(|) {*} multi method BUILD(:$!dist-id, CompUnit::Repository :$repo --> Nil) { unless $repo.can('name') and $!repo-name = $repo.name and $!repo-name ne '' { $!repo = $repo.path-spec; $!repo-name = Str; } } multi method BUILD(:$!dist-id, :$repo, Str :$!repo-name --> Nil) { } multi method BUILD(:$!dist-id, Str :$!repo, :$repo-name --> Nil) { } # Alternate instantiator called from Actions.nqp during compilation # of %?RESOURCES method from-precomp(Distribution::Resources:U:) is implementation-detail { if %*ENV -> $dist { my %data := Rakudo::Internals::JSON.from-json: $dist; self.new: :repo(%data), :repo-name(%data), :dist-id(%data); } else { Nil } } multi method AT-KEY(Distribution::Resources:D: $key) { Distribution::Resource.new(:$.repo, :$.repo-name, :$.dist-id, :$key) } multi method Str(Distribution::Resources:D:) { Rakudo::Internals::JSON.to-json: {:$!repo, :$!repo-name, :$!dist-id} } # More sensible error messages if %?RESOURCES are trying to be changed multi method ASSIGN-KEY(Distribution::Resources:D: $key, Mu) { die "Cannot assign to key '$key' in an immutable " ~ self.^name; } multi method BIND-KEY(Distribution::Resources:D: $key, Mu) { die "Cannot bind to key '$key' in an immutable " ~ self.^name; } multi method DELETE-KEY(Distribution::Resources:D: $key) { die "Cannot remove key '$key' in an immutable " ~ self.^name; } } #line 1 SETTING::src/core.c/CompUnit/DependencySpecification.rakumod class CompUnit::DependencySpecification { has str $.short-name is built(:bind) is required; # must be native has str $.from is built(:bind) = 'Perl6'; # must be native has str $.stringified; # needs to be public for the JVM # The matcher methods will return True if implicitly or explicitly # initialized with an undefined value. In the case of version and # API, a Version object for the given defined value, will be returned # if it was not already specified as a Version object. This does # *not* cause an issue with stringification, as the stringification # of a Version object returns its original string. has $.auth-matcher is built(:bind); # either True or smartmatchee has $.version-matcher is built(:bind); # either True or Version has $.api-matcher is built(:bind); # either True or Version # Sadly this is needed because we have a spectest that explicitly # passes True for unspecified matchers. Hopefully this can go when # we accept that an undefined value indicates no interest in matching. method TWEAK() { $!version-matcher := Any if nqp::eqaddr($!version-matcher,True); $!auth-matcher := Any if nqp::eqaddr($!auth-matcher,True); $!api-matcher := Any if nqp::eqaddr($!api-matcher,True); } method auth-matcher() { $!auth-matcher // True } method version-matcher() { nqp::defined($!version-matcher) ?? nqp::istype($!version-matcher,Version) ?? $!version-matcher !! ($!version-matcher := Version.new($!version-matcher)) !! True } method api-matcher() { nqp::defined($!api-matcher) ?? nqp::istype($!api-matcher,Version) ?? $!api-matcher !! ($!api-matcher := Version.new($!api-matcher)) !! True } method !stringify() { my $parts := nqp::list_s($!short-name); nqp::push_s($parts,":from<$!from>") if $!from ne 'Raku' && $!from ne 'Perl6'; nqp::push_s($parts,":ver<$!version-matcher>") if nqp::defined($!version-matcher); nqp::push_s($parts,":auth<$!auth-matcher>") if nqp::defined($!auth-matcher); nqp::push_s($parts,":api<$!api-matcher>") if nqp::defined($!api-matcher); $!stringified = nqp::join('',$parts) } # Oddly enough, the exact format of this method can make / break # dependency testing. This should probably depend on the .WHICH. method Str(CompUnit::DependencySpecification:D: --> Str:D) { $!stringified ?? $!stringified !! self!stringify } multi method raku(CompUnit::DependencySpecification:D: --> Str:D) { my $parts := nqp::list_s( "CompUnit::DependencySpecification.new(:short-name<$!short-name>" ); nqp::push_s($parts,",:from<$!from>") if $!from ne 'Raku' && $!from ne 'Perl6'; nqp::push_s($parts,",:version-matcher<$!version-matcher>") if nqp::defined($!version-matcher); nqp::push_s($parts,",:auth-matcher<$!auth-matcher>") if nqp::defined($!auth-matcher); nqp::push_s($parts,",:api-matcher<$!api-matcher>") if nqp::defined($!api-matcher); nqp::push_s($parts,')'); nqp::join('',$parts) } multi method WHICH(CompUnit::DependencySpecification:D --> ValueObjAt:D) { nqp::box_s( nqp::concat( nqp::concat(self.^name, '|'), self.Str ), ValueObjAt ) } } #line 1 SETTING::src/core.c/CompUnit/Handle.rakumod class CompUnit::Handle { has Mu $!module_ctx; has Mu $!unit; proto submethod new(|) {*} multi submethod new() { nqp::create(self) } method ctxsave() { $!module_ctx := nqp::ctxcaller(nqp::ctx()) unless $!module_ctx; } multi submethod new(Mu \module_ctx) { nqp::p6bindattrinvres( nqp::create(self),CompUnit::Handle,'$!module_ctx', module_ctx ) } submethod from-unit(Stash $unit) { nqp::p6bindattrinvres( nqp::create(self),CompUnit::Handle,'$!unit',nqp::decont($unit) ) } # If the compilation unit has a callable EXPORT subroutine, it will # be returned here. Nil otherwise. method export-sub(--> Callable:D) { my $module := self.unit; $module && nqp::existskey($module, '&EXPORT') ?? nqp::atkey($module, '&EXPORT') !! Nil } # The EXPORT package from the UNIT of the compilation unit; a # Nil if none method export-package(--> Stash:D) { my $module := self.unit; if $module and nqp::existskey($module, 'EXPORT') { my $EXPORT := nqp::atkey($module, 'EXPORT'); nqp::istype($EXPORT.WHO, Stash) ?? $EXPORT.WHO !! nqp::p6bindattrinvres(Stash.new, Map, '$!storage', $EXPORT.WHO); } else { Nil } } # The EXPORTHOW package from the UNIT of the compilation unit; # Nil if none. method export-how-package(--> Stash:D) { my $module := self.unit; if $module and nqp::existskey($module, 'EXPORTHOW') { my $EXPORTHOW := nqp::atkey($module, 'EXPORTHOW'); my $who := $EXPORTHOW.WHO; nqp::istype($who, Stash) ?? $who !! nqp::p6bindattrinvres(Stash.new, Map, '$!storage', $who); } else { Nil } } # The GLOBALish package from the UNIT of the compilation unit # (the module's contributions to GLOBAL, for merging); # Nil if none. method globalish-package() { # returns Stash { nqp::if( nqp::defined($!module_ctx), nqp::ifnull(nqp::atkey(nqp::ctxlexpad($!module_ctx),'GLOBALish').WHO, Nil), nqp::if(nqp::defined($!unit), $!unit, Nil) ) } method unit() { nqp::defined($!unit) ?? $!unit !! nqp::defined($!module_ctx) ?? nqp::ctxlexpad($!module_ctx) !! {} } } #line 1 SETTING::src/core.c/CompUnit/Loader.rakumod class CompUnit::Loader is repr('Uninstantiable') { # Load a file from source and compile it method load-source-file(IO::Path:D $path --> CompUnit::Handle:D) { # Get the compiler and compile the code, then run it # (which runs the mainline and captures UNIT). my $?FILES := $path.Str; self.load-source($path.slurp(:bin)) } # Decode the specified byte buffer as source code, and compile it method load-source(Blob:D $bytes --> CompUnit::Handle:D) { my $original-GLOBAL := nqp::ifnull(nqp::gethllsym('Raku','GLOBAL'),Mu); CATCH { # use CATCH instead of LEAVE: makes the normal flow faster default { nqp::bindhllsym('Raku','GLOBAL',$original-GLOBAL); .rethrow; } } my $handle := my $*CTXSAVE := CompUnit::Handle.new; nqp::getcomp('Raku').compile($bytes.decode)(); # compile *and* run nqp::bindhllsym('Raku','GLOBAL',$original-GLOBAL); $handle } # Load a pre-compiled file proto method load-precompilation-file(|) {*} multi method load-precompilation-file( IO::Path:D $precompiled-file --> CompUnit::Handle:D) { my $handle := my $*CTXSAVE := CompUnit::Handle.new; nqp::loadbytecode($precompiled-file.Str); $handle } multi method load-precompilation-file( IO::Handle:D $precompiled-handle --> CompUnit::Handle:D) { my $compunit-handle := my $*CTXSAVE := CompUnit::Handle.new; # Switch file handle to binary mode before passing it off to the VM, # so we don't lose things hanging around in the decoder. $precompiled-handle.encoding(Nil); nqp::loadbytecodefh( nqp::getattr($precompiled-handle,IO::Handle,'$!PIO'), $precompiled-handle.path.Str ); $compunit-handle } # Load the specified byte buffer as if it was the contents of a # precompiled file method load-precompilation(Blob:D $bytes --> CompUnit::Handle:D) { my $handle := my $*CTXSAVE := CompUnit::Handle.new; nqp::loadbytecodebuffer($bytes); $handle } } #line 1 SETTING::src/core.c/CompUnit/PrecompilationId.rakumod class CompUnit::PrecompilationId { has Str $.id is built(False) handles ; method new(str $id --> CompUnit::PrecompilationId:D) { nqp::atpos(nqp::radix_I(16,$id,0,0,Int),2) == 40 ?? nqp::p6bindattrinvres(nqp::create(self), CompUnit::PrecompilationId,'$!id',$id) !! die "Invalid precompilation id: '$id'" } method new-from-string(str $id --> CompUnit::PrecompilationId:D) { nqp::p6bindattrinvres(nqp::create(self), CompUnit::PrecompilationId,'$!id',nqp::sha1($id)) } method new-without-check(str $id --> CompUnit::PrecompilationId:D) { nqp::p6bindattrinvres(nqp::create(self), CompUnit::PrecompilationId,'$!id',$id) } multi method WHICH(CompUnit::PrecompilationId:D: --> ValueObjAt:D) { nqp::box_s( nqp::concat('CompUnit::PrecompilationId|',$!id), ValueObjAt ) } } #line 1 SETTING::src/core.c/CompUnit/PrecompilationDependency.rakumod role CompUnit::PrecompilationDependency { method id(--> CompUnit::PrecompilationId:D) { ... } method src(--> Str:D) { ... } method spec(--> CompUnit::DependencySpecification:D) { ... } method checksum(--> Str:D) { ... } method Str() { "$.id $.src $.spec" } method serialize(--> Str:D) { ... } method deserialize(Str, --> CompUnit::PrecompilationDependency:D) { ... } } #line 1 SETTING::src/core.c/CompUnit/PrecompilationDependency/File.rakumod class CompUnit::PrecompilationDependency::File does CompUnit::PrecompilationDependency { has CompUnit::PrecompilationId $.id is built(:bind); has CompUnit::DependencySpecification $.spec is built(:bind); has Str $.src is built(:bind); has Str $.checksum is rw; has Str $!serialized-spec is built(:bind); method source-name() { "$.src ($.spec.short-name())" } method deserialize(str $str) { my $parts := nqp::split("\0",$str); self.new( :id(CompUnit::PrecompilationId.new-without-check(nqp::atpos($parts,0))), :src(nqp::atpos($parts,1)), :checksum(nqp::atpos($parts,2)) :serialized-spec(nqp::atpos($parts,3)) ) } method spec(--> CompUnit::DependencySpecification:D) { $!spec //= $!serialized-spec ?? do { use MONKEY-SEE-NO-EVAL; EVAL $!serialized-spec; } !! Nil; } method serialize(--> Str:D) { "$!id\0$!src\0$!checksum\0{ $!serialized-spec ?? $!serialized-spec !! $!spec.raku }" } method Str() { "$.id $.src $.checksum {$!serialized-spec ?? $!serialized-spec !! $!spec.raku}" } } #line 1 SETTING::src/core.c/CompUnit/PrecompilationUnit.rakumod role CompUnit::PrecompilationUnit { method id(--> CompUnit::PrecompilationId:D) { ... } method path(--> IO::Path:D) { ... } method modified(--> Instant:D) { ... } method dependencies(--> Array[CompUnit::PrecompilationDependency]) { ... } method bytecode(--> Buf:D) { ... } method checksum(--> Str:D) { ... } method source-checksum(--> Str:D) { ... } method bytecode-handle(--> IO::Handle:D) { ... } method close(--> Nil) { ... } method is-up-to-date( CompUnit::PrecompilationDependency $dependency, Bool :$check-source --> Bool:D) { my $RMD := $*RAKUDO_MODULE_DEBUG; # a repo changed, so maybe it's a change in our source file if $check-source { my $srcIO := CompUnit::RepositoryRegistry.file-for-spec($dependency.src) // $dependency.src.IO; return False unless $srcIO.e; my $current-source-checksum := $srcIO.CHECKSUM; $RMD( "$.path\nspec: $dependency.spec()\nsource: $srcIO\n" ~ "source-checksum: $.source-checksum\ncurrent-source-checksum: $current-source-checksum" ) if $RMD; return False if $.source-checksum ne $current-source-checksum; } $RMD("dependency checksum $dependency.checksum() unit: $.checksum()") if $RMD; $.checksum eq $dependency.checksum } } #line 1 SETTING::src/core.c/CompUnit/PrecompilationUnit/File.rakumod my class CompUnit::PrecompilationUnit::File does CompUnit::PrecompilationUnit { has CompUnit::PrecompilationId:D $.id is built(:bind) is required; has IO::Path $.path is built(:bind); has Str $!checksum is built; has Str $!source-checksum is built; has CompUnit::PrecompilationDependency @!dependencies is built(:bind); has $!bytecode is built(:bind); has $!store is built(:bind); has Bool $!initialized; has IO::Handle $!handle; has $!update-lock; submethod TWEAK(--> Nil) { if $!bytecode { $!checksum = nqp::sha1($!bytecode.decode('iso-8859-1')); $!initialized := True; } else { $!initialized := False; } $!update-lock := Lock.new; } method modified(--> Instant:D) { $!path.modified } method !read-dependencies(--> Nil) { $!initialized || $!update-lock.protect: { unless $!initialized { # another thread beat us $!handle := $!path.open(:r) unless $!handle; $!checksum = $!handle.get; $!source-checksum = $!handle.get; my $dependency := $!handle.get; my $dependencies := nqp::create(IterationBuffer); while $dependency { nqp::push( $dependencies, CompUnit::PrecompilationDependency::File.deserialize($dependency) ); $dependency := $!handle.get; } nqp::bindattr(@!dependencies,List,'$!reified',$dependencies); $!initialized := True; } } } method dependencies(--> Array[CompUnit::PrecompilationDependency]) { self!read-dependencies; @!dependencies } method bytecode(--> Buf:D) { $!update-lock.protect: { unless $!bytecode { self!read-dependencies; $!bytecode := $!handle.slurp(:bin,:close) } $!bytecode } } method bytecode-handle(--> IO::Handle:D) { self!read-dependencies; $!handle } method source-checksum() is rw { self!read-dependencies; $!source-checksum } method checksum() is rw { self!read-dependencies; $!checksum } method Str(--> Str:D) { self.path.Str } method close(--> Nil) { $!update-lock.protect: { $!handle.close if $!handle; $!handle := IO::Handle; $!initialized := False; } } method save-to(IO::Path $precomp-file) { my $handle := $precomp-file.open(:w); $handle.print($!checksum ~ "\n"); $handle.print($!source-checksum ~ "\n"); $handle.print($_.serialize ~ "\n") for @!dependencies; $handle.print("\n"); $handle.write($!bytecode); $handle.close; $!path := $precomp-file; } method is-up-to-date( CompUnit::PrecompilationDependency:D $dependency, Bool :$check-source --> Bool:D) { my $result := self.CompUnit::PrecompilationUnit::is-up-to-date($dependency, :$check-source); $!store.remove-from-cache($.id) unless $result; $result } } #line 1 SETTING::src/core.c/CompUnit/PrecompilationStore.rakumod role CompUnit::PrecompilationStore { # Prepare a new implementation specific PrecompilationUnit for storage method new-unit(| --> CompUnit::PrecompilationUnit:D) { ... } # Load the precompilation identified by the pairing of the specified # compiler and precompilation ID. method load-unit(CompUnit::PrecompilationId $compiler-id, CompUnit::PrecompilationId $precomp-id) { ... } # Return the repository id for which the specified precomp file's # dependencies have been validated method load-repo-id(CompUnit::PrecompilationId $compiler-id, CompUnit::PrecompilationId $precomp-id) { ... } # Store the file at the specified path in the precompilation store, # under the given compiler ID and precompilation ID. method store-file(CompUnit::PrecompilationId $compiler-id, CompUnit::PrecompilationId $precomp-id, IO::Path:D $path, :$extension = '') { ... } # Store the given precompilation unit in the precompilation store # under the given compiler ID and precompilation ID. method store-unit(CompUnit::PrecompilationId $compiler-id, CompUnit::PrecompilationId $precomp-id, CompUnit::PrecompilationUnit $unit) { ... } # Store the given repo-id for a precompilation under the given # compiler ID and precompilation ID. method store-repo-id(CompUnit::PrecompilationId $compiler-id, CompUnit::PrecompilationId $precomp-id, :$repo-id!) { ... } # Delete an individual precompilation. method delete(CompUnit::PrecompilationId $compiler-id, CompUnit::PrecompilationId $precomp-id) { ... } # Delete all precompilations for a particular compiler. method delete-by-compiler(CompUnit::PrecompilationId $compiler-id) { ... } } #line 1 SETTING::src/core.c/CompUnit/PrecompilationStore/FileSystem.rakumod class CompUnit::PrecompilationStore::FileSystem does CompUnit::PrecompilationStore { has IO::Path:D $.prefix is built(:bind) is required; has IO::Handle $!lock; has atomicint $!lock-count; has $!loaded; has $!dir-cache; has $!compiler-cache; has $!update-lock; submethod TWEAK(--> Nil) { $!update-lock := Lock.new; $!loaded := nqp::hash; $!dir-cache := nqp::hash; $!compiler-cache := nqp::hash; } method new-unit(|c) { CompUnit::PrecompilationUnit::File.new(|c, :store(self)) } method !dir( CompUnit::PrecompilationId:D $compiler-id, CompUnit::PrecompilationId:D $precomp-id ) { $!update-lock.protect: { my str $compiler = $compiler-id.Str; my str $precomp = $precomp-id.Str; nqp::ifnull( nqp::atkey($!dir-cache,nqp::concat($compiler,$precomp)), nqp::bindkey($!dir-cache,nqp::concat($compiler,$precomp), nqp::ifnull( nqp::atkey($!compiler-cache,$compiler), nqp::bindkey($!compiler-cache,$compiler, self.prefix.add($compiler) ) ).add(nqp::substr($precomp,0,2)) ) ) } } method path( CompUnit::PrecompilationId:D $compiler-id, CompUnit::PrecompilationId:D $precomp-id, Str:D :$extension = '' ) { self!dir($compiler-id, $precomp-id).add($precomp-id ~ $extension) } method !lock($path --> Nil) { $!update-lock.lock; $!lock := "$path.lock".IO.open(:create, :rw) unless $!lock; $!lock.lock if ⚛$!lock-count == 0; ++⚛$!lock-count; } method unlock() { LEAVE $!update-lock.unlock; die "unlock when we're not locked!" if ⚛$!lock-count == 0; $!lock-count⚛-- if ⚛$!lock-count > 0; if $!lock && ⚛$!lock-count == 0 { $!lock.unlock; $!lock.close; $!lock := IO::Handle; } True } method load-unit( CompUnit::PrecompilationId:D $compiler-id, CompUnit::PrecompilationId:D $precomp-id ) { $!update-lock.protect: { my str $key = $precomp-id.Str; nqp::ifnull( nqp::atkey($!loaded,$key), do { my $path := self.path($compiler-id, $precomp-id); $path.s ?? nqp::bindkey($!loaded,$key, CompUnit::PrecompilationUnit::File.new( :id($precomp-id), :$path, :store(self))) !! Nil } ) } } method load-repo-id( CompUnit::PrecompilationId:D $compiler-id, CompUnit::PrecompilationId:D $precomp-id ) { my $path := self.path($compiler-id, $precomp-id, :extension<.repo-id>); $path.s ?? $path.slurp !! Nil } method remove-from-cache(CompUnit::PrecompilationId:D $precomp-id) { $!update-lock.protect: { nqp::deletekey($!loaded,$precomp-id.Str); } } method destination( CompUnit::PrecompilationId:D $compiler-id, CompUnit::PrecompilationId:D $precomp-id, Str:D :$extension = '' --> IO::Path:D) { # have a writable prefix, assume it's a directory if $!prefix.w { self!lock(self!file($compiler-id, $precomp-id)); self!file($compiler-id, $precomp-id, :$extension); } # directory creation successful and writeable elsif $!prefix.mkdir && $!prefix.w { # make sure we have a tag in it $!prefix.child('CACHEDIR.TAG').spurt: 'Signature: 8a477f597d28d172789f06886806bc55 # This file is a cache directory tag created by Rakudo. # For information about cache directory tags, see: # http://www.brynosaurus.com/cachedir'; # call ourselves again, now that we haz a cache directory self.destination($compiler-id, $precomp-id, :$extension) } # huh? else { Nil } } method !file( CompUnit::PrecompilationId:D $compiler-id, CompUnit::PrecompilationId:D $precomp-id, Str:D :$extension = '' --> IO::Path:D) { my $compiler-dir := self.prefix.add($compiler-id); $compiler-dir.mkdir unless $compiler-dir.e; my $dest := self!dir($compiler-id, $precomp-id); $dest.mkdir unless $dest.e; $dest.add($precomp-id ~ $extension) } my sub really-rename( IO::Path:D $from, IO::Path:D $to, int $n is copy = 10 --> True) { # attempt to rename until succeeds at .1 second intervals # needed on Windows because it can easily race and fail there until $from.rename($to) -> $failure { !Rakudo::Internals.IS-WIN || --$n == 0 ?? $failure.throw !! $failure.Bool; # disable Failure for cleaner DESTROY sleep .1; } } method store-file( CompUnit::PrecompilationId:D $compiler-id, CompUnit::PrecompilationId:D $precomp-id, IO::Path:D $temp-file, Str:D :$extension = '' --> Nil) { really-rename $temp-file, self!file: $compiler-id, $precomp-id, :$extension; } method store-unit( CompUnit::PrecompilationId:D $compiler-id, CompUnit::PrecompilationId:D $precomp-id, CompUnit::PrecompilationUnit:D $unit ) { my $extension := self!tmp-extension; my $precomp-file := self!file($compiler-id, $precomp-id, :$extension); $unit.save-to($precomp-file); really-rename $precomp-file, self!file($compiler-id, $precomp-id); self.remove-from-cache($precomp-id); } method store-repo-id( CompUnit::PrecompilationId:D $compiler-id, CompUnit::PrecompilationId:D $precomp-id, Str:D :$repo-id! ) { my $extension := ".repo-id" ~ self!tmp-extension; my $repo-id-file := self!file($compiler-id, $precomp-id, :$extension); $repo-id-file.spurt($repo-id); really-rename $repo-id-file, self!file($compiler-id, $precomp-id, :extension<.repo-id>); } method delete( CompUnit::PrecompilationId:D $compiler-id, CompUnit::PrecompilationId:D $precomp-id, Str:D :$extension = '' ) { self.path($compiler-id, $precomp-id, :$extension).unlink; } method delete-by-compiler(CompUnit::PrecompilationId:D $compiler-id) { my $compiler-dir := self.prefix.add($compiler-id); for $compiler-dir.dir -> $subdir { .unlink for $subdir.dir; $subdir.rmdir; } $compiler-dir.rmdir; } method !tmp-extension(--> Str:D) { '.' ~ (^2**128).pick.base(36) ~ '.tmp' } } class CompUnit::PrecompilationStore::File is CompUnit::PrecompilationStore::FileSystem { method new(|) { DEPRECATED( "the 'CompUnit::PrecompilationStore::FileSystem' class", :what("Use of the 'CompUnit::PrecompilationStore::File' class") ); nextsame; } } #line 1 SETTING::src/core.c/CompUnit/PrecompilationRepository.rakumod role CompUnit::PrecompilationRepository { method try-load( CompUnit::PrecompilationDependency::File $dependency, IO::Path :$source, CompUnit::PrecompilationStore :@precomp-stores, --> CompUnit::Handle:D) { Nil } method load(CompUnit::PrecompilationId $id --> Nil) { } has Bool $!may-precomp; method may-precomp(--> Bool:D) { nqp::if( nqp::defined($!may-precomp), $!may-precomp, ($!may-precomp := !nqp::hllbool(nqp::if(nqp::atkey(%*ENV,'RAKUDO_NO_PRECOMPILATION'), 1, 0))) ) } } BEGIN CompUnit::PrecompilationRepository:: := CompUnit::PrecompilationRepository.new; class CompUnit { ... } class CompUnit::PrecompilationRepository::Default does CompUnit::PrecompilationRepository { has CompUnit::PrecompilationStore:D $.store is required is built(:bind); has $!RMD; has $!RRD; method TWEAK() { $!RMD := $*RAKUDO_MODULE_DEBUG; $!RRD := nqp::ifnull(nqp::atkey(%*ENV,'RAKUDO_RERESOLVE_DEPENDENCIES'),1); } my $loaded := nqp::hash; my $resolved := nqp::hash; my $loaded-lock := Lock.new; my $first-repo-id; my constant $compiler-id = CompUnit::PrecompilationId.new-without-check(Compiler.id); method try-load( CompUnit::PrecompilationDependency::File:D $dependency, IO::Path:D :$source = $dependency.src.IO, CompUnit::PrecompilationStore :@precomp-stores = Array[CompUnit::PrecompilationStore].new($.store), --> CompUnit::Handle:D) { my $id := $dependency.id; $!RMD("try-load source at $source") if $!RMD; # Even if we may no longer precompile, we should use already loaded files return $_ if $_ := $loaded-lock.protect: { nqp::atkey($loaded,$id.Str) }; my ($handle, $checksum) = ( self.may-precomp and ( my $precomped := self.load($id, :source($source), :checksum($dependency.checksum), :@precomp-stores) # already precompiled? or self.precompile($source, $id, :source-name($dependency.source-name), :force(nqp::hllbool(nqp::istype($precomped,Failure))), :@precomp-stores) and self.load($id, :@precomp-stores) # if not do it now ) ); my $World := $*W; if $World && $World.record_precompilation_dependencies or CALLERS::.EXISTS-KEY('$*CU') && $*CU.record-precompilation-dependencies { if $handle { $dependency.checksum = $checksum; say $dependency.serialize; $*OUT.flush; } else { nqp::exit(0); } } $handle ?? $handle !! Nil } method !load-handle-for-path(CompUnit::PrecompilationUnit:D $unit) { my $preserve_global := nqp::ifnull(nqp::gethllsym('Raku','GLOBAL'),Mu); CATCH { default { nqp::bindhllsym('Raku', 'GLOBAL', $preserve_global); .rethrow; } } $!RMD("Loading precompiled\n$unit") if $!RMD; my $handle := CompUnit::Loader.load-precompilation-file($unit.bytecode-handle); nqp::bindhllsym('Raku', 'GLOBAL', $preserve_global); $handle } method !load-repo-id( CompUnit::PrecompilationStore @precomp-stores, CompUnit::PrecompilationId:D $id, ) { $!RMD("Trying to load $id.repo-id") if $!RMD; for @precomp-stores -> $store { with $store.load-repo-id($compiler-id, $id) { $!RMD(" Loaded from $store.prefix()") if $!RMD; return $_; } } Nil } method !load-file( CompUnit::PrecompilationStore @precomp-stores, CompUnit::PrecompilationId:D $id, ) { $!RMD("Trying to load $id") if $!RMD; for @precomp-stores -> $store { with $store.load-unit($compiler-id, $id) { $!RMD(" Loaded from $store.prefix()") if $!RMD; return $_; } } Nil } method !load-refreshed-file( CompUnit::PrecompilationStore @precomp-stores, CompUnit::PrecompilationId:D $id, ) { $!RMD("Trying to load refreshed $id") if $!RMD; for @precomp-stores -> $store { $store.remove-from-cache($id); with $store.load-unit($compiler-id, $id) { $!RMD(" Loaded from $store.prefix()") if $!RMD; return $_; } } Nil } method !load-dependencies( CompUnit::PrecompilationUnit:D $precomp-unit, @precomp-stores --> Bool:D) { my $resolve := False; my $REPO := $*REPO; my $REPO-id := $REPO.id; $first-repo-id := $REPO-id unless $first-repo-id; my $unit-id := self!load-repo-id(@precomp-stores, $precomp-unit.id); if $unit-id ne $REPO-id { $!RMD("Repo changed: $unit-id $REPO-id Need to re-check dependencies.") if $!RMD; $resolve := True; } if $REPO-id ne $first-repo-id { $!RMD("Repo chain changed: $REPO-id $first-repo-id Need to re-check dependencies.") if $!RMD; $resolve := True; } $resolve := False unless $!RRD; my $dependencies := nqp::create(IterationBuffer); for $precomp-unit.dependencies -> $dependency { $!RMD("dependency: $dependency") if $!RMD; if $resolve { my str $serialized-id = $dependency.serialize; nqp::ifnull( nqp::atkey($resolved,$serialized-id), nqp::if(do { my $comp-unit := $REPO.resolve($dependency.spec); $!RMD("Old id: $dependency.id(), new id: { $comp-unit and $comp-unit.repo-id }") if $!RMD; $comp-unit and $comp-unit.repo-id eq $dependency.id }, $loaded-lock.protect({ nqp::bindkey($resolved,$serialized-id, 1) }), (return False))); } my $dependency-precomp := @precomp-stores .map({ $_.load-unit($compiler-id, $dependency.id) }) .first(*.defined) or do { $!RMD("Could not find $dependency.spec()") if $!RMD; return False; } unless $dependency-precomp.is-up-to-date( $dependency, :check-source($resolve) ) { $dependency-precomp.close; return False; } nqp::push($dependencies,$dependency-precomp); } $loaded-lock.protect: { for $dependencies.List -> $dependency-precomp { my str $key = $dependency-precomp.id.Str; nqp::bindkey($loaded,$key, self!load-handle-for-path($dependency-precomp) ) unless nqp::existskey($loaded,$key); $dependency-precomp.close; } } # report back id and source location of dependency to dependant my $World := $*W; if $World && $World.record_precompilation_dependencies or CALLERS::.EXISTS-KEY('$*CU') && $*CU.record-precompilation-dependencies { my $dependencies := nqp::list_s(); nqp::push_s($dependencies,.serialize) for $precomp-unit.dependencies; nqp::push_s($dependencies,""); # for final \n my $out := $*OUT; $out.print(nqp::join($out.nl-out,$dependencies)); $out.flush; } if $resolve { if self.store.destination( $compiler-id, $precomp-unit.id, :extension<.repo-id> ) { self.store.store-repo-id( $compiler-id, $precomp-unit.id, :repo-id($REPO-id) ); self.store.unlock; } } True } proto method load(|) {*} multi method load( Str:D $id, Instant :$since, IO::Path :$source, CompUnit::PrecompilationStore :@precomp-stores = Array[CompUnit::PrecompilationStore].new($.store), ) { self.load( CompUnit::PrecompilationId.new($id), :$since, :@precomp-stores) } multi method load( CompUnit::PrecompilationId:D $id, IO::Path :$source, Str :$checksum is copy, Instant :$since, CompUnit::PrecompilationStore :@precomp-stores = Array[CompUnit::PrecompilationStore].new($.store), ) { return $_ if $_ := $loaded-lock.protect: { nqp::atkey($loaded,$id.Str) }; if self!load-file(@precomp-stores, $id) -> $unit { if (not $since or $unit.modified > $since) and (not $source or ($checksum //= $source.CHECKSUM) eq $unit.source-checksum) and self!load-dependencies($unit, @precomp-stores) { my $unit-checksum := $unit.checksum; my $precomped := self!load-handle-for-path($unit); $unit.close; $loaded-lock.protect: { nqp::bindkey($loaded,$id.Str,$precomped) } ($precomped, $unit-checksum) } else { $!RMD("Outdated precompiled {$unit}{ $source ?? " for $source" !! '' }\n mtime: {$unit.modified}{ $since ?? ", since: $since" !! ''} \n checksum: { $unit.source-checksum }, expected: $checksum") if $!RMD; $unit.close; "Outdated precompiled $unit".Failure } } else { Nil } } method !already-precompiled($path, $source, $destination, $bap --> True) { $!RMD( $source ~ "\nalready precompiled into\n" ~ $destination ~ ($bap ?? ' by another process' !! '') ) if $!RMD; if Rakudo::Internals.STAGESTATS { my $err := $*ERR; $err.print("\n load $path.relative()\n"); $err.flush; } self.store.unlock; } proto method precompile(|) {*} multi method precompile( IO::Path:D $path, Str:D $id, Bool :$force, Str:D :$source-name = $path.Str --> Bool:D) { self.precompile( $path, CompUnit::PrecompilationId.new($id), :$force, :$source-name) } multi method precompile( IO::Path:D $path, CompUnit::PrecompilationId:D $id, Bool :$force, Str :$source-name = $path.Str, :$precomp-stores, --> Bool:D) { my $env := nqp::clone(nqp::getattr(%*ENV,Map,'$!storage')); my $rpl := nqp::atkey($env,'RAKUDO_PRECOMP_LOADING'); if $rpl { my @modules := Rakudo::Internals::JSON.from-json: $rpl; die "Circular module loading detected trying to precompile $path" if $path.Str (elem) @modules; } # obtain destination, lock the store for other processes my $store := self.store; my $io := $store.destination($compiler-id, $id); return False unless $io; if $force { return self!already-precompiled($path,$source-name,$io,1) if $precomp-stores and my $unit := self!load-refreshed-file($precomp-stores, $id) and do { LEAVE $unit.close; $path.CHECKSUM eq $unit.source-checksum and self!load-dependencies($unit, $precomp-stores) } } elsif Rakudo::Internals.FILETEST-ES($io.absolute) { return self!already-precompiled($path,$source-name,$io,0) } my $REPO := $*REPO; nqp::bindkey($env,'RAKUDO_PRECOMP_WITH', $REPO.repo-chain.map(*.path-spec).join(',') ); if $rpl { nqp::bindkey($env,'RAKUDO_PRECOMP_LOADING', $rpl.chop ~ ',' ~ Rakudo::Internals::JSON.to-json($path.Str) ~ ']'); } else { nqp::bindkey($env,'RAKUDO_PRECOMP_LOADING', '[' ~ Rakudo::Internals::JSON.to-json($path.Str) ~ ']'); } my $stagestats := Rakudo::Internals.STAGESTATS; my $distribution := $*DISTRIBUTION; nqp::bindkey($env,'RAKUDO_PRECOMP_DIST', $distribution ?? $distribution.serialize !! '{}'); my $bc := "$io.bc".IO; $!RMD("Precompiling $path into $bc ({ Rakudo::Internals.LL-EXCEPTION } { Rakudo::Internals.PROFILE } { Rakudo::Internals.OPTIMIZE } $stagestats)") if $!RMD; my $raku := $*EXECUTABLE.absolute .subst('perl6-debug', 'perl6') # debugger would try to precompile it's UI .subst('perl6-gdb', 'perl6') .subst('perl6-jdb-server', 'perl6-j') ; if $stagestats { note "\n precomp $path.relative()"; $*ERR.flush; } my $out := nqp::list_s; my $err := nqp::list_s; my $status; react { my $proc := Proc::Async.new( $raku, Rakudo::Internals.LL-EXCEPTION, Rakudo::Internals.PROFILE, Rakudo::Internals.OPTIMIZE, Rakudo::Internals.TARGET, $stagestats, "--output=$bc", "--source-name=$source-name", $path ); whenever $proc.stdout { nqp::push_s($out,$_); } unless $!RMD { whenever $proc.stderr { nqp::push_s($err,$_); } } if $stagestats { whenever $proc.stderr.lines { note(" $_"); $*ERR.flush; } } whenever $proc.start(ENV => nqp::hllize($env)) { $status = .exitcode } } if $status { # something wrong $store.unlock; $!RMD("Precompiling $path failed: $status") if $!RMD; Rakudo::Internals.VERBATIM-EXCEPTION(1); die $!RMD ?? nqp::join('',$out).lines.unique.List !! nqp::join('',$err); } if nqp::elems($err) && not($!RMD || $stagestats) { $*ERR.print(nqp::join('',$err)); } unless Rakudo::Internals.FILETEST-ES($bc.absolute) { $!RMD("$path aborted precompilation without failure") if $!RMD; $store.unlock; return False; } $!RMD("Precompiled $path into $bc") if $!RMD; my $dependencies := nqp::create(IterationBuffer); my $seen := nqp::hash; for nqp::join('',$out).lines.unique -> str $outstr { if nqp::atpos(nqp::radix(16,$outstr,0,0),2) == 40 && nqp::eqat($outstr,"\0",40) && nqp::chars($outstr) > 41 { my $dependency := CompUnit::PrecompilationDependency::File.deserialize($outstr); if $dependency { my str $dependency-str = $dependency.Str; unless nqp::existskey($seen,$dependency-str) { $!RMD($dependency-str) if $!RMD; nqp::bindkey($seen,$dependency-str,1); nqp::push($dependencies,$dependency); } } } # huh? malformed dependency? else { say $outstr; } } # HLLize dependencies my CompUnit::PrecompilationDependency::File @dependencies; nqp::bindattr(@dependencies,List,'$!reified',$dependencies); my $source-checksum := $path.CHECKSUM; $!RMD("Writing dependencies and byte code to $io.tmp for source checksum: $source-checksum") if $!RMD; $store.store-repo-id($compiler-id, $id, :repo-id($REPO.id)); $store.store-unit( $compiler-id, $id, $store.new-unit( :$id, :@dependencies, :$source-checksum, :bytecode($bc.slurp(:bin)) ), ); $bc.unlink; $store.unlock; True } } #line 1 SETTING::src/core.c/CompUnit/Repository.rakumod role CompUnit::Repository { has CompUnit::Repository $.next-repo is rw; # Resolves a dependency specification to a concrete dependency. If the # dependency was not already loaded, loads it. Returns a CompUnit # object that represents the selected dependency. If there is no # matching dependency, throws X::CompUnit::UnsatisfiedDependency. method need(CompUnit::DependencySpecification $spec, # If we're first in the chain, our precomp repo is the chosen one. CompUnit::PrecompilationRepository $precomp = self.precomp-repository(), CompUnit::PrecompilationStore :@precomp-stores = Array[CompUnit::PrecompilationStore].new($precomp.store) --> CompUnit:D) { ... } # Resolves a dependency specification to a concrete dependency. # Returns a CompUnit object that represents the selected dependency. # If there is no matching dependency, Nil is returned. method resolve(CompUnit::DependencySpecification $spec --> CompUnit:D) { self.next-repo ?? self.next-repo.resolve($spec) !! Nil } # Just load the file and return a CompUnit object representing it. method load(IO::Path:D $file --> CompUnit:D) { self.next-repo ?? self.next-repo.load($file) !! X::NotFoundInRepository.new(:$file).throw; } # Returns the CompUnit objects describing all of the compilation # units that have been loaded by this repository in the current # process. method loaded(--> Iterable:D) { ... } # Returns a unique ID of this repository method id(--> Str:D) { ... } method precomp-store(--> CompUnit::PrecompilationStore) { CompUnit::PrecompilationStore } method precomp-repository(--> CompUnit::PrecompilationRepository) { CompUnit::PrecompilationRepository::None } method repo-chain() { my $buffer := nqp::create(IterationBuffer); nqp::push($buffer,my $repo := self); nqp::while( ($repo := $repo.next-repo).defined, nqp::push($buffer,$repo) ); $buffer.List } } #line 1 SETTING::src/core.c/CompUnit/Repository/Spec.rakumod class CompUnit::Repository::Spec { has Str $.short-id is built(:bind); has Str $.Str is built(:bind); has Str $.path is built(:bind); has %.options; # alternate instantiator called from CompUnit::RepositoryRegistry method from-string( str $spec, str $default-short-id --> CompUnit::Repository::Spec:D) is implementation-detail { # Examples: # perl5# # inst#/Foo/Bar/rakudo/gen/build_rakudo_home/core # CompUnit::Repository::Staging#name(core)#/Foo/Bar/rakudo/install/share/perl6/core my $parts := nqp::split("#",$spec); if nqp::elems($parts) == 1 { # no # found my str $path = PROCESS::<$SPEC>.canonpath($spec); self.new: :short-id($default-short-id), :$path, Str => $default-short-id ~ '#' ~ $path } else { # found at least one # nqp::pop($parts) unless nqp::chars(nqp::atpos($parts,nqp::elems($parts) - 1)); my str $short-id = nqp::shift($parts); if nqp::elems($parts) { # has a path my str $path = nqp::pop($parts) // ""; if nqp::elems($parts) -> int $nr-options { # has options my %options; my int $i = -1; while ++$i < $nr-options { %options{$0} := $1.Str if nqp::atpos($parts,$i).match: / ^ (<[\w-]>+) <[ <([{ ]> (<-[ >)\]} ]>+) <[ >)\]} ]> $ /; } self.new: :$short-id, :$path, :Str($spec), :%options } else { # short-id and just a path self.new: :$short-id, :$path, :Str($spec) } } else { # short-id without path self.new: :$short-id, :path(''), :Str($short-id ~ "#") } } } } #line 1 SETTING::src/core.c/CompUnit/Repository/Installable.rakumod role CompUnit::Repository::Installable does CompUnit::Repository { # Installs a distribution into the repository. method install(Distribution $dist) { ... } # Returns True if we can install modules (this will typically do a # .w check on the module database). method can-install(--> Bool:D) { ... } # Returns the Distribution objects for all installed distributions. method installed(--> Iterable:D) { } } #line 1 SETTING::src/core.c/CompUnit.rakumod class CompUnit::Repository::Distribution { ... } class CompUnit { has Str:D $.from is built(:bind) = 'Perl6'; has Str:D $.short-name is built(:bind) is required; has Version $.version is built(:bind); has Str $.auth is built(:bind); has Version $.api is built(:bind); # The CompUnit::Repository that loaded this CompUnit. has CompUnit::Repository:D $.repo is built(:bind) is required; # That repository's identifier for the compilation unit. # This is not globally unique. has Str:D $.repo-id is built(:bind) is required; # The low-level handle. has CompUnit::Handle $.handle is built(:bind); # Whether the module was loaded from a precompilation or not. has Bool:D $.precompiled is built(:bind) = False; # The distribution that this compilation unit was installed as part of # (if known). has Distribution $.distribution is built(:bind); has ValueObjAt $!WHICH; multi method WHICH(CompUnit:D: --> ValueObjAt:D) { nqp::isconcrete($!WHICH) ?? $!WHICH !! self!WHICH } method !WHICH() { my $parts := nqp::list_s($!from,$!short-name,$!repo-id,$!precompiled.Str); nqp::push_s($parts,$!version.Str) if $!version; nqp::push_s($parts,$!auth) if $!auth; nqp::push_s($parts,$!api.Str) if $!api; nqp::push_s($parts,$!distribution ?? CompUnit::Repository::Distribution.new( $!distribution, :repo($!repo-id) ).Str !! $!repo-id ); $!WHICH := nqp::box_s( nqp::concat( nqp::concat(self.^name, '|'), nqp::sha1(nqp::join("\0",$parts)) ), ValueObjAt ) } multi method Str(CompUnit:D: --> Str:D) { $!short-name } multi method gist(CompUnit:D: --> Str:D) { self.short-name } method unit() { $.handle.unit } } #line 1 SETTING::src/core.c/CompUnit/RepositoryRegistry.rakumod class CompUnit::Repository::FileSystem { ... } class CompUnit::Repository::Installation { ... } class CompUnit::Repository::AbsolutePath { ... } class CompUnit::Repository::Unknown { ... } class CompUnit::Repository::NQP { ... } class CompUnit::Repository::Perl5 { ... } class CompUnit::RepositoryRegistry { my $lock := Lock.new; my $include-spec2cur := nqp::hash; my $custom-lib := nqp::hash(); proto method repository-for-spec(|) { * } multi method repository-for-spec( Str:D $spec, CompUnit::Repository :$next-repo --> CompUnit::Repository:D) { $spec ?? self.repository-for-spec( CompUnit::Repository::Spec.from-string($spec, 'file'), :$next-repo ) !! Nil } multi method repository-for-spec( CompUnit::Repository::Spec:D $spec, CompUnit::Repository :$next-repo, --> CompUnit::Repository:D) { my $short-id := $spec.short-id; my %options := $spec.options; my $path := $spec.path; my $class := short-id2class($short-id); if nqp::istype($class,CompUnit::Repository) { my $prefix := nqp::can($class,"absolutify") ?? $class.absolutify($path) !! $path; %options = $next-repo if $next-repo; self!register-repository( "$short-id#$prefix", $class.new(:$prefix, |%options) ); } else { CompUnit::Repository::Unknown.new( :path-spec($spec), :short-name($short-id) ) } } method !register-custom-lib-repository( str $type, str $id, CompUnit::Repository $repo ) { $lock.protect: { nqp::bindkey( $custom-lib, $type, nqp::ifnull( nqp::atkey($include-spec2cur,$id), nqp::bindkey($include-spec2cur,$id,$repo) ) ) } } method !register-repository(str $id, CompUnit::Repository $repo) { $lock.protect: { nqp::ifnull( nqp::atkey($include-spec2cur,$id), nqp::bindkey($include-spec2cur,$id,$repo) ) } } method setup-repositories() { my $raw-specs; # only look up environment once my $ENV := nqp::getattr(%*ENV,Map,'$!storage'); my $sep := $*SPEC.dir-sep; my $precomp-specs := nqp::ifnull(nqp::atkey($ENV,'RAKUDO_PRECOMP_WITH'),False); my CompUnit::Repository $next-repo; # starting up for creating precomp if $precomp-specs { # assume well formed strings $raw-specs := nqp::split(',',$precomp-specs); } # normal start up else { $raw-specs := nqp::list(); for Rakudo::Internals.INCLUDE -> $specs { nqp::push($raw-specs,$_) for parse-include-specS($specs); } if nqp::existskey($ENV,'RAKUDOLIB') { nqp::push($raw-specs,$_) for parse-include-specS(nqp::atkey($ENV,'RAKUDOLIB')); } if nqp::existskey($ENV,'RAKULIB') { nqp::push($raw-specs,$_) for parse-include-specS(nqp::atkey($ENV,'RAKULIB')); } if nqp::existskey($ENV,'PERL6LIB') { nqp::push($raw-specs,$_) for parse-include-specS(nqp::atkey($ENV,'PERL6LIB')); } # your basic repo chain $next-repo := CompUnit::Repository::AbsolutePath.new( :next-repo(CompUnit::Repository::NQP.new( :next-repo(CompUnit::Repository::Perl5.new( )) )) ); } # create reverted, unique list of path-specs my $unique := nqp::hash(); my $specs := nqp::list(); while nqp::elems($raw-specs) { my $repo-spec := nqp::shift($raw-specs); my str $path-spec = $repo-spec.Str; unless nqp::existskey($unique,$path-spec) { nqp::bindkey($unique,$path-spec,1); nqp::unshift($specs,$repo-spec); } } # set up and normalize $prefix if needed my str $prefix = nqp::ifnull( nqp::atkey($ENV,'RAKUDO_PREFIX'), nqp::gethllsym('default', 'SysConfig').rakudo-home() ); $prefix = $prefix.subst(:g, '/', $sep) if Rakudo::Internals.IS-WIN; # set up custom libs my str $core = 'inst#' ~ $prefix ~ $sep ~ 'core'; my str $vendor = 'inst#' ~ $prefix ~ $sep ~ 'vendor'; my str $site = 'inst#' ~ $prefix ~ $sep ~ 'site'; my str $home; my str $home-spec; if nqp::ifnull( nqp::atkey($ENV,'HOME'), nqp::concat( nqp::ifnull(nqp::atkey($ENV,'HOMEDRIVE'),''), nqp::ifnull(nqp::atkey($ENV,'HOMEPATH'),'') ) ) -> $home-path { $home = $home-path ~ $sep ~ '.raku'; $home-spec = 'inst#' ~ $home; } unless $precomp-specs { $next-repo := self!register-custom-lib-repository( 'core', $core, CompUnit::Repository::Installation.new( :prefix("$prefix/core"), :$next-repo ) ) unless nqp::existskey($unique,$core); $next-repo := self!register-custom-lib-repository( 'vendor', $vendor, CompUnit::Repository::Installation.new( :prefix("$prefix/vendor"), :$next-repo ) ) unless nqp::existskey($unique, $vendor); $next-repo := self!register-custom-lib-repository( 'site', $site, CompUnit::Repository::Installation.new( :prefix("$prefix/site"), :$next-repo ) ) unless nqp::existskey($unique, $site); $next-repo := self!register-custom-lib-repository( 'home', $home-spec, CompUnit::Repository::Installation.new( :prefix($home), :$next-repo ) ) if $home-spec and nqp::not_i(nqp::existskey($unique,$home-spec)); } # convert repo-specs to repos my $repos := nqp::hash(); while nqp::elems($specs) { my $spec := nqp::shift($specs); $next-repo := self.use-repository( self.repository-for-spec($spec), :current($next-repo)); nqp::bindkey($repos,$spec.Str,$next-repo); } # register manually set custom-lib repos $lock.protect: { unless nqp::existskey($custom-lib,'core') { my \repo := nqp::atkey($repos,$core); nqp::bindkey($custom-lib,'core',repo) if repo; } unless nqp::existskey($custom-lib,'vendor') { my \repo := nqp::atkey($repos,$vendor); nqp::bindkey($custom-lib,'vendor',repo) if repo; } unless nqp::existskey($custom-lib,'site') { my \repo := nqp::atkey($repos,$site); nqp::bindkey($custom-lib,'site',repo) if repo; } unless nqp::existskey($custom-lib,'home') { if $home-spec { my \repo := nqp::atkey($repos,$home-spec); nqp::bindkey($custom-lib,'home',repo) if repo; } } } $next-repo } method !remove-from-chain( CompUnit::Repository $repo, CompUnit::Repository $current --> Nil) { my $item := $current; while $item { if $item.next-repo === $repo { $item.next-repo = $repo.next-repo; last; } $item := $item.next-repo; } } method use-repository( CompUnit::Repository $repo, CompUnit::Repository :$current = $*REPO --> CompUnit::Repository:D) { if $current === $repo { $repo } else { self!remove-from-chain($repo, $current); $repo.next-repo = $current; PROCESS::<$REPO> := $repo; } } method repository-for-name(str $name) { $*REPO; # initialize if not yet done $lock.protect: { nqp::ifnull(nqp::atkey($custom-lib,$name),Nil) } } method register-name($name, CompUnit::Repository $repo) { $lock.protect: { nqp::bindkey($custom-lib, $name, $repo) } } method name-for-repository(CompUnit::Repository $repo) { $*REPO; # initialize if not yet done $lock.protect: { my $iter := nqp::iterator($custom-lib); while $iter { my \pair = nqp::shift($iter); return nqp::iterkey_s(pair) if nqp::iterval(pair).prefix eq $repo.prefix; } } Nil } method file-for-spec(Str $spec) { my @parts is List = $spec.split('#', 2); if @parts.elems == 2 { my $repo = self.repository-for-name(@parts[0]); return $repo.source-file(@parts[1]) if $repo.can('source-file'); } Nil } method run-script($script) { my @installations = $*REPO.repo-chain.grep(CompUnit::Repository::Installation); my @metas = @installations.map({ .files("bin/$script").head }).grep(*.defined); unless +@metas { @metas = flat @installations.map({ .files("bin/$script").Slip }).grep(*.defined); if +@metas { note "===SORRY!===\n" ~ "No candidate found for '$script' that match your criteria.\n" ~ "Did you perhaps mean one of these?"; my %caps = :name(['Distribution', 12]), :auth(['Author(ity)', 11]), :ver(['Version', 7]); for @metas -> $meta { for %caps.kv -> $caption, @opts { @opts[1] = max @opts[1], ($meta{$caption} // '').Str.chars } } note ' ' ~ %caps.values.map({ sprintf('%-*s', .[1], .[0]) }).join(' | '); for @metas -> $meta { note ' ' ~ %caps.kv.map( -> $k, $v { sprintf('%-*s', $v.[1], $meta{$k} // '') } ).join(' | ') } } else { note "===SORRY!===\nNo candidate found for '$script'.\n"; } exit 1; } my $meta = @metas.sort(*.).sort(*.).reverse.head; my $bin = $meta; require "$bin"; } method head() { # mostly usefull for access from NQP $*REPO } method resolve-unknown-repos($first-repo --> Nil) { my $repo := $first-repo; my $prev-repo; my $world := $*W; while nqp::isconcrete($repo) { if nqp::istype($repo,CompUnit::Repository::Unknown) { my $next-repo := $repo.next-repo; my $head := PROCESS::<$REPO>; PROCESS::<$REPO> := $next-repo; my $comp_unit := $next-repo.need( CompUnit::DependencySpecification.new( :short-name($repo.short-name)) ); PROCESS::<$REPO> := $head; # Cannot just use GLOBAL.WHO here as that gives a BOOTHash ($world ?? $world.find_single_symbol("GLOBAL").WHO !! GLOBAL.WHO) .merge-symbols($comp_unit.handle.globalish-package); $repo := self.repository-for-spec($repo.path-spec, :$next-repo); nqp::isconcrete($prev-repo) ?? ($prev-repo.next-repo = $repo) !! (PROCESS::<$REPO> := $repo); } $prev-repo := $repo; $repo := $repo.next-repo; } } # Handles any object repossession conflicts that occurred during module load, # or complains about any that cannot be resolved. method resolve_repossession_conflicts(@conflicts) { for @conflicts -> $orig is raw, $current is raw { # If it's a Stash in conflict, we make sure any original entries get # appropriately copied. if $orig.HOW.name($orig) eq 'Stash' { $current.merge-symbols($orig); } # We could complain about anything else, and may in the future; for # now, we let it pass by with "latest wins" semantics. } } my constant $short-id2class = nqp::hash( 'file', CompUnit::Repository::FileSystem, 'inst', CompUnit::Repository::Installation, 'ap', CompUnit::Repository::AbsolutePath, 'nqp', CompUnit::Repository::NQP, 'perl5', CompUnit::Repository::Perl5, ); my $sid-lock := Lock.new; sub short-id2class(Str:D $short-id) is rw { Proxy.new( FETCH => { $sid-lock.protect( { nqp::ifnull( nqp::atkey($short-id2class,$short-id), nqp::if( nqp::istype((my \type := ::($short-id)),Failure), (type.defined || Any), # no unhandled Failure warnings nqp::if( nqp::can(type,"short-id") && (my str $id = type.short-id), nqp::ifnull( nqp::atkey($short-id2class,$id), nqp::bindkey($short-id2class,$id,type) ), (die "Class '{type.^name}' is not a 'CompUnit::Repository'") ) ) ) } ); }, STORE => -> $, $class { nqp::istype((my \type = ::($class)),Failure) ?? X::AdHoc.new( payload => "Must load class '$class' first" ).throw !! $sid-lock.protect: { nqp::bindkey($short-id2class,$short-id,type) } }, ); } sub parse-include-specS(Str:D $specs --> List:D) { my $found := nqp::create(IterationBuffer); my $default-short-id := 'file'; if $*RAKUDO_MODULE_DEBUG -> $RMD { $RMD("Parsing specs: $specs") } # for *all possible specs my $spec-list := nqp::split(',',$specs); while nqp::elems($spec-list) { if nqp::shift($spec-list).trim -> $spec { if CompUnit::Repository::Spec.from-string( $spec, $default-short-id ) -> $repo-spec { nqp::push($found,$repo-spec); $default-short-id := $repo-spec.short-id; } else { die "Don't know how to handle '$spec'"; } } } $found.List } } #line 1 SETTING::src/core.c/CompUnit/Repository/Locally.rakumod role CompUnit::Repository::Locally { has IO::Path $.prefix is built(:bind); has Str $.abspath is built(False); has ValueObjAt $.WHICH is built(False); has Str $.path-spec is built(False); my $instances := nqp::hash; # cache with instances, keyed on WHICH my $lock := Lock.new; # serializing access to instances hash # handle a new object that wasn't cached before method !SET-SELF(Str:D $abspath, str $WHICH) { $!abspath := $abspath; $!WHICH := ValueObjAt.new($WHICH); $!path-spec := self.short-id ~ '#' ~ $abspath; nqp::bindkey($instances,$WHICH,self) } # CompUnit::Repository::Locally objects are special in that there # can only be one for each combination of class and directory that # they consider their work space. So any parameters passed apart # from the "prefix" parameter, will be *ignored* any subsequent # attempt at creating an object of that type on that prefix. method new(CompUnit::Repository::Locally: Any:D :$prefix) { my $abspath = nqp::istype($prefix,IO::Path) ?? $prefix.absolute !! $*SPEC.rel2abs($prefix.Str); my str $WHICH = self.^name ~ '|' ~ $abspath; $lock.protect: { nqp::ifnull( nqp::atkey($instances,$WHICH), self.bless( :prefix( nqp::istype($prefix,IO::Path) ?? $prefix !! $abspath.IO ), |%_ )!SET-SELF($abspath, $WHICH) ) } } multi method WHICH(CompUnit::Repository::Locally:D: --> ValueObjAt:D) { $!WHICH } multi method Str(CompUnit::Repository::Locally:D: --> Str:D) { $!abspath } multi method gist(CompUnit::Repository::Locally:D: --> Str:D) { $!path-spec } multi method raku(CompUnit::Repository::Locally:D: --> Str:D) { $?CLASS.^name ~ '.new(prefix => ' ~ $!abspath.raku ~ ')'; } method source-file(Str:D $name --> IO::Path:D) { $!prefix.add($name) } method id(--> Str:D) { nqp::sha1(self.next-repo ?? $!path-spec ~ ',' ~ self.next-repo.id !! $!path-spec ) } # stubs method short-id(CompUnit::Repository::Locally:D: --> Str:D) { ... } } #line 1 SETTING::src/core.c/CompUnit/Repository/Distribution.rakumod # A distribution passed to `CURI.install()` will get encapsulated in this # class, which normalizes the meta6 data and adds identifiers/content-id class CompUnit::Repository::Distribution does Distribution { has Distribution $.dist is built(:bind) handles ; has $.repo is built(:bind); has $.dist-id is built(:bind); has $.repo-name is built(:bind); has %.meta is built(False); method TWEAK(--> Nil) { my %meta := $!dist.meta.hash; %meta //= %meta // ''; %meta //= %meta // %meta // ''; %meta //= ''; %!meta := %meta; $!repo-name := $!repo.name if nqp::not_i(nqp::isconcrete($!repo-name)) && nqp::can($!repo,"name"); $!repo := $!repo.path-spec if nqp::isconcrete($!repo) && nqp::not_i(nqp::istype($!repo,Str)); } method new(Distribution:D $dist --> CompUnit::Repository::Distribution:D) { self.bless(:$dist, |%_) } method id(--> Str:D) { nqp::sha1(self.Str) } method meta(CompUnit::Repository::Distribution:D:) { %!meta.item } # Alternate instantiator called from Actions.nqp during compilation # of $?DISTRIBUTION method from-precomp(CompUnit::Repository::Distribution:U: --> CompUnit::Repository::Distribution:D) is implementation-detail { if %*ENV -> $json { my %data := Rakudo::Internals::JSON.from-json: $json; my $name := %data; my $spec := %data; # XXX badly named field? my $id := %data; my $repo := $name ?? CompUnit::RepositoryRegistry.repository-for-name($name) !! CompUnit::RepositoryRegistry.repository-for-spec($spec); self.bless: :dist($repo.distribution($id)), :repo($spec), :repo-name($name), :dist-id($id); } else { Nil } } method serialize(--> Str:D) is implementation-detail { Rakudo::Internals::JSON.to-json: {:$.repo, :$.repo-name, :$.dist-id} } multi method Str(CompUnit::Repository::Distribution:D:--> Str:D) { "%!meta:ver<%!meta>:auth<%!meta>:api<%!meta>" } multi method raku(CompUnit::Repository::Distribution:D:--> Str:D) { self.^name ~ ".new($!dist.raku(), repo => $!repo.raku(), repo-name => $!repo-name.raku())" } } #line 1 SETTING::src/core.c/CompUnit/Repository/FileSystem.rakumod class CompUnit::Repository::FileSystem does CompUnit::Repository::Locally does CompUnit::Repository { has $!loaded-lock; has %!loaded; # cache compunit lookup for self.need(...) has $!seen-lock; has $!seen; # cache distribution lookup for self!matching-dist(...) has $!precomp; has $!id; has $!precomp-stores; has $!precomp-store; has $!distribution; has $!files-prefix; has @.extensions = ; method TWEAK(--> Nil) { $!loaded-lock := Lock.new; $!seen-lock := Lock.new; $!seen := nqp::hash; # turn ".rakumod .pm6" into ".rakumod", ".pm6" # for e.g. file#extensions<.rakumod .pm6>#lib @!extensions = @!extensions.words; } # An equivalent of self.candidates($spec).head that caches the best match method !matching-dist(CompUnit::DependencySpecification:D $spec) { $!seen-lock.protect: { nqp::ifnull( nqp::atkey($!seen,~$spec), nqp::if( (my $candidate := self.candidates($spec).head), nqp::bindkey($!seen,~$spec,$candidate), Nil ) ) } } method !comp-unit-id($name) { CompUnit::PrecompilationId.new-from-string(self!distribution.id ~ $name); } method !precomp-stores() { ⚛$!precomp-stores // cas $!precomp-stores, { $_ // Array[CompUnit::PrecompilationStore].new( gather { my $repo = $*REPO; while $repo { my \store = $repo.precomp-store; take store if store.defined; $repo = $repo.next-repo; } } ) } } method id() { ⚛$!id // cas $!id, { $_ // do with self!distribution -> $distribution { my $meta6 := Rakudo::Internals::JSON.to-json: :sorted-keys, $distribution.meta.hash; my $parts := grep { .defined }, (.id with self.next-repo), slip # slip next repo id into hash parts to be hashed together map { nqp::sha1($_) }, map { $distribution.content($_).open(:enc).slurp(:close) }, $distribution.meta.values.unique.sort; nqp::sha1($meta6 ~ $parts.join('')); } } } method resolve(CompUnit::DependencySpecification $spec --> CompUnit:D) { with self!matching-dist($spec) { return CompUnit.new( :short-name($spec.short-name), :repo-id(self!comp-unit-id($spec.short-name).Str), :repo(self), :distribution($_), ); } return self.next-repo.resolve($spec) if self.next-repo; Nil } method need( CompUnit::DependencySpecification $spec, CompUnit::PrecompilationRepository $precomp = self.precomp-repository(), CompUnit::PrecompilationStore :@precomp-stores = self!precomp-stores(), --> CompUnit:D) { my $spec-key = ~$spec; my $spec-promise; my $loaded-cu; my $matching-dist; $!loaded-lock.protect: { with %!loaded{$spec-key} { $loaded-cu = $_; } else { with $matching-dist = self!matching-dist($spec) { %!loaded{$spec-key} = $spec-promise = Promise.new; } } } return $_ ~~ Promise ?? $*AWAITER.await($_) !! $_ with $loaded-cu; with $matching-dist { my $name = $spec.short-name; my $id = self!comp-unit-id($name); my $*DISTRIBUTION = $_; my $*RESOURCES = Distribution::Resources.new(:repo(self), :dist-id('')); my $source-handle = $_.content($_.meta{$name}); my $precomp-handle = $precomp.try-load( CompUnit::PrecompilationDependency::File.new( :$id, :src($source-handle.path.absolute), :$spec, ), :@precomp-stores, ); $!loaded-lock.protect: { CATCH { $spec-promise.break: $_; .rethrow } $spec-promise.keep( %!loaded{$spec-key} = CompUnit.new( :short-name($name), :handle($precomp-handle // CompUnit::Loader.load-source($source-handle.open(:bin).slurp(:close))), :repo(self), :repo-id($id.Str), :precompiled($precomp-handle.defined), :distribution($_))); } return %!loaded{$spec-key} } return self.next-repo.need($spec, $precomp, :@precomp-stores) if self.next-repo; X::CompUnit::UnsatisfiedDependency.new(:specification($spec)).throw; } method load(IO::Path:D $file --> CompUnit:D) { unless $file.is-absolute { # We have a $file when we hit: require "PATH" or use/require Foo:file; my $precompiled = $file.Str.ends-with(Rakudo::Internals.PRECOMP-EXT); my $path = $!prefix.add($file); if $path.f { $!loaded-lock.protect: { %!loaded{$file.Str} //= CompUnit.new( :handle( $precompiled ?? CompUnit::Loader.load-precompilation-file($path) !! CompUnit::Loader.load-source-file($path) ), :short-name($file.Str), :repo(self), :repo-id($file.Str), :$precompiled, :distribution(self!distribution), ); } return %!loaded{$file.Str} } } return self.next-repo.load($file) if self.next-repo; X::NotFoundInRepository.new(:$file).throw; } method short-id() { 'file' } method loaded(--> Iterable:D) { $!loaded-lock.protect: { %!loaded.values.grep(* !~~ Promise) } } # This allows -Ilib to find resources/ ( and by extension bin/ ) for %?RESOURCES. # Note this only works in the well formed case, i.e. given Foo::Bar and no META6.json -- # use lib 'packages'; use 'Foo::Bar'; # well formed -- %?RESOURCES uses packages/../resources # use lib 'packages/Foo'; use 'Bar'; # not well formed -- %?RESOURCES is ambigious now... # packages/../resources? # packages/resources? method !files-prefix { ⚛$!files-prefix // cas $!files-prefix, { $_ // ($!prefix.child('META6.json').e ?? $!prefix !! $!prefix.parent) } } proto method candidates(|) {*} multi method candidates(Str:D $name, :$auth, :$ver, :$api) { return samewith(CompUnit::DependencySpecification.new( short-name => $name, auth-matcher => $auth, version-matcher => $ver, api-matcher => $api, )); } multi method candidates(CompUnit::DependencySpecification $spec) { return Empty unless $spec.from eq 'Raku' || $spec.from eq 'Perl6'; my $distribution = self!distribution; unless ($distribution.meta && $distribution.meta{$spec.short-name}) or ($distribution.meta && $distribution.meta{$spec.short-name}) { # Only break the cache if there is no inclusion authority (i.e. META6.json) return Empty if $!prefix.child('META6.json').e; # Break the !distribution cache if we failed to find a match using the cached distribution # but still found an existing file that matches the $spec.short-name my $name-path := $spec.short-name.subst(:g, "::", $*SPEC.dir-sep); return Empty unless @!extensions.map({ $!prefix.add($name-path ~ '.' ~ $_) }).first(*.f); $!distribution := Nil; $distribution = self!distribution; } return Empty unless (($distribution.meta // '') eq '' || ($distribution.meta // '') ~~ $spec.auth-matcher) and (($distribution.meta // '*') eq '*' || Version.new($distribution.meta // 0) ~~ $spec.version-matcher) and (($distribution.meta // '*') eq '*' || Version.new($distribution.meta // 0) ~~ $spec.api-matcher); return ($distribution,); } proto method files(|) {*} multi method files($file, Str:D :$name!, :$auth, :$ver, :$api) { my $spec = CompUnit::DependencySpecification.new( short-name => $name, auth-matcher => $auth, version-matcher => $ver, api-matcher => $api, ); with self.candidates($spec) { my $matches := $_.grep: { .meta{$file}:exists } my $absolutified-metas := $matches.map: { my $meta = $_.meta; $meta = $!prefix.add($meta{$file}); $meta; } return $absolutified-metas.grep(*..e); } } multi method files($file, :$auth, :$ver, :$api) { my $spec = CompUnit::DependencySpecification.new( short-name => $file, auth-matcher => $auth, version-matcher => $ver, api-matcher => $api, ); with self.candidates($spec) { my $absolutified-metas := $_.map: { my $meta = $_.meta; $meta = self!files-prefix.add($meta{$file}); $meta; } return $absolutified-metas.grep(*..e); } } method !distribution { if nqp::isconcrete($!distribution) { $!distribution } # need to create a new distribution else { # Path contains a META6.json file, so only use paths/modules # explicitly declared therein ( -I ./ ) my $dist := $!prefix.add('META6.json').f ?? Distribution::Path.new($!prefix) !! self!dist-from-ls; $!distribution := .clone(:dist-id(.Str)) with CompUnit::Repository::Distribution.new($dist, :repo(self)); } } # Path does not contain a META6.json file so grep for files to be used # to map to arbitrary module names later ( -I ./lib ). This is considered # a developmental mode of library inclusion -- technically a Distribution, # but probably a poorly formed one. method !dist-from-ls { my $prefix := self!files-prefix; my $SPEC := $*SPEC; my $CWD := $*CWD; my &ls := { Rakudo::Internals.DIR-RECURSE($_).map({ IO::Path.new($_, :$SPEC, :$CWD) }) } my &to-relative := { $_.relative($prefix).subst(:g, '\\', '/') } # files is a non-spec internal field used by # CompUnit::Repository::Installation included to make cross CUR # install easier my %files; # all the files in bin %files{$_} = $_ for ls($prefix.child('bin').absolute).map(&to-relative); # all the files in resources %files{ m/^resources\/libraries\/(.*)/ ?? 'resources/libraries/' ~ ($0.IO.dirname eq '.' ?? '' !! $0.IO.dirname ~ "/") ~ $0.IO.basename.subst(/^lib/, '').subst(/\..*/, '') !! $_ } = $_ for ls($prefix.child('resources').absolute).map(&to-relative); # already grepped resources/ for %files, so reuse that information my @resources := %files.keys .grep(*.starts-with('resources/')) .map(*.substr(10)) .List.eager; # Set up hash of hashes of files found that could be modules. # Then select the most prominent one from there when done. my %provides-exts = @!extensions.map(* => True); my $provides-files := ls($!prefix.absolute).grep({ my $ext = $_.extension; %provides-exts{$ext} }); my %provides; %provides{ .subst(:g, /\//, "::") .subst(:g, /\:\:+/, '::') .subst(/^.*?'::'/, '') .subst(/\..*/, '') }{ $SPEC.extension($_) } = $_ for $provides-files.map(&to-relative); # precedence is determined by the order of @!extensions $_ = @!extensions.map(-> $ext { $_{$ext} }).first(*.defined) for %provides.values; # The .pm file extension can also match perl modules. We should encourage users # to stop using it for their raku code so we don't waste cycles sha1ing their # source code or attempt to precompile them. if %provides.values.grep(*.ends-with('.pm')) { DEPRECATED( "the .rakumod extension for raku modules, or include a META6.json file that explicitly declares each raku module file", :what(".pm file extension in raku library path"), :file(self.path-spec), :line(0), ); } Distribution::Hash.new(:$prefix, %( name => ~$!prefix, # must make up a name when using -Ilib ver => '*', api => '*', auth => '', files => %files, resources => @resources, provides => %provides, )) } method resource($dist-id, $key) { if self!distribution -> $dist { if $dist.meta.hash.{$key} -> IO() $path { return $path.is-relative ?? $dist.prefix.add( $path ) !! $path; } } } method distribution(Str $id? --> Distribution) { # CURFS is a single-distribution repository so there is no need for $id # ( similar to $dist-id of method resource ) return self!distribution; } method precomp-store(--> CompUnit::PrecompilationStore:D) { ⚛$!precomp-store // cas $!precomp-store, { $_ // CompUnit::PrecompilationStore::FileSystem.new( :prefix(self.prefix.add('.precomp')), ) } } method precomp-repository(--> CompUnit::PrecompilationRepository:D) { ⚛$!precomp // cas $!precomp, { $_ // CompUnit::PrecompilationRepository::Default.new( :store(self.precomp-store), ) } } } #line 1 SETTING::src/core.c/CompUnit/Repository/Installation.rakumod class CompUnit::Repository::Installation does CompUnit::Repository::Locally does CompUnit::Repository::Installable { has $!lock; has $!loaded; # cache compunit lookup for self.need(...) has $!seen; # cache distribution lookup for self!matching-dist(...) has $!dist-metas; # cache for .resource has $!precomp; has $!id; has Int $!version; has $!precomp-store; # cache for .precomp-store has $!precomp-stores; # cache for !precomp-stores my $verbose = nqp::getenvhash; my constant @script-postfixes = '', '-m', '-j', '-js'; my constant @all-script-extensions = '', '-m', '-j', '-js', '.bat', '-m.bat', '-j.bat', '-js.bat'; my constant $windows-wrapper = Q/@rem = '--*-Perl-*-- @echo off if "%OS%" == "Windows_NT" goto WinNT #raku# "%~dpn0" %1 %2 %3 %4 %5 %6 %7 %8 %9 goto endofraku :WinNT #raku# "%~dpn0" %* if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto endofraku if %errorlevel% == 9009 echo You do not have Rakudo in your PATH. if errorlevel 1 goto script_failed_so_exit_with_non_zero_val 2>nul goto endofraku @rem '; __END__ :endofraku /; my constant $raku-wrapper = '#!/usr/bin/env #raku# sub MAIN(*@, *%) { CompUnit::RepositoryRegistry.run-script("#name#"); }'; method TWEAK() { $!lock := Lock.new; $!loaded := nqp::hash; $!seen := nqp::hash; $!dist-metas := nqp::hash; $!precomp-store := $!precomp-stores := nqp::null; } my class InstalledDistribution is Distribution::Hash { method content($address) { my $entry = $.meta.values.first: { $_{$address}:exists }; my $file = $entry ?? $.prefix.add('sources').add($entry{$address}) !! $.prefix.add('resources').add($.meta{$address}); IO::Handle.new(:path($file)) } } # A distribution that provides a subset of its meta data without parsing # the full original json version, while lazily parsing once fields outside # of that subset are used. my role LazyMetaReader { has $.meta-reader; method AT-KEY($key) { $!meta-reader($key) } method EXISTS-KEY($key) { $!meta-reader($key).defined } } my role MetaAssigner { has $.meta-writer; method ASSIGN-KEY($key, $value) { $!meta-writer($key, $value) } method BIND-KEY($key, $value) { $!meta-writer($key, $value) } } my class LazyDistribution does Distribution::Locally { has $.dist-id; has $.read-dist; has $!installed-dist; has $.meta; # Parses dist info from json and populates $.meta with any new fields method !dist { unless $!installed-dist.defined { $!installed-dist = InstalledDistribution.new($.read-dist()($!dist-id), :$.prefix); # Keep fields of the meta data subset that do not exist in # the full meta data (source, default values for versions, etc) my %hash = $!installed-dist.meta.hash; %hash{$_} //= $!meta{$_} for $!meta.hash.keys; $!meta = %hash; } $!installed-dist; } method meta(--> Hash:D) { my %hash = $!meta.hash; unless $!installed-dist.defined { # Allow certain meta fields to be read without a full parsing, # and fallback to calling self!dist to populate the entire # meta data from json. %hash does LazyMetaReader({ $!meta.hash{$^a} // self!dist.meta.{$^a} }); # Allows absolutifying paths in .meta to keep # .files() happy %hash does MetaAssigner({ $!meta.ASSIGN-KEY($^a, $^b) }); } %hash; } method content($content-id --> IO::Handle:D) { self!dist.content($content-id) } method Str { CompUnit::Repository::Distribution.new(self).Str } method id { $.dist-id } } method !prefix-writeable(--> Bool:D) { if Rakudo::Internals.IS-WIN { if $.prefix.add('test-file').open(:create, :w) -> $handle { $handle.close; $handle.path.unlink # always True } else { False } } else { $.prefix.w } } method writeable-path { self!prefix-writeable ?? $.prefix !! IO::Path } method !writeable-path { self.can-install ?? $.prefix !! IO::Path } method can-install() { self!prefix-writeable || (!$.prefix.e && ?$.prefix.mkdir) } method !sources-dir { with $.prefix.add('sources') { .mkdir unless .e; $_ } } method !resources-dir { with $.prefix.add('resources') { .mkdir unless .e; $_ } } method !dist-dir { with $.prefix.add('dist') { .mkdir unless .e; $_ } } method !bin-dir { with $.prefix.add('bin') { .mkdir unless .e; $_ } } method !short-dir { with $.prefix.add('short') { .mkdir unless .e; $_ } } method !add-short-name($name, $dist, $source = "", $checksum = "" --> Nil) { my %meta := $dist.meta; self!short-dir .add(nqp::sha1($name)) # add the id derived from the name .mkdir # make sure there's a dir for it .add($dist.id) # a file for this distribution .spurt( # make sure it contains the right data (%meta // "") ~ "\n" ~ (%meta // "") ~ "\n" ~ (%meta // "") ~ "\n" ~ "$source\n$checksum\n" ); } method !file-id(str $name, str $dist-id) { nqp::sha1($name ~ $dist-id) } method name(--> Str:D) { CompUnit::RepositoryRegistry.name-for-repository(self) } method !repo-prefix() { self.name ?? (self.name ~ '#') !! '' } method !read-dist(Str:D $id) { my %meta := Rakudo::Internals::JSON.from-json: self!dist-dir.add($id).slurp; %meta := Version.new: %meta // '0'; %meta := Version.new: %meta // '0'; %meta } method !repository-version(--> Int:D) { $!version //= do { my $version-file = $.prefix.add('version'); $version-file.f ?? $version-file.slurp.Int !! 0 } } method upgrade-repository() { my $version = self!repository-version; my $short-dir = self!short-dir; mkdir $short-dir unless $short-dir.e; my $precomp-dir = $.prefix.add('precomp'); mkdir $precomp-dir unless $precomp-dir.e; self!sources-dir; my $resources-dir = self!resources-dir; my $dist-dir = self!dist-dir; self!bin-dir; if ($version < 1) { for $short-dir.dir -> $file { my @ids is List = $file.lines.unique; $file.unlink; $file.mkdir; for @ids -> $id { my $meta = self!read-dist($id); $file.add($id).spurt("{$meta // ''}\n{$meta // ''}\n{$meta // ''}\n"); } } } if ($version < 2) { for $dist-dir.dir -> $dist-file { my %meta = Rakudo::Internals::JSON.from-json($dist-file.slurp); my $files = %meta //= []; for eager $files.keys -> $file { $files{"resources/$file"} = $files{$file}:delete if $resources-dir.add($files{$file}).e and not $.prefix.add($file).e; # bin/ is already included in the path } $dist-file.spurt: Rakudo::Internals::JSON.to-json(%meta, :sorted-keys); } } $.prefix.add('version').spurt('2'); $!version = 2; } method install( Distribution:D $distribution, Bool :$force, Bool :$precompile = True, ) { my $dist = CompUnit::Repository::Distribution.new($distribution); my %files = $dist.meta.grep(*.defined).map: -> $link { $link ~~ Str ?? ($link => $link) !! ($link.keys[0] => $link.values[0]) } $!lock.protect( { my @*MODULES; my $path = self!writeable-path or die "No writeable path found, $.prefix not writeable"; my $lock = $.prefix.add('repo.lock').open(:create, :w); $lock.lock; my $version = self!repository-version; self.upgrade-repository unless $version == 2; my $dist-id = $dist.id; my $dist-dir = self!dist-dir; if not $force and $dist-dir.add($dist-id) ~~ :e { $lock.unlock; fail "$dist already installed"; } my $sources-dir = self!sources-dir; my $resources-dir = self!resources-dir; my $bin-dir = self!bin-dir; my $is-win = Rakudo::Internals.IS-WIN; self!add-short-name($dist.meta, $dist); # so scripts can find their dist my %links; # map name-path to new content address my %provides; # meta data gets added, but the format needs to change to # only extend the structure, not change it # the following 3 `for` loops should be a single loop, but has been # left this way due to impeding precomp changes # lib/ source files for $dist.meta.kv -> $name, $file is copy { # $name is "Inline::Perl5" while $file is "lib/Inline/Perl5.rakumod" my $id = self!file-id(~$name, $dist-id); my $destination = $sources-dir.add($id); my $handle = $dist.content($file); my $content = $handle.open(:bin).slurp(:close); self!add-short-name($name, $dist, $id, nqp::sha1(nqp::join("\n", nqp::split("\r\n", $content.decode('iso-8859-1'))))); %provides{ $name } = ~$file => { :file($id), :time(try $file.IO.modified.Num), }; note("Installing {$name} for {$dist.meta}") if $verbose and $name ne $dist.meta; $destination.spurt($content); } # bin/ scripts for %files.kv -> $name-path, $file is copy { next unless $name-path.starts-with('bin/'); my $name = $name-path.subst(/^bin\//, ''); my $id = self!file-id(~$file, $dist-id); # wrappers are put in bin/; originals in resources/ my $destination = $resources-dir.add($id); my $withoutext = $name-path.subst(/\.[exe|bat]$/, ''); for @script-postfixes -> $be { $.prefix.add("$withoutext$be").IO.spurt: $raku-wrapper.subst('#name#', $name, :g).subst('#raku#', "rakudo$be"); if $is-win { $.prefix.add("$withoutext$be.bat").IO.spurt: $windows-wrapper.subst('#raku#', "rakudo$be", :g); } else { $.prefix.add("$withoutext$be").IO.chmod(0o755); } } self!add-short-name($name-path, $dist, $id); %links{$name-path} = $id; my $handle = $dist.content($file); my $content = $handle.open.slurp(:bin,:close); $destination.spurt($content); $handle.close; } # resources/ for %files.kv -> $name-path, $file is copy { next unless $name-path.starts-with('resources/'); # $name-path is 'resources/libraries/p5helper' while $file is 'resources/libraries/libp5helper.so' my $id = self!file-id(~$name-path, $dist-id) ~ '.' ~ $file.IO.extension; my $destination = $resources-dir.add($id); %links{$name-path} = $id; my $handle = $dist.content($file); my $content = $handle.open.slurp(:bin,:close); $destination.spurt($content); $handle.close; } my %meta = %($dist.meta); %meta = %links; # add our new name-path => content-id mapping %meta = %provides; # new meta data added to provides nqp::bindkey($!dist-metas,$dist-id,%meta); $dist-dir.add($dist-id).spurt: Rakudo::Internals::JSON.to-json(%meta, :sorted-keys); # reset cached id so it's generated again on next access. # identity changes with every installation of a dist. $!id = Any; my $precomp := self.precomp-repository; if $precompile && $precomp.may-precomp() { my $head := $*REPO; CATCH { PROCESS::<$REPO> := $head } # Precomp files should only depend on downstream repos PROCESS::<$REPO> := self; my $repo-prefix = self!repo-prefix; my $*DISTRIBUTION = CompUnit::Repository::Distribution.new($dist, :repo(self), :$dist-id); my $*RESOURCES = Distribution::Resources.new(:repo(self), :$dist-id); my %done; my $compiler-id = CompUnit::PrecompilationId.new-without-check($*RAKU.compiler.id); for %provides.sort { my $id = CompUnit::PrecompilationId.new-without-check($_.value.values[0]); $precomp.store.delete($compiler-id, $id); } for %provides.sort { my $id = $_.value.values[0]; my $source = $sources-dir.add($id); my $source-file = $repo-prefix ?? $repo-prefix ~ $source.relative($.prefix) !! $source; if %done{$id} { note "(Already did $id)" if $verbose; next; } note("Precompiling $id ($_.key())") if $verbose; $precomp.precompile( $source, CompUnit::PrecompilationId.new-without-check($id), :source-name("$source-file ($_.key())"), ); %done{$id} = 1; } PROCESS::<$REPO> := $head; } $lock.unlock; } ) } my sub unlink-if-exists(IO::Path:D $io) { $io.unlink if $io.e } method uninstall(Distribution:D $distribution --> True) { # XXX: currently needs to be passed in a distribution object that # has meta pointing at content-ids, so you cannot yet just # pass in the original meta data and have it discovered and deleted # (i.e. update resolve to return such a ::Installation::Distribution) my $dist := CompUnit::Repository::Distribution.new($distribution); my $dist-id := $dist.id; my %meta := $dist.meta; my $prefix := $.prefix; # remove dist from short-name lookup files my $short-dir := $prefix.add('short'); if $short-dir.e { for $short-dir.dir -> $dir { $dir.add($dist-id).unlink; $dir.rmdir unless $dir.dir.elems; # dir-with-entries PR 4848 } } # delete special directory files if %meta -> %files { my $resources-dir := $prefix.add('resources'); for %files.kv -> $name-path, $file { if $name-path.starts-with('bin/') { # wrappers are located in $bin-dir (only delete if no other # versions use wrapper) unless self.files($name-path, :name(%meta)).elems { my $basename := $name-path.substr(4); # skip bin/ my $bin-dir := $prefix.add('bin'); unlink-if-exists($bin-dir.add($basename ~ $_)) for @all-script-extensions; } # original bin scripts are in $resources-dir unlink-if-exists($resources-dir.add($file)) } elsif $name-path.starts-with('resources/') { unlink-if-exists($resources-dir.add($file)) } } } # delete any sources if %meta -> %provides { my $sources-dir := $prefix.add('sources'); unlink-if-exists($sources-dir.add($_)) for %provides.values.flatmap(*.values.map(*)); } # delete the meta file $prefix.add('dist').add($dist-id).unlink; } # Ideally this would return Distributions, but it'd break older bin/ scripts proto method files(|) {*} # if we have to include :$name then we take the slow path multi method files($file, Str:D :$name!, :$auth, :$ver, :$api) { self.candidates( CompUnit::DependencySpecification.new: short-name => $name, auth-matcher => $auth, version-matcher => $ver, api-matcher => $api, ).map: { my %meta := .meta; if %meta -> %files { if %files{$file} -> $source { my $io := self!resources-dir.add($source); if $io.e { %meta := $io; %meta } } } } } # avoid parsing json if we don't need to know the short-name multi method files($file, :$auth, :$ver, :$api) { self.candidates( CompUnit::DependencySpecification.new: short-name => $file, auth-matcher => $auth, version-matcher => $ver, api-matcher => $api, ).map: { my %meta := .meta; if %meta || %meta{$file} -> $source { my $io := self!resources-dir.add($source); if $io.e { %meta := $io; %meta } } } } proto method candidates(|) {*} multi method candidates(Str:D $name, :$auth, :$ver, :$api) { self.candidates: CompUnit::DependencySpecification.new: short-name => $name, auth-matcher => $auth, version-matcher => $ver, api-matcher => $api, } multi method candidates(CompUnit::DependencySpecification:D $spec) { if $spec.from eq 'Raku' | 'Perl6' # $lookup is a file system resource that acts as a fast meta data # lookup for a given module short name. && (my $lookup = self!short-dir.add(nqp::sha1($spec.short-name))).e { my $auth-matcher := $spec.auth-matcher; my $version-matcher := $spec.version-matcher; my $api-matcher := $spec.api-matcher; # Each item contains a subset of meta data - notably items needed # `use "Foo:ver<*>"`. All items match the given module short name, $lookup.dir.map(-> $entry { my ($ver,$auth,$api,$source,$checksum) = $entry.slurp.lines; if ($ver := Version.new($ver || 0)) ~~ $version-matcher { if $auth ~~ $auth-matcher { if ($api := Version.new($api || 0)) ~~ $api-matcher { Pair.new: $entry.basename, Map.new(( :$ver, :$auth, :$api, :source($source || Any), :checksum($checksum || Str), )) } } } }) # Sort from highest to lowest by version and api .sort(*.value) .sort(*.value) .reverse # There is nothing left to do with the subset of meta data, so # initialize a lazy distribution with it .map({ LazyDistribution.new: :dist-id(.key), :meta(.value), :read-dist(-> $dist { self!read-dist($dist) }), :$.prefix }) # A different policy might wish to implement additional/alternative # filtering or sorting at this point, with the caveat that calling # a non-lazy field will require parsing json for each matching # distribution. # .grep({.meta eq 'Artistic-2.0'}).sort(-*.meta)` } } # An equivalent of self.candidates($spec).head that caches the best match method !matching-dist(CompUnit::DependencySpecification:D $spec) { $!lock.protect: { nqp::ifnull( nqp::atkey($!seen,~$spec), nqp::if( (my $candidate := self.candidates($spec).head), nqp::bindkey($!seen,~$spec,$candidate), Nil ) ) } } method resolve(CompUnit::DependencySpecification:D $spec --> CompUnit:D) { if self!matching-dist($spec) -> $distribution { my %meta := $distribution.meta; CompUnit.new( :handle(CompUnit::Handle), :short-name($spec.short-name), :version(%meta), :auth(%meta), :api(%meta), :repo(self), :repo-id(%meta), :$distribution, ) } elsif self.next-repo -> $next-repo { $next-repo.resolve($spec) } else { Nil } } method !find-precomp-stores() { my CompUnit::PrecompilationStore @stores = self.precomp-store; my $repo := self; nqp::while( ($repo := $repo.next-repo).defined, nqp::stmts( nqp::if( (my $store := $repo.precomp-store).defined, @stores.push($store) ) ) ); $!precomp-stores := @stores } method !precomp-stores() { nqp::ifnull($!precomp-stores,self!find-precomp-stores) } method need( CompUnit::DependencySpecification $spec, CompUnit::PrecompilationRepository $precomp = self.precomp-repository(), CompUnit::PrecompilationStore :@precomp-stores = self!precomp-stores(), --> CompUnit:D) { # found a distribution for this spec if self!matching-dist($spec) -> $distribution { my %meta := $distribution.meta; my $source-file-name := %meta; X::CompUnit::UnsatisfiedDependency.new(:specification($spec)).throw unless $source-file-name; my $loader := $.prefix.add('sources').add($source-file-name); my str $repo-id = $loader.basename; # already loaded before, fast path return $_ if $_ := $!lock.protect: { nqp::atkey($!loaded,$repo-id) }; # Set up dynamics for compilation my $dist-id := $distribution.id; my $*DISTRIBUTION := CompUnit::Repository::Distribution.new: $distribution, :repo(self), :$dist-id; my $*RESOURCES := Distribution::Resources.new: :repo(self), :$dist-id; # could load precompiled (the fast path in production) my $repo-prefix := self!repo-prefix; if $precomp.try-load( CompUnit::PrecompilationDependency::File.new( :id(CompUnit::PrecompilationId.new-without-check($repo-id)), :src($repo-prefix ?? $repo-prefix ~ $loader.relative($!prefix) !! $loader.absolute ), :checksum(%meta // Str), :$spec, ), :source($loader), :@precomp-stores, ) -> $handle { $!lock.protect: { nqp::bindkey($!loaded,$repo-id,CompUnit.new: :$handle, :short-name($spec.short-name), :version(%meta), :auth(%meta), :api(%meta), :repo(self), :$repo-id, :precompiled, :$distribution, ) } } # could load from source? (slower path) elsif CompUnit::Loader.load-source-file($loader) -> $handle { $!lock.protect: { nqp::bindkey($!loaded,$repo-id,CompUnit.new: :$handle, :short-name($spec.short-name), :version(%meta), :auth(%meta), :api(%meta), :repo(self), :$repo-id, :$distribution, ) } } # just in case? else { die "Could not loaded $spec from source"; } } # not in this repo, maybe the next? elsif self.next-repo -> $next-repo { $next-repo.need($spec, $precomp, :@precomp-stores) } # alas else { X::CompUnit::UnsatisfiedDependency.new(:specification($spec)).throw; } } method resource(str $dist-id, str $key) { self!resources-dir.add: ($!lock.protect: { nqp::ifnull( nqp::atkey($!dist-metas,$dist-id), nqp::bindkey($!dist-metas,$dist-id, Rakudo::Internals::JSON.from-json: self!dist-dir.add($dist-id).slurp ) ) }){$key} } method id() { return $!id if $!id; my $name = self.path-spec; $name ~= ',' ~ self.next-repo.id if self.next-repo; my $dist-dir = $.prefix.add('dist'); $!id = nqp::sha1(nqp::sha1($name) ~ ($dist-dir.e ?? $dist-dir.dir !! '')) } method short-id() { 'inst' } method loaded(--> Iterable:D) { $!lock.protect: { nqp::p6bindattrinvres(nqp::create(Map),Map,'$!storage',$!loaded) .values; } } method distribution(Str $id --> Distribution) { InstalledDistribution.new(self!read-dist($id), :prefix(self.prefix)) } method installed(--> Iterable:D) { my $dist-dir = self.prefix.add('dist'); $dist-dir.e ?? $dist-dir.dir(:test(!*.starts-with("."))).map: { self.distribution(.basename) } !! Nil } method precomp-store(--> CompUnit::PrecompilationStore:D) { nqp::ifnull( $!precomp-store, $!precomp-store := CompUnit::PrecompilationStore::FileSystem.new( :prefix($.prefix.add('precomp')) ) ) } method precomp-repository(--> CompUnit::PrecompilationRepository:D) { $!precomp := CompUnit::PrecompilationRepository::Default.new( :store(self.precomp-store), ) unless $!precomp; $!precomp } sub provides-warning($is-win, $name --> Nil) { my ($red,$clear) = Rakudo::Internals.error-rcgye; note "$red==={$clear}WARNING!$red===$clear The distribution $name does not seem to have a \"provides\" section in its META6.json file, and so the packages will not be installed in the correct location. Please ask the author to add a \"provides\" section, mapping every exposed namespace to a file location in the distribution. See http://design.raku.org/S22.html#provides for more information.\n"; } } #line 1 SETTING::src/core.c/CompUnit/Repository/AbsolutePath.rakumod class CompUnit::Repository::AbsolutePath does CompUnit::Repository { has $!lock; has $!loaded; method TWEAK(--> Nil) { $!loaded := nqp::hash; $!lock := Lock.new; } method need(CompUnit::Repository::AbsolutePath:D: CompUnit::DependencySpecification $spec, CompUnit::PrecompilationRepository $precomp = self.precomp-repository() --> CompUnit:D) { (my $repo := self.next-repo) ?? $repo.need($spec, $precomp) !! X::CompUnit::UnsatisfiedDependency.new(:specification($spec)).throw } method load(CompUnit::Repository::AbsolutePath:D: IO::Path:D $file --> CompUnit:D) { if $file.is-absolute && $file.f { # we have a $file when we hit: require "PATH" # or use/require Foo:file; my $key := $file.Str; $!lock.protect: { nqp::ifnull( nqp::atkey($!loaded,$key), nqp::stmts( (my $precompiled := $key.ends-with(Rakudo::Internals.PRECOMP-EXT)), nqp::bindkey($!loaded,$key,CompUnit.new( :handle($precompiled ?? CompUnit::Loader.load-precompilation-file($file) !! CompUnit::Loader.load-source-file($file) ), :short-name($key), :repo(self), :repo-id($key), :$precompiled )) ) ) } } elsif self.next-repo -> $repo { $repo.load($file) } else { X::NotFoundInRepository.new(:$file).throw; } } method loaded(CompUnit::Repository::AbsolutePath:D: --> Iterable:D) { my $loaded := $!lock.protect: { nqp::clone($!loaded) } nqp::p6bindattrinvres( nqp::create(Map),Map,'$!storage',$loaded ).values } method id(--> Str:D) { 'ap' } method path-spec(--> Str:D) { 'ap#' } multi method gist(CompUnit::Repository::AbsolutePath:D:--> Str:D) { self.path-spec } } #line 1 SETTING::src/core.c/CompUnit/Repository/NQP.rakumod class CompUnit::Repository::NQP does CompUnit::Repository { my constant %opts = :from,; # comma needed to make a Map method need(CompUnit::Repository::NQP:D: CompUnit::DependencySpecification $spec, CompUnit::PrecompilationRepository $precomp?, --> CompUnit:D) { if $spec.from eq 'NQP' { my $key := $spec.short-name; CompUnit.new: :short-name($key), :handle(CompUnit::Handle.new( nqp::gethllsym('Raku','ModuleLoader').load_module($key, %opts) )), :repo(self), :repo-id($key), :from; } elsif self.next-repo -> $repo { $repo.need($spec, $precomp // self.precomp-repository) } else { X::CompUnit::UnsatisfiedDependency.new(:specification($spec)).throw; } } method loaded(CompUnit::Repository::NQP:D: --> Empty) { } method id(--> Str:D) { 'NQP' } method path-spec(--> Str:D) { 'nqp#' } multi method gist(CompUnit::Repository::NQP:D: --> Str:D) { self.path-spec } } #line 1 SETTING::src/core.c/CompUnit/Repository/Perl5.rakumod class CompUnit::Repository::Perl5 does CompUnit::Repository { my constant $perl5-dependency = CompUnit::DependencySpecification.new(:short-name); method need(CompUnit::Repository::Perl5:D: CompUnit::DependencySpecification $spec, CompUnit::PrecompilationRepository $precomp?, --> CompUnit:D) { if $spec.from eq 'Perl5' { CATCH { when X::CompUnit::UnsatisfiedDependency { X::NYI::Available.new( :available('Inline::Perl5'), :feature('Perl 5') ).throw; } } my $compunit := $*REPO.need($perl5-dependency); my $perl5 := $compunit.handle.globalish-package.WHO.default_perl5; if $*RAKUDO_MODULE_DEBUG -> $RMD { $RMD("Loading $spec.short-name() via Inline::Perl5"); } my $short-name := $spec.short-name; my $handle := $perl5.require( $short-name, $spec.version-matcher =:= True ?? Num !! $spec.version-matcher.Num, :handle ); CompUnit.new: :$short-name, :$handle, :repo(self), :repo-id($short-name), :from; } elsif self.next-repo -> $repo { $repo.need($spec, $precomp // self.precomp-repository) } else { X::CompUnit::UnsatisfiedDependency.new(:specification($spec)).throw; } } method loaded(--> Empty) { } method id(--> Str:D) { 'Perl5' } method path-spec(--> Str:D) { 'perl5#' } multi method gist(CompUnit::Repository::Perl5:D:) { self.path-spec } } #line 1 SETTING::src/core.c/CompUnit/Repository/Unknown.rakumod class CompUnit::Repository::Unknown does CompUnit::Repository { has $.path-spec; has $.short-name; method need( CompUnit::DependencySpecification $spec, CompUnit::PrecompilationRepository $precomp?, CompUnit::PrecompilationStore :@precomp-stores = Array[CompUnit::PrecompilationStore].new( self.repo-chain.map(*.precomp-store).grep(*.defined) ), --> CompUnit:D) { return $precomp ?? self.next-repo.need($spec, $precomp, :@precomp-stores) !! self.next-repo.need($spec, :@precomp-stores) if self.next-repo; X::CompUnit::UnsatisfiedDependency.new(:specification($spec)).throw; } method loaded() { [] } method id() { $.path-spec } method Str() { self.^name ~ " $.short-name $.path-spec" } } #line 1 SETTING::src/core.c/WalkList.rakumod # This is what Mu::WALK method returns. my class WalkList is List { has Mu $.invocant; has $.is-quiet is built(False) = False; proto method invoke(|) {*} multi method invoke(::?CLASS:D: Capture:D $args --> Seq:D) { Seq.new(class :: does Iterator { has $!is-quiet; has $!invocant; has $!wl-iterator; has $.is-lazy = True; method !SET-SELF(\wlist) { $!is-quiet = wlist.is-quiet; $!wl-iterator = wlist.iterator; $!invocant = wlist.invocant; self } method new(\wlist) { nqp::create(self)!SET-SELF(wlist) } method pull-one() { CATCH { $!is-quiet ?? (return .Failure) !! .rethrow } nqp::eqaddr( (my $method := nqp::decont($!wl-iterator.pull-one)), IterationEnd ) ?? IterationEnd !! $!invocant.$method(|$args) } }.new(self)) } multi method invoke(::?CLASS:D: |c --> Seq:D) { self.invoke(c) } method quiet(::?CLASS:D: Bool() $quiet = True --> ::?CLASS:D) { $!is-quiet = $quiet; self } method reverse(::?CLASS:D: --> ::?CLASS:D) { self.WHAT.new(|self.List::reverse) .set_invocant($!invocant) .quiet($!is-quiet) } method set_invocant(::?CLASS:D: Mu \inv) { $!invocant = inv; self } method CALL-ME(::?CLASS:D: |c) { self.invoke(c) } } #line 1 SETTING::src/core.c/Argfiles.rakumod Rakudo::Internals.REGISTER-DYNAMIC: '@*ARGS', { my @ARGS; my Mu $argiter := nqp::getcurhllsym('$!ARGITER'); @ARGS.push(nqp::p6box_s(nqp::shift($argiter))) while $argiter; PROCESS::<@ARGS> := @ARGS; } Rakudo::Internals.REGISTER-DYNAMIC: '$*ARGFILES', { # Here, we use $*IN's attributes to init the arg files because # the $*ARGFILES won't get instantiated until first access and by that # time the user may have already modified $*IN's attributes to their liking PROCESS::<$ARGFILES> = @*ARGS ?? IO::ArgFiles.new(@*ARGS) !! $*IN } #line 1 SETTING::src/core.c/Process.rakumod Rakudo::Internals.REGISTER-DYNAMIC: '$*RAKUDO_MODULE_DEBUG', { PROCESS::<$RAKUDO_MODULE_DEBUG> := ?%*ENV ?? -> *@str --> Nil { state $level = %*ENV++; state $root = $*CWD.Str; my $indent = (($level - 1) * 4) + 1; note sprintf "%2d%sRMD: %s", $level, " " x $indent, @str>>.indent(7 + $indent) .join("\n") .substr(7 + $indent) .subst($root, '.'); } !! ?%*ENV ?? -> $note --> Nil { state $level = %*ENV++ - 1; state $module; my $message := $note.trim-leading; if $message.starts-with("Late loading '") { $module = $message.substr(14, *-1); } elsif $message.starts-with("Precompiling ") { note " " x $level ~ "Precompiling $module"; } } !! False } Rakudo::Internals.REGISTER-DYNAMIC: '$*EXECUTABLE', { PROCESS::<$EXECUTABLE> := IO::Path.new(:CWD(INIT nqp::cwd()), nqp::execname() || ($*VM.config ~ '/bin/' ~ ($*VM.config eq 'MSWin32' ?? 'perl6-m.exe' !! 'perl6-m')) ); } Rakudo::Internals.REGISTER-DYNAMIC: '$*EXECUTABLE-NAME', { PROCESS::<$EXECUTABLE-NAME> := $*EXECUTABLE.basename; } Rakudo::Internals.REGISTER-DYNAMIC: '$*PROGRAM-NAME', { PROCESS::<$PROGRAM-NAME> := nqp::getcomp('Raku').user-progname; } Rakudo::Internals.REGISTER-DYNAMIC: '$*PROGRAM', { PROCESS::<$PROGRAM> := IO::Path.new(:CWD(INIT nqp::cwd()), $*PROGRAM-NAME); } Rakudo::Internals.REGISTER-DYNAMIC: '$*TMPDIR', { PROCESS::<$TMPDIR> = $*SPEC.tmpdir; } Rakudo::Internals.REGISTER-DYNAMIC: '$*TOLERANCE', { PROCESS::<$TOLERANCE> := item 1e-15; } Rakudo::Internals.REGISTER-DYNAMIC: '$*REPO', { my $repo := PROCESS::<$REPO> := CompUnit::RepositoryRegistry.setup-repositories; if $*W -> $world { $world.suspend_recording_precompilation_dependencies; CompUnit::RepositoryRegistry.resolve-unknown-repos($repo); $world.resume_recording_precompilation_dependencies; } else { CompUnit::RepositoryRegistry.resolve-unknown-repos($repo); } PROCESS::<$REPO> # Cannot be $repo, as CU:RepositoryRegistry changes $*REPO } Rakudo::Internals.REGISTER-DYNAMIC: '$*HOME', { my $HOME is default(Nil); if %*ENV -> $home { $HOME = $home; } elsif Rakudo::Internals.IS-WIN { my $env := %*ENV; $env && $env && ($HOME = nqp::concat($env, $env)); } $HOME = IO::Path.new($HOME) if $HOME; PROCESS::<$HOME> := $HOME # bind container so Nil default is kept } { sub fetch($what) { once if !Rakudo::Internals.IS-WIN && try { qx/LC_MESSAGES=POSIX id/ } -> $id { if $id ~~ m/^ [ uid "=" $=(\d+) ] [ "(" $=(<-[ ) ]>+) ")" ] \s+ [ gid "=" $=(\d+) ] [ "(" $=(<-[ ) ]>+) ")" ] / { PROCESS::<$USER> := IntStr.new(+$,~$); PROCESS::<$GROUP> := IntStr.new(+$,~$); } # alas, no support yet else { PROCESS::<$USER> := Nil; PROCESS::<$GROUP> := Nil; } } PROCESS::{$what} } Rakudo::Internals.REGISTER-DYNAMIC: '$*USER', { fetch('$USER') }; Rakudo::Internals.REGISTER-DYNAMIC: '$*GROUP', { fetch('$GROUP') }; } #line 1 SETTING::src/core.c/Slang.rakumod class Slang { has $.grammar; has $.actions; multi method gist(Slang:D:) { # Handle NQP objects like Perl6::Grammar 'Slang.new(' ~ (':grammar(' ~ $!grammar.^name ~ ')', ':actions(' ~ $!actions.^name ~ ')').join(', ') ~ ')' } method parse (|c) { $!grammar.parse(:$!actions, |c); } } #line 1 SETTING::src/core.c/REPL.rakumod class REPL { ... } do { my sub sorted-set-insert(@values, $value) { my $low = 0; my $high = @values.end; my $insert_pos = 0; while $low <= $high { my $middle = floor($low + ($high - $low) / 2); my $middle_elem = @values[$middle]; if $middle == @values.end { if $value eq $middle_elem { return; } elsif $value lt $middle_elem { $high = $middle - 1; } else { $insert_pos = +@values; last; } } else { my $middle_plus_one_elem = @values[$middle + 1]; if $value eq $middle_elem || $value eq $middle_plus_one_elem { return; } elsif $value lt $middle_elem { $high = $middle - 1; } elsif $value gt $middle_plus_one_elem { $low = $middle + 1; } else { $insert_pos = $middle + 1; last; } } } splice(@values, $insert_pos, 0, $value); } my role ReadlineBehavior[$WHO] { my &readline = $WHO<&readline>; my &add_history = $WHO<&add_history>; my $Readline = try { require Readline } my $read = $Readline.new; if !Rakudo::Internals.IS-WIN { $read.read-init-file("/etc/inputrc"); $read.read-init-file(%*ENV // "~/.inputrc"); } method init-line-editor { $read.read-history($.history-file); } method repl-read(Mu \prompt) { my $line = $read.readline(prompt); if $line.defined && $line.match(/\S/) { $read.add-history($line); $read.append-history(1, $.history-file); } $line } } my role LinenoiseBehavior[$WHO] { my &linenoise = $WHO<&linenoise>; my &linenoiseHistoryAdd = $WHO<&linenoiseHistoryAdd>; my &linenoiseSetCompletionCallback = $WHO<&linenoiseSetCompletionCallback>; my &linenoiseAddCompletion = $WHO<&linenoiseAddCompletion>; my &linenoiseHistoryLoad = $WHO<&linenoiseHistoryLoad>; my &linenoiseHistorySave = $WHO<&linenoiseHistorySave>; method completions-for-line(Str $line, int $cursor-index) { ... } method history-file(--> Str:D) { ... } method init-line-editor { linenoiseSetCompletionCallback(sub ($line, $c) { eager self.completions-for-line($line, $line.chars).map(&linenoiseAddCompletion.assuming($c)); }); linenoiseHistoryLoad($.history-file); } method teardown-line-editor { my $err = linenoiseHistorySave($.history-file); return if !$err; note "Couldn't save your history to $.history-file"; } method repl-read(Mu \prompt) { self.update-completions; my $line = linenoise(prompt); if $line.defined && $line.match(/\S/) { linenoiseHistoryAdd($line); } $line } } my role TerminalLineEditorBehavior[$WHO] { my $cli-input = $WHO; my $cli; method completions-for-line(Str $line, int $cursor-index) { ... } method history-file(--> Str:D) { ... } method init-line-editor { my sub get-completions($contents, $pos) { eager self.completions-for-line($contents, $pos) } $cli = $cli-input.new(:&get-completions); $cli.load-history($.history-file); } method teardown-line-editor { $cli.save-history($.history-file); } method repl-read(Mu \prompt) { self.update-completions; my $line = $cli.prompt(prompt); if $line.defined && $line.match(/\S/) { $cli.add-history($line); } $line } } my role FallbackBehavior { method repl-read(Mu \prompt) { print prompt; get } } my role Completions { has @!completions = CORE::.keys.flatmap({ /^ "&"? $=[\w* <.lower> \w*] $/ ?? ~$ !! [] }).sort; method update-completions(--> Nil) { my $context := self.compiler.context; return unless $context; my $pad := nqp::ctxlexpad($context); my $it := nqp::iterator($pad); while $it { my $k := nqp::iterkey_s(nqp::shift($it)); my $m = $k ~~ /^ "&"? $=[\w* <.lower> \w*] $/; next if !$m; my $word = ~$m; sorted-set-insert(@!completions, $word); } my $PACKAGE = self.compiler.eval('$?PACKAGE', :outer_ctx($context)); for $PACKAGE.WHO.keys -> $k { sorted-set-insert(@!completions, $k); } } method extract-last-word(Str $line) { my $m = $line ~~ /^ $=[.*?] <|w>$=[\w*]$/; return ( $line, '') unless $m; ( ~$m, ~$m ) } method completions-for-line(Str $line, int $cursor-index) { return @!completions unless $line; # ignore $cursor-index until we have a backend that provides it my ( $prefix, $word-at-cursor ) = self.extract-last-word($line); # XXX this could be more efficient if we had a smarter starting index gather for @!completions -> $word { if $word ~~ /^ "$word-at-cursor" / { take $prefix ~ $word; } } } } class REPL { also does Completions; has Mu $.compiler; has Bool $!multi-line-enabled; has IO::Path $!history-file; has $!save_ctx; # Unique internal values for out-of-band eval results has $!need-more-input = {}; has $!control-not-allowed = {}; sub do-mixin($self, Str $module-name, $behavior, :@extra-modules, Str :$fallback, Bool :$classlike) { my Bool $problem = False; try { CATCH { when { $_ ~~ X::CompUnit::UnsatisfiedDependency and .specification.Str.contains: $module-name } { # ignore it } default { say "I ran into a problem while trying to set up $module-name: $_"; if $fallback { say "Falling back to $fallback (if present)"; } $problem = True; } } (require ::($_)) for @extra-modules; my $module = do require ::($module-name); my $who = $classlike ?? $module.WHO !! $module.WHO.WHO.WHO; my $new-self = $self but $behavior.^parameterize($who); $new-self.?init-line-editor(); return ( $new-self, False ); } ( Any, $problem ) } sub mixin-readline($self, |c) { do-mixin($self, 'Readline', ReadlineBehavior, |c) } sub mixin-linenoise($self, |c) { do-mixin($self, 'Linenoise', LinenoiseBehavior, |c) } sub mixin-terminal-lineeditor($self, |c) { do-mixin($self, 'Terminal::LineEditor', TerminalLineEditorBehavior, :extra-modules('Terminal::LineEditor::RawTerminalInput',), :classlike, |c) } sub mixin-line-editor($self) { return $self but FallbackBehavior if %*ENV; my %editor-to-mixin = ( :Linenoise(&mixin-linenoise), :Readline(&mixin-readline), :LineEditor(&mixin-terminal-lineeditor), :none(-> $self { ( $self but FallbackBehavior, False ) }), ); if %*ENV -> $line-editor { if !%editor-to-mixin{$line-editor} { say "Unrecognized line editor '$line-editor'"; return $self but FallbackBehavior; } my $mixin = %editor-to-mixin{$line-editor}; my ( $new-self, $problem ) = $mixin($self); return $new-self if $new-self; say "Could not find $line-editor module" unless $problem; return $self but FallbackBehavior; } my ( $new-self, $problem ) = mixin-readline($self, :fallback); return $new-self if $new-self; ( $new-self, $problem ) = mixin-linenoise($self, :fallback); return $new-self if $new-self; ( $new-self, $problem ) = mixin-terminal-lineeditor($self); return $new-self if $new-self; if $problem { say 'Continuing without tab completions or line editor'; say 'You may want to consider using rlwrap for simple line editor functionality'; } elsif !Rakudo::Internals.IS-WIN and !( %*ENV<_>:exists and %*ENV<_>.ends-with: 'rlwrap' ) { say 'You may want to `zef install Readline`, `zef install Linenoise`, or `zef install Terminal::LineEditor` or use rlwrap for a line editor'; } say ''; $self but FallbackBehavior } method new(Mu \compiler, Mu \adverbs, $skip?) { unless $skip { say compiler.version_string( :shorten-versions, :no-unicode(Rakudo::Internals.IS-WIN) ); say ''; } my $multi-line-enabled = !%*ENV; my $self = self.bless(); $self.init(compiler, $multi-line-enabled); $self = mixin-line-editor($self); $self } method init(Mu \compiler, $multi-line-enabled --> Nil) { $!compiler := compiler; $!multi-line-enabled = $multi-line-enabled; PROCESS::<$SCHEDULER>.uncaught_handler = -> $exception { note "Uncaught exception on thread $*THREAD.id():\n" ~ $exception.gist.indent(4); } } method teardown { self.?teardown-line-editor; } # Calling this method is apparently being done by some ecosystem # modules, while this should really be considered an implementation # detail. Keep the original state of the method for now, but # this should probably be DEPRECATED and/or given a properly # documented and tested API. method repl-eval($code, \exception, *%adverbs) { CATCH { when X::Syntax::Missing { return $!need-more-input if $!multi-line-enabled && .pos == $code.chars; .throw; } when X::Comp::FailGoal { return $!need-more-input if $!multi-line-enabled && .pos == $code.chars; .throw; } when X::ControlFlow::Return { return $!control-not-allowed; } default { exception = $_; return; } } CONTROL { when CX::Emit | CX::Take { .rethrow; } when CX::Warn { .gist.say; .resume; } return $!control-not-allowed; } self.compiler.eval($code, |%adverbs) } method new-repl-eval($code, \exception, @*_, *%adverbs) { CATCH { when X::Syntax::Missing { return $!need-more-input if $!multi-line-enabled && .pos == $code.chars; .throw; } when X::Comp::FailGoal { return $!need-more-input if $!multi-line-enabled && .pos == $code.chars; .throw; } when X::ControlFlow::Return { return $!control-not-allowed; } default { exception = $_; return; } } CONTROL { when CX::Emit | CX::Take { .rethrow; } when CX::Warn { .gist.say; .resume; } return $!control-not-allowed; } self.compiler.eval( $code.subst(/ '$*' \d+ /, { '@*_[' ~ $/.substr(2) ~ ']' }, :g), |%adverbs ) } method interactive_prompt($index) { "[$index] > " } method repl-loop(:$no-exit, *%adverbs) { my int $stopped; # did we press CTRL-c just now? my $previous-evals := IterationBuffer.new; # previous values signal(SIGINT).tap: { exit if $stopped++; say "Pressed CTRL-c, press CTRL-c again to exit"; print self.interactive_prompt($previous-evals.elems); } say $no-exit ?? "Type 'exit' to leave" !! Rakudo::Internals.IS-WIN ?? "To exit type 'exit' or '^Z'" !! "To exit type 'exit' or '^D'"; my $prompt; my $code; sub reset(--> Nil) { $code = ''; $prompt = self.interactive_prompt($previous-evals.elems); } reset; REPL: loop { # Why doesn't the catch-default in repl-eval catch all? CATCH { default { say $_; reset } } my $newcode = self.repl-read(~$prompt); last if $no-exit and $newcode and $newcode eq 'exit'; $stopped = 0; my $initial_out_position = $*OUT.tell; # An undef $newcode implies ^D or similar if !$newcode.defined { last; } $code = $code ~ $newcode ~ "\n"; if $code ~~ /^ <.ws> $/ { next; } my $*CTXSAVE := self; my $*MAIN_CTX; my $output is default(Nil) = self.new-repl-eval( $code, my $exception, $previous-evals.List, :outer_ctx($!save_ctx), |%adverbs); if self.input-incomplete($output) { $prompt = '* '; next; } if self.input-toplevel-control($output) { say "Control flow commands not allowed in toplevel"; reset; next; } if $*MAIN_CTX { $!save_ctx := $*MAIN_CTX; } # Print the result if: # - there wasn't some other output # - the result is an *unhandled* Failure # - print an exception if one had occured if $exception.DEFINITE { self.repl-print($exception); } elsif $initial_out_position == $*OUT.tell or nqp::istype($output, Failure) and not $output.handled { self.repl-print($output); $previous-evals.push: $output<>; } reset; } self.teardown; } # Inside of the EVAL it does like caller.ctxsave method ctxsave(--> Nil) { $*MAIN_CTX := nqp::ctxcaller(nqp::ctx()); $*CTXSAVE := 0; } method input-incomplete(Mu $value --> Bool:D) { nqp::hllbool(nqp::can($value, 'WHERE')) and $value.WHERE == $!need-more-input.WHERE } method input-toplevel-control(Mu $value --> Bool:D) { nqp::hllbool(nqp::can($value, 'WHERE')) and $value.WHERE == $!control-not-allowed.WHERE } method repl-print(Mu $value --> Nil) { my $method := %*ENV // "gist"; CATCH { default { say ."$method"() } } nqp::can($value,$method) and say $value."$method"() or say "(low-level object `$value.^name()`)"; } method history-file(--> Str:D) { without $!history-file { if %*ENV -> $history-file { $!history-file = $history-file.IO; } else { my $dir := $*HOME || $*TMPDIR; my $old := $dir.add('.perl6/rakudo-history'); my $new := $dir.add('.raku/rakudo-history'); if $old.e && !$new.e { # migrate old hist to new location $new.spurt($old.slurp); $old.unlink; } $!history-file = $new; } without mkdir $!history-file.parent { note "I ran into a problem trying to set up history: {.exception.message}"; note 'Sorry, but history will not be saved at the end of your session'; } } # make sure there is a history file $!history-file.open(:a).close unless $!history-file.e; $!history-file.absolute } } } sub repl(*%_) { my $repl := REPL.new(nqp::getcomp("Raku"), %_, True); nqp::bindattr($repl,REPL,'$!save_ctx',nqp::ctxcaller(nqp::ctx)); $repl.repl-loop(:no-exit); } #line 1 SETTING::src/core.c/Rakudo/Metaops.rakumod # This class contains methods for generating callables to be used # in metaoperators. There are two reasons for having this in a # separate class: # # 1. It needs to know about all possible builtin operators. If it # would be part of Rakudo::Internals, it would be too early in # building the settings. Augmenting Rakudo::Internals at the # the end of building the settings would also have been an option, # but that would probably slow down settings building significantly. # And since the class name is really not that important, this seemed # like a good solution. # 2. Nice to have a separate file for similar stuff. Rakudo::Internals # has become a hodgepodge of stuff of late. class Rakudo::Metaops { my $mappers := nqp::hash( nqp::tostr_I(&infix:<+>.WHERE), # optimized version for &[+] -> \list { nqp::if( nqp::iseq_i(nqp::elems(list),2), (nqp::atpos(list,0) + nqp::atpos(list,1)), nqp::if( nqp::elems(list), nqp::stmts( (my $result := nqp::shift(list)), nqp::while( nqp::elems(list), ($result := $result + nqp::shift(list)) ), $result ), 0 ) ) }, nqp::tostr_I(&infix:<~>.WHERE), # optimized version for &[~] -> \list { nqp::if( nqp::iseq_i(nqp::elems(list),2), (nqp::atpos(list,0) ~ nqp::atpos(list,1)), nqp::if( nqp::elems(list), nqp::stmts( # could possibly be done smarter (my $result := nqp::shift(list)), nqp::while( nqp::elems(list), ($result := $result ~ nqp::shift(list)) ), $result ), '' ) ) }, nqp::tostr_I(&infix:<< => >>.WHERE), # optimized version for &[=>] -> \list { nqp::if( nqp::iseq_i(nqp::elems(list),2), Pair.new(nqp::atpos(list,0),nqp::atpos(list,1)), nqp::if( nqp::isgt_i(nqp::elems(list),2), nqp::stmts( (my $result := nqp::pop(list)), nqp::while( nqp::elems(list), ($result := Pair.new(nqp::pop(list),$result)) ), $result ), (die "Too few positionals passed; expected 2 arguments but got {nqp::elems(list)}") ) ) }, nqp::tostr_I(&infix:<,>.WHERE), # optimized version for &[,] -> \list { nqp::p6bindattrinvres(nqp::create(List),List,'$!reified',list) } ); method MapperForOp(&op) is raw { nqp::if( nqp::existskey($mappers,(my str $where = nqp::tostr_I(&op.WHERE))), nqp::atkey($mappers,$where), nqp::if( nqp::iseq_i(nqp::chars(my str $assoc = &op.associative),0) || nqp::iseq_s($assoc,'left'), -> \list { # generic left-assoc op nqp::if( nqp::iseq_i(nqp::elems(list),2), op(nqp::atpos(list,0),nqp::atpos(list,1)), nqp::if( nqp::elems(list), nqp::stmts( (my $result := nqp::shift(list)), nqp::while( nqp::elems(list), ($result := op($result,nqp::shift(list))) ), $result ), op() ) ) }, nqp::if( nqp::iseq_s($assoc,"chain"), -> \list { # generic chain-assoc op nqp::if( nqp::iseq_i(nqp::elems(list),2), op(nqp::atpos(list,0),nqp::atpos(list,1)), nqp::if( nqp::elems(list), nqp::stmts( (my $state = True), (my $current := nqp::shift(list)), nqp::while( nqp::elems(list) && ($state := op( $current, (my $next := nqp::shift(list)) )), ($current := $next) ), $state ), op() ) ) }, nqp::if( nqp::iseq_s($assoc,'right'), -> \list { # generic right-assoc op nqp::if( nqp::iseq_i(nqp::elems(list),2), op(nqp::atpos(list,0),nqp::atpos(list,1)), nqp::if( nqp::elems(list), nqp::stmts( (my $result := nqp::pop(list)), nqp::while( nqp::elems(list), ($result := op(nqp::pop(list),$result)) ), $result ), op() ) ) }, nqp::if( nqp::iseq_s($assoc,'non'), -> \list { # generic non-assoc op nqp::if( nqp::iseq_i(nqp::elems(list),2), op(nqp::atpos(list,0),nqp::atpos(list,1)), (die "Incorrect number of elements for non-associative operator: expected 2, got {nqp::elems(list)}") ) }, nqp::if( nqp::iseq_s($assoc,"list"), -> \list { # generic list/listinfix op op( nqp::p6bindattrinvres( nqp::create(List),List,'$!reified',list) ) }, (die "Don't know how to process '$assoc' associativity") ) ) ) ) ) ) } } #line 1 SETTING::src/core.c/Rakudo/Internals/PostcircumfixAdverbs.rakumod # Handle adverbs for array postcircumfix here late so that we can use # all of the high-level language features, such as native arrays, that # are not available (or needed) earlier in the settings build. augment class Rakudo::Internals { # Dispatch table for single element access in Int index my constant $access-element-dispatch = nqp::list( Array::Element::Access::none, Array::Element::Access::kv, Array::Element::Access::not-kv, Array::Element::Access::p, Array::Element::Access::not-p, Array::Element::Access::k, Array::Element::Access::not-k, Array::Element::Access::v, Array::Element::Access::exists, Array::Element::Access::exists-kv, Array::Element::Access::exists-not-kv, Array::Element::Access::exists-p, Array::Element::Access::exists-not-p, Array::Element::Access::exists-delete, Array::Element::Access::exists-delete-kv, Array::Element::Access::exists-delete-not-kv, Array::Element::Access::exists-delete-p, Array::Element::Access::exists-delete-not-p, Array::Element::Access::not-exists, Array::Element::Access::not-exists-kv, Array::Element::Access::not-exists-not-kv, Array::Element::Access::not-exists-p, Array::Element::Access::not-exists-not-p, Array::Element::Access::not-exists-delete, Array::Element::Access::not-exists-delete-kv, Array::Element::Access::not-exists-delete-not-kv, Array::Element::Access::not-exists-delete-p, Array::Element::Access::not-exists-delete-not-p, Array::Element::Access::delete, Array::Element::Access::delete-kv, Array::Element::Access::delete-not-kv, Array::Element::Access::delete-p, Array::Element::Access::delete-not-p, Array::Element::Access::delete-k, Array::Element::Access::delete-not-k, Array::Element::Access::delete-v ); method ACCESS-ELEMENT-DISPATCH-CLASS(int $index) { nqp::atpos($access-element-dispatch,$index) } # Dispatch table for single element access in Any index my constant $access-element-dispatch-any = nqp::list( Array::Element::Access::none-any, Array::Element::Access::kv-any, Array::Element::Access::not-kv-any, Array::Element::Access::p-any, Array::Element::Access::not-p-any, Array::Element::Access::k-any, Array::Element::Access::not-k-any, Array::Element::Access::v-any, Array::Element::Access::exists-any, Array::Element::Access::exists-kv-any, Array::Element::Access::exists-not-kv-any, Array::Element::Access::exists-p-any, Array::Element::Access::exists-not-p-any, Array::Element::Access::exists-delete-any, Array::Element::Access::exists-delete-kv-any, Array::Element::Access::exists-delete-not-kv-any, Array::Element::Access::exists-delete-p-any, Array::Element::Access::exists-delete-not-p-any, Array::Element::Access::not-exists-any, Array::Element::Access::not-exists-kv-any, Array::Element::Access::not-exists-not-kv-any, Array::Element::Access::not-exists-p-any, Array::Element::Access::not-exists-not-p-any, Array::Element::Access::not-exists-delete-any, Array::Element::Access::not-exists-delete-kv-any, Array::Element::Access::not-exists-delete-not-kv-any, Array::Element::Access::not-exists-delete-p-any, Array::Element::Access::not-exists-delete-not-p-any, Array::Element::Access::delete-any, Array::Element::Access::delete-kv-any, Array::Element::Access::delete-not-kv-any, Array::Element::Access::delete-p-any, Array::Element::Access::delete-not-p-any, Array::Element::Access::delete-k-any, Array::Element::Access::delete-not-k-any, Array::Element::Access::delete-v-any ); method ACCESS-ELEMENT-ANY-DISPATCH-CLASS(int $index) { nqp::atpos($access-element-dispatch-any,$index) } # Constants to allow mapping of valid slice adverb combinations to # a value that can be used in lookup table to get a dispatch table # lookup value. my constant SLICE_NO_ADVERBS = 0x0000; my constant SLICE_DELETE = 0x0001; # :delete my constant SLICE_EXISTS = 0x0002; # :exists my constant SLICE_NOT_EXISTS = 0x0004; # :!exists my constant SLICE_KV = 0x0008; # :kv my constant SLICE_NOT_KV = 0x0010; # :!kv my constant SLICE_P = 0x0020; # :p my constant SLICE_NOT_P = 0x0040; # :!p my constant SLICE_K = 0x0080; # :k my constant SLICE_NOT_K = 0x0100; # :!k my constant SLICE_V = 0x0200; # :v # Array of above constants with their names, used for generating # error messages. my constant @pc-constant-name = ( SLICE_DELETE, 'delete', SLICE_EXISTS, 'exists', SLICE_NOT_EXISTS, '!exists', SLICE_KV, 'kv', SLICE_NOT_KV, '!kv', SLICE_P, 'p', SLICE_NOT_P, '!p', SLICE_K, 'k', SLICE_NOT_K, '!k', SLICE_V, 'v', ); # Array that contains dispatch table value for postcircumfix # adverbs. When mapping into this array results in 0, then it # was an unsupported combination of adverbs. Any other value # indicates an index value into a dispatch table that should # be initialized in the order in which these combinations are # initialized here. my constant @pc-adverb-mapper = do { my uint $i; my uint16 @map; # add the simple access version, e.g. with :!delete or :!v @map[SLICE_NO_ADVERBS] = $i++; # simple filtering adverbs @map[SLICE_KV] = $i++; @map[SLICE_NOT_KV] = $i++; @map[SLICE_P] = $i++; @map[SLICE_NOT_P] = $i++; @map[SLICE_K] = $i++; @map[SLICE_NOT_K] = $i++; @map[SLICE_V] = $i++; # adverbs that return whether exists / existed @map[SLICE_EXISTS] = $i++; @map[SLICE_EXISTS + SLICE_KV] = $i++; @map[SLICE_EXISTS + SLICE_NOT_KV] = $i++; @map[SLICE_EXISTS + SLICE_P] = $i++; @map[SLICE_EXISTS + SLICE_NOT_P] = $i++; @map[SLICE_EXISTS + SLICE_DELETE] = $i++; @map[SLICE_EXISTS + SLICE_DELETE + SLICE_KV] = $i++; @map[SLICE_EXISTS + SLICE_DELETE + SLICE_NOT_KV] = $i++; @map[SLICE_EXISTS + SLICE_DELETE + SLICE_P] = $i++; @map[SLICE_EXISTS + SLICE_DELETE + SLICE_NOT_P] = $i++; # adverbs that return whether NOT exists / existed @map[SLICE_NOT_EXISTS] = $i++; @map[SLICE_NOT_EXISTS + SLICE_KV] = $i++; @map[SLICE_NOT_EXISTS + SLICE_NOT_KV] = $i++; @map[SLICE_NOT_EXISTS + SLICE_P] = $i++; @map[SLICE_NOT_EXISTS + SLICE_NOT_P] = $i++; @map[SLICE_NOT_EXISTS + SLICE_DELETE] = $i++; @map[SLICE_NOT_EXISTS + SLICE_DELETE + SLICE_KV] = $i++; @map[SLICE_NOT_EXISTS + SLICE_DELETE + SLICE_NOT_KV] = $i++; @map[SLICE_NOT_EXISTS + SLICE_DELETE + SLICE_P] = $i++; @map[SLICE_NOT_EXISTS + SLICE_DELETE + SLICE_NOT_P] = $i++; # adverbs that just delete @map[SLICE_DELETE] = $i++; @map[SLICE_DELETE + SLICE_KV] = $i++; @map[SLICE_DELETE + SLICE_NOT_KV] = $i++; @map[SLICE_DELETE + SLICE_P] = $i++; @map[SLICE_DELETE + SLICE_NOT_P] = $i++; @map[SLICE_DELETE + SLICE_K] = $i++; @map[SLICE_DELETE + SLICE_NOT_K] = $i++; @map[SLICE_DELETE + SLICE_V] = $i++; @map } # Take a set of adverbs in a hash, and a name and value of an # additional named parameter and return either a positive # dispatch table index value if it was a valid combination of # adverbs, or an X::Adverb exception object with the "unexpected" # and "nogo" fields set. method ADVERBS_AND_NAMED_TO_DISPATCH_INDEX( %adverbs, str $name, \extra ) { my $nameds := nqp::getattr(%adverbs,Map,'$!storage'); my int $bitmap; my $value; # Initialize bitmap with additional named mapping, assuming it # is one of . nqp::if( nqp::iseq_s($name,'exists'), ($bitmap = nqp::if(extra,SLICE_EXISTS,SLICE_NOT_EXISTS)), nqp::if( nqp::iseq_s($name,'delete'), nqp::if(extra,$bitmap = SLICE_DELETE), nqp::if( nqp::iseq_s($name,'kv'), ($bitmap = nqp::if(extra,SLICE_KV,SLICE_NOT_KV)), nqp::if( nqp::iseq_s($name,'p'), ($bitmap = nqp::if(extra,SLICE_P,SLICE_NOT_P)), nqp::if( nqp::iseq_s($name,'k'), ($bitmap = nqp::if(extra,SLICE_K,SLICE_NOT_K)), nqp::if(extra,$bitmap = SLICE_V) ) ) ) ) ); # Check all the other valid adverbs nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'exists')), nqp::stmts( ($bitmap = nqp::bitor_i( $bitmap, nqp::if($value,SLICE_EXISTS,SLICE_NOT_EXISTS) )), nqp::deletekey($nameds,'exists') ) ); nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'delete')), nqp::stmts( nqp::if( $value, ($bitmap = nqp::bitor_i($bitmap,SLICE_DELETE)) ), nqp::deletekey($nameds,'delete') ) ); nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'kv')), nqp::stmts( ($bitmap = nqp::bitor_i($bitmap,nqp::if( $value, SLICE_KV, SLICE_NOT_KV ))), nqp::deletekey($nameds,'kv') ) ); nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'p')), nqp::stmts( ($bitmap = nqp::bitor_i($bitmap,nqp::if( $value, SLICE_P, SLICE_NOT_P ))), nqp::deletekey($nameds,'p') ) ); nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'k')), nqp::stmts( ($bitmap = nqp::bitor_i($bitmap,nqp::if( $value, SLICE_K, SLICE_NOT_K ))), nqp::deletekey($nameds,'k') ) ); nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'v')), nqp::stmts( nqp::if( $value, ($bitmap = nqp::bitor_i($bitmap,SLICE_V)) ), nqp::deletekey($nameds,'v') ) ); # Perform the actual lookup and handling my int $index = nqp::atpos_u(@pc-adverb-mapper,$bitmap); nqp::if( nqp::elems($nameds), X::Adverb.new( # Unexpected adverbs unexpected => %adverbs.keys.sort.list, nogo => @pc-constant-name.map( -> \constant, \adverb { adverb if nqp::bitand_i(constant,$bitmap); } ).list ), nqp::if( $index, $index, # All adverbs accounted for have a dispatch index nqp::if( $bitmap, X::Adverb.new( # Did not find a dispatch index, had valid adverbs nogo => @pc-constant-name.map( -> \constant, \adverb { adverb if nqp::bitand_i(constant,$bitmap) } ).list ), 0 # Had valid adverbs, no special handling required ) ) ) } # Take a set of adverbs in a hash, and return either a positive # dispatch table index value if it was a valid combination of # adverbs, or an X::Adverb exception object with the "unexpected" # and "nogo" fields set. method ADVERBS_TO_DISPATCH_INDEX(%adverbs) { my $nameds := nqp::getattr(%adverbs,Map,'$!storage'); my int $bitmap; my $value; # Check all the valid adverbs nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'exists')), nqp::stmts( ($bitmap = nqp::if($value,SLICE_EXISTS,SLICE_NOT_EXISTS)), nqp::deletekey($nameds,'exists') ) ); nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'delete')), nqp::stmts( nqp::if( $value, ($bitmap = nqp::bitor_i($bitmap,SLICE_DELETE)) ), nqp::deletekey($nameds,'delete') ) ); nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'kv')), nqp::stmts( ($bitmap = nqp::bitor_i($bitmap,nqp::if( $value, SLICE_KV, SLICE_NOT_KV ))), nqp::deletekey($nameds,'kv') ) ); nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'p')), nqp::stmts( ($bitmap = nqp::bitor_i($bitmap,nqp::if( $value, SLICE_P, SLICE_NOT_P ))), nqp::deletekey($nameds,'p') ) ); nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'k')), nqp::stmts( ($bitmap = nqp::bitor_i($bitmap,nqp::if( $value, SLICE_K, SLICE_NOT_K ))), nqp::deletekey($nameds,'k') ) ); nqp::unless( nqp::isnull($value := nqp::atkey($nameds,'v')), nqp::stmts( nqp::if( $value, ($bitmap = nqp::bitor_i($bitmap,SLICE_V)) ), nqp::deletekey($nameds,'v') ) ); # Perform the actual lookup and handling my int $index = nqp::atpos_u(@pc-adverb-mapper,$bitmap); nqp::if( nqp::elems($nameds), X::Adverb.new( # Unexpected adverbs unexpected => %adverbs.keys.sort.list, nogo => @pc-constant-name.map( -> \constant, \adverb { adverb if nqp::bitand_i(constant,$bitmap); } ).list ), nqp::if( $index, $index, # All adverbs accounted for have a dispatch index nqp::if( $bitmap, X::Adverb.new( # Did not find a dispatch index, had valid adverbs nogo => @pc-constant-name.map( -> \constant, \adverb { adverb if nqp::bitand_i(constant,$bitmap) } ).list ), 0 # Had valid adverbs, no special handling required ) ) ) } method SLICE_POSITIONS_WITH_ADVERBS(\SELF, Iterable:D $positions, %nameds) { nqp::istype( (my $lookup := self.ADVERBS_TO_DISPATCH_INDEX(%nameds)), X::Adverb ) ?? self.FAIL_X_ADVERB($lookup, 'slice', SELF) !! self.ACCESS-SLICE-DISPATCH-CLASS( $lookup ).new(SELF).slice($positions.iterator) } method SLICE_WITH_ADVERBS(\SELF, str $what, %nameds) { nqp::istype( (my $lookup := self.ADVERBS_TO_DISPATCH_INDEX(%nameds)), X::Adverb ) ?? self.FAIL_X_ADVERB($lookup, $what, SELF) !! self.ACCESS-SLICE-DISPATCH-CLASS( $lookup ).new(SELF).slice(Rakudo::Iterator.IntRange(0,SELF.end)) } method FAIL_X_ADVERB(X::Adverb:D $x-adverb, str $what, \from) { $x-adverb.what = $what; $x-adverb.source = try { from.VAR.name } // from.^name; $x-adverb.Failure } my constant $access-slice-dispatch = nqp::list( Array::Slice::Access::none, Array::Slice::Access::kv, Array::Slice::Access::not-kv, Array::Slice::Access::p, Array::Slice::Access::not-p, Array::Slice::Access::k, Array::Slice::Access::not-k, Array::Slice::Access::v, Array::Slice::Access::exists, Array::Slice::Access::exists-kv, Array::Slice::Access::exists-not-kv, Array::Slice::Access::exists-p, Array::Slice::Access::exists-not-p, Array::Slice::Access::exists-delete, Array::Slice::Access::exists-delete-kv, Array::Slice::Access::exists-delete-not-kv, Array::Slice::Access::exists-delete-p, Array::Slice::Access::exists-delete-not-p, Array::Slice::Access::not-exists, Array::Slice::Access::not-exists-kv, Array::Slice::Access::not-exists-not-kv, Array::Slice::Access::not-exists-p, Array::Slice::Access::not-exists-not-p, Array::Slice::Access::not-exists-delete, Array::Slice::Access::not-exists-delete-kv, Array::Slice::Access::not-exists-delete-not-kv, Array::Slice::Access::not-exists-delete-p, Array::Slice::Access::not-exists-delete-not-p, Array::Slice::Access::delete, Array::Slice::Access::delete-kv, Array::Slice::Access::delete-not-kv, Array::Slice::Access::delete-p, Array::Slice::Access::delete-not-p, Array::Slice::Access::delete-k, Array::Slice::Access::delete-not-k, Array::Slice::Access::delete-v ); method ACCESS-SLICE-DISPATCH-CLASS(int $index) { nqp::atpos($access-slice-dispatch,$index) } } #line 1 SETTING::src/core.c/unicodey.rakumod # a helper class to abstract support for unicodey functions my class Rakudo::Unicodey is implementation-detail { my constant $nuprop = nqp::unipropcode("Numeric_Value_Numerator"); my constant $deprop = nqp::unipropcode("Numeric_Value_Denominator"); method unival(int $ord) { nqp::chars(my str $de = nqp::getuniprop_str($ord,$deprop)) ?? nqp::iseq_s($de,"NaN") # some string to work with ?? NaN # no value found !! nqp::iseq_s($de,"1") # some value ?? nqp::coerce_si(nqp::getuniprop_str($ord,$nuprop)) !! Rat.new( nqp::coerce_si(nqp::getuniprop_str($ord,$nuprop)), nqp::coerce_si($de) ) !! Nil # not valid, so no value } method ords(str $str) { nqp::strtocodes( $str, nqp::const::NORMALIZE_NFC, nqp::create(array[uint32]) ) } method unimatch(int $code, str $pvalname, str $propname) { my $prop := nqp::unipropcode($propname); nqp::hllbool( nqp::matchuniprop($code,$prop,nqp::unipvalcode($prop,$pvalname)) ) || uniprop($code, $propname) eq $pvalname } my constant $gcprop = nqp::unipropcode("General_Category"); method uniprop-general(int $code) { nqp::getuniprop_str($code,$gcprop) } my $name2pref-lock = Lock.new; ## The code below was generated by tools/build/makeUNIPROP.raku my $name2pref := nqp::hash( 'AHex','B','ASCII_Hex_Digit','B','Age','S','Alpha','B','Alphabetic','B', 'Bidi_C','B','Bidi_Class','S','Bidi_Control','B','Bidi_M','B', 'Bidi_Mirrored','B','Bidi_Mirroring_Glyph','bmg', 'Bidi_Paired_Bracket_Type','S','Block','S','CE','B','CI','B','CWCF','B', 'CWCM','B','CWKCF','B','CWL','B','CWT','B','CWU','B', 'Canonical_Combining_Class','S','Case_Folding','S','Case_Ignorable','B', 'Cased','B','Changes_When_Casefolded','B','Changes_When_Casemapped','B', 'Changes_When_Lowercased','B','Changes_When_NFKC_Casefolded','B', 'Changes_When_Titlecased','B','Changes_When_Uppercased','B','Comp_Ex','B', 'Composition_Exclusion','B','DI','B','Dash','B', 'Decomposition_Mapping','S','Decomposition_Type','S', 'Default_Ignorable_Code_Point','B','Dep','B','Deprecated','B','Dia','B', 'Diacritic','B','East_Asian_Width','S','Emoji','B','Emoji_Modifier','B', 'Emoji_Modifier_Base','B','Emoji_Presentation','B','Expands_On_NFC','B', 'Expands_On_NFD','B','Expands_On_NFKC','B','Expands_On_NFKD','B', 'Ext','B','Extender','B','FC_NFKC','S','FC_NFKC_Closure','S', 'Full_Composition_Exclusion','B','GCB','S','General_Category','S', 'Gr_Base','B','Gr_Ext','B','Gr_Link','B','Grapheme_Base','B', 'Grapheme_Cluster_Break','S','Grapheme_Extend','B','Grapheme_Link','B', 'Hangul_Syllable_Type','S','Hex','B','Hex_Digit','B','Hyphen','B', 'IDC','B','IDS','B','IDSB','B','IDST','B','IDS_Binary_Operator','B', 'IDS_Trinary_Operator','B','ID_Continue','B','ID_Start','B', 'ISO_Comment','S','Ideo','B','Ideographic','B','InPC','S','InSC','S', 'Indic_Positional_Category','S','Indic_Syllabic_Category','S', 'Join_C','B','Join_Control','B','Joining_Group','S','Joining_Type','S', 'LOE','B','Line_Break','S','Logical_Order_Exception','B','Lower','B', 'Lowercase','B','Lowercase_Mapping','lc','Math','B','NChar','B', 'NFC_QC','S','NFC_Quick_Check','S','NFD_QC','S','NFD_Quick_Check','S', 'NFKC_CF','S','NFKC_Casefold','S','NFKC_QC','S','NFKC_Quick_Check','S', 'NFKD_QC','S','NFKD_Quick_Check','S','Name','na', 'Noncharacter_Code_Point','B','Numeric_Type','S','Numeric_Value','nv', 'OAlpha','B','ODI','B','OGr_Ext','B','OIDC','B','OIDS','B','OLower','B', 'OMath','B','OUpper','B','Other_Alphabetic','B', 'Other_Default_Ignorable_Code_Point','B','Other_Grapheme_Extend','B', 'Other_ID_Continue','B','Other_ID_Start','B','Other_Lowercase','B', 'Other_Math','B','Other_Uppercase','B','PCM','B','Pat_Syn','B', 'Pat_WS','B','Pattern_Syntax','B','Pattern_White_Space','B', 'Prepended_Concatenation_Mark','B','QMark','B','Quotation_Mark','B', 'RI','B','Radical','B','Regional_Indicator','B','SB','S','SD','B', 'STerm','B','Script','S','Sentence_Break','S','Sentence_Terminal','B', 'Simple_Case_Folding','S','Simple_Lowercase_Mapping','S', 'Simple_Titlecase_Mapping','S','Simple_Uppercase_Mapping','S', 'Soft_Dotted','B','Term','B','Terminal_Punctuation','B', 'Titlecase_Mapping','tc','UIdeo','B','Unified_Ideograph','B','Upper','B', 'Uppercase','B','Uppercase_Mapping','uc','VS','B', 'Variation_Selector','B','Vertical_Orientation','S','WB','S','WSpace','B', 'White_Space','B','Word_Break','S','XIDC','B','XIDS','B', 'XID_Continue','B','XID_Start','B','XO_NFC','B','XO_NFD','B', 'XO_NFKC','B','XO_NFKD','B','age','S','bc','S','blk','S','bmg','bmg', 'bpt','S','ccc','S','cf','S','cjkCompatibilityVariant','S','dm','S', 'dt','S','ea','S','gc','S','hst','S','isc','S','jg','S','jt','S', 'kCompatibilityVariant','S','lb','S','lc','lc','na','na','nt','S', 'nv','nv','sc','S','scf','S','sfc','S','slc','S','space','B','stc','S', 'suc','S','tc','tc','uc','uc','vo','S', ); my constant $prop2pref = nqp::list_s("", "", "", "S", "S", "bmg", "S", "S", "nv", "S", "", "", "S", "S", "S", "S", "S", "S", "S", "", "S", "S", "S", "S", "S", "", "S", "S", "B", "B", "", "", "B", "B", "", "", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "", "B", "B", "B", "", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "", "lc", "B", "B", "", "", "", "B", "", "", "", "S", "S", "B", "B", "B", "B", "B", "B", "B", "B", "B", "", "B", "B", "B", "B", "B", "B", "", "B", "B", "B", "B", "B", "B", "B", "B", "B"); # helper sub to set prop value and representation preference for a # given code and propname my sub codename2proppref(uint $code, str $propname, $prop is rw, $pref is rw --> Nil) { $prop = nqp::unipropcode($propname); $pref = nqp::atpos_s($prop2pref,$prop) || $name2pref-lock.protect: { nqp::ifnull( nqp::atkey($name2pref,$propname), nqp::bindkey( $name2pref, $propname, nqp::if( nqp::istrue(my $result := nqp::getuniprop_str($code,$prop)), 'S', 'I' ))) }; } method uniprop(uint $code, str $propname) { codename2proppref($code, $propname, my int $prop, my str $pref); nqp::if( nqp::iseq_s($pref,'S'), nqp::getuniprop_str($code,$prop), nqp::if( nqp::iseq_s($pref,'I'), nqp::getuniprop_int($code,$prop), nqp::if( nqp::iseq_s($pref,'B'), nqp::hllbool(nqp::getuniprop_bool($code,$prop)), nqp::if( nqp::iseq_s($pref,'lc'), nqp::lc(nqp::chr(nqp::unbox_i($code))), nqp::if( nqp::iseq_s($pref,'tc'), nqp::tc(nqp::chr(nqp::unbox_i($code))), nqp::if( nqp::iseq_s($pref,'uc'), nqp::uc(nqp::chr(nqp::unbox_i($code))), nqp::if( nqp::iseq_s($pref,'na'), nqp::getuniname($code), nqp::if( nqp::iseq_s($pref,'nv'), $code.unival, nqp::if( (my int $bmg-ord = nqp::getuniprop_int($code, $prop)), nqp::chr($bmg-ord), '' ) ) ) ) ) ) ) ) ) } my class UnipropsIterator does PredictiveIterator { has &!propify; has $!codes; method new(str $str, str $propname) { my $self := nqp::create(self); my $codes := Rakudo::Unicodey.ords($str); codename2proppref( nqp::atpos_u($codes,0), $propname, my int $prop, my str $pref ); nqp::bindattr($self,self,'$!codes',$codes); nqp::bindattr($self,self,'&!propify',nqp::if( nqp::iseq_s($pref,'S'), (-> int $code { nqp::getuniprop_str($code,$prop) }), nqp::if( nqp::iseq_s($pref,'I'), (-> int $code { nqp::getuniprop_int($code,$prop) }), nqp::if( nqp::iseq_s($pref,'B'), (-> int $code { nqp::hllbool(nqp::getuniprop_bool($code,$prop)) }), nqp::if( nqp::iseq_s($pref,'lc'), (-> int $code { nqp::lc(nqp::chr(nqp::unbox_i($code))) }), nqp::if( nqp::iseq_s($pref,'tc'), (-> int $code { nqp::tc(nqp::chr(nqp::unbox_i($code))) }), nqp::if( nqp::iseq_s($pref,'uc'), (-> int $code { nqp::uc(nqp::chr(nqp::unbox_i($code))) }), nqp::if( nqp::iseq_s($pref,'na'), (-> int $code {nqp::getuniname($code) }), nqp::if( nqp::iseq_s($pref,'nv'), (-> Int:D $code { $code.unival }), (-> int $code { nqp::if( (my int $bmg-ord = nqp::getuniprop_int($code, $prop)), nqp::chr($bmg-ord), '' ) }) ) ) ) ) ) ) ) )); $self } method pull-one() { nqp::elems($!codes) ?? &!propify(nqp::shift_i($!codes)) !! IterationEnd } method push-all(\target --> IterationEnd) { my $codes := $!codes; my &propify := &!propify; nqp::while( nqp::elems($codes), target.push(propify(nqp::shift_i($codes))) ); } method skip-one() { nqp::if( nqp::elems($!codes), nqp::shift_i($!codes) ) } method count-only(--> Int:D) { nqp::elems($!codes) } method bool-only(--> Bool:D) { nqp::hllbool(nqp::elems($!codes)) } } method uniprops(str $str, str $propname) { Seq.new(UnipropsIterator.new($str, $propname)) } method NFC(str $str) { nqp::strtocodes($str,nqp::const::NORMALIZE_NFC,nqp::create(NFC)) } method NFD(str $str) { nqp::strtocodes($str,nqp::const::NORMALIZE_NFD,nqp::create(NFD)) } method NFKC(str $str) { nqp::strtocodes($str,nqp::const::NORMALIZE_NFKC,nqp::create(NFKC)) } method NFKD(str $str) { nqp::strtocodes($str,nqp::const::NORMALIZE_NFKD,nqp::create(NFKD)) } my role UnicodeyIterator does PredictiveIterator { has $!codes; method new(str $str) { nqp::p6bindattrinvres( nqp::create(self), self, '$!codes', Rakudo::Unicodey.ords($str) ) } method skip-one() { nqp::if( nqp::elems($!codes), nqp::shift_i($!codes) ) } method count-only(--> Int:D) { nqp::elems($!codes) } method bool-only(--> Bool:D) { nqp::hllbool(nqp::elems($!codes)) } } my class UninamesIterator does UnicodeyIterator { method pull-one() { nqp::elems($!codes) ?? nqp::getuniname(nqp::shift_i($!codes)) !! IterationEnd } method push-all(\target --> IterationEnd) { my $codes := $!codes; nqp::while( nqp::elems($codes), target.push(nqp::getuniname(nqp::shift_i($codes))) ); } } method uninames(str $str) { UninamesIterator.new($str) } my class UnivalsIterator does UnicodeyIterator { method pull-one() { nqp::elems($!codes) ?? Rakudo::Unicodey.unival(nqp::shift_i($!codes)) !! IterationEnd } method push-all(\target --> IterationEnd) { my $codes := $!codes; nqp::while( nqp::elems($codes), target.push(Rakudo::Unicodey.unival(nqp::shift_i($codes))) ); } } method univals(str $str) { UnivalsIterator.new($str) } } augment class Cool { proto method chr(*%) is pure {*} multi method chr(Cool:D:) { self.Int.chr } # proto method chrs(*%) is pure {*} # lives in Any-iterable multi method chrs(Cool:D:) { self.list.chrs } proto method ord(*%) is pure {*} multi method ord(Cool:D: --> Int:D) { self.Str.ord } proto method ords(*%) is pure {*} multi method ords(Cool:D:) { self.Str.ords } proto method unimatch($, $?, *%) is pure {*} multi method unimatch(Cool:D: Str:D $pvalname --> Bool:D) { self.Int.unimatch($pvalname) } multi method unimatch(Cool:D: Str:D $pvalname, Str:D $propname --> Bool:D) { self.Int.unimatch($pvalname, $propname) } proto method uniname(*%) is pure {*} multi method uniname(Cool:D: --> Str:D) { self.Str.uniname } proto method uninames(*%) is pure {*} multi method uninames(Cool:D: --> Seq:D) { self.Str.uninames } proto method uniparse(*%) is pure {*} multi method uniparse(Cool:D: --> Str:D) { self.Str.uniparse } proto method uniprop($?, *%) is pure {*} multi method uniprop(Cool:D:) { self.Str.uniprop } multi method uniprop(Cool:D: Str:D $propname) { self.Str.uniprop($propname) } proto method uniprops($?, *%) is pure {*} multi method uniprops(Cool:D:) { self.Str.uniprops } multi method uniprops(Cool:D: Str:D $propname) { self.Str.uniprops($propname) } proto method unival(*%) is pure {*} multi method unival(Cool:D:) { self.Int.unival } proto method univals(*%) is pure {*} multi method univals(Cool:D:) { self.Str.univals } proto method NFC(*%) {*} multi method NFC(Cool:D:) { self.Str.NFC } proto method NFD(*%) {*} multi method NFD(Cool:D:) { self.Str.NFD } proto method NFKC(*%) {*} multi method NFKC(Cool:D:) { self.Str.NFKC } proto method NFKD(*%) {*} multi method NFKD(Cool:D:) { self.Str.NFKD } } augment class Int { method !codepoint-out-of-bounds(str $action) { die "Codepoint %i (0x%X) is out of bounds in '$action'".sprintf(self,self) } multi method chr(Int:D: --> Str:D) { nqp::isbig_I(self) || nqp::islt_I(self,0) ?? self!codepoint-out-of-bounds('chr') !! nqp::chr(self) } multi method unimatch(Int:D: Str:D $pvalname --> Bool:D) { nqp::islt_I(self,0) ?? self!codepoint-out-of-bounds('unimatch') !! nqp::isbig_I(self) ?? False !! Rakudo::Unicodey.unimatch(self, $pvalname, $pvalname) } multi method unimatch(Int:D: Str:D $pvalname, Str:D $propname --> Bool:D) { nqp::islt_I(self,0) ?? self!codepoint-out-of-bounds('unimatch') !! nqp::isbig_I(self) ?? False !! Rakudo::Unicodey.unimatch(self, $pvalname, $propname) } multi method uniname(Int:D: --> Str:D) { nqp::islt_I(self,0) # (bigint) negative number? ?? '' !! nqp::isbig_I(self) # bigint positive number? ?? '' !! nqp::getuniname(self) } multi method uniprop(Int:D:) { nqp::islt_I(self,0) ?? self!codepoint-out-of-bounds('uniprop') !! nqp::isbig_I(self) ?? "" !! Rakudo::Unicodey.uniprop-general(self) } multi method uniprop(Int:D: Str:D $propname) { nqp::islt_I(self,0) ?? self!codepoint-out-of-bounds('uniprop') !! nqp::isbig_I(self) ?? "" !! Rakudo::Unicodey.uniprop(self, $propname) } multi method unival(Int:D:) { nqp::isbig_I(self) || nqp::islt_I(self,0) ?? self!codepoint-out-of-bounds('unival') !! Rakudo::Unicodey.unival(self) } } augment class Str { multi method ord(Str:D: --> Int:D) { nqp::chars($!value) ?? nqp::p6box_i(nqp::ord($!value)) !! Nil } multi method ords(Str:D: --> Seq:D) { Seq.new(Rakudo::Unicodey.ords($!value).iterator) } multi method unimatch(Str:D: Str:D $pvalname --> Bool:D) { nqp::iseq_i((my int $ord = nqp::ord($!value)),-1) ?? Nil !! Rakudo::Unicodey.unimatch($ord, $pvalname, $pvalname) } multi method unimatch(Str:D: Str:D $pvalname, Str:D $propname --> Bool:D) { nqp::iseq_i((my int $ord = nqp::ord($!value)),-1) ?? Nil !! Rakudo::Unicodey.unimatch($ord, $pvalname, $propname) } multi method uniname(Str:D: --> Str:D) { nqp::iseq_i((my int $ord = nqp::ord($!value)),-1) ?? Nil !! nqp::getuniname($ord) } multi method uninames(Str:D:) { Seq.new(Rakudo::Unicodey.uninames(self)) } multi method uniparse(Str:D: --> Str:D) { my $names := nqp::split(',', self); my $parts := nqp::list_s; nqp::while( nqp::elems($names), nqp::push_s( $parts, nqp::unless( nqp::strfromname(my $name := nqp::shift($names).trim), X::Str::InvalidCharName.new(:$name).fail ) ) ); nqp::join("",$parts) } multi method uniprop(Str:D:) { nqp::iseq_i((my int $ord = nqp::ord($!value)),-1) ?? Nil !! Rakudo::Unicodey.uniprop-general($ord) } multi method uniprop(Str:D: Str:D $propname) { nqp::iseq_i((my int $ord = nqp::ord($!value)),-1) ?? Nil !! Rakudo::Unicodey.uniprop($ord, $propname) } multi method uniprops(Str:D: Str:D $propname = 'General_Category') { Rakudo::Unicodey.uniprops(self, $propname) } multi method unival(Str:D:) { nqp::iseq_i((my int $ord = nqp::ord($!value)),-1) ?? Nil !! Rakudo::Unicodey.unival($ord) } multi method univals(Str:D:) { Seq.new(Rakudo::Unicodey.univals(self)) } multi method NFC(Str:D:) { Rakudo::Unicodey.NFC($!value) } multi method NFD(Str:D:) { Rakudo::Unicodey.NFD($!value) } multi method NFKC(Str:D:) { Rakudo::Unicodey.NFKC($!value) } multi method NFKD(Str:D:) { Rakudo::Unicodey.NFKD($!value) } } augment class List { multi method chrs(List:D: --> Str:D) { nqp::if( self.is-lazy, self.fail-iterator-cannot-be-lazy('.chrs'), nqp::stmts( (my int $i = -1), (my int $elems = self.elems), # reifies (my $result := nqp::setelems(nqp::list_s,$elems)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::istype((my $value := nqp::atpos($!reified,$i)),Int), nqp::bindpos_s($result,$i,nqp::chr(my uint $ = $value)), nqp::if( nqp::istype($value,Str), nqp::if( nqp::istype(($value := +$value),Failure), (return $value), nqp::bindpos_s($result,$i,nqp::chr($value)) ), (return X::TypeCheck.new( operation => "converting element #$i to .chr", got => $value, expected => Int ).Failure) ) ) ), nqp::join("",$result) ) ) } } augment class Nil { # These suggest using Nil.new if they fall through, which is LTA multi method ords(Nil:) { self.Str.ords } multi method chrs(Nil:) { self.Int.chrs } } # all proto's in one place so they're available on all (conditional) backends #------------------------------------------------------------------------------- proto sub chr($, *%) is pure {*} proto sub chrs(|) is pure {*} proto sub ord($, *%) is pure {*} proto sub ords($, *%) is pure {*} proto sub unimatch($, $, $?, *%) is pure {*} proto sub uniname($, *%) is pure {*} proto sub uninames($, *%) is pure {*} proto sub uniparse($, *%) {*} proto sub uniprop($, $?, *%) is pure {*} proto sub uniprops($, $?, *%) is pure {*} proto sub unival($, *%) is pure {*} proto sub univals($, *%) is pure {*} #------------------------------------------------------------------------------- multi sub chr(\what) { what.chr } multi sub chrs(*@c --> Str:D) { @c.chrs } multi sub ord(\what) { what.ord } multi sub ords($s) { $s.ords } multi sub unimatch(\what, Str:D $pvalname) { what.unimatch($pvalname) } multi sub unimatch(\what, Str:D $pvalname, Str:D $propname) { what.unimatch($pvalname, $propname) } multi sub uniname(\what) { what.uniname } multi sub uninames(\what) { what.uninames } multi sub uniparse(\what --> Str:D) { what.uniparse } multi sub uniprop(\what) { what.uniprop } multi sub uniprop(\what, Str:D $propname) { what.uniprop($propname) } multi sub uniprops(\what) { what.uniprops } multi sub uniprops(\what, Str:D $propname) { what.uniprops($propname) } multi sub unival(\what) { what.unival } multi sub univals(\what) { what.univals } multi sub infix:(Str:D $a, Str:D $b) { ORDER(nqp::unicmp_s($a,$b,85,0,0)) } multi sub infix:(Cool:D $a, Cool:D $b) { ORDER(nqp::unicmp_s($a.Str,$b.Str,85,0,0)) } multi sub infix:(Pair:D $a, Pair:D $b) { nqp::eqaddr((my $cmp := ($a.key unicmp $b.key)),Order::Same) ?? ($a.value unicmp $b.value) !! $cmp } multi sub infix:(Str:D $a, Str:D $b) { ORDER(nqp::unicmp_s($a,$b,$*COLLATION.collation-level,0,0)) } multi sub infix:(Cool:D $a, Cool:D $b) { ORDER(nqp::unicmp_s($a.Str,$b.Str,$*COLLATION.collation-level,0,0)) } multi sub infix:(Pair:D $a, Pair:D $b) { nqp::eqaddr((my $cmp := ($a.key coll $b.key)),Order::Same) ?? ($a.value coll $b.value) !! $cmp } #line 1 SETTING::src/core.c/Unicode.rakumod my class Unicode { my constant NFG = True; # This constant specifies the current Unicode version being supported my constant VERSION = ( '1.1' => 'a', '2.0' => 'ẛ', '2.1' => '€', '3.0' => 'ϟ', '3.1' => 'ϴ', '3.2' => '⁇', '4.0' => 'ȡ', '4.1' => 'ℼ', '5.0' => 'ↄ', '5.1' => 'Ϗ', '5.2' => 'Ɒ', '6.0' => '✅', '6.1' => 'Ɦ', '6.2' => '₺', '6.3' => 0x061C.chr, '7.0' => 0x037F.chr, '8.0' => 0x218A.chr, '9.0' => 0xA7AE.chr, '10.0' => 0x20BF.chr, '11.0' => 0xA7AF.chr, '12.0' => 0xA7BA.chr, '12.1' => 0x32FF.chr, '13.0' => 0x1F972.chr, '14.0' => 0x061D.chr, '15.0' => 0x0CF3.chr, '15.1' => 0x2EE5F.chr, # PLEASE ADD NEWER UNICODE VERSIONS HERE, AS SOON AS THE UNICODE # CONSORTIUM HAS RELEASED A NEW VERSION ).first(*.value.uniprop('Age') ne 'Unassigned', :end).key.Version; has Version $.version = VERSION; has Bool $.NFG = NFG; proto method version(|) {*} multi method version(Unicode:U:) { VERSION } multi method version(Unicode:D:) { $!version } proto method NFG(|) {*} multi method NFG(Unicode:U:) { NFG } multi method NFG(Unicode:D:) { $!NFG } } #line 1 SETTING::src/core.c/RakuAST/HTML/Entities.rakumod my class RakuAST::HTML::Entities { my constant $entity2ord = nqp::hash( #- start of generated part of HTML entities ------------------------------------ #- Generated on 2023-04-16T18:26:45+02:00 by tools/build/makeHTML-ENTITIES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE 'Aacute', 193, 'aacute', 225, 'Abreve', 258, 'abreve', 259, 'ac', 8766, 'acd', 8767, 'acE', (8766,819), 'Acirc', 194, 'acirc', 226, 'acute', 180, 'Acy', 1040, 'acy', 1072, 'AElig', 198, 'aelig', 230, 'af', 8289, 'Afr', 120068, 'afr', 120094, 'Agrave', 192, 'agrave', 224, 'alefsym', 8501, 'aleph', 8501, 'Alpha', 913, 'alpha', 945, 'Amacr', 256, 'amacr', 257, 'amalg', 10815, 'AMP', 38, 'amp', 38, 'And', 10835, 'and', 8743, 'andand', 10837, 'andd', 10844, 'andslope', 10840, 'andv', 10842, 'ang', 8736, 'ange', 10660, 'angle', 8736, 'angmsd', 8737, 'angmsdaa', 10664, 'angmsdab', 10665, 'angmsdac', 10666, 'angmsdad', 10667, 'angmsdae', 10668, 'angmsdaf', 10669, 'angmsdag', 10670, 'angmsdah', 10671, 'angrt', 8735, 'angrtvb', 8894, 'angrtvbd', 10653, 'angsph', 8738, 'angst', 197, 'angzarr', 9084, 'Aogon', 260, 'aogon', 261, 'Aopf', 120120, 'aopf', 120146, 'ap', 8776, 'apacir', 10863, 'apE', 10864, 'ape', 8778, 'apid', 8779, 'apos', 39, 'ApplyFunction', 8289, 'approx', 8776, 'approxeq', 8778, 'Aring', 197, 'aring', 229, 'Ascr', 119964, 'ascr', 119990, 'Assign', 8788, 'ast', 42, 'asymp', 8776, 'asympeq', 8781, 'Atilde', 195, 'atilde', 227, 'Auml', 196, 'auml', 228, 'awconint', 8755, 'awint', 10769, 'backcong', 8780, 'backepsilon', 1014, 'backprime', 8245, 'backsim', 8765, 'backsimeq', 8909, 'Backslash', 8726, 'Barv', 10983, 'barvee', 8893, 'Barwed', 8966, 'barwed', 8965, 'barwedge', 8965, 'bbrk', 9141, 'bbrktbrk', 9142, 'bcong', 8780, 'Bcy', 1041, 'bcy', 1073, 'bdquo', 8222, 'becaus', 8757, 'Because', 8757, 'because', 8757, 'bemptyv', 10672, 'bepsi', 1014, 'bernou', 8492, 'Bernoullis', 8492, 'Beta', 914, 'beta', 946, 'beth', 8502, 'between', 8812, 'Bfr', 120069, 'bfr', 120095, 'bigcap', 8898, 'bigcirc', 9711, 'bigcup', 8899, 'bigodot', 10752, 'bigoplus', 10753, 'bigotimes', 10754, 'bigsqcup', 10758, 'bigstar', 9733, 'bigtriangledown', 9661, 'bigtriangleup', 9651, 'biguplus', 10756, 'bigvee', 8897, 'bigwedge', 8896, 'bkarow', 10509, 'blacklozenge', 10731, 'blacksquare', 9642, 'blacktriangle', 9652, 'blacktriangledown', 9662, 'blacktriangleleft', 9666, 'blacktriangleright', 9656, 'blank', 9251, 'blk12', 9618, 'blk14', 9617, 'blk34', 9619, 'block', 9608, 'bne', (61,8421), 'bnequiv', (8801,8421), 'bNot', 10989, 'bnot', 8976, 'Bopf', 120121, 'bopf', 120147, 'bot', 8869, 'bottom', 8869, 'bowtie', 8904, 'boxbox', 10697, 'boxDL', 9559, 'boxDl', 9558, 'boxdL', 9557, 'boxdl', 9488, 'boxDR', 9556, 'boxDr', 9555, 'boxdR', 9554, 'boxdr', 9484, 'boxH', 9552, 'boxh', 9472, 'boxHD', 9574, 'boxHd', 9572, 'boxhD', 9573, 'boxhd', 9516, 'boxHU', 9577, 'boxHu', 9575, 'boxhU', 9576, 'boxhu', 9524, 'boxminus', 8863, 'boxplus', 8862, 'boxtimes', 8864, 'boxUL', 9565, 'boxUl', 9564, 'boxuL', 9563, 'boxul', 9496, 'boxUR', 9562, 'boxUr', 9561, 'boxuR', 9560, 'boxur', 9492, 'boxV', 9553, 'boxv', 9474, 'boxVH', 9580, 'boxVh', 9579, 'boxvH', 9578, 'boxvh', 9532, 'boxVL', 9571, 'boxVl', 9570, 'boxvL', 9569, 'boxvl', 9508, 'boxVR', 9568, 'boxVr', 9567, 'boxvR', 9566, 'boxvr', 9500, 'bprime', 8245, 'Breve', 728, 'breve', 728, 'brvbar', 166, 'Bscr', 8492, 'bscr', 119991, 'bsemi', 8271, 'bsim', 8765, 'bsime', 8909, 'bsol', 92, 'bsolb', 10693, 'bsolhsub', 10184, 'bull', 8226, 'bullet', 8226, 'bump', 8782, 'bumpE', 10926, 'bumpe', 8783, 'Bumpeq', 8782, 'bumpeq', 8783, 'Cacute', 262, 'cacute', 263, 'Cap', 8914, 'cap', 8745, 'capand', 10820, 'capbrcup', 10825, 'capcap', 10827, 'capcup', 10823, 'capdot', 10816, 'CapitalDifferentialD', 8517, 'caps', (8745,65024), 'caret', 8257, 'caron', 711, 'Cayleys', 8493, 'ccaps', 10829, 'Ccaron', 268, 'ccaron', 269, 'Ccedil', 199, 'ccedil', 231, 'Ccirc', 264, 'ccirc', 265, 'Cconint', 8752, 'ccups', 10828, 'ccupssm', 10832, 'Cdot', 266, 'cdot', 267, 'cedil', 184, 'Cedilla', 184, 'cemptyv', 10674, 'cent', 162, 'CenterDot', 183, 'centerdot', 183, 'Cfr', 8493, 'cfr', 120096, 'CHcy', 1063, 'chcy', 1095, 'check', 10003, 'checkmark', 10003, 'Chi', 935, 'chi', 967, 'cir', 9675, 'circ', 710, 'circeq', 8791, 'circlearrowleft', 8634, 'circlearrowright', 8635, 'circledast', 8859, 'circledcirc', 8858, 'circleddash', 8861, 'CircleDot', 8857, 'circledR', 174, 'circledS', 9416, 'CircleMinus', 8854, 'CirclePlus', 8853, 'CircleTimes', 8855, 'cirE', 10691, 'cire', 8791, 'cirfnint', 10768, 'cirmid', 10991, 'cirscir', 10690, 'ClockwiseContourIntegral', 8754, 'CloseCurlyDoubleQuote', 8221, 'CloseCurlyQuote', 8217, 'clubs', 9827, 'clubsuit', 9827, 'Colon', 8759, 'colon', 58, 'Colone', 10868, 'colone', 8788, 'coloneq', 8788, 'comma', 44, 'commat', 64, 'comp', 8705, 'compfn', 8728, 'complement', 8705, 'complexes', 8450, 'cong', 8773, 'congdot', 10861, 'Congruent', 8801, 'Conint', 8751, 'conint', 8750, 'ContourIntegral', 8750, 'Copf', 8450, 'copf', 120148, 'coprod', 8720, 'Coproduct', 8720, 'COPY', 169, 'copy', 169, 'copysr', 8471, 'CounterClockwiseContourIntegral', 8755, 'crarr', 8629, 'Cross', 10799, 'cross', 10007, 'Cscr', 119966, 'cscr', 119992, 'csub', 10959, 'csube', 10961, 'csup', 10960, 'csupe', 10962, 'ctdot', 8943, 'cudarrl', 10552, 'cudarrr', 10549, 'cuepr', 8926, 'cuesc', 8927, 'cularr', 8630, 'cularrp', 10557, 'Cup', 8915, 'cup', 8746, 'cupbrcap', 10824, 'CupCap', 8781, 'cupcap', 10822, 'cupcup', 10826, 'cupdot', 8845, 'cupor', 10821, 'cups', (8746,65024), 'curarr', 8631, 'curarrm', 10556, 'curlyeqprec', 8926, 'curlyeqsucc', 8927, 'curlyvee', 8910, 'curlywedge', 8911, 'curren', 164, 'curvearrowleft', 8630, 'curvearrowright', 8631, 'cuvee', 8910, 'cuwed', 8911, 'cwconint', 8754, 'cwint', 8753, 'cylcty', 9005, 'Dagger', 8225, 'dagger', 8224, 'daleth', 8504, 'Darr', 8609, 'dArr', 8659, 'darr', 8595, 'dash', 8208, 'Dashv', 10980, 'dashv', 8867, 'dbkarow', 10511, 'dblac', 733, 'Dcaron', 270, 'dcaron', 271, 'Dcy', 1044, 'dcy', 1076, 'DD', 8517, 'dd', 8518, 'ddagger', 8225, 'ddarr', 8650, 'DDotrahd', 10513, 'ddotseq', 10871, 'deg', 176, 'Del', 8711, 'Delta', 916, 'delta', 948, 'demptyv', 10673, 'dfisht', 10623, 'Dfr', 120071, 'dfr', 120097, 'dHar', 10597, 'dharl', 8643, 'dharr', 8642, 'DiacriticalAcute', 180, 'DiacriticalDot', 729, 'DiacriticalDoubleAcute', 733, 'DiacriticalGrave', 96, 'DiacriticalTilde', 732, 'diam', 8900, 'Diamond', 8900, 'diamond', 8900, 'diamondsuit', 9830, 'diams', 9830, 'die', 168, 'DifferentialD', 8518, 'digamma', 989, 'disin', 8946, 'div', 247, 'divide', 247, 'divideontimes', 8903, 'divonx', 8903, 'DJcy', 1026, 'djcy', 1106, 'dlcorn', 8990, 'dlcrop', 8973, 'dollar', 36, 'Dopf', 120123, 'dopf', 120149, 'Dot', 168, 'dot', 729, 'DotDot', 8412, 'doteq', 8784, 'doteqdot', 8785, 'DotEqual', 8784, 'dotminus', 8760, 'dotplus', 8724, 'dotsquare', 8865, 'doublebarwedge', 8966, 'DoubleContourIntegral', 8751, 'DoubleDot', 168, 'DoubleDownArrow', 8659, 'DoubleLeftArrow', 8656, 'DoubleLeftRightArrow', 8660, 'DoubleLeftTee', 10980, 'DoubleLongLeftArrow', 10232, 'DoubleLongLeftRightArrow', 10234, 'DoubleLongRightArrow', 10233, 'DoubleRightArrow', 8658, 'DoubleRightTee', 8872, 'DoubleUpArrow', 8657, 'DoubleUpDownArrow', 8661, 'DoubleVerticalBar', 8741, 'DownArrow', 8595, 'Downarrow', 8659, 'downarrow', 8595, 'DownArrowBar', 10515, 'DownArrowUpArrow', 8693, 'DownBreve', 785, 'downdownarrows', 8650, 'downharpoonleft', 8643, 'downharpoonright', 8642, 'DownLeftRightVector', 10576, 'DownLeftTeeVector', 10590, 'DownLeftVector', 8637, 'DownLeftVectorBar', 10582, 'DownRightTeeVector', 10591, 'DownRightVector', 8641, 'DownRightVectorBar', 10583, 'DownTee', 8868, 'DownTeeArrow', 8615, 'drbkarow', 10512, 'drcorn', 8991, 'drcrop', 8972, 'Dscr', 119967, 'dscr', 119993, 'DScy', 1029, 'dscy', 1109, 'dsol', 10742, 'Dstrok', 272, 'dstrok', 273, 'dtdot', 8945, 'dtri', 9663, 'dtrif', 9662, 'duarr', 8693, 'duhar', 10607, 'dwangle', 10662, 'DZcy', 1039, 'dzcy', 1119, 'dzigrarr', 10239, 'Eacute', 201, 'eacute', 233, 'easter', 10862, 'Ecaron', 282, 'ecaron', 283, 'ecir', 8790, 'Ecirc', 202, 'ecirc', 234, 'ecolon', 8789, 'Ecy', 1069, 'ecy', 1101, 'eDDot', 10871, 'Edot', 278, 'eDot', 8785, 'edot', 279, 'ee', 8519, 'efDot', 8786, 'Efr', 120072, 'efr', 120098, 'eg', 10906, 'Egrave', 200, 'egrave', 232, 'egs', 10902, 'egsdot', 10904, 'el', 10905, 'Element', 8712, 'elinters', 9191, 'ell', 8467, 'els', 10901, 'elsdot', 10903, 'Emacr', 274, 'emacr', 275, 'empty', 8709, 'emptyset', 8709, 'EmptySmallSquare', 9723, 'emptyv', 8709, 'EmptyVerySmallSquare', 9643, 'emsp13', 8196, 'emsp14', 8197, 'emsp', 8195, 'ENG', 330, 'eng', 331, 'ensp', 8194, 'Eogon', 280, 'eogon', 281, 'Eopf', 120124, 'eopf', 120150, 'epar', 8917, 'eparsl', 10723, 'eplus', 10865, 'epsi', 949, 'Epsilon', 917, 'epsilon', 949, 'epsiv', 1013, 'eqcirc', 8790, 'eqcolon', 8789, 'eqsim', 8770, 'eqslantgtr', 10902, 'eqslantless', 10901, 'Equal', 10869, 'equals', 61, 'EqualTilde', 8770, 'equest', 8799, 'Equilibrium', 8652, 'equiv', 8801, 'equivDD', 10872, 'eqvparsl', 10725, 'erarr', 10609, 'erDot', 8787, 'Escr', 8496, 'escr', 8495, 'esdot', 8784, 'Esim', 10867, 'esim', 8770, 'Eta', 919, 'eta', 951, 'ETH', 208, 'eth', 240, 'Euml', 203, 'euml', 235, 'euro', 8364, 'excl', 33, 'exist', 8707, 'Exists', 8707, 'expectation', 8496, 'ExponentialE', 8519, 'exponentiale', 8519, 'fallingdotseq', 8786, 'Fcy', 1060, 'fcy', 1092, 'female', 9792, 'ffilig', 64259, 'fflig', 64256, 'ffllig', 64260, 'Ffr', 120073, 'ffr', 120099, 'filig', 64257, 'FilledSmallSquare', 9724, 'FilledVerySmallSquare', 9642, 'fjlig', (102,106), 'flat', 9837, 'fllig', 64258, 'fltns', 9649, 'fnof', 402, 'Fopf', 120125, 'fopf', 120151, 'ForAll', 8704, 'forall', 8704, 'fork', 8916, 'forkv', 10969, 'Fouriertrf', 8497, 'fpartint', 10765, 'frac12', 189, 'frac13', 8531, 'frac14', 188, 'frac15', 8533, 'frac16', 8537, 'frac18', 8539, 'frac23', 8532, 'frac25', 8534, 'frac34', 190, 'frac35', 8535, 'frac38', 8540, 'frac45', 8536, 'frac56', 8538, 'frac58', 8541, 'frac78', 8542, 'frasl', 8260, 'frown', 8994, 'Fscr', 8497, 'fscr', 119995, 'gacute', 501, 'Gamma', 915, 'gamma', 947, 'Gammad', 988, 'gammad', 989, 'gap', 10886, 'Gbreve', 286, 'gbreve', 287, 'Gcedil', 290, 'Gcirc', 284, 'gcirc', 285, 'Gcy', 1043, 'gcy', 1075, 'Gdot', 288, 'gdot', 289, 'gE', 8807, 'ge', 8805, 'gEl', 10892, 'gel', 8923, 'geq', 8805, 'geqq', 8807, 'geqslant', 10878, 'ges', 10878, 'gescc', 10921, 'gesdot', 10880, 'gesdoto', 10882, 'gesdotol', 10884, 'gesl', (8923,65024), 'gesles', 10900, 'Gfr', 120074, 'gfr', 120100, 'Gg', 8921, 'gg', 8811, 'ggg', 8921, 'gimel', 8503, 'GJcy', 1027, 'gjcy', 1107, 'gl', 8823, 'gla', 10917, 'glE', 10898, 'glj', 10916, 'gnap', 10890, 'gnapprox', 10890, 'gnE', 8809, 'gne', 10888, 'gneq', 10888, 'gneqq', 8809, 'gnsim', 8935, 'Gopf', 120126, 'gopf', 120152, 'grave', 96, 'GreaterEqual', 8805, 'GreaterEqualLess', 8923, 'GreaterFullEqual', 8807, 'GreaterGreater', 10914, 'GreaterLess', 8823, 'GreaterSlantEqual', 10878, 'GreaterTilde', 8819, 'Gscr', 119970, 'gscr', 8458, 'gsim', 8819, 'gsime', 10894, 'gsiml', 10896, 'GT', 62, 'gt', 62, 'Gt', 8811, 'gtcc', 10919, 'gtcir', 10874, 'gtdot', 8919, 'gtlPar', 10645, 'gtquest', 10876, 'gtrapprox', 10886, 'gtrarr', 10616, 'gtrdot', 8919, 'gtreqless', 8923, 'gtreqqless', 10892, 'gtrless', 8823, 'gtrsim', 8819, 'gvertneqq', (8809,65024), 'gvnE', (8809,65024), 'Hacek', 711, 'hairsp', 8202, 'half', 189, 'hamilt', 8459, 'HARDcy', 1066, 'hardcy', 1098, 'hArr', 8660, 'harr', 8596, 'harrcir', 10568, 'harrw', 8621, 'Hat', 94, 'hbar', 8463, 'Hcirc', 292, 'hcirc', 293, 'hearts', 9829, 'heartsuit', 9829, 'hellip', 8230, 'hercon', 8889, 'Hfr', 8460, 'hfr', 120101, 'HilbertSpace', 8459, 'hksearow', 10533, 'hkswarow', 10534, 'hoarr', 8703, 'homtht', 8763, 'hookleftarrow', 8617, 'hookrightarrow', 8618, 'Hopf', 8461, 'hopf', 120153, 'horbar', 8213, 'HorizontalLine', 9472, 'Hscr', 8459, 'hscr', 119997, 'hslash', 8463, 'Hstrok', 294, 'hstrok', 295, 'HumpDownHump', 8782, 'HumpEqual', 8783, 'hybull', 8259, 'hyphen', 8208, 'Iacute', 205, 'iacute', 237, 'ic', 8291, 'Icirc', 206, 'icirc', 238, 'Icy', 1048, 'icy', 1080, 'Idot', 304, 'IEcy', 1045, 'iecy', 1077, 'iexcl', 161, 'iff', 8660, 'Ifr', 8465, 'ifr', 120102, 'Igrave', 204, 'igrave', 236, 'ii', 8520, 'iiiint', 10764, 'iiint', 8749, 'iinfin', 10716, 'iiota', 8489, 'IJlig', 306, 'ijlig', 307, 'Im', 8465, 'Imacr', 298, 'imacr', 299, 'image', 8465, 'ImaginaryI', 8520, 'imagline', 8464, 'imagpart', 8465, 'imath', 305, 'imof', 8887, 'imped', 437, 'Implies', 8658, 'in', 8712, 'incare', 8453, 'infin', 8734, 'infintie', 10717, 'inodot', 305, 'Int', 8748, 'int', 8747, 'intcal', 8890, 'integers', 8484, 'Integral', 8747, 'intercal', 8890, 'Intersection', 8898, 'intlarhk', 10775, 'intprod', 10812, 'InvisibleComma', 8291, 'InvisibleTimes', 8290, 'IOcy', 1025, 'iocy', 1105, 'Iogon', 302, 'iogon', 303, 'Iopf', 120128, 'iopf', 120154, 'Iota', 921, 'iota', 953, 'iprod', 10812, 'iquest', 191, 'Iscr', 8464, 'iscr', 119998, 'isin', 8712, 'isindot', 8949, 'isinE', 8953, 'isins', 8948, 'isinsv', 8947, 'isinv', 8712, 'it', 8290, 'Itilde', 296, 'itilde', 297, 'Iukcy', 1030, 'iukcy', 1110, 'Iuml', 207, 'iuml', 239, 'Jcirc', 308, 'jcirc', 309, 'Jcy', 1049, 'jcy', 1081, 'Jfr', 120077, 'jfr', 120103, 'jmath', 567, 'Jopf', 120129, 'jopf', 120155, 'Jscr', 119973, 'jscr', 119999, 'Jsercy', 1032, 'jsercy', 1112, 'Jukcy', 1028, 'jukcy', 1108, 'Kappa', 922, 'kappa', 954, 'kappav', 1008, 'Kcedil', 310, 'kcedil', 311, 'Kcy', 1050, 'kcy', 1082, 'Kfr', 120078, 'kfr', 120104, 'kgreen', 312, 'KHcy', 1061, 'khcy', 1093, 'KJcy', 1036, 'kjcy', 1116, 'Kopf', 120130, 'kopf', 120156, 'Kscr', 119974, 'kscr', 120000, 'lAarr', 8666, 'Lacute', 313, 'lacute', 314, 'laemptyv', 10676, 'lagran', 8466, 'Lambda', 923, 'lambda', 955, 'Lang', 10218, 'lang', 10216, 'langd', 10641, 'langle', 10216, 'lap', 10885, 'Laplacetrf', 8466, 'laquo', 171, 'Larr', 8606, 'lArr', 8656, 'larr', 8592, 'larrb', 8676, 'larrbfs', 10527, 'larrfs', 10525, 'larrhk', 8617, 'larrlp', 8619, 'larrpl', 10553, 'larrsim', 10611, 'larrtl', 8610, 'lat', 10923, 'lAtail', 10523, 'latail', 10521, 'late', 10925, 'lates', (10925,65024), 'lBarr', 10510, 'lbarr', 10508, 'lbbrk', 10098, 'lbrace', 123, 'lbrack', 91, 'lbrke', 10635, 'lbrksld', 10639, 'lbrkslu', 10637, 'Lcaron', 317, 'lcaron', 318, 'Lcedil', 315, 'lcedil', 316, 'lceil', 8968, 'lcub', 123, 'Lcy', 1051, 'lcy', 1083, 'ldca', 10550, 'ldquo', 8220, 'ldquor', 8222, 'ldrdhar', 10599, 'ldrushar', 10571, 'ldsh', 8626, 'lE', 8806, 'le', 8804, 'LeftAngleBracket', 10216, 'LeftArrow', 8592, 'Leftarrow', 8656, 'leftarrow', 8592, 'LeftArrowBar', 8676, 'LeftArrowRightArrow', 8646, 'leftarrowtail', 8610, 'LeftCeiling', 8968, 'LeftDoubleBracket', 10214, 'LeftDownTeeVector', 10593, 'LeftDownVector', 8643, 'LeftDownVectorBar', 10585, 'LeftFloor', 8970, 'leftharpoondown', 8637, 'leftharpoonup', 8636, 'leftleftarrows', 8647, 'LeftRightArrow', 8596, 'Leftrightarrow', 8660, 'leftrightarrow', 8596, 'leftrightarrows', 8646, 'leftrightharpoons', 8651, 'leftrightsquigarrow', 8621, 'LeftRightVector', 10574, 'LeftTee', 8867, 'LeftTeeArrow', 8612, 'LeftTeeVector', 10586, 'leftthreetimes', 8907, 'LeftTriangle', 8882, 'LeftTriangleBar', 10703, 'LeftTriangleEqual', 8884, 'LeftUpDownVector', 10577, 'LeftUpTeeVector', 10592, 'LeftUpVector', 8639, 'LeftUpVectorBar', 10584, 'LeftVector', 8636, 'LeftVectorBar', 10578, 'lEg', 10891, 'leg', 8922, 'leq', 8804, 'leqq', 8806, 'leqslant', 10877, 'les', 10877, 'lescc', 10920, 'lesdot', 10879, 'lesdoto', 10881, 'lesdotor', 10883, 'lesg', (8922,65024), 'lesges', 10899, 'lessapprox', 10885, 'lessdot', 8918, 'lesseqgtr', 8922, 'lesseqqgtr', 10891, 'LessEqualGreater', 8922, 'LessFullEqual', 8806, 'LessGreater', 8822, 'lessgtr', 8822, 'LessLess', 10913, 'lesssim', 8818, 'LessSlantEqual', 10877, 'LessTilde', 8818, 'lfisht', 10620, 'lfloor', 8970, 'Lfr', 120079, 'lfr', 120105, 'lg', 8822, 'lgE', 10897, 'lHar', 10594, 'lhard', 8637, 'lharu', 8636, 'lharul', 10602, 'lhblk', 9604, 'LJcy', 1033, 'ljcy', 1113, 'Ll', 8920, 'll', 8810, 'llarr', 8647, 'llcorner', 8990, 'Lleftarrow', 8666, 'llhard', 10603, 'lltri', 9722, 'Lmidot', 319, 'lmidot', 320, 'lmoust', 9136, 'lmoustache', 9136, 'lnap', 10889, 'lnapprox', 10889, 'lnE', 8808, 'lne', 10887, 'lneq', 10887, 'lneqq', 8808, 'lnsim', 8934, 'loang', 10220, 'loarr', 8701, 'lobrk', 10214, 'LongLeftArrow', 10229, 'Longleftarrow', 10232, 'longleftarrow', 10229, 'LongLeftRightArrow', 10231, 'Longleftrightarrow', 10234, 'longleftrightarrow', 10231, 'longmapsto', 10236, 'LongRightArrow', 10230, 'Longrightarrow', 10233, 'longrightarrow', 10230, 'looparrowleft', 8619, 'looparrowright', 8620, 'lopar', 10629, 'Lopf', 120131, 'lopf', 120157, 'loplus', 10797, 'lotimes', 10804, 'lowast', 8727, 'lowbar', 95, 'LowerLeftArrow', 8601, 'LowerRightArrow', 8600, 'loz', 9674, 'lozenge', 9674, 'lozf', 10731, 'lpar', 40, 'lparlt', 10643, 'lrarr', 8646, 'lrcorner', 8991, 'lrhar', 8651, 'lrhard', 10605, 'lrm', 8206, 'lrtri', 8895, 'lsaquo', 8249, 'Lscr', 8466, 'lscr', 120001, 'Lsh', 8624, 'lsh', 8624, 'lsim', 8818, 'lsime', 10893, 'lsimg', 10895, 'lsqb', 91, 'lsquo', 8216, 'lsquor', 8218, 'Lstrok', 321, 'lstrok', 322, 'LT', 60, 'lt', 60, 'Lt', 8810, 'ltcc', 10918, 'ltcir', 10873, 'ltdot', 8918, 'lthree', 8907, 'ltimes', 8905, 'ltlarr', 10614, 'ltquest', 10875, 'ltri', 9667, 'ltrie', 8884, 'ltrif', 9666, 'ltrPar', 10646, 'lurdshar', 10570, 'luruhar', 10598, 'lvertneqq', (8808,65024), 'lvnE', (8808,65024), 'macr', 175, 'male', 9794, 'malt', 10016, 'maltese', 10016, 'Map', 10501, 'map', 8614, 'mapsto', 8614, 'mapstodown', 8615, 'mapstoleft', 8612, 'mapstoup', 8613, 'marker', 9646, 'mcomma', 10793, 'Mcy', 1052, 'mcy', 1084, 'mdash', 8212, 'mDDot', 8762, 'measuredangle', 8737, 'MediumSpace', 8287, 'Mellintrf', 8499, 'Mfr', 120080, 'mfr', 120106, 'mho', 8487, 'micro', 181, 'mid', 8739, 'midast', 42, 'midcir', 10992, 'middot', 183, 'minus', 8722, 'minusb', 8863, 'minusd', 8760, 'minusdu', 10794, 'MinusPlus', 8723, 'mlcp', 10971, 'mldr', 8230, 'mnplus', 8723, 'models', 8871, 'Mopf', 120132, 'mopf', 120158, 'mp', 8723, 'Mscr', 8499, 'mscr', 120002, 'mstpos', 8766, 'Mu', 924, 'mu', 956, 'multimap', 8888, 'mumap', 8888, 'nabla', 8711, 'Nacute', 323, 'nacute', 324, 'nang', (8736,8402), 'nap', 8777, 'napE', (10864,824), 'napid', (8779,824), 'napos', 329, 'napprox', 8777, 'natur', 9838, 'natural', 9838, 'naturals', 8469, 'nbsp', 160, 'nbump', (8782,824), 'nbumpe', (8783,824), 'ncap', 10819, 'Ncaron', 327, 'ncaron', 328, 'Ncedil', 325, 'ncedil', 326, 'ncong', 8775, 'ncongdot', (10861,824), 'ncup', 10818, 'Ncy', 1053, 'ncy', 1085, 'ndash', 8211, 'ne', 8800, 'nearhk', 10532, 'neArr', 8663, 'nearr', 8599, 'nearrow', 8599, 'nedot', (8784,824), 'NegativeMediumSpace', 8203, 'NegativeThickSpace', 8203, 'NegativeThinSpace', 8203, 'NegativeVeryThinSpace', 8203, 'nequiv', 8802, 'nesear', 10536, 'nesim', (8770,824), 'NestedGreaterGreater', 8811, 'NestedLessLess', 8810, 'NewLine', 10, 'nexist', 8708, 'nexists', 8708, 'Nfr', 120081, 'nfr', 120107, 'ngE', (8807,824), 'nge', 8817, 'ngeq', 8817, 'ngeqq', (8807,824), 'ngeqslant', (10878,824), 'nges', (10878,824), 'nGg', (8921,824), 'ngsim', 8821, 'nGt', (8811,8402), 'ngt', 8815, 'ngtr', 8815, 'nGtv', (8811,824), 'nhArr', 8654, 'nharr', 8622, 'nhpar', 10994, 'ni', 8715, 'nis', 8956, 'nisd', 8954, 'niv', 8715, 'NJcy', 1034, 'njcy', 1114, 'nlArr', 8653, 'nlarr', 8602, 'nldr', 8229, 'nlE', (8806,824), 'nle', 8816, 'nLeftarrow', 8653, 'nleftarrow', 8602, 'nLeftrightarrow', 8654, 'nleftrightarrow', 8622, 'nleq', 8816, 'nleqq', (8806,824), 'nleqslant', (10877,824), 'nles', (10877,824), 'nless', 8814, 'nLl', (8920,824), 'nlsim', 8820, 'nLt', (8810,8402), 'nlt', 8814, 'nltri', 8938, 'nltrie', 8940, 'nLtv', (8810,824), 'nmid', 8740, 'NoBreak', 8288, 'NonBreakingSpace', 160, 'Nopf', 8469, 'nopf', 120159, 'not', 172, 'Not', 10988, 'NotCongruent', 8802, 'NotCupCap', 8813, 'NotDoubleVerticalBar', 8742, 'NotElement', 8713, 'NotEqual', 8800, 'NotEqualTilde', (8770,824), 'NotExists', 8708, 'NotGreater', 8815, 'NotGreaterEqual', 8817, 'NotGreaterFullEqual', (8807,824), 'NotGreaterGreater', (8811,824), 'NotGreaterLess', 8825, 'NotGreaterSlantEqual', (10878,824), 'NotGreaterTilde', 8821, 'NotHumpDownHump', (8782,824), 'NotHumpEqual', (8783,824), 'notin', 8713, 'notindot', (8949,824), 'notinE', (8953,824), 'notinva', 8713, 'notinvb', 8951, 'notinvc', 8950, 'NotLeftTriangle', 8938, 'NotLeftTriangleBar', (10703,824), 'NotLeftTriangleEqual', 8940, 'NotLess', 8814, 'NotLessEqual', 8816, 'NotLessGreater', 8824, 'NotLessLess', (8810,824), 'NotLessSlantEqual', (10877,824), 'NotLessTilde', 8820, 'NotNestedGreaterGreater', (10914,824), 'NotNestedLessLess', (10913,824), 'notni', 8716, 'notniva', 8716, 'notnivb', 8958, 'notnivc', 8957, 'NotPrecedes', 8832, 'NotPrecedesEqual', (10927,824), 'NotPrecedesSlantEqual', 8928, 'NotReverseElement', 8716, 'NotRightTriangle', 8939, 'NotRightTriangleBar', (10704,824), 'NotRightTriangleEqual', 8941, 'NotSquareSubset', (8847,824), 'NotSquareSubsetEqual', 8930, 'NotSquareSuperset', (8848,824), 'NotSquareSupersetEqual', 8931, 'NotSubset', (8834,8402), 'NotSubsetEqual', 8840, 'NotSucceeds', 8833, 'NotSucceedsEqual', (10928,824), 'NotSucceedsSlantEqual', 8929, 'NotSucceedsTilde', (8831,824), 'NotSuperset', (8835,8402), 'NotSupersetEqual', 8841, 'NotTilde', 8769, 'NotTildeEqual', 8772, 'NotTildeFullEqual', 8775, 'NotTildeTilde', 8777, 'NotVerticalBar', 8740, 'npar', 8742, 'nparallel', 8742, 'nparsl', (11005,8421), 'npart', (8706,824), 'npolint', 10772, 'npr', 8832, 'nprcue', 8928, 'npre', (10927,824), 'nprec', 8832, 'npreceq', (10927,824), 'nrArr', 8655, 'nrarr', 8603, 'nrarrc', (10547,824), 'nrarrw', (8605,824), 'nRightarrow', 8655, 'nrightarrow', 8603, 'nrtri', 8939, 'nrtrie', 8941, 'nsc', 8833, 'nsccue', 8929, 'nsce', (10928,824), 'Nscr', 119977, 'nscr', 120003, 'nshortmid', 8740, 'nshortparallel', 8742, 'nsim', 8769, 'nsime', 8772, 'nsimeq', 8772, 'nsmid', 8740, 'nspar', 8742, 'nsqsube', 8930, 'nsqsupe', 8931, 'nsub', 8836, 'nsubE', (10949,824), 'nsube', 8840, 'nsubset', (8834,8402), 'nsubseteq', 8840, 'nsubseteqq', (10949,824), 'nsucc', 8833, 'nsucceq', (10928,824), 'nsup', 8837, 'nsupE', (10950,824), 'nsupe', 8841, 'nsupset', (8835,8402), 'nsupseteq', 8841, 'nsupseteqq', (10950,824), 'ntgl', 8825, 'Ntilde', 209, 'ntilde', 241, 'ntlg', 8824, 'ntriangleleft', 8938, 'ntrianglelefteq', 8940, 'ntriangleright', 8939, 'ntrianglerighteq', 8941, 'Nu', 925, 'nu', 957, 'num', 35, 'numero', 8470, 'numsp', 8199, 'nvap', (8781,8402), 'nVDash', 8879, 'nVdash', 8878, 'nvDash', 8877, 'nvdash', 8876, 'nvge', (8805,8402), 'nvgt', (62,8402), 'nvHarr', 10500, 'nvinfin', 10718, 'nvlArr', 10498, 'nvle', (8804,8402), 'nvlt', (60,8402), 'nvltrie', (8884,8402), 'nvrArr', 10499, 'nvrtrie', (8885,8402), 'nvsim', (8764,8402), 'nwarhk', 10531, 'nwArr', 8662, 'nwarr', 8598, 'nwarrow', 8598, 'nwnear', 10535, 'Oacute', 211, 'oacute', 243, 'oast', 8859, 'ocir', 8858, 'Ocirc', 212, 'ocirc', 244, 'Ocy', 1054, 'ocy', 1086, 'odash', 8861, 'Odblac', 336, 'odblac', 337, 'odiv', 10808, 'odot', 8857, 'odsold', 10684, 'OElig', 338, 'oelig', 339, 'ofcir', 10687, 'Ofr', 120082, 'ofr', 120108, 'ogon', 731, 'Ograve', 210, 'ograve', 242, 'ogt', 10689, 'ohbar', 10677, 'ohm', 937, 'oint', 8750, 'olarr', 8634, 'olcir', 10686, 'olcross', 10683, 'oline', 8254, 'olt', 10688, 'Omacr', 332, 'omacr', 333, 'Omega', 937, 'omega', 969, 'Omicron', 927, 'omicron', 959, 'omid', 10678, 'ominus', 8854, 'Oopf', 120134, 'oopf', 120160, 'opar', 10679, 'OpenCurlyDoubleQuote', 8220, 'OpenCurlyQuote', 8216, 'operp', 10681, 'oplus', 8853, 'Or', 10836, 'or', 8744, 'orarr', 8635, 'ord', 10845, 'order', 8500, 'orderof', 8500, 'ordf', 170, 'ordm', 186, 'origof', 8886, 'oror', 10838, 'orslope', 10839, 'orv', 10843, 'oS', 9416, 'Oscr', 119978, 'oscr', 8500, 'Oslash', 216, 'oslash', 248, 'osol', 8856, 'Otilde', 213, 'otilde', 245, 'Otimes', 10807, 'otimes', 8855, 'otimesas', 10806, 'Ouml', 214, 'ouml', 246, 'ovbar', 9021, 'OverBar', 8254, 'OverBrace', 9182, 'OverBracket', 9140, 'OverParenthesis', 9180, 'par', 8741, 'para', 182, 'parallel', 8741, 'parsim', 10995, 'parsl', 11005, 'part', 8706, 'PartialD', 8706, 'Pcy', 1055, 'pcy', 1087, 'percnt', 37, 'period', 46, 'permil', 8240, 'perp', 8869, 'pertenk', 8241, 'Pfr', 120083, 'pfr', 120109, 'Phi', 934, 'phi', 966, 'phiv', 981, 'phmmat', 8499, 'phone', 9742, 'Pi', 928, 'pi', 960, 'pitchfork', 8916, 'piv', 982, 'planck', 8463, 'planckh', 8462, 'plankv', 8463, 'plus', 43, 'plusacir', 10787, 'plusb', 8862, 'pluscir', 10786, 'plusdo', 8724, 'plusdu', 10789, 'pluse', 10866, 'PlusMinus', 177, 'plusmn', 177, 'plussim', 10790, 'plustwo', 10791, 'pm', 177, 'Poincareplane', 8460, 'pointint', 10773, 'Popf', 8473, 'popf', 120161, 'pound', 163, 'Pr', 10939, 'pr', 8826, 'prap', 10935, 'prcue', 8828, 'prE', 10931, 'pre', 10927, 'prec', 8826, 'precapprox', 10935, 'preccurlyeq', 8828, 'Precedes', 8826, 'PrecedesEqual', 10927, 'PrecedesSlantEqual', 8828, 'PrecedesTilde', 8830, 'preceq', 10927, 'precnapprox', 10937, 'precneqq', 10933, 'precnsim', 8936, 'precsim', 8830, 'Prime', 8243, 'prime', 8242, 'primes', 8473, 'prnap', 10937, 'prnE', 10933, 'prnsim', 8936, 'prod', 8719, 'Product', 8719, 'profalar', 9006, 'profline', 8978, 'profsurf', 8979, 'prop', 8733, 'Proportion', 8759, 'Proportional', 8733, 'propto', 8733, 'prsim', 8830, 'prurel', 8880, 'Pscr', 119979, 'pscr', 120005, 'Psi', 936, 'psi', 968, 'puncsp', 8200, 'Qfr', 120084, 'qfr', 120110, 'qint', 10764, 'Qopf', 8474, 'qopf', 120162, 'qprime', 8279, 'Qscr', 119980, 'qscr', 120006, 'quaternions', 8461, 'quatint', 10774, 'quest', 63, 'questeq', 8799, 'QUOT', 34, 'quot', 34, 'rAarr', 8667, 'race', (8765,817), 'Racute', 340, 'racute', 341, 'radic', 8730, 'raemptyv', 10675, 'Rang', 10219, 'rang', 10217, 'rangd', 10642, 'range', 10661, 'rangle', 10217, 'raquo', 187, 'Rarr', 8608, 'rArr', 8658, 'rarr', 8594, 'rarrap', 10613, 'rarrb', 8677, 'rarrbfs', 10528, 'rarrc', 10547, 'rarrfs', 10526, 'rarrhk', 8618, 'rarrlp', 8620, 'rarrpl', 10565, 'rarrsim', 10612, 'Rarrtl', 10518, 'rarrtl', 8611, 'rarrw', 8605, 'rAtail', 10524, 'ratail', 10522, 'ratio', 8758, 'rationals', 8474, 'RBarr', 10512, 'rBarr', 10511, 'rbarr', 10509, 'rbbrk', 10099, 'rbrace', 125, 'rbrack', 93, 'rbrke', 10636, 'rbrksld', 10638, 'rbrkslu', 10640, 'Rcaron', 344, 'rcaron', 345, 'Rcedil', 342, 'rcedil', 343, 'rceil', 8969, 'rcub', 125, 'Rcy', 1056, 'rcy', 1088, 'rdca', 10551, 'rdldhar', 10601, 'rdquo', 8221, 'rdquor', 8221, 'rdsh', 8627, 'Re', 8476, 'real', 8476, 'realine', 8475, 'realpart', 8476, 'reals', 8477, 'rect', 9645, 'REG', 174, 'reg', 174, 'ReverseElement', 8715, 'ReverseEquilibrium', 8651, 'ReverseUpEquilibrium', 10607, 'rfisht', 10621, 'rfloor', 8971, 'Rfr', 8476, 'rfr', 120111, 'rHar', 10596, 'rhard', 8641, 'rharu', 8640, 'rharul', 10604, 'Rho', 929, 'rho', 961, 'rhov', 1009, 'RightAngleBracket', 10217, 'RightArrow', 8594, 'Rightarrow', 8658, 'rightarrow', 8594, 'RightArrowBar', 8677, 'RightArrowLeftArrow', 8644, 'rightarrowtail', 8611, 'RightCeiling', 8969, 'RightDoubleBracket', 10215, 'RightDownTeeVector', 10589, 'RightDownVector', 8642, 'RightDownVectorBar', 10581, 'RightFloor', 8971, 'rightharpoondown', 8641, 'rightharpoonup', 8640, 'rightleftarrows', 8644, 'rightleftharpoons', 8652, 'rightrightarrows', 8649, 'rightsquigarrow', 8605, 'RightTee', 8866, 'RightTeeArrow', 8614, 'RightTeeVector', 10587, 'rightthreetimes', 8908, 'RightTriangle', 8883, 'RightTriangleBar', 10704, 'RightTriangleEqual', 8885, 'RightUpDownVector', 10575, 'RightUpTeeVector', 10588, 'RightUpVector', 8638, 'RightUpVectorBar', 10580, 'RightVector', 8640, 'RightVectorBar', 10579, 'ring', 730, 'risingdotseq', 8787, 'rlarr', 8644, 'rlhar', 8652, 'rlm', 8207, 'rmoust', 9137, 'rmoustache', 9137, 'rnmid', 10990, 'roang', 10221, 'roarr', 8702, 'robrk', 10215, 'ropar', 10630, 'Ropf', 8477, 'ropf', 120163, 'roplus', 10798, 'rotimes', 10805, 'RoundImplies', 10608, 'rpar', 41, 'rpargt', 10644, 'rppolint', 10770, 'rrarr', 8649, 'Rrightarrow', 8667, 'rsaquo', 8250, 'Rscr', 8475, 'rscr', 120007, 'Rsh', 8625, 'rsh', 8625, 'rsqb', 93, 'rsquo', 8217, 'rsquor', 8217, 'rthree', 8908, 'rtimes', 8906, 'rtri', 9657, 'rtrie', 8885, 'rtrif', 9656, 'rtriltri', 10702, 'RuleDelayed', 10740, 'ruluhar', 10600, 'rx', 8478, 'Sacute', 346, 'sacute', 347, 'sbquo', 8218, 'Sc', 10940, 'sc', 8827, 'scap', 10936, 'Scaron', 352, 'scaron', 353, 'sccue', 8829, 'scE', 10932, 'sce', 10928, 'Scedil', 350, 'scedil', 351, 'Scirc', 348, 'scirc', 349, 'scnap', 10938, 'scnE', 10934, 'scnsim', 8937, 'scpolint', 10771, 'scsim', 8831, 'Scy', 1057, 'scy', 1089, 'sdot', 8901, 'sdotb', 8865, 'sdote', 10854, 'searhk', 10533, 'seArr', 8664, 'searr', 8600, 'searrow', 8600, 'sect', 167, 'semi', 59, 'seswar', 10537, 'setminus', 8726, 'setmn', 8726, 'sext', 10038, 'Sfr', 120086, 'sfr', 120112, 'sfrown', 8994, 'sharp', 9839, 'SHCHcy', 1065, 'shchcy', 1097, 'SHcy', 1064, 'shcy', 1096, 'ShortDownArrow', 8595, 'ShortLeftArrow', 8592, 'shortmid', 8739, 'shortparallel', 8741, 'ShortRightArrow', 8594, 'ShortUpArrow', 8593, 'shy', 173, 'Sigma', 931, 'sigma', 963, 'sigmaf', 962, 'sigmav', 962, 'sim', 8764, 'simdot', 10858, 'sime', 8771, 'simeq', 8771, 'simg', 10910, 'simgE', 10912, 'siml', 10909, 'simlE', 10911, 'simne', 8774, 'simplus', 10788, 'simrarr', 10610, 'slarr', 8592, 'SmallCircle', 8728, 'smallsetminus', 8726, 'smashp', 10803, 'smeparsl', 10724, 'smid', 8739, 'smile', 8995, 'smt', 10922, 'smte', 10924, 'smtes', (10924,65024), 'SOFTcy', 1068, 'softcy', 1100, 'sol', 47, 'solb', 10692, 'solbar', 9023, 'Sopf', 120138, 'sopf', 120164, 'spades', 9824, 'spadesuit', 9824, 'spar', 8741, 'sqcap', 8851, 'sqcaps', (8851,65024), 'sqcup', 8852, 'sqcups', (8852,65024), 'Sqrt', 8730, 'sqsub', 8847, 'sqsube', 8849, 'sqsubset', 8847, 'sqsubseteq', 8849, 'sqsup', 8848, 'sqsupe', 8850, 'sqsupset', 8848, 'sqsupseteq', 8850, 'squ', 9633, 'Square', 9633, 'square', 9633, 'SquareIntersection', 8851, 'SquareSubset', 8847, 'SquareSubsetEqual', 8849, 'SquareSuperset', 8848, 'SquareSupersetEqual', 8850, 'SquareUnion', 8852, 'squarf', 9642, 'squf', 9642, 'srarr', 8594, 'Sscr', 119982, 'sscr', 120008, 'ssetmn', 8726, 'ssmile', 8995, 'sstarf', 8902, 'Star', 8902, 'star', 9734, 'starf', 9733, 'straightepsilon', 1013, 'straightphi', 981, 'strns', 175, 'Sub', 8912, 'sub', 8834, 'subdot', 10941, 'subE', 10949, 'sube', 8838, 'subedot', 10947, 'submult', 10945, 'subnE', 10955, 'subne', 8842, 'subplus', 10943, 'subrarr', 10617, 'Subset', 8912, 'subset', 8834, 'subseteq', 8838, 'subseteqq', 10949, 'SubsetEqual', 8838, 'subsetneq', 8842, 'subsetneqq', 10955, 'subsim', 10951, 'subsub', 10965, 'subsup', 10963, 'succ', 8827, 'succapprox', 10936, 'succcurlyeq', 8829, 'Succeeds', 8827, 'SucceedsEqual', 10928, 'SucceedsSlantEqual', 8829, 'SucceedsTilde', 8831, 'succeq', 10928, 'succnapprox', 10938, 'succneqq', 10934, 'succnsim', 8937, 'succsim', 8831, 'SuchThat', 8715, 'Sum', 8721, 'sum', 8721, 'sung', 9834, 'sup1', 185, 'sup2', 178, 'sup3', 179, 'Sup', 8913, 'sup', 8835, 'supdot', 10942, 'supdsub', 10968, 'supE', 10950, 'supe', 8839, 'supedot', 10948, 'Superset', 8835, 'SupersetEqual', 8839, 'suphsol', 10185, 'suphsub', 10967, 'suplarr', 10619, 'supmult', 10946, 'supnE', 10956, 'supne', 8843, 'supplus', 10944, 'Supset', 8913, 'supset', 8835, 'supseteq', 8839, 'supseteqq', 10950, 'supsetneq', 8843, 'supsetneqq', 10956, 'supsim', 10952, 'supsub', 10964, 'supsup', 10966, 'swarhk', 10534, 'swArr', 8665, 'swarr', 8601, 'swarrow', 8601, 'swnwar', 10538, 'szlig', 223, 'Tab', 9, 'target', 8982, 'Tau', 932, 'tau', 964, 'tbrk', 9140, 'Tcaron', 356, 'tcaron', 357, 'Tcedil', 354, 'tcedil', 355, 'Tcy', 1058, 'tcy', 1090, 'tdot', 8411, 'telrec', 8981, 'Tfr', 120087, 'tfr', 120113, 'there4', 8756, 'Therefore', 8756, 'therefore', 8756, 'Theta', 920, 'theta', 952, 'thetasym', 977, 'thetav', 977, 'thickapprox', 8776, 'thicksim', 8764, 'ThickSpace', (8287,8202), 'thinsp', 8201, 'ThinSpace', 8201, 'thkap', 8776, 'thksim', 8764, 'THORN', 222, 'thorn', 254, 'Tilde', 8764, 'tilde', 732, 'TildeEqual', 8771, 'TildeFullEqual', 8773, 'TildeTilde', 8776, 'times', 215, 'timesb', 8864, 'timesbar', 10801, 'timesd', 10800, 'tint', 8749, 'toea', 10536, 'top', 8868, 'topbot', 9014, 'topcir', 10993, 'Topf', 120139, 'topf', 120165, 'topfork', 10970, 'tosa', 10537, 'tprime', 8244, 'TRADE', 8482, 'trade', 8482, 'triangle', 9653, 'triangledown', 9663, 'triangleleft', 9667, 'trianglelefteq', 8884, 'triangleq', 8796, 'triangleright', 9657, 'trianglerighteq', 8885, 'tridot', 9708, 'trie', 8796, 'triminus', 10810, 'TripleDot', 8411, 'triplus', 10809, 'trisb', 10701, 'tritime', 10811, 'trpezium', 9186, 'Tscr', 119983, 'tscr', 120009, 'TScy', 1062, 'tscy', 1094, 'TSHcy', 1035, 'tshcy', 1115, 'Tstrok', 358, 'tstrok', 359, 'twixt', 8812, 'twoheadleftarrow', 8606, 'twoheadrightarrow', 8608, 'Uacute', 218, 'uacute', 250, 'Uarr', 8607, 'uArr', 8657, 'uarr', 8593, 'Uarrocir', 10569, 'Ubrcy', 1038, 'ubrcy', 1118, 'Ubreve', 364, 'ubreve', 365, 'Ucirc', 219, 'ucirc', 251, 'Ucy', 1059, 'ucy', 1091, 'udarr', 8645, 'Udblac', 368, 'udblac', 369, 'udhar', 10606, 'ufisht', 10622, 'Ufr', 120088, 'ufr', 120114, 'Ugrave', 217, 'ugrave', 249, 'uHar', 10595, 'uharl', 8639, 'uharr', 8638, 'uhblk', 9600, 'ulcorn', 8988, 'ulcorner', 8988, 'ulcrop', 8975, 'ultri', 9720, 'Umacr', 362, 'umacr', 363, 'uml', 168, 'UnderBar', 95, 'UnderBrace', 9183, 'UnderBracket', 9141, 'UnderParenthesis', 9181, 'Union', 8899, 'UnionPlus', 8846, 'Uogon', 370, 'uogon', 371, 'Uopf', 120140, 'uopf', 120166, 'UpArrow', 8593, 'Uparrow', 8657, 'uparrow', 8593, 'UpArrowBar', 10514, 'UpArrowDownArrow', 8645, 'UpDownArrow', 8597, 'Updownarrow', 8661, 'updownarrow', 8597, 'UpEquilibrium', 10606, 'upharpoonleft', 8639, 'upharpoonright', 8638, 'uplus', 8846, 'UpperLeftArrow', 8598, 'UpperRightArrow', 8599, 'Upsi', 978, 'upsi', 965, 'upsih', 978, 'Upsilon', 933, 'upsilon', 965, 'UpTee', 8869, 'UpTeeArrow', 8613, 'upuparrows', 8648, 'urcorn', 8989, 'urcorner', 8989, 'urcrop', 8974, 'Uring', 366, 'uring', 367, 'urtri', 9721, 'Uscr', 119984, 'uscr', 120010, 'utdot', 8944, 'Utilde', 360, 'utilde', 361, 'utri', 9653, 'utrif', 9652, 'uuarr', 8648, 'Uuml', 220, 'uuml', 252, 'uwangle', 10663, 'vangrt', 10652, 'varepsilon', 1013, 'varkappa', 1008, 'varnothing', 8709, 'varphi', 981, 'varpi', 982, 'varpropto', 8733, 'vArr', 8661, 'varr', 8597, 'varrho', 1009, 'varsigma', 962, 'varsubsetneq', (8842,65024), 'varsubsetneqq', (10955,65024), 'varsupsetneq', (8843,65024), 'varsupsetneqq', (10956,65024), 'vartheta', 977, 'vartriangleleft', 8882, 'vartriangleright', 8883, 'Vbar', 10987, 'vBar', 10984, 'vBarv', 10985, 'Vcy', 1042, 'vcy', 1074, 'VDash', 8875, 'Vdash', 8873, 'vDash', 8872, 'vdash', 8866, 'Vdashl', 10982, 'Vee', 8897, 'vee', 8744, 'veebar', 8891, 'veeeq', 8794, 'vellip', 8942, 'Verbar', 8214, 'verbar', 124, 'Vert', 8214, 'vert', 124, 'VerticalBar', 8739, 'VerticalLine', 124, 'VerticalSeparator', 10072, 'VerticalTilde', 8768, 'VeryThinSpace', 8202, 'Vfr', 120089, 'vfr', 120115, 'vltri', 8882, 'vnsub', (8834,8402), 'vnsup', (8835,8402), 'Vopf', 120141, 'vopf', 120167, 'vprop', 8733, 'vrtri', 8883, 'Vscr', 119985, 'vscr', 120011, 'vsubnE', (10955,65024), 'vsubne', (8842,65024), 'vsupnE', (10956,65024), 'vsupne', (8843,65024), 'Vvdash', 8874, 'vzigzag', 10650, 'Wcirc', 372, 'wcirc', 373, 'wedbar', 10847, 'Wedge', 8896, 'wedge', 8743, 'wedgeq', 8793, 'weierp', 8472, 'Wfr', 120090, 'wfr', 120116, 'Wopf', 120142, 'wopf', 120168, 'wp', 8472, 'wr', 8768, 'wreath', 8768, 'Wscr', 119986, 'wscr', 120012, 'xcap', 8898, 'xcirc', 9711, 'xcup', 8899, 'xdtri', 9661, 'Xfr', 120091, 'xfr', 120117, 'xhArr', 10234, 'xharr', 10231, 'Xi', 926, 'xi', 958, 'xlArr', 10232, 'xlarr', 10229, 'xmap', 10236, 'xnis', 8955, 'xodot', 10752, 'Xopf', 120143, 'xopf', 120169, 'xoplus', 10753, 'xotime', 10754, 'xrArr', 10233, 'xrarr', 10230, 'Xscr', 119987, 'xscr', 120013, 'xsqcup', 10758, 'xuplus', 10756, 'xutri', 9651, 'xvee', 8897, 'xwedge', 8896, 'Yacute', 221, 'yacute', 253, 'YAcy', 1071, 'yacy', 1103, 'Ycirc', 374, 'ycirc', 375, 'Ycy', 1067, 'ycy', 1099, 'yen', 165, 'Yfr', 120092, 'yfr', 120118, 'YIcy', 1031, 'yicy', 1111, 'Yopf', 120144, 'yopf', 120170, 'Yscr', 119988, 'yscr', 120014, 'YUcy', 1070, 'yucy', 1102, 'yuml', 255, 'Yuml', 376, 'Zacute', 377, 'zacute', 378, 'Zcaron', 381, 'zcaron', 382, 'Zcy', 1047, 'zcy', 1079, 'Zdot', 379, 'zdot', 380, 'zeetrf', 8488, 'ZeroWidthSpace', 8203, 'Zeta', 918, 'zeta', 950, 'Zfr', 8488, 'zfr', 120119, 'ZHcy', 1046, 'zhcy', 1078, 'zigrarr', 8669, 'Zopf', 8484, 'zopf', 120171, 'Zscr', 119989, 'zscr', 120015, 'zwj', 8205, 'zwnj', 8204, #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of HTML entities -------------------------------------- ); method parse(Str:D $entity --> Str:D) { if nqp::atkey( $entity2ord, ($entity.starts-with('&') ?? $entity.substr(1) !! $entity ).chomp(';') ) -> $value { nqp::chr(nqp::istype($value,List) ?? $value.head !! $value) } else { Nil } } } #line 1 SETTING::src/core.c/RakuAST/Fixups.rakumod my class RakuAST::LegacyPodify { ... } # A class that acts as a Hash as well as an Array, with $=data semantics. # This needs to live rather late to have "handles" support actually working # in the setting. my class Hashray does Iterable { has %.Hash handles < AT-KEY ASSIGN-KEY BIND-KEY EXISTS-KEY DELETE-KEY Map keys kv pairs anti-pairs >; has @.Array handles < AT-POS ASSIGN-POS BIND-POS EXISTS-POS DELETE-POS List values push pop shift unshift splice slice iterator >; } # This file contains augmentations to classes that are created in the # RakuAST bootstrap to allow a lot of logic (which will **NOT** be # needed to compile the Raku setting) to be written in Raku rather # than in NQP. augment class RakuAST::Node { # Helper method to produce the outer Rakudoc objects of a given # AST (aka, the RakuAST::Doc::Block and RakuAST::Doc::Declarator # objects). Note that the RakuAST::Doc::Block may have embedded # RakuAST::Doc::Block in its .paragraphs, so recursion may be # necessary. method rakudoc(RakuAST::Node:D:) { gather self.visit-children: -> $ast --> Nil { if nqp::istype($ast,RakuAST::Doc::Block) { take $ast; } elsif nqp::istype($ast,RakuAST::Doc::DeclaratorTarget) { take $_ with $ast.WHY; } $ast.visit-children(&?BLOCK); } } # Helper sub to set @*LINEAGE inside visitor code my sub beget($parent, &doula --> Nil) { @*LINEAGE.unshift: $parent; $parent.visit-children(&doula); @*LINEAGE.shift; } # Process all nodes with given mapper multi method map(RakuAST::Node:D: &mapper, :$depth-first) { gather { my @*LINEAGE; self.visit-children: $depth-first ?? -> $ast --> Nil { beget $ast, &?BLOCK; my $result := mapper($ast); take $result unless nqp::eqaddr($result,Empty); } !! -> $ast --> Nil { my $result := mapper($ast); take $result unless nqp::eqaddr($result,Empty); beget $ast, &?BLOCK; } } } # Return list of RakuAST nodes that match, potentially depth-first multi method grep(RakuAST::Node:D: $test, :$depth-first!) { my int $index = -1; my sub k($ast --> Nil) { beget $ast, &?ROUTINE; take ++$index if $test.ACCEPTS($ast); } my sub kv($ast --> Nil) { beget $ast, &?ROUTINE; if $test.ACCEPTS($ast) { take ++$index; take $ast; } } my sub p($ast --> Nil) { beget $ast, &?ROUTINE; take Pair.new(++$index, $ast) if $test.ACCEPTS($ast); } my sub v($ast --> Nil) { beget $ast, &?ROUTINE; take $ast if $test.ACCEPTS($ast); } $depth-first ?? gather { my @*LINEAGE; self.visit-children: %_ ?? &k !! %_ ?? &kv !! %_

?? &p !! &v; } !! self.grep($test) } # Return list of RakuAST nodes that match, breadth first multi method grep(RakuAST::Node:D: $test) { my int $index = -1; my sub k($ast --> Nil) { take ++$index if $test.ACCEPTS($ast); beget $ast, &?ROUTINE; } my sub kv($ast --> Nil) { if $test.ACCEPTS($ast) { take ++$index; take $ast; } beget $ast, &?ROUTINE; } my sub p($ast --> Nil) { take Pair.new(++$index, $ast) if $test.ACCEPTS($ast); beget $ast, &?ROUTINE; } my sub v($ast --> Nil) { take $ast if $test.ACCEPTS($ast); beget $ast, &?ROUTINE; } gather { my @*LINEAGE; self.visit-children: %_ ?? &k !! %_ ?? &kv !! %_

?? &p !! &v; } } # Return first of RakuAST nodes that match multi method first(RakuAST::Node:D: $test, :$end, :$depth-first) { if $end { my $nodes := nqp::create(IterationBuffer); my @*LINEAGE; self.visit-children: $depth-first ?? -> $ast --> Nil { beget $ast, &?BLOCK; $nodes.push($ast); $nodes.push(@*LINEAGE.List); } !! -> $ast --> Nil { $nodes.push($ast); $nodes.push(@*LINEAGE.List); beget $ast, &?BLOCK; } my $found := Nil; # must use .map as .grep doesn't take 2 arg Callables $nodes.List.reverse.map: -> @*LINEAGE, $ast { if $test.ACCEPTS($ast) { $found := $ast; last; } } $found } else { (gather { my @*LINEAGE; self.visit-children: $depth-first ?? -> $ast --> Nil { beget $ast, &?BLOCK; if $test.ACCEPTS($ast) { take $ast; last; } } !! -> $ast --> Nil { if $test.ACCEPTS($ast) { take $ast; last; } beget $ast, &?BLOCK; } }).head } } } my class RakuAST::Doc::Row is RakuAST::Node { has str $.column-dividers; has $.column-offsets is built(:bind); # native int array has $.cells is built(:bind); # Str or Markup has Bool $.multi-line is built(False); # columns are multi-line # Merge the cells of one or more rows with the current, by # concatenating the corresponding cells with a newline. method merge-rows(RakuAST::Doc::Row:D: *@rows --> Nil) { if @rows { my str @merged = $!cells; $!multi-line := True; # simplified for now, assuming no format strings in cells for @rows -> $row { my $other := $row.cells; my int $elems = $other.elems; if nqp::isgt_i($elems,nqp::elems(@merged)) { nqp::until( nqp::isge_i(nqp::elems(@merged),$elems), nqp::push_s(@merged,"") ); } else { $elems = nqp::elems(@merged); } my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems) && $other.AT-POS($i), nqp::bindpos_s(@merged,$i, nqp::concat( nqp::atpos_s(@merged,$i), nqp::concat("\n", $other.AT-POS($i)) ) ) ); } $!cells := @merged; } } multi method raku(RakuAST::Doc::Row:D:) { my sub nameds() { RakuAST::Node.^find_private_method('nameds')( self, ) } # No $*INDENT yet if nqp::istype($*INDENT,Failure) { my $*INDENT = ""; nameds; } # has an $*INDENT already else { nameds; } } multi method Str(RakuAST::Doc::Row:D:) { my str $dividers = nqp::hllizefor($!column-dividers,'Raku') // ''; # Stringify the given strings with the current dividers / offsets my sub stringify-cells(\cells) { if $dividers { my int $columns = cells.elems; my int $i = -1; # column index my int $j = -1; # offset index my str @parts; # atoms of string to be assembled # push divider if first cell started with divider if nqp::iseq_i(nqp::atpos_i($!column-offsets,0),2) { @parts.push(nqp::substr($dividers,++$j,1)); @parts.push(' '); } nqp::while( nqp::islt_i(++$i,$columns), nqp::stmts( @parts.push(cells.AT-POS($i).Str), @parts.push(' '), @parts.push(nqp::substr($dividers,++$j,1)), @parts.push(' ') ) ); @parts.join.trim-trailing ~ "\n" } else { cells.join(' ') ~ "\n" } } # cells may contain multiple lines, implies visual dividers if $!multi-line { my @rows; for $!cells -> $cell { my int $row = -1; for $cell.lines { (@rows[++$row] // (@rows[$row] := my str @)).push: $_; } } @rows.map(&stringify-cells).join } # only a single line else { stringify-cells($!cells) } } # conceptual leading whitespace of first element method leading-whitespace() { $!cells.head.leading-whitespace } } augment class RakuAST::Doc { # just pass it on for now, make using another class possible # in the not too distant future proto method podify(|) {*} multi method podify() { RakuAST::LegacyPodify.podify(self) } multi method podify($WHEREFORE) { RakuAST::LegacyPodify.podify(self, $WHEREFORE) } } augment class RakuAST::Doc::Markup { # convert the contents of E<> to a codepoint method !convert-entity(RakuAST::Doc::Markup: Str:D $entity) { my $codepoint := val $entity; my $string; if nqp::not_i(nqp::istype($codepoint,Allomorph)) { # not numeric if $entity.is-whitespace { $string := Nil; } else { $string := RakuAST::HTML::Entities.parse($entity); unless $string { $string := $entity.uniparse; unless $string { self.worry-ad-hoc: qq/"$entity" is not a valid HTML5 entity./; } } } } elsif try $codepoint.chr -> $chr { $string := $chr; } else { self.sorry-ad-hoc: "Codepoint $codepoint ($codepoint.base(16)) is out of bounds in E<>"; $string := ''; } $string } # Extract any meta information from the atoms, perform the expected # flattening of 'C', 'V' and letterless markup, and set that in the # meta information of the given markup method !extract-meta(--> Nil) { my @atoms; my @meta; for self.atoms { if nqp::istype($_,RakuAST::Doc::Markup) { if @meta { my str $letter = .letter; # flatten verbatim markup here if $letter eq "" { my $atom := .opener ~ .atoms ~ .closer; @meta.push: nqp::istype(@meta.tail,Str) ?? @meta.pop ~ $atom !! $atom; } else { .set-atoms(.atoms.join) if $letter eq 'C' | 'V'; @meta.push($_); } } else { @atoms.push($_); } } # it's a string elsif @meta { @meta.push: nqp::istype(@meta.tail,RakuAST::Doc::Markup) ?? $_ !! @meta.pop ~ $_; } else { my ($before, $after) = nqp::hllize($_).split("|", 2); @atoms.push($before) if $before; @meta.push($_) with $after; } } if @meta { self.set-atoms(@atoms); @meta.shift if @meta > 1 && !@meta.head; # empty leading string self.set-meta(@meta); } } # set up meta info from the last atom as appropriate method check-meta(RakuAST::Doc::Markup:D:) { my str $letter = $!letter; if $letter eq 'L' | 'D' | 'M' | 'X' { self!extract-meta; } elsif $letter eq 'E' { my @atoms = self.atoms; if nqp::istype(@atoms.tail,Str) { self.set-atoms; # reset so we can add again for @atoms.pop.split(';') -> $entity { with self!convert-entity($entity) -> $converted { self.add-meta($entity); self.add-atom($converted); } else { self.add-atom($entity); } } } } elsif $letter eq 'A' { my $aliases := $*DOC-ALIASES; unless nqp::istype($aliases,Failure) { if nqp::atkey($aliases,self.atoms.head) -> $alias { self.set-meta($alias); } } } } # flatten this markup recursively method flatten(RakuAST::Doc::Markup:D: :$container--> Str:D) { my str @parts = self.atoms.map: { nqp::istype($_,RakuAST::Doc::Markup) ?? .flatten(:container) !! $_ } if $container { @parts.unshift: $!opener; @parts.unshift: $!letter; } if self.meta -> @meta { $!letter eq 'E' ?? @parts.pop # stringification so far is incorrect !! @parts.push('|'); @parts.push: @meta.join } @parts.push: $!closer if $container; nqp::join('',@parts) } # splat letterless markups method splat-letterless(RakuAST::Doc::Markup:D: --> Nil) { my @atoms; # join any string atom with previous string atom, else push sub splat($atom) { nqp::istype($atom,Str) && nqp::istype(@atoms.tail,Str) ?? (@atoms.tail ~= $atom) !! @atoms.push($atom) } for self.atoms -> $atom { if nqp::istype($atom,RakuAST::Doc::Markup) { $atom.verbatimize; # recurse first if $atom.letter { splat($atom) } else { splat($atom.opener); splat($_) for $atom.atoms; splat($atom.closer); } } else { splat($atom) } } self.set-atoms(@atoms.List); } # recursively verbatimize any C<> and V<> markups and splay <> markup method verbatimize(RakuAST::Doc::Markup:D: --> Nil) { $!letter eq 'C' | 'V' ?? self.set-atoms(self.flatten.List) !! self.splat-letterless } multi method Str(RakuAST::Doc::Markup:D:) { my str $letter = self.letter; my str @parts = $letter, self.opener; if $letter eq 'E' { @parts.push: self.meta.join(';'); } else { @parts.push: self.atoms.join; if $letter eq 'L' { if self.meta.join -> $meta { @parts.push: '|'; @parts.push: $meta; } } elsif $letter eq 'D' | 'M' | 'X' { if self.meta -> @meta { @parts.push: '|'; @parts.push: @meta.map({ $_.join(", ") }).join(' '); } } } @parts.push: self.closer; @parts.join } } augment class RakuAST::Doc::Paragraph { # conceptual leading whitespace of first element method leading-whitespace() { nqp::istype((my $first := self.atoms.head),Str) ?? $first.leading-whitespace !! "" } # easy integer checks my int32 $open = 60; # < my int32 $close = 62; # > my int32 $oopen = 171; # « my int32 $cclose = 187; # » my int $gcprop = BEGIN nqp::unipropcode("General_Category"); # create object from string, parsing any markup sequences method from-string(RakuAST::Doc::Paragraph:U: Str:D $string) { my int32 $this; # the current grapheme my int32 $prev; # the previous grapheme my int32 $stopper; # the current (first) stopper grapheme my int32 @codes; # the graphemes of the given string my int32 @graphemes; # graphemes collected so far my $paragraph := RakuAST::Doc::Paragraph.new; my $markups := nqp::list; # stack of Markup objects my $current; # current Markup object # Sadly, NFC normalization will not normalize synthetics # to their internal value, but will instead drop them in # here decomposed. This means that the index $i can NOT # be used to do an eqat in the original string, as the # the index would get out of sync when a synthetic is # encountered. nqp::strtocodes($string,nqp::const::NORMALIZE_NFC,@codes); my int $i = -1; # index of current char my int $elems = nqp::elems(@codes); # number of codes to parse my int $openers; # number of consecutive openers # return the object currently collecting sub collector() { nqp::elems($markups) ?? nqp::atpos($markups,nqp::sub_i(nqp::elems($markups),1)) !! $paragraph } # return the opener of the Markup object currently collecting sub opener() { nqp::elems($markups) ?? nqp::atpos($markups,nqp::sub_i(nqp::elems($markups),1)).opener !! "\0" # never matches } # add collected graphemes to given object, if any, and reset sub add-graphemes($markup --> Nil) { nqp::if( nqp::elems(@graphemes), nqp::stmts( $markup.add-atom(nqp::strfromcodes(@graphemes)), nqp::setelems(@graphemes, 0) ) ); } # calculate the number of openers sub calculate-openers() { ($openers = 1), nqp::if( nqp::iseq_i($this,$open), nqp::stmts( nqp::while( nqp::islt_i(++$i,$elems) && nqp::iseq_i(nqp::atpos_i(@codes,$i),$open), ++$openers ), --$i # gone one too far ) ) } # create new Markup object for given letter and stack it sub push-markup(str $letter --> Nil) { nqp::push($markups,$current := RakuAST::Doc::Markup.new( :letter($letter), :opener(nqp::x( nqp::if(nqp::iseq_i($this,$open),'<','«'),$openers )), :closer(nqp::x( nqp::if(nqp::iseq_i($this,$open),'>','»'),$openers )) )); $stopper = nqp::iseq_i($this,$open) ?? $close !! $cclose; } # Whether we're at a real stopper (after the initial stopper # matched, but there are potentially multiple stoppers needed # e.g. in case of >> as a stopper. sub is-real-stopper() { nqp::if( nqp::istype($current,RakuAST::Doc::Markup) && (my int $todo = $current.closer.chars - 1), nqp::stmts( (my int $j = $i), nqp::while( nqp::iseq_i(nqp::atpos_i(@codes,++$j),$stopper) && --$todo, nqp::null ), nqp::if( $todo, 0, # not all stoppers found ($i = $j) # advance index, also: True ) ), 1 # single char stopper, or no Markup ) } # Do all of the markup parsing in one pass. The idea behind this # is that we don't need to create a Match object for every character # being checked. And we also work on integer codepoints to prevent # having to create a string object for each codepoint being checked: # comparing integers is what computers do very well. The actual # string can also be accessed at the same index: that is used to # quickly check matching the current opener / closer, which may be # multi character in the case of << >>. nqp::while( nqp::islt_i(++$i,$elems), # for all graphemes nqp::stmts( nqp::if( nqp::iseq_i(($this = nqp::atpos_i(@codes,$i)),$open) || nqp::iseq_i($this,$oopen), nqp::if( # < or « nqp::iseq_s(nqp::getuniprop_str($prev,$gcprop),'Lu'), nqp::stmts( # A< nqp::pop_i(@graphemes), # letter is not part of string add-graphemes(collector), calculate-openers, push-markup(nqp::chr($prev)) ), nqp::if( # bare < nqp::eqat($string,opener,$i), nqp::stmts( # same, must balance add-graphemes(collector), calculate-openers, push-markup("") # fake markup to ensure balanced ), nqp::push_i(@graphemes,$this) # bare < or « ) ), nqp::if( # not < or « nqp::iseq_i($this,$stopper) && is-real-stopper, nqp::if( # > or » nqp::elems($markups), nqp::stmts( # markups left add-graphemes(nqp::pop($markups)), nqp::if( nqp::istype($current,RakuAST::Doc::Markup), $current.check-meta ), collector.add-atom($current), ($stopper = nqp::istype(($current := collector),RakuAST::Doc::Markup) && nqp::ord($current.closer) ) ), nqp::push_i(@graphemes,$this) # bare > or » ), nqp::push_i(@graphemes,$this) # other grapheme ) ), ($prev = $this) ) ); # we have open markups left if nqp::elems($markups) -> int $elems { my $markup := nqp::atpos($markups,nqp::sub_i($elems,1)); self.worry-ad-hoc: "RakuDoc markup code $markup.letter() missing endtag '$markup.closer()'."; nqp::while( nqp::elems($markups), nqp::stmts( add-graphemes($current := nqp::pop($markups)), collector.add-atom($current) ) ); } # no markup seen, so the string itself is fine if nqp::elems(@graphemes) == $elems { $string } # some markup created else { add-graphemes($paragraph); .verbatimize for $paragraph.atoms.grep(RakuAST::Doc::Markup); $paragraph } } multi method Str(RakuAST::Doc::Paragraph:D:) { self.atoms.map(*.Str).join } } augment class RakuAST::Doc::Block { # return a new Hashray class instance method Hashray() is implementation-detail { Hashray.new } # conceptual leading whitespace of first element method leading-whitespace() is implementation-detail { self.paragraphs.head.leading-whitespace } # return a Map with allowed markup codes as keys, conceptually method allowed-markup(RakuAST::Doc::Block:D:) { # default for allowable markup letters my class OK is Map { method AT-KEY(Str:D $letter --> Bool:D) { $letter.uniprop eq 'Lu' } } my class NOK is Map { method AT-KEY(Str:D $ --> False) { } } # a specific set my $config := self.resolved-config; if $config && $config -> $allow { Map.new( @$allow.map( { $_ => True } ) ) } # all or nothing else { $!type eq .any ?? NOK !! OK } } # remove left margin whitespace, if any method !marginalize(@raw) { # some whitespace at margin if self.margin.chars -> int $margin { my $buffer := nqp::create(IterationBuffer); for @raw -> $lines { $buffer.push: $lines.lines(:!chomp).map({ if .leading-whitespace.chars >= $margin { .substr($margin) } elsif .is-whitespace { "\n" } else { self.worry-ad-hoc: "'$_.chomp()' does not have enough whitespace to allow for a margin of $margin positions"; .trim-leading } }).join; } $buffer.List } # no whitespace at left margin else { @raw } } # create block from =alias method from-alias( :$lemma, :paragraphs(@raw), *%_ --> RakuAST::Doc::Block:D) is implementation-detail { # set up basic block my $block := self.new(|%_); my @paragraphs := $block!marginalize(@raw); # add rest with possible markup my $paragraph := RakuAST::Doc::Paragraph.from-string(@paragraphs.join("\n")); # collect alias info if being collected my $aliases := $*DOC-ALIASES; nqp::bindkey($aliases,$lemma,$paragraph) unless nqp::istype($aliases,Failure); $block.add-paragraph($lemma); $block.add-paragraph($paragraph); $block } # create block from =config method from-config(:$key, *%_) is implementation-detail { my $block := self.new(:paragraphs(nqp::list($key)), |%_); # Save the configuration in the dynamic config if possible. # Note that the values in the configuration hash are Maps # of which the values are RakuAST objects that will need # literalization before actually usable. my $CONFIG := $*DOC-CONFIG; # may be a BOOTHash $CONFIG{$key} := $block.config unless nqp::istype($CONFIG,Failure); $block } # create block with type/paragraph introspection method from-paragraphs(:paragraphs(@raw), *%_ --> RakuAST::Doc::Block:D) { my constant %implicit = :1cell, :1defn, :1item, :1nested, :1pod, :1rakudoc, :1section; # set up basic block my $block := self.new(|%_); my @paragraphs := $block!marginalize(@raw); # verbatim, no postprocessing my str $type = $block.type; if $type eq 'comment' | 'data' { $block.add-paragraph($_) for @paragraphs; } # verbatim, needs postprocessing elsif $type eq 'code' | 'input' | 'output' { $block.add-paragraph( RakuAST::Doc::Paragraph.from-string($_) ) for @paragraphs; } elsif $type eq 'table' { $block!interpret-as-table(@paragraphs); } elsif $type eq 'defn' { my @parts = @paragraphs; # first line is the lemma, separate that @parts.splice(0,1,@parts.head.split("\n",2)); # lemma does not allow markup $block.add-paragraph(@parts.shift); # add rest with implicit code block detection $block!interpret-implicit-code-blocks(@parts); } elsif %implicit.AT-KEY($type) { $block!interpret-implicit-code-blocks(@paragraphs); } # these just need the paragraphs else { $block.add-paragraph( nqp::istype($_,Str) ?? RakuAST::Doc::Paragraph.from-string($_) !! $_ ) for @paragraphs; } $block } my int @row-dividers; @row-dividers[.ord] = 1 for ' ', '_', '-', '+', '|', '='; my int32 $space = 32; # " " my int32 $plus = 43; # "+" my int32 $backslash = 92; # "\\" my int32 $pipe = 124; # "|" my int $gcprop = nqp::unipropcode("General_Category"); method !interpret-as-table(@matched --> Nil) { # Set up the lines to be parsed my str @lines = @matched.join.subst(/ \n+ $/).lines; return unless @lines; # nothing to do # Remove common leading whitespace from all lines if @lines[0].leading-whitespace.chars -> int $leading is copy { my int $i; my int $elems = nqp::elems(@lines); my int $offset; my str $line; nqp::while( $leading && nqp::islt_i(++$i,$elems), nqp::stmts( ($line = nqp::atpos_s(@lines,$i)), nqp::if( nqp::islt_i( ($offset = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE,$line,0,nqp::chars($line) )), $leading ), ($leading = $offset) ) ) ); # found common whitespace if $leading { $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_s(@lines,$i, nqp::substr(nqp::atpos_s(@lines,$i),$leading) ) ); } } # Error handling for mixed column divider types my sub mixed-up($line) { self.sorry-ad-hoc( "Table has a mixture of visible and invisible column-separator types in line '$line'" ); } my %config = self.config; # Parse the given lines assuming virtual dividers were used. # Quits if actual dividers were found after it found rows with # virtual dividers, or any empty array if none were found so far. # Otherwise returns a Seq of RakuAST::Doc::Row objects with Str # row dividers. my sub parse-assuming-virtual-dividers() { my int $start; my @codes-per-row; my @offsets-per-line; for @lines -> str $line { nqp::strtocodes($line,nqp::const::NORMALIZE_NFC,my int32 @codes); my int $elems = nqp::elems(@codes); my int @offsets; my int $is-row; my int $no-more-leading; my int $prev; my int $curr; my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::stmts( # for all chars nqp::if( nqp::iseq_i(($curr = nqp::atpos_i(@codes,$i)),$space) && nqp::iseq_i($prev,$space), nqp::if( # found 2 spaces $no-more-leading, nqp::stmts( # in a column nqp::while( # eat next spaces nqp::islt_i(++$i,$elems) && nqp::iseq_i(nqp::atpos_i(@codes,$i),$space), nqp::null ), nqp::if( # done eating spaces nqp::islt_i($i,$elems), nqp::stmts( # NOT at end ($no-more-leading = 1), @offsets.push($i), ), ), --$i # one too far ), ), nqp::stmts( # not 2 spaces nqp::if( nqp::atpos_i(@row-dividers,$curr), nqp::if( # a divider $is-row && nqp::isne_i($prev,$backslash) && (nqp::iseq_i($curr,$pipe) || nqp::iseq_i($curr,$plus)), nqp::if( # visual divider @codes-per-row.elems, mixed-up($line), # mixed, give up (return ()) # handle elsewhere ) ), ($is-row = 1) # NOT a divider ), nqp::if( nqp::isne_i($curr,$space), ($no-more-leading = 1) ) ) ), ($prev = $curr) ) ); # offsets on divider lines do not count if $is-row { @codes-per-row.push: @codes; @offsets-per-line.push: @offsets; } else { @offsets-per-line.push: Any; } } # Calculate the valid column offsets from the offsets seen # so far. Only offsets that are either past the end of a # row, or which only have a space at *each* row two positions # before that offset, are accepted. Return them in ascending # order. my int @offsets = @offsets-per-line.map({ .Slip if $_ }).unique.grep({ my int $offset = $_ - 2; # must have 2 spaces before # disqualify any offset that has a defined non-space # char on any of the rows !@codes-per-row.first: -> @codes { $offset < nqp::elems(@codes) && nqp::isne_i(nqp::atpos_i(@codes,$offset),$space) } }).sort; # To provide consistency with offsets produced by # columnify, we prefix the offset of the first # column my int @column-offsets = @offsets; @column-offsets.unshift(0); # Process all of the info into the final Seq @lines.kv.map: -> $index, str $line { # it's a row, build it from cells and offsets if @offsets-per-line[$index].defined { my $cells := nqp::create(IterationBuffer); my int $chars = nqp::chars($line); my int $start; for @offsets -> int $offset { $cells.push: $start > $chars ?? '' !! RakuAST::Doc::Paragraph.from-string( nqp::substr($line,$start,$offset - $start - 2) ); $start = $offset; } $cells.push: $start > $chars ?? '' !! RakuAST::Doc::Paragraph.from-string( nqp::substr($line,$start) ); RakuAST::Doc::Row.new(:@column-offsets, :cells($cells.List)) } #divider else { $line } } } # Parse the given line and find out offsets of columns and dividers my sub columnify($line) { # is a given codepoint horizontal whitespace my sub is-ws(int $codepoint) { nqp::iseq_i($codepoint,$space) || nqp::iseq_s(nqp::getuniprop_str($codepoint,$gcprop),'Zs') } nqp::strtocodes($line,nqp::const::NORMALIZE_NFC,my int32 @codes); my int $elems = nqp::elems(@codes); @codes.push($space); # create virtual space at end for trailing | my str @dividers; # strings of dividers encountered my int @offsets; # offsets where columns start (except first) # Check the current line for column dividers. Sets the @dividers # and @offsets arrays, returns whether this line should be # considered a row (any char that is not a row|column divider). my sub inspect-real-dividers() { my int32 $prev = $space; # fake space at start for leading | my int32 $curr; my int $is-row; my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( # for all chars nqp::iseq_i(($curr = nqp::atpos_i(@codes,$i)),$pipe) || nqp::iseq_i($curr,$plus), nqp::stmts( # | or + nqp::push_s(@dividers,nqp::chr($curr)), nqp::if( is-ws($prev) && is-ws(nqp::atpos_i(@codes,$i + 1)), nqp::stmts( # real column divider nqp::push_i(@offsets,nqp::add_i(++$i,1)), ($prev = 0), ) ) ), nqp::stmts( # NOT | or + nqp::unless( nqp::atpos_i(@row-dividers,$curr), ($is-row = 1), # not a row divider ), ($prev = $curr) ) ) ); $is-row } # is it a row if inspect-real-dividers() { # no dividers found, must have at least one mixed-up($line) unless nqp::elems(@dividers); my $cells := nqp::create(IterationBuffer); my int $chars = nqp::chars($line); my int $start; for @offsets -> int $offset { # If the first offset is 2, then this implies that # the line started with a divider, so there is no # cell to push here, as there is no cell before it unless $offset == 2 { $cells.push: RakuAST::Doc::Paragraph.from-string( nqp::substr($line,$start,$offset - $start - 3) ) unless $start > $chars; } $start = $offset; } $cells.push: RakuAST::Doc::Paragraph.from-string( nqp::substr($line,$start) ) unless $start > $chars; RakuAST::Doc::Row.new( :column-dividers(@dividers.join), :column-offsets(@offsets), :cells($cells.List) ) } # not a row, so a row divider, so return as is else { $line } } my @sofar; # rows collected so far my @paragraphs; # ready made paragraphs for Block object my $merge-multi-row; # whether to merge multiple rows my str @leading-dividers; # leading dividers, to be prepended at end # Add the rows collected so far, merge them if so specified # or implied by the occurrence of multiple dividers sub add-rows-collected-sofar(:$merge = $merge-multi-row--> Nil) { if $merge && @sofar > 1 { my $first := @sofar.shift; $first.merge-rows(@sofar.splice); @paragraphs.push: $first; } else { @paragraphs.append: @sofar; } @sofar = (); } # First try virtual dividers, then visual if failed my @rows = parse-assuming-virtual-dividers; @rows = @lines.map(&columnify) unless @rows; # get rid of any last divider to make multi-row merge checks # easier my $last-divider := @rows.pop if nqp::istype(@rows.tail,Str); # Post-process rows, merging where appropriate for @rows { # a divider if nqp::istype($_,Str) { # not first divider, implies multi-line mode from now on if @paragraphs { if @sofar { $merge-multi-row := True; add-rows-collected-sofar; } @paragraphs.push: $_; } # first divider will *always* merge multiple rows elsif @sofar { add-rows-collected-sofar(:merge); @paragraphs.push: $_; } # divider *before* any data row, keep them for later else { @leading-dividers.push: $_; } } else { # NOT a divider @sofar.push: $_; } } add-rows-collected-sofar; # no explicit header specification: use legacy heuristic of # second divider being different from the first divider unless %config { my $seen-row; my $first-divider; my int $other-dividers; for @paragraphs { # is it a divider? if nqp::istype($_,Str) { # seen a divider after a row before? if $first-divider.defined { if $_ ne $first-divider { %config := RakuAST::IntLiteral.new(0); last; # different, we're done! } ++$other-dividers; } # seen a row before? elsif $seen-row { $first-divider := $_; } } # it's a row else { $seen-row = True; } } # set headers if only one divider was seen after the first row %config := RakuAST::IntLiteral.new(0) if %config:!exists && $first-divider.defined && !$other-dividers; } # post-process and save @paragraphs.prepend(@leading-dividers) if @leading-dividers; @paragraphs.push($_) with $last-divider; self.set-config(%config.Map); self.set-paragraphs(@paragraphs); } method !interpret-implicit-code-blocks(@paragraphs) { my str $current-ws; my int $current-offset; # set current whitespace / offset conveniently sub set-current-ws($ws) { $current-ws = $ws // ''; $current-offset = nqp::chars($current-ws); } # store collected lines as the next paragraph my @lines; sub add-lines() { self.add-paragraph( RakuAST::Doc::Paragraph.from-string(@lines.join) ); @lines = (); } # store collected code as the next paragraph my @codes; sub add-codes() { self.add-paragraph( RakuAST::Doc::Block.new( :margin($current-ws), :type, :paragraphs(RakuAST::Doc::Paragraph.from-string(@codes.join)) ) ); @codes = (); } set-current-ws(""); for @paragraphs -> $paragraph { # need further introspection if nqp::istype($paragraph,Str) { for $paragraph.lines(:!chomp) { # only whitespace means adding to what we're collecting if .is-whitespace { (@codes || @lines).push("\n"); } # leading whitespace is the same, or we're collecting # lines and the last line was not empty elsif .leading-whitespace eq $current-ws || (@lines && @lines.tail ne "\n") { @codes ?? @codes.push(.substr($current-offset)) !! @lines.push(.trim-leading); } # change in leading whitespace else { my str $ws = .leading-whitespace; my int $leading = nqp::chars($ws); # deeper indented, start / continue code block if $leading > $current-offset { add-lines if @lines; set-current-ws($ws) unless @codes; @codes.push: .substr($current-offset); } # (still) indented, so start new code block elsif $leading { add-codes if @codes; set-current-ws($ws); @codes.push: .substr($current-offset); } # back to original level, or even less else { add-codes if @codes; set-current-ws($ws); @lines.push: .trim-leading; } } } add-lines if @lines; } # already introspected else { add-codes if @codes; add-lines if @lines; set-current-ws($paragraph.leading-whitespace); self.add-paragraph($paragraph); } } add-codes if @codes; add-lines if @lines; } multi method Str(RakuAST::Doc::Block:D:) { self.paragraphs.map(*.Str).join } # Post-process any unresolved asts in the config method literalize-config() { my $config := self.config; my %resolved := $config.Hash; %resolved.deepmap({ if nqp::istype($_,RakuAST::Node) { my $value := .literalize; $value.defined ?? ($_ = $value) # ok, update in hash !! (return .DEPARSE); # failed, stop now, and return } }); nqp::bindattr(self, RakuAST::Doc::Block, '$!resolved-config', %resolved.Map); Nil } method resolved-config() { nqp::getattr(self, RakuAST::Doc::Block, '$!resolved-config') // Map.new } } augment class RakuAST::Type::Enum { # Hidden enumeration traits get mixed in. We don't want to expose # these for .raku and .DEPARSE. This creates a clone with a clean # set of traits and returns that. method clean-clone(RakuAST::Type::Enum:D:) { my $enum := self.clone; $enum.set-traits(self.traits.grep({ !(nqp::istype($_,RakuAST::Trait::Does) && .type.name.canonicalize.ends-with('Enumeration')) }).List); $enum } } augment class RakuAST::Doc::Declarator { # Return a 2-element list with all of the leading doc joined and # parsed as the first elements, and the trailing doc joined and # parsed as the second element method paragraphs() { $!paragraphs // nqp::bindattr(self,RakuAST::Doc::Declarator,'$!paragraphs', (self.leading, self.trailing).map({ $_ ?? RakuAST::Doc::Paragraph.from-string(.join("\n")) !! '' }).List ) } } #line 1 SETTING::src/core.c/RakuAST/Deparse.rakumod # This is the default class handling deparsing (aka, converting a given # RakuAST::Node object into Raku source code). # # It is supposed to be subclassed to provide customization and further # optimizations (although optimizations should probably live here). # # All methods are class methods, so do not require any type of instantiation. # # The "deparse" multi method expects an instance if a subclass of a # RakuAST::Node as the first positional parameter. All other publick methods # are used to provide some standard functionality used by the "deparse" methods. class RakuAST::Deparse { #------------------------------------------------------------------------------- # General lookup hashes my constant %processor-attribute = 'exec', 'x', 'quotewords', 'ww', 'val', 'v', 'words', 'w', 'heredoc', 'to', ; my constant %single-processor-prefix = 'exec', 'qx/', 'quotewords', 'qqww/', 'val', 'qq:v/', 'words', 'qqw/', ; my constant %twigil2type = < ! var-attribute . var-attribute ? var-compiler * var-compiler = var-rakudoc >; #------------------------------------------------------------------------------- # These methods are effectively constants that can be overridden by a # subclass. method before-comma(--> ' ') { } method after-comma( --> ' ') { } method parens-open( --> '(') { } method parens-close(--> ')') { } method square-open( --> '[') { } method square-close(--> ']') { } method reduce-open( --> '[') { } method reduce-triangle(--> '[\\') { } method reduce-close( --> '] ') { } method bracket-open( --> '{') { } method bracket-close(--> '}') { } method pointy-open( --> '<') { } method pointy-close(--> '>') { } method double-pointy-open( --> '<<') { } method double-pointy-close(--> '>>') { } method block-open( --> "\{\n") { } method block-close(--> "\}\n") { } method regex-open( --> '/ ') { } method regex-close( --> '/') { } method regex-alternation( --> '| ') { } method regex-sequential-alternation(--> '|| ') { } method regex-conjunction( --> '& ') { } method regex-sequential-conjunction(--> '&& ') { } method regex-any( --> '.') { } method regex-beginning-of-string(--> '^ ') { } method regex-end-of-string( --> '$ ') { } method regex-beginning-of-line( --> '^^ ') { } method regex-end-of-line( --> '$$ ') { } method regex-left-word-boundary( --> '<< ') { } method regex-right-word-boundary(--> '>> ') { } method regex-assertion-pass(--> ' ') { } method regex-assertion-fail(--> ' ') { } method regex-assertion-recurse(--> '<~~> ') { } method regex-backtrack-frugal( --> '?') { } method regex-backtrack-ratchet(--> ':') { } method regex-backtrack-greedy( --> '!') { } method regex-match-from(--> '<( ') { } method regex-match-to( --> ')> ') { } method before-infix(--> ' ') { } method after-infix( --> ' ') { } method list-infix-comma( --> ', ') { } method list-infix-semi-colon(--> '; ') { } method dotty-infix-call( --> ' .') { } method dotty-infix-call-assign(--> ' .= ') { } method function-infix-open( --> '[') { } method function-infix-close(--> ']') { } method slurpy-flattened( --> '*') { } method slurpy-single-argument(--> '+') { } method slurpy-unflattened( --> '**') { } method slurpy-capture( --> '|') { } method term-hyperwhatever(--> '**') { } method term-rand( --> 'rand') { } method term-empty-set( --> '∅') { } method term-self( --> 'self') { } method term-whatever( --> '*') { } method var-compiler-file(--> '$?FILE') { } method var-compiler-line(--> '$?LINE') { } method assign(--> ' = ') { } method bind( --> ' := ') { } method before-list-infix(--> '') { } method after-list-infix(--> ' ') { } method loop-separator(--> '; ') { } method pointy-sig( --> '-> ') { } method pointy-return( --> ' --> ') { } method fatarrow( --> ' => ') { } method end-statement( --> ";\n") { } method last-statement( --> "\n") { } method indent-with(--> ' ') { } method ternary1(--> ' ?? ') { } method ternary2(--> ' !! ') { } #------------------------------------------------------------------------------- # Setting up the deparse method proto method deparse(|) { if nqp::istype($*INDENT,Failure) { my $*INDENT = ""; # indentation level my $*DELIMITER = ""; # delimiter to add, reset if added {*} } else { {*} } } # Base class catcher multi method deparse(RakuAST::Node:D $ast) { NYI("Deparsing $ast.^name() objects").throw } # Odd value catcher, avoiding long dispatch options in error message multi method deparse(Mu:D $ast) { die "You cannot deparse a $ast.^name() instance: $ast.raku()"; } multi method deparse(Mu:U $ast) { die "You cannot deparse a $ast.^name() type object"; } #------------------------------------------------------------------------------- # Load any deparsing slang by given string method slang(Str:D $slang) { qq:to/CODE/.EVAL use experimental :rakuast; use RakuAST::Deparse::L10N::$slang; RakuAST::Deparse::L10N::$slang CODE } #------------------------------------------------------------------------------- # Provide translation for given syntax feature of Raku # The default implementation of the "xsyn" method is basically a no-op, # because it will ignore the prefix (which can be any of ). The idea is that you can mixin a role with this # method (such as RakuAST::Deparse::L10N::NL) that will provide # translations of the Raku Programming Language syntax elements to a # language different from English. # # Please see lib/RakuAST/Deparse/L10N/CORE.rakumod for the default # mapping and an example of implementation of the "xsyn" method for # translations. method xsyn(str $prefix, str $key) { $key } #------------------------------------------------------------------------------- # Provide highlighting for given syntax feature of Raku # The default implementation of the "hsyn" method is basically a no-op, # because it will ignore the prefix (which can be any of ). The idea is that you can mixin a role with this # method (such as RakuAST::Deparse::Highlight::HTML) to provide some # kind of syntax highlighting. # # Please see lib/RakuAST/Deparse/Highlight/HTML.rakumod for an example # of implementation of the "hsyn" method for highlighting. proto method hsyn(|) {*} multi method hsyn(Str:D $prefix, Str:D $key) { $key } #------------------------------------------------------------------------------- # Helper methods # helper method for deparsing contextualizers proto method context-target(|) {*} multi method context-target(RakuAST::StatementSequence $target --> Str:D) { self.parenthesize($target) } multi method context-target($target --> Str:D) { self.deparse($target) } method indent($indent = $.indent-with--> Str:D) { $_ = $_ ~ $indent with $*INDENT; } method dedent($indent = $.indent-with--> Str:D) { $_ = $_.chomp($indent) with $*INDENT; } method handle-signature($ast, str $header) { my str @parts = $header; sub add-traits() { if $ast.traits -> @traits { @parts.push(self.deparse($_)) for @traits; } } my $signature := $ast.signature; my $WHY := $ast.WHY; if $signature.parameters-initialized && $signature.parameters.first(*.WHY) { @parts.push("(\n"); @parts = self.add-any-docs(@parts.join(' '), $WHY) ~ self.deparse($signature) ~ ')'; add-traits; } else { @parts.push(self.parenthesize($signature)) if $signature.parameters-initialized; add-traits; if $WHY { $*DELIMITER = ""; @parts.push('{'); return self.add-any-docs(@parts.join(' '), $WHY) ~ self.deparse($ast.body, :multi).substr(2) # lose {\n } } @parts.push(self.deparse($ast.body)); @parts.join(' ') } method method(RakuAST::Methodish:D $ast, str $kind --> Str:D) { my str @parts = self.syn-routine($kind); if $ast.multiness -> $multiness { @parts.unshift(self.syn-multi($multiness)); } my str $scope = $ast.scope; @parts.unshift(self.xsyn('scope', $scope)) if $scope ne 'has' && $scope ne $ast.default-scope; if $ast.name -> $ast-name { my str $name = self.deparse($ast-name); @parts.push(nqp::istype($ast,RakuAST::Method) ?? $ast.private ?? "!$name" !! $ast.meta ?? "^$name" !! $name !! $name ); } self.handle-signature($ast, @parts.join(' ')) } method conditional($self: $ast, str $type --> Str:D) { self.syn-block($type) ~ " $self.deparse($ast.condition) $self.deparse($ast.then)$.last-statement" } method negated-conditional($self: $ast, str $type --> Str:D) { self.syn-block($type) ~ " $self.deparse($ast.condition) $self.deparse($ast.body)$.last-statement" } method simple-loop($self: $ast, str $type --> Str:D) { self.syn-block($type) ~ " $self.deparse($ast.condition) $self.deparse($ast.body)" } method simple-repeat($ast, str $type --> Str:D) { self.syn-block('repeat') ~ ' ' ~ self.deparse($ast.body).chomp ~ ' ' ~ self.syn-modifier($type) ~ ' ' ~ self.deparse($ast.condition) } method assemble-quoted-string($ast --> Str:D) { $ast.segments.map({ nqp::istype($_,RakuAST::StrLiteral) ?? .value.raku.substr(1,*-1) !! self.deparse($_) }).join } method multiple-processors(str $string, @processors --> Str:D) { self.xsyn('quote-lang',"qq") ~ "@processors.map({ ':' ~ self.xsyn( 'adverb-q', %processor-attribute{$_} // NYI("String processors '$_'") ) }).join()/$string/" } method branches(RakuAST::Regex::Branching:D $ast, str $joiner --> Str:D) { if $ast.branches -> @branches { @branches.map({ self.deparse($_) }).join($joiner) } else { '' } } method colonpairs($ast, Str:D $xsyn = "") { $ast.colonpairs.map({ self.deparse($_, $xsyn) }).join } method quantifier( RakuAST::Regex::Quantifier:D $ast, str $quantifier --> Str:D) { $quantifier ~ self.deparse($ast.backtrack) } method parenthesize($ast, :$only-non-empty --> Str:D) { my str $deparsed = $ast.defined ?? self.deparse($ast).chomp !! ''; $deparsed || !$only-non-empty ?? $.parens-open ~ $deparsed ~ $.parens-close !! $deparsed } method bracketize($ast --> Str:D) { $.bracket-open ~ ($ast.defined ?? self.deparse($ast) !! '') ~ $.bracket-close } method squarize($ast --> Str:D) { $.square-open ~ ($ast.defined ?? self.deparse($ast) !! '') ~ $.square-close } method method-call( $ast, str $dot, $macroish?, :$xsyn, :$only-non-empty --> Str:D) { my $name := (nqp::istype($_,Str) ?? $_ !! self.deparse($_)) with $ast.name; self.syn-routine($dot) ~ ($xsyn ?? self.xsyn('core', $name) !! $name) ~ ($macroish ?? '' !! self.parenthesize($ast.args, :$only-non-empty)) } method quote-if-needed(str $literal) { my int $find = nqp::findnotcclass( nqp::const::CCLASS_WORD,$literal,0,nqp::chars($literal) ); $find == nqp::chars($literal) ?? $literal # just word chars !! $literal.raku # need quoting } method deparse-unquoted($ast) { if nqp::istype($ast,Str) { $ast } elsif nqp::istype($ast,RakuAST::StrLiteral) { $ast.value } else { my $literal := self.deparse($ast); $literal.starts-with(Q/"/) && $literal.ends-with(Q/"/) || $literal.starts-with(Q/'/) && $literal.ends-with(Q/'/) ?? $literal.substr(1,*-1) !! $literal } } method labels(RakuAST::Statement:D $ast) { $ast.labels.map({ self.deparse($_) }).join } method use-no(str $what, $ast) { my str @parts = self.xsyn('use', $what), ' ', self.deparse($ast.module-name); if $ast.argument -> $argument { @parts.push(' '); @parts.push(self.deparse($argument)); } self.labels($ast) ~ @parts.join } method prefix-any-leading-doc(str $body, $WHY) { if $WHY && $WHY.leading -> @leading { self.hsyn('doc-leading', @leading.map({ self.deparse-unquoted($_).lines(:!chomp).Slip }).map({ "#| $_$*INDENT" }).join) ~ $body } else { $body } } method postfix-any-trailing-doc(str $body, $WHY) { if $WHY && $WHY.trailing -> @trailing { my str @lines = @trailing.map: { self.deparse-unquoted($_).lines.Slip } ($body ~ $*DELIMITER).chomp ~ (@lines > 1 ?? "\n" !! ' ') ~ self.hsyn( 'doc-trailing', @lines.map({ "#= $_" }).join("$*INDENT\n") ) ~ "\n" } else { $body ~ $*DELIMITER } } method add-any-docs(str $body, $WHY) { self.postfix-any-trailing-doc( self.prefix-any-leading-doc($body, $WHY), $WHY ) } method statement-modifier(str $type, $ast) { self.syn-modifier($type) ~ ' ' ~ self.deparse($ast.expression) } method syn-block(str $type) { self.hsyn("block-$type", self.xsyn('block', $type)) } method syn-infix(str $operator) { self.hsyn("infix", self.xsyn('infix', $operator)) } method syn-infix-ws(Str:D $operator) { $operator.leading-whitespace ~ self.hsyn("infix", self.xsyn('infix', $operator.trim)) ~ $operator.trailing-whitespace } method syn-modifier(str $type) { self.hsyn("modifier-$type", self.xsyn('modifier', $type)) } method syn-multi(str $type) { self.hsyn("multi-$type", self.xsyn('multi', $type)) } method syn-package(str $declarator) { self.hsyn("package-$declarator", self.xsyn('package', $declarator)) } method syn-phaser(str $phaser) { self.hsyn("phaser-$phaser", self.xsyn('phaser', $phaser)) } method syn-routine(str $type) { self.hsyn("routine-$type", self.xsyn('routine', $type)) } method syn-scope(str $scope) { self.hsyn("scope-$scope", self.xsyn('scope', $scope)) } method syn-trait(str $trait) { self.hsyn("traitmod-$trait", self.xsyn('traitmod', $trait)) } method syn-type($type) { self.hsyn('type', self.deparse($type)) } method syn-typer($typer) { self.hsyn('typer',self.xsyn('typer', $typer)) } #- A --------------------------------------------------------------------------- multi method deparse(RakuAST::ApplyInfix:D $ast --> Str:D) { self.deparse($ast.left) ~ $.before-infix ~ self.deparse($ast.infix) ~ $.after-infix ~ self.deparse($ast.right) } multi method deparse(RakuAST::ApplyDottyInfix:D $ast --> Str:D) { self.deparse($ast.left) ~ self.deparse($ast.infix) # lose the ".", as it is provided by the infix ~ self.deparse($ast.right).substr(1) } multi method deparse(RakuAST::ApplyListInfix:D $ast --> Str:D) { my $infix := $ast.infix; my str $operator = nqp::istype($infix,RakuAST::MetaInfix) || nqp::istype($infix,RakuAST::Feed) ?? (' ' ~ self.deparse($infix)) !! self.deparse($infix); my str @parts = $ast.operands.map({ self.deparse($_) }); @parts ?? $operator eq ',' ?? @parts == 1 ?? @parts.head ~ $.list-infix-comma.chomp !! @parts.join($.list-infix-comma) !! @parts.join( $.before-list-infix ~ $operator ~ $.after-list-infix ) !! '' } multi method deparse(RakuAST::ApplyPostfix:D $ast --> Str:D) { my $postfix := $ast.postfix; my str $deparsed-postfix = self.deparse($postfix); if $ast.on-topic && nqp::istype($postfix,RakuAST::Call::Method) { $deparsed-postfix } else { my $operand := $ast.operand; my str $deparsed-operand = self.deparse($operand); nqp::istype($operand,RakuAST::ApplyInfix) || nqp::istype($operand,RakuAST::ApplyListInfix) ?? $.parens-open ~ $deparsed-operand ~ $.parens-close ~ $deparsed-postfix !! $deparsed-operand ~ $deparsed-postfix } } multi method deparse(RakuAST::ApplyPrefix:D $ast --> Str:D) { self.hsyn('prefix', self.xsyn('prefix', self.deparse($ast.prefix))) ~ self.deparse($ast.operand) } multi method deparse(RakuAST::ArgList:D $ast --> Str:D) { $ast.args.map({ if nqp::istype($_,RakuAST::Heredoc) { my ($top, $bottom) = self.deparse($_, :split); @*HEREDOCS.push($bottom); $top } else { nqp::istype($_,RakuAST::ColonPair) ?? self.deparse($_, "named") !! self.deparse($_) } }).join($.list-infix-comma) } #- B --------------------------------------------------------------------------- multi method deparse(RakuAST::Block:D $ast --> Str:D) { if $ast.WHY -> $WHY { $*DELIMITER = ""; self.add-any-docs('{', $WHY) ~ self.deparse($ast.body, :multi).substr(2) # lose {\n } else { self.deparse($ast.body, |%_) } } multi method deparse(RakuAST::Blockoid:D $ast, :$multi, :$unit --> Str:D) { my $statement-list := $ast.statement-list; if $unit { self.deparse($statement-list) } elsif $multi || $statement-list.statements { self.indent; $.block-open ~ self.deparse($statement-list) ~ self.dedent ~ $.bracket-close } else { "$.bracket-open $.bracket-close" } } #- Call ------------------------------------------------------------------------ multi method deparse(RakuAST::Call::MaybeMethod:D $ast --> Str:D) { self.method-call($ast, '.?', :only-non-empty) } multi method deparse(RakuAST::Call::MetaMethod:D $ast --> Str:D) { self.method-call($ast, '.^', :only-non-empty) } multi method deparse(RakuAST::Call::Method:D $ast --> Str:D) { self.method-call($ast, '.', $ast.macroish, :xsyn, :only-non-empty) } multi method deparse(RakuAST::Call::PrivateMethod:D $ast --> Str:D) { self.method-call($ast, '!', :only-non-empty) } multi method deparse(RakuAST::Call::QuotedMethod:D $ast --> Str:D) { self.method-call($ast, '.') } multi method deparse(RakuAST::Call::VarMethod:D $ast --> Str:D) { self.method-call($ast, '.&') } multi method deparse(RakuAST::Call::Name:D $ast --> Str:D) { my $name := self.xsyn('core', self.deparse($ast.name)); $name.ends-with('::') ?? $name !! $name ~ self.parenthesize($ast.args) } multi method deparse(RakuAST::Call::Name::WithoutParentheses:D $ast --> Str:D) { my $name := self.xsyn('core', self.deparse($ast.name)); my $args := $ast.args.defined ?? self.deparse($ast.args).chomp !! ''; $args ?? "$name $args" !! $name } multi method deparse(RakuAST::Call::Term:D $ast --> Str:D) { self.parenthesize($ast.args, :only-non-empty) } #- Circumfix ------------------------------------------------------------------- multi method deparse(RakuAST::Circumfix::ArrayComposer:D $ast --> Str:D) { self.squarize($ast.semilist) } multi method deparse(RakuAST::Circumfix::HashComposer:D $ast --> Str:D) { self.bracketize($ast.expression) } multi method deparse(RakuAST::Circumfix::Parentheses:D $ast --> Str:D) { self.parenthesize($ast.semilist) } #- ColonPair ------------------------------------------------------------------- multi method deparse(RakuAST::ColonPair:D $ast, Str $xsyn = "" --> Str:D) { my str $key = $ast.named-arg-name; ':' ~ ($xsyn ?? self.xsyn($xsyn,$key) !! $key) ~ $.parens-open ~ self.deparse($ast.named-arg-value) ~ $.parens-close } multi method deparse( RakuAST::ColonPair::False:D $ast, Str:D $xsyn = "" --> Str:D) { my str $key = $ast.key; ':!' ~ ($xsyn ?? self.xsyn($xsyn,$key) !! $key) } multi method deparse( RakuAST::ColonPair::Number:D $ast, Str:D $xsyn = "" --> Str:D) { my str $key = $ast.key; ':' ~ self.deparse($ast.value) ~ ($xsyn ?? self.xsyn($xsyn,$key) !! $key) } multi method deparse( RakuAST::ColonPair::True:D $ast, Str:D $xsyn = "" --> Str:D) { my str $key = $ast.key; ':' ~ ($xsyn ?? self.xsyn($xsyn,$key) !! $key) } multi method deparse( RakuAST::ColonPair::Value:D $ast, Str:D $xsyn = "" --> Str:D) { my str $key = $ast.key; my $value := $ast.value; ':' ~ ($xsyn ?? self.xsyn($xsyn,$key) !! $key) ~ (nqp::istype($value,RakuAST::QuotedString) ?? self.deparse($value) !! $.parens-open ~ self.deparse($value) ~ $.parens-close ) } multi method deparse(RakuAST::ColonPair::Variable:D $ast --> Str:D) { ':' ~ self.deparse($ast.value) } multi method deparse(RakuAST::Constant:D $ast --> Str:D) { $ast.deparse } #- Co -------------------------------------------------------------------------- multi method deparse(RakuAST::CompUnit:D $ast --> Str:D) { my str $deparsed = self.deparse($ast.statement-list); with $ast.finish-content { $deparsed ~="\n=finish\n$_"; } else { $deparsed } } multi method deparse(RakuAST::Contextualizer:D $ast --> Str:D) { $ast.sigil ~ self.context-target($ast.target) } #- D --------------------------------------------------------------------------- multi method deparse(RakuAST::Declaration:D $ast --> Str:D) { self.xsyn('scope', $ast.scope) } multi method deparse( RakuAST::Declaration::ResolvedConstant:D $ast --> Str:D) { $ast.compile-time-value.raku } #- Doc ------------------------------------------------------------------------- multi method deparse(RakuAST::Doc::Block:D $ast --> Str:D) { my str $margin = $ast.margin; my str $type = $ast.type; my str $name = self.hsyn('rakudoc-type', $type ~ $ast.level); # indent string with given margin, unless all whitespace sub indent(Str:D $string) { $margin ?? $string.lines(:!chomp).map({ .is-whitespace ?? "\n" !! $margin ~ $_ }).join !! $string } # handle =alias directive if $type eq 'alias' { my ($lemma, $paragraph) = $ast.paragraphs; $paragraph = self.deparse($paragraph) unless nqp::istype($paragraph,Str); return "$margin=$name $lemma $paragraph.subst( "\n", "\n$margin= ", :global )\n"; } # handle =defn blocks my $abbreviated := $ast.abbreviated; my str $prefix = "$margin=$name"; if $type eq 'defn' { my str @paras = $ast.paragraphs; my str $lemma = @paras.shift; my str $spec = "$lemma\n" ~ @paras.map(&indent).join; return $abbreviated ?? "$prefix $spec" !! $ast.for ?? "$margin=for $name\n$margin$spec" !! "$margin=begin $name\n$margin$spec$margin=end $name\n\n"; } # preprocess any config my str $config = $ast.config.sort({ .key eq 'numbered' ?? '' !! .key # numbered always first }).map({ my str $key = .key; if $key eq 'numbered' && $abbreviated { '#' } else { my $deparsed := self.deparse(.value); $deparsed eq 'True' ?? ":$key" !! $deparsed eq 'False' ?? ":!$key" !! ":$key$deparsed" } }).join(' '); $config = $config ?? ' ' ~ self.hsyn('rakudoc-config', $config) ~ "\n" !! "\n"; # handle =row / =column directives if $type eq 'row' | 'column' { return $prefix ~ $config; } # handle =config directive elsif $type eq 'config' { return "$prefix $ast.paragraphs.head()$config" } # set up paragraphs my $paragraphs := indent $ast.paragraphs.map({ nqp::istype($_,Str) ?? $_ !! self.deparse($_) }).join; # handle implicite code blocks if $type eq 'implicit-code' { $paragraphs := self.deparse($_) with try $paragraphs.AST; # try do highlighting on code self.hsyn('rakudoc-verbatim', $paragraphs) } # handle explicit code blocks elsif $type eq 'code' { $paragraphs := self.deparse($_) with try $paragraphs.AST; # try do highlighting on code $paragraphs := self.hsyn('rakudoc-verbatim', $paragraphs); $abbreviated ?? "$prefix\n$paragraphs" !! $ast.for ?? "$margin=for $name$config$paragraphs" !! "$margin=begin $name$config$paragraphs$margin=end $name\n\n" } # handle tables (to be expanded soon) elsif $type eq 'table' { $paragraphs := self.hsyn('rakudoc-table', $paragraphs); $abbreviated ?? "$prefix$config$paragraphs\n" !! $ast.for ?? "$margin=for $name$config$paragraphs\n" !! "$margin=begin $name$config$paragraphs$margin=end $name\n\n" } # other blocks else { $paragraphs := self.hsyn( $type eq 'comment' | 'data' | 'input' | 'output' ?? 'rakudoc-verbatim' !! 'rakudoc-content', $paragraphs.chomp ) ~ "\n"; $abbreviated ?? "$prefix$config.chomp() $paragraphs.trim-leading()\n" !! $ast.for ?? "$margin=for $name$config$paragraphs" !! "$margin=begin $name$config$paragraphs$margin=end $name\n\n" } } multi method deparse(RakuAST::Doc::Declarator:D $ast --> Str:D) { (my $wherefore := nqp::clone($ast.WHEREFORE)).set-WHY($ast); self.deparse($wherefore).chomp } multi method deparse(RakuAST::Doc::Markup:D $ast --> Str:D) { self.hsyn("markup-$ast.letter()", $ast.Str) } multi method deparse(RakuAST::Doc::Paragraph:D $ast --> Str:D) { $ast.atoms.map({ self.deparse-unquoted($_) }).join } multi method deparse(RakuAST::Doc::Row:D $ast --> Str:D) { $ast.Str } #- Dot ------------------------------------------------------------------------- multi method deparse(RakuAST::DottyInfix::Call:D $ --> Str:D) { $.dotty-infix-call } multi method deparse(RakuAST::DottyInfix::CallAssign:D $ --> Str:D) { $.dotty-infix-call-assign } #- F --------------------------------------------------------------------------- multi method deparse(RakuAST::FatArrow:D $ast --> Str:D) { $ast.key ~ $.fatarrow ~ self.deparse($ast.value) } multi method deparse(RakuAST::FunctionInfix:D $ast --> Str:D) { $.function-infix-open ~ self.deparse($ast.function) ~ $.function-infix-close } #- H --------------------------------------------------------------------------- multi method deparse(RakuAST::Heredoc:D $ast, :$split) { my $string := self.assemble-quoted-string($ast); my @processors = $ast.processors; @processors.push('heredoc'); my $stop := $ast.stop; my $indent := $stop eq "\n" ?? '' !! " " x ($stop.chars - $stop.trim-leading.chars); my $top := self.multiple-processors($stop.trim, @processors); my $bottom := $string.chomp('\n').split(Q/\n/).map({ $_ ?? "$indent$_\n" !! "\n" }).join ~ $stop; $split ?? ($top, $bottom) !! "$top\n$bottom" } #- I --------------------------------------------------------------------------- # Also for ::FlipFlop multi method deparse(RakuAST::Infix:D $ast --> Str:D) { self.syn-infix($ast.operator) } multi method deparse(RakuAST::Initializer::Assign:D $ast --> Str:D) { self.syn-infix-ws($.assign) ~ self.deparse($ast.expression) } multi method deparse(RakuAST::Initializer::Bind:D $ast --> Str:D) { self.syn-infix-ws($.bind) ~ self.deparse($ast.expression) } multi method deparse(RakuAST::Initializer::CallAssign:D $ast --> Str:D) { self.syn-infix-ws($.dotty-infix-call-assign) ~ self.deparse($ast.postfixish).substr(1) } #- L --------------------------------------------------------------------------- multi method deparse(RakuAST::Label:D $ast --> Str:D) { self.hsyn('label', $ast.name ~ ': ') } # handles all RakuAST::xxxLiteral classes multi method deparse(RakuAST::Literal:D $ast --> Str:D) { self.hsyn('literal', $ast.value.raku) } #- M --------------------------------------------------------------------------- multi method deparse(RakuAST::MetaInfix::Assign:D $ast --> Str:D) { self.syn-infix(self.deparse($ast.infix) ~ '=') } multi method deparse(RakuAST::MetaInfix::Cross:D $ast --> Str:D) { self.syn-infix(self.xsyn('meta','X') ~ self.deparse($ast.infix)) } multi method deparse(RakuAST::MetaInfix::Hyper:D $ast --> Str:D) { self.hsyn('infix', ($ast.dwim-left ?? '<<' !! '>>') ~ self.xsyn('infix', self.deparse($ast.infix)) ~ ($ast.dwim-right ?? '>>' !! '<<') ) } multi method deparse(RakuAST::MetaInfix::Negate:D $ast --> Str:D) { self.syn-infix(self.deparse($ast.infix) ~ '!') } multi method deparse(RakuAST::MetaInfix::Reverse:D $ast --> Str:D) { self.syn-infix(self.xsyn('meta','R') ~ self.deparse($ast.infix)) } multi method deparse(RakuAST::MetaInfix::Zip:D $ast --> Str:D) { self.syn-infix(self.xsyn('meta','Z') ~ self.deparse($ast.infix)) } multi method deparse(RakuAST::Method:D $ast --> Str:D) { self.method($ast, 'method') } #- N --------------------------------------------------------------------------- multi method deparse(RakuAST::Name:D $ast --> Str:D) { $ast.canonicalize } multi method deparse(RakuAST::Nqp:D $ast --> Str:D) { self.hsyn('nqp', "nqp::" ~ $ast.op) ~ self.parenthesize($ast.args) } multi method deparse(RakuAST::Nqp::Const:D $ast --> Str:D) { self.hsyn('nqp', "nqp::const::" ~ $ast.name) } #- O --------------------------------------------------------------------------- multi method deparse(RakuAST::OnlyStar:D $ --> '{*}') { } #- P --------------------------------------------------------------------------- multi method deparse(RakuAST::Package:D $ast --> Str:D) { my str $scope = $ast.augmented ?? 'augment' !! $ast.scope; my str @parts; if $scope { @parts.push(self.syn-scope($scope)) if $scope ne $ast.default-scope; } my str $declarator = $ast.declarator; @parts.push(self.syn-package($declarator)); my str $name = self.deparse($ast.name); if $ast.parameterization -> $signature { @parts.push((my $deparsed := self.deparse($signature)) ?? $name ~ '[' ~ $deparsed ~ ']' !! $name ); } else { @parts.push($name); } if $ast.traits -> @traits { for @traits -> $trait { @parts.push(self.deparse($trait)); } } my $body := $declarator eq 'role' ?? RakuAST::Block.new( body => RakuAST::Blockoid.new( # lose fabricated return value RakuAST::StatementList.new( |$ast.body.body.statement-list.statements.head(*-1) ) ) ) !! $ast.body; if $ast.WHY -> $WHY { if $scope eq 'unit' { self.add-any-docs(@parts.join(' ') ~ ';', $WHY) ~ self.deparse($body, :unit).chomp } else { @parts.push('{'); self.add-any-docs(@parts.join(' '), $WHY).chomp ~ self.deparse($body, :multi).substr(1).chomp } } elsif $scope eq 'unit' { @parts.join(' ') ~ $.end-statement ~ self.deparse($body, :unit).chomp } else { @parts.push(self.deparse($body)); @parts.join(' ') } } multi method deparse(RakuAST::Pragma:D $ast --> Str:D) { my str @parts = self.hsyn('use', self.xsyn('use', $ast.off ?? "no" !! "use")), self.hsyn('pragma', $ast.name); @parts.push(self.deparse($_)) with $ast.argument; @parts.join(' ') ~ $*DELIMITER } #- Parameter ------------------------------------------------------------------- multi method deparse(RakuAST::Parameter:D $ast --> Str:D) { return self.add-any-docs(self.hsyn('literal',.raku), $ast.WHY) with $ast.value; my $target := $ast.target; my @captures := $ast.type-captures; my str @parts; if !@captures && $ast.type -> $type { my str $deparsed = self.deparse($type); unless $deparsed eq 'Any' | 'SETTING::' { @parts.push($deparsed); @parts.push(' ') if $target; } } if $ast.type-captures -> @captures { @parts.push(self.deparse($_)) for @captures; } elsif $target { my str $var = self.deparse($target, :slurpy($ast.slurpy)); # named parameter if $ast.names -> @names { my str $varname = $var.substr(1); # lose the sigil my int $parens; my int $seen; for @names -> $name { if $name eq $varname { $seen = 1; } else { @parts.push(':'); @parts.push($name); @parts.push('('); ++$parens; } } @parts.push(':') if $seen; @parts.push($var); @parts.push(nqp::x(')',$parens)) if $parens; @parts.push('?') if $ast.is-declared-optional; @parts.push('!') if $ast.is-declared-required; } # positional parameter else { given $ast.slurpy -> $prefix { @parts.push(self.deparse($prefix)); } @parts.push($var); if $ast.invocant { @parts.push(':'); } elsif $ast.is-declared-optional { @parts.push('?'); } elsif $ast.is-declared-required { @parts.push('!'); } } if $ast.traits -> @traits { for @traits { @parts.push(' '); @parts.push(self.deparse($_)); } } } elsif nqp::eqaddr($ast.slurpy,RakuAST::Parameter::Slurpy::Capture) { @parts.push(self.deparse($ast.slurpy)); } @parts = self.hsyn('param', @parts.join); if $ast.default -> $default { @parts.push(self.syn-infix-ws($.assign) ~ self.deparse($default)); } self.add-any-docs(@parts.join, $ast.WHY) } multi method deparse(RakuAST::Parameter::Slurpy:U $ --> '') { } multi method deparse(RakuAST::Parameter::Slurpy::Flattened:U $ --> Str:D) { $.slurpy-flattened } multi method deparse( RakuAST::Parameter::Slurpy::SingleArgument:U $ --> Str:D) { $.slurpy-single-argument } multi method deparse( RakuAST::Parameter::Slurpy::Unflattened:U $ --> Str:D) { $.slurpy-unflattened } multi method deparse(RakuAST::Parameter::Slurpy::Capture:U $ --> Str:D) { $.slurpy-capture } multi method deparse(RakuAST::ParameterTarget::Var:D $ast --> Str:D) { $ast.name } multi method deparse( RakuAST::ParameterTarget::Term:D $ast, :$slurpy --> Str:D) { ($slurpy === RakuAST::Parameter::Slurpy ?? '\\' !! '') ~ $ast.name.canonicalize } multi method deparse(RakuAST::ParameterDefaultThunk:D $ --> '') { } #- Po -------------------------------------------------------------------------- multi method deparse(RakuAST::PointyBlock:D $ast --> Str:D) { my str @parts = '->'; my $signature := $ast.signature; my $WHY := $ast.WHY; if $signature.parameters-initialized && $signature.parameters.first(*.WHY) { @parts.push("\n"); @parts = self.add-any-docs(@parts.join(' '), $WHY) ~ self.deparse($signature); } else { @parts.push(self.deparse($signature)) if $signature.parameters-initialized; if $WHY { $*DELIMITER = ""; @parts.push('{'); return self.add-any-docs(@parts.join(' '), $WHY) ~ self.deparse($ast.body, :multi).substr(2) # lose {\n } } @parts.push(self.deparse($ast.body)); @parts.join(' ') } multi method deparse(RakuAST::Postcircumfix::ArrayIndex:D $ast --> Str:D) { self.squarize($ast.index) ~ self.colonpairs($ast, 'adverb-pc') } multi method deparse(RakuAST::Postcircumfix::HashIndex:D $ast --> Str:D) { self.bracketize($ast.index) ~ self.colonpairs($ast, 'adverb-pc') } multi method deparse( RakuAST::Postcircumfix::LiteralHashIndex:D $ast --> Str:D) { self.deparse($ast.index) ~ self.colonpairs($ast, 'adverb-pc') } multi method deparse(RakuAST::Postfix:D $ast --> Str:D) { $ast.operator ~ self.colonpairs($ast, 'adverb-pc') } multi method deparse(RakuAST::Postfix::Power:D $ast --> Str:D) { $ast.power.Str(:superscript) } multi method deparse(RakuAST::Postfix::Vulgar:D $ast --> Str:D) { my $rat := $ast.vulgar; "$rat.numerator.Str(:superscript)/$rat.denominator.Str(:subscript)" } multi method deparse(RakuAST::Prefix:D $ast --> Str:D) { self.xsyn('prefix', $ast.operator) } #- Q --------------------------------------------------------------------------- multi method deparse(RakuAST::QuotedRegex:D $ast --> Str:D) { my str $adverbs = $ast.adverbs.map({ self.deparse($_, 'adverb-rx') }).join; ($ast.match-immediately ?? 'm' !! $adverbs ?? 'rx' !! '') ~ $adverbs ~ $.regex-open ~ self.deparse($ast.body) ~ $.regex-close } multi method deparse(RakuAST::QuotedString:D $ast --> Str:D) { my str $string = self.assemble-quoted-string($ast); if $ast.processors -> @processors { if @processors == 1 && @processors.head -> $processor { if %single-processor-prefix{$processor} -> str $p { ($p eq 'exec' && $ast.has-variables ?? 'qqx/' !! $p) ~ $string ~ '/' } else { NYI("Quoted string processor '$processor'").throw } } elsif @processors == 2 && !$ast.has-variables { my str $joined = @processors.join(' '); if $joined eq 'words val' { $.pointy-open ~ $string ~ $.pointy-close } elsif $joined eq 'quotewords val' { $.double-pointy-open ~ $string ~ $.double-pointy-close } else { self.multiple-processors($string, @processors) } } else { self.multiple-processors($string, @processors) } } else { self.hsyn('literal', '"' ~ $string ~ '"') } } multi method deparse(RakuAST::QuoteWordsAtom:D $ast --> Str:D) { self.deparse($ast.atom) } #- Regex ----------------------------------------------------------------------- multi method deparse( RakuAST::Regex::Anchor::BeginningOfString $ --> Str:D) { $.regex-beginning-of-string } multi method deparse(RakuAST::Regex::Anchor::EndOfString $ --> Str:D) { $.regex-end-of-string } multi method deparse( RakuAST::Regex::Anchor::BeginningOfLine $ --> Str:D) { $.regex-beginning-of-line } multi method deparse(RakuAST::Regex::Anchor::EndOfLine $ --> Str:D) { $.regex-end-of-line } multi method deparse(RakuAST::Regex::Anchor::LeftWordBoundary $ --> Str:D) { $.regex-left-word-boundary } multi method deparse( RakuAST::Regex::Anchor::RightWordBoundary $ --> Str:D) { $.regex-right-word-boundary } multi method deparse(RakuAST::Regex::Literal:D $ast --> Str:D) { self.quote-if-needed($ast.text) } multi method deparse(RakuAST::Regex::Alternation:D $ast --> Str:D) { self.branches($ast, $.regex-alternation) } #- Regex::Assertion ------------------------------------------------------------ multi method deparse(RakuAST::Regex::Assertion::Alias:D $ast --> Str:D) { '<' ~ $ast.name ~ '=' ~ self.deparse($ast.assertion).substr(1) } multi method deparse( RakuAST::Regex::Assertion::Callable:D $ast --> Str:D) { my $args := $ast.args; '<' ~ self.deparse($ast.callee) ~ ($args && $args.args ?? self.parenthesize($args) !! "") ~ '>' } multi method deparse( RakuAST::Regex::Assertion::CharClass:D $ast --> Str:D) { '<' ~ $ast.elements.map({ self.deparse($_) }).join(' ') ~ '>' } multi method deparse(RakuAST::Regex::Assertion::Fail $ --> Str:D) { $.regex-assertion-fail } multi method deparse( RakuAST::Regex::Assertion::InterpolatedBlock:D $ast --> Str:D) { NYI "DEPARSE of sequential interpolated block NYI" if $ast.sequential; '<' ~ self.deparse($ast.block).chomp ~ '>' } multi method deparse( RakuAST::Regex::Assertion::InterpolatedVar:D $ast --> Str:D) { NYI "DEPARSE of sequential interpolated block NYI" if $ast.sequential; '<' ~ self.deparse($ast.var) ~ '>' } multi method deparse( RakuAST::Regex::Assertion::Lookahead:D $ast --> Str:D) { ($ast.negated ?? ' Str:D) { ($ast.capturing ?? '<' !! '<.') ~ self.deparse($ast.name) ~ '>' } multi method deparse( RakuAST::Regex::Assertion::Named::Args:D $ast --> Str:D) { ($ast.capturing ?? '<' !! '<.') ~ self.deparse($ast.name) ~ self.parenthesize($ast.args) ~ '>' } multi method deparse( RakuAST::Regex::Assertion::Named::RegexArg:D $ast --> Str:D) { '<' ~ self.deparse($ast.name) ~ ' ' ~ self.deparse($ast.regex-arg) ~ '>' } multi method deparse(RakuAST::Regex::Assertion::Pass $ --> Str:D) { $.regex-assertion-pass } multi method deparse(RakuAST::Regex::Assertion::Recurse $ --> Str:D) { $.regex-assertion-recurse } multi method deparse( RakuAST::Regex::Assertion::PredicateBlock:D $ast --> Str:D) { '<' ~ ($ast.negated ?? '!' !! '?') ~ self.deparse($ast.block).chomp ~ '>' } #- Regex::B -------------------------------------------------------------------- multi method deparse( RakuAST::Regex::BackReference::Positional:D $ast --> Str:D) { '$' ~ $ast.index } multi method deparse( RakuAST::Regex::BackReference::Named:D $ast --> Str:D) { '$<' ~ $ast.name ~ '>' } # This candidate needed to represent *no* backtracking specification multi method deparse(RakuAST::Regex::Backtrack:U $ --> '') { } multi method deparse(RakuAST::Regex::Backtrack::Frugal:U $ --> Str:D) { $.regex-backtrack-frugal } multi method deparse(RakuAST::Regex::Backtrack::Greedy:U $ --> Str:D) { $.regex-backtrack-greedy } multi method deparse(RakuAST::Regex::Backtrack::Ratchet:U $ --> Str:D) { $.regex-backtrack-ratchet } multi method deparse( RakuAST::Regex::BacktrackModifiedAtom:D $ast --> Str:D) { self.deparse($ast.atom) ~ self.deparse($ast.backtrack) } multi method deparse(RakuAST::Regex::Block:D $ast --> Str:D) { self.deparse($ast.block).chomp } #- Regex::C -------------------------------------------------------------------- multi method deparse(RakuAST::Regex::CapturingGroup:D $ast --> Str:D) { self.parenthesize($ast.regex) } #- Regex::Charclass ------------------------------------------------------------ multi method deparse(RakuAST::Regex::CharClass::Any $ast --> Str:D) { $.regex-any } multi method deparse( RakuAST::Regex::CharClass::BackSpace:D $ast --> Str:D) { $ast.negated ?? '\\B' !! '\\b' } multi method deparse( RakuAST::Regex::CharClass::CarriageReturn:D $ast --> Str:D) { $ast.negated ?? '\\R' !! '\\r' } multi method deparse(RakuAST::Regex::CharClass::Digit:D $ast --> Str:D) { $ast.negated ?? '\\D' !! '\\d' } multi method deparse(RakuAST::Regex::CharClass::Escape:D $ast --> Str:D) { $ast.negated ?? '\\E' !! '\\e' } multi method deparse(RakuAST::Regex::CharClass::FormFeed:D $ast --> Str:D) { $ast.negated ?? '\\F' !! '\\f' } multi method deparse( RakuAST::Regex::CharClass::HorizontalSpace:D $ast --> Str:D) { $ast.negated ?? '\\H' !! '\\h' } multi method deparse(RakuAST::Regex::CharClass::Newline:D $ast --> Str:D) { $ast.negated ?? '\\N' !! '\\n' } multi method deparse(RakuAST::Regex::CharClass::Nul:D $ast --> '\0') { } multi method deparse(RakuAST::Regex::CharClass::Space:D $ast --> Str:D) { $ast.negated ?? '\\S' !! '\\s' } multi method deparse( RakuAST::Regex::CharClass::Specified:D $ast --> Str:D) { ($ast.negated ?? '\\C' !! '\\c') ~ '[' ~ $ast.characters.ords.map(*.uniname).join(', ') ~ ']' } multi method deparse(RakuAST::Regex::CharClass::Tab:D $ast --> Str:D) { $ast.negated ?? '\\T' !! '\\t' } multi method deparse( RakuAST::Regex::CharClass::VerticalSpace:D $ast --> Str:D) { $ast.negated ?? '\\V' !! '\\v' } multi method deparse(RakuAST::Regex::CharClass::Word:D $ast --> Str:D) { $ast.negated ?? '\\W' !! '\\w' } multi method deparse( RakuAST::Regex::CharClassElement::Enumeration:D $ast --> Str:D) { ($ast.negated ?? '-' !! '+') ~ '[' ~ $ast.elements.map({ self.deparse($_) }).join(' ') ~ ']' } multi method deparse( RakuAST::Regex::CharClassElement::Property:D $ast --> Str:D) { my str @parts; @parts.push($ast.negated ?? '-' !! '+'); @parts.push(':'); @parts.push('!') if $ast.inverted; @parts.push($ast.property); with $ast.predicate { if nqp::istype($_,RakuAST::StrLiteral) { @parts.push('<'); @parts.push(self.deparse-unquoted($_)); @parts.push('>'); } else { @parts.push(self.deparse($_)) } } @parts.join } multi method deparse( RakuAST::Regex::CharClassElement::Rule:D $ast --> Str:D) { ($ast.negated ?? '-' !! '+') ~ $ast.name } multi method deparse( RakuAST::Regex::CharClassEnumerationElement::Character:D $ast --> Str:D) { $ast.character } multi method deparse( RakuAST::Regex::CharClassEnumerationElement::Range:D $ast --> Str:D) { $ast.from.chr ~ '..' ~ $ast.to.chr } #- Regex::Co ------------------------------------------------------------------- multi method deparse(RakuAST::Regex::Conjunction:D $ast --> Str:D) { self.branches($ast, $.regex-conjunction) } #- Regex::G -------------------------------------------------------------------- multi method deparse(RakuAST::Regex::Group:D $ast --> Str:D) { self.squarize($ast.regex) } #- Regex::I -------------------------------------------------------------------- multi method deparse( RakuAST::Regex::InternalModifier:D $ast --> Str:D) { ':' ~ ($ast.negated ?? '!' !! '') ~ self.xsyn('adverb-rx', $ast.modifier) ~ ' ' } multi method deparse(RakuAST::Regex::Interpolation:D $ast --> Str:D) { ($ast.sequential ?? '|| ' !! '') ~ self.deparse($ast.var) } #- Regex::M -------------------------------------------------------------------- multi method deparse(RakuAST::Regex::MatchFrom:D $ --> Str:D) { $.regex-match-from } multi method deparse(RakuAST::Regex::MatchTo:D $ --> Str:D) { $.regex-match-to } #- Regex::N -------------------------------------------------------------------- multi method deparse(RakuAST::Regex::NamedCapture:D $ast --> Str:D) { '$<' ~ $ast.name ~ '>=' ~ self.deparse($ast.regex) } #- Regex::Q -------------------------------------------------------------------- multi method deparse(RakuAST::Regex::QuantifiedAtom:D $ast --> Str:D) { my str @parts = self.deparse($ast.atom), self.deparse($ast.quantifier); if $ast.separator -> $separator { @parts.push($ast.trailing-separator ?? '%% ' !! '% '); @parts.push(self.deparse($separator)); } @parts.join } multi method deparse( RakuAST::Regex::Quantifier::BlockRange:D $ast --> Str:D) { my $backtrack := $ast.backtrack; '**' ~ (self.deparse($backtrack) unless nqp::eqaddr( $ast.backtrack, RakuAST::Regex::Backtrack )) ~ ' ' ~ self.deparse($ast.block) } multi method deparse( RakuAST::Regex::Quantifier::OneOrMore:D $ast --> Str:D) { self.quantifier($ast, '+') } multi method deparse(RakuAST::Regex::Quantifier::Range:D $ast --> Str:D) { my str @parts = '**'; my $backtrack := $ast.backtrack; @parts.push(self.deparse($backtrack)) unless nqp::eqaddr($backtrack,RakuAST::Regex::Backtrack); @parts.push(' '); with $ast.min -> $min { @parts.push($min.Str); with $ast.max -> $max { if $min != $max { @parts.push('^') if $ast.excludes-min; @parts.push('..'); @parts.push('^') if $ast.excludes-max; @parts.push($max.Str); } } else { @parts.push('^') if $ast.excludes-min; @parts.push('..*'); } } else { @parts.push('^') if $ast.excludes-max; @parts.push($ast.max.Str); } @parts.join } multi method deparse( RakuAST::Regex::Quantifier::ZeroOrMore:D $ast --> Str:D) { self.quantifier($ast, '*') } multi method deparse( RakuAST::Regex::Quantifier::ZeroOrOne:D $ast --> Str:D) { self.quantifier($ast, '?') } multi method deparse(RakuAST::Regex::Quote:D $ast --> Str:D) { my str $quoted = self.deparse($ast.quoted); $quoted.chars > 2 ?? $quoted.starts-with('"') ?? $quoted.substr(1,$quoted.chars - 2) !! ('<{ ' ~ $quoted ~ ' }>') !! '' } #- Regex::S -------------------------------------------------------------------- multi method deparse(RakuAST::Regex::Sequence:D $ast --> Str:D) { $ast.terms.map({ nqp::istype($_,RakuAST::Regex::CharClass::BackSpace) ?? ('"' ~ self.deparse($_) ~ '"') !! self.deparse($_) }).join } multi method deparse( RakuAST::Regex::SequentialAlternation:D $ast --> Str:D) { self.branches($ast, $.regex-sequential-alternation) } multi method deparse( RakuAST::Regex::SequentialConjunction:D $ast --> Str:D) { self.branches($ast, $.regex-sequential-conjunction) } multi method deparse(RakuAST::Regex::Statement:D $ast --> Str:D) { ':' ~ self.deparse($ast.statement) ~ '; ' } #- Regex::W -------------------------------------------------------------------- multi method deparse(RakuAST::Regex::WithWhitespace:D $ast --> Str:D) { self.deparse($ast.regex) ~ " " } #- RegexD ---------------------------------------------------------------------- # also for ::TokenDeclaration and ::RuleDeclaration multi method deparse(RakuAST::RegexDeclaration:D $ast --> Str:D) { my str @parts = self.xsyn('routine', $ast.declarator); if $ast.multiness -> $multiness { @parts.unshift(self.syn-multi($multiness)); } my str $scope = $ast.scope; @parts.unshift(self.xsyn('scope', $scope)) if $scope ne 'has' && $scope ne $ast.default-scope; @parts.push(self.deparse($_)) with $ast.name; # at least one parameter with declarator doc my $signature := $ast.signature; if $signature.parameters.first(*.WHY) { @parts.push("(\n"); @parts.push(self.deparse($signature)); @parts.push(')'); } # no parameters with declarator doc else { @parts.push(self.parenthesize($signature)) if $signature.parameters-initialized; } if $ast.traits.map({self.deparse($_)}).join(' ') -> $traits { @parts.push($traits); } if $ast.WHY -> $WHY { @parts.push('{'); @parts = self.add-any-docs(@parts.join(' '), $WHY); @parts.push($*INDENT); } else { @parts.push('{ '); @parts = @parts.join(' '); } @parts.push(self.deparse($ast.body)); @parts.push('}'); @parts.join } #- S --------------------------------------------------------------------------- multi method deparse(RakuAST::SemiList:D $ast --> Str:D) { my @statements := $ast.statements; @statements == 1 ?? self.deparse(@statements.head.expression) !! @statements.map({ self.deparse($_) }).join($.list-infix-semi-colon) } multi method deparse(RakuAST::Signature:D $ast --> Str:D) { my str @parts; if $ast.parameters -> @parameters { # need special handling for declarator doc if @parameters.first(*.WHY) { my $last := @parameters.tail; my $*DELIMITER = $.list-infix-comma.trim ~ "\n"; my str @atoms; self.indent(' '); for @parameters -> $param { $*DELIMITER = "\n" if $param === $last; @atoms.push($*INDENT); @atoms.push(self.deparse($param)); } self.dedent(' '); @parts.push(@atoms.join); } # no special action else { my $*DELIMITER = $.list-infix-comma; @parts.push(@parameters.map({ self.deparse($_) }).join.chomp($.list-infix-comma)) } } with $ast.returns { @parts.push('-->'); @parts.push(self.deparse($_)); } @parts.join(' ') } #- Statement ------------------------------------------------------------------- multi method deparse(RakuAST::Statement::Catch:D $ast --> Str:D) { self.labels($ast) ~ self.syn-phaser('CATCH') ~ ' ' ~ self.deparse($ast.body) } multi method deparse(RakuAST::Statement::Control:D $ast --> Str:D) { self.labels($ast) ~ self.syn-phaser('CONTROL') ~ ' ' ~ self.deparse($ast.body) } multi method deparse(RakuAST::Statement::Default:D $ast --> Str:D) { self.labels($ast) ~ self.syn-block('default') ~ ' ' ~ self.deparse($ast.body) } multi method deparse(RakuAST::Statement::Elsif:D $ast --> Str:D) { self.conditional($ast, 'elsif') # cannot have labels } multi method deparse(RakuAST::Statement::Empty:D $ast --> Str:D) { self.labels($ast) ~ $*DELIMITER } multi method deparse(RakuAST::Statement::Expression:D $ast --> Str:D) { my @*HEREDOCS; my $expression := $ast.expression; my str @parts = self.deparse($expression); if $ast.condition-modifier -> $condition { @parts.push(self.deparse($condition)); } if $ast.loop-modifier -> $loop { @parts.push(self.deparse($loop)); } my $text := self.labels($ast) ~ @parts.join(' ') ~ (nqp::istype($expression,RakuAST::Doc::DeclaratorTarget) ?? "" !! $*DELIMITER ); @*HEREDOCS ?? $text ~ @*HEREDOCS.join !! $text } multi method deparse(RakuAST::Statement::For:D $ast --> Str:D) { my str @parts = self.syn-block('for'), self.deparse($ast.source), self.deparse($ast.body) ; if $ast.mode -> str $mode { @parts.unshift($mode) if $mode ne 'serial'; } self.labels($ast) ~ @parts.join(' ') } multi method deparse(RakuAST::Statement::Given:D $ast --> Str:D) { self.labels($ast) ~ self.syn-block('given') ~ ' ' ~ self.deparse($ast.source) ~ ' ' ~ self.deparse($ast.body) } # handling both ::If and ::With multi method deparse(RakuAST::Statement::IfWith:D $ast --> Str:D) { my str @parts = self.conditional($ast, $ast.IMPL-QAST-TYPE); my $INDENT := $*INDENT; if $ast.elsifs -> @elsifs { for @elsifs { @parts.push($INDENT); @parts.push(self.deparse($_)); } } if $ast.else -> $else { @parts.push($INDENT); @parts.push(self.syn-block('else')); @parts.push(' '); @parts.push(self.deparse($else)); @parts.push($.last-statement); } self.labels($ast) ~ @parts.join } multi method deparse(RakuAST::Statement::Import:D $ast --> Str:D) { my str @parts = self.xsyn('use','import'), self.deparse($ast.module-name); @parts.push(self.deparse($_)) with $ast.argument; self.labels($ast) ~ @parts.join(' ') ~ $*DELIMITER } multi method deparse(RakuAST::Statement::Loop:D $ast --> Str:D) { my $condition := $ast.setup ?? (' (' ~ self.deparse($ast.setup) ~ $.loop-separator ~ self.deparse($ast.condition) ~ $.loop-separator ~ self.deparse($ast.increment) ~ ') ' ) !! " "; self.labels($ast) ~ self.syn-block('loop') ~ $condition ~ self.deparse($ast.body) } multi method deparse( RakuAST::Statement::Loop::RepeatUntil:D $ast --> Str:D) { self.labels($ast) ~ self.simple-repeat($ast, 'until') } multi method deparse( RakuAST::Statement::Loop::RepeatWhile:D $ast --> Str:D) { self.labels($ast) ~ self.simple-repeat($ast, 'while') } multi method deparse(RakuAST::Statement::Loop::Until:D $ast --> Str:D) { self.labels($ast) ~ self.simple-loop($ast, 'until') } multi method deparse(RakuAST::Statement::Loop::While:D $ast --> Str:D) { self.labels($ast) ~ self.simple-loop($ast, 'while') } multi method deparse(RakuAST::Statement::Need:D $ast --> Str:D) { self.labels($ast) ~ self.xsyn('use', 'need') ~ ' ' ~ $ast.module-names.map({self.deparse($_)}).join($.list-infix-comma) ~ $*DELIMITER } multi method deparse(RakuAST::Statement::Orwith:D $ast --> Str:D) { self.conditional($ast, 'orwith') # cannot have labels } multi method deparse(RakuAST::Statement::Require:D $ast --> Str:D) { self.labels($ast) ~ self.xsyn('use', 'require') ~ ' ' ~ self.deparse($ast.module-name) } multi method deparse(RakuAST::Statement::Unless:D $ast --> Str:D) { self.labels($ast) ~ self.negated-conditional($ast, 'unless'); } multi method deparse(RakuAST::Statement::Use:D $ast --> Str:D) { self.use-no("use", $ast) ~ $*DELIMITER } multi method deparse(RakuAST::Statement::When:D $ast --> Str:D) { self.labels($ast) ~ self.syn-block('when') ~ ' ' ~ self.deparse($ast.condition) ~ ' ' ~ self.deparse($ast.body) } multi method deparse(RakuAST::Statement::Whenever:D $ast --> Str:D) { self.labels($ast) ~ self.syn-block('whenever') ~ ' ' ~ self.deparse($ast.trigger) ~ ' ' ~ self.deparse($ast.body) } multi method deparse(RakuAST::Statement::Without:D $ast --> Str:D) { self.labels($ast) ~ self.negated-conditional($ast, 'without'); } multi method deparse(RakuAST::StatementList:D $ast --> Str:D) { if $ast.statements -> @statements { my str @parts; my str $spaces = $*INDENT; my $last-statement := @statements.first({ !nqp::istype($_,RakuAST::Doc::Block) }, :end) // @statements.tail; my $code; my $*DELIMITER; for @statements -> $statement { $*DELIMITER = $statement === $last-statement ?? $.last-statement !! $.end-statement; my $deparsed := self.deparse($statement); $deparsed := $deparsed.chop(2) if $deparsed.ends-with("};\n"); @parts.push($spaces); @parts.push($deparsed); @parts.push("\n") if $deparsed.ends-with('}'); } @parts.join } else { '' } } #- Statement::Modifier --------------------------------------------------------- multi method deparse(RakuAST::StatementModifier::Given:D $ast --> Str:D) { self.statement-modifier('given', $ast) } multi method deparse(RakuAST::StatementModifier::If:D $ast --> Str:D) { self.statement-modifier('if', $ast) } multi method deparse( RakuAST::StatementModifier::For:D $ast --> Str:D) { self.statement-modifier('for', $ast) } multi method deparse(RakuAST::StatementModifier::For::Thunk:D $ --> '') { } multi method deparse(RakuAST::StatementModifier::Unless:D $ast --> Str:D) { self.statement-modifier('unless', $ast) } multi method deparse(RakuAST::StatementModifier::Until:D $ast --> Str:D) { self.statement-modifier('until', $ast) } multi method deparse(RakuAST::StatementModifier::When:D $ast --> Str:D) { self.statement-modifier('when', $ast) } multi method deparse(RakuAST::StatementModifier::While:D $ast --> Str:D) { self.statement-modifier('while', $ast) } multi method deparse(RakuAST::StatementModifier::With:D $ast --> Str:D) { self.statement-modifier('with', $ast) } multi method deparse(RakuAST::StatementModifier::Without:D $ast --> Str:D) { self.statement-modifier('without', $ast) } #- StatementPrefix ------------------------------------------------------------- # handles all statement prefixes multi method deparse(RakuAST::StatementPrefix:D $ast --> Str:D) { self.hsyn('stmt-prefix', self.xsyn('stmt-prefix', $ast.type)) ~ ' ' ~ self.deparse($ast.blorst).chomp } # handles most phasers multi method deparse(RakuAST::StatementPrefix::Phaser:D $ast --> Str:D) { my $*DELIMITER = ''; self.syn-phaser($ast.type) ~ ' ' ~ self.deparse($ast.blorst).chomp } multi method deparse(RakuAST::StatementPrefix::Phaser::First:D $ast --> Str:D) { my $*DELIMITER = ''; self.syn-phaser($ast.type) ~ ' ' ~ self.deparse($ast.original-blorst).chomp } multi method deparse( RakuAST::StatementPrefix::Phaser::Post:D $ast --> Str:D) { # POST phasers get extra code inserted at RakuAST level, which # wraps the original blorst into a statement in which the blorst # becomes the condition modifier my $expression := $ast.blorst.body.statement-list.statements.head .condition-modifier.expression; self.syn-phaser('POST') ~ ' ' ~ self.deparse( nqp::istype($expression,RakuAST::ApplyPostfix) ?? $expression.operand !! $expression ).chomp } multi method deparse( RakuAST::StatementPrefix::Phaser::Pre:D $ast --> Str:D) { # PRE phasers get extra code inserted at RakuAST level, which # wraps the original blorst into a statement in which the blorst # becomes the condition modifier my $expression := $ast.blorst.condition-modifier.expression; self.syn-phaser('PRE') ~ ' ' ~ self.deparse( nqp::istype($expression,RakuAST::ApplyPostfix) ?? $expression.operand !! $expression ).chomp } #- Stu ------------------------------------------------------------------------- multi method deparse(RakuAST::Stub:D $ast --> Str:D) { my str $hsyn = self.hsyn('stub', $ast.name); if $ast.args -> $real-args { $hsyn ~ ' ' ~ self.deparse($real-args) } else { $hsyn } } #- Su -------------------------------------------------------------------------- multi method deparse(RakuAST::Sub:D $ast --> Str:D) { my str @parts = self.syn-routine('sub'); if $ast.multiness -> $multiness { @parts.unshift(self.syn-multi($multiness)) } my str $scope = $ast.scope; @parts.unshift(self.xsyn('scope', $scope)) if $scope ne $ast.default-scope && ($ast.name || $scope ne 'anon'); if $ast.name -> $name { @parts.push(self.deparse($name)); } self.handle-signature($ast, @parts.join(' ')) } multi method deparse(RakuAST::Submethod:D $ast --> Str:D) { self.method($ast, 'submethod') } multi method deparse(RakuAST::Substitution:D $ast --> Str:D) { my str @parts = $ast.immutable ?? 'S' !! 's'; @parts.push(':samespace') if $ast.samespace; if $ast.adverbs -> @adverbs { @parts.push(self.deparse($_)) for @adverbs; } if $ast.infix -> $infix { @parts.push('{'); @parts.push(self.deparse($ast.pattern)); @parts.push('} '); @parts.push(self.deparse($infix)); @parts.push(' '); @parts.push(self.deparse($ast.replacement)); } else { @parts.push('/'); @parts.push(self.deparse($ast.pattern)); @parts.push('/'); @parts.push(self.deparse($ast.replacement).substr(1,*-1)); @parts.push('/'); } @parts.join } multi method deparse( RakuAST::SubstitutionReplacementThunk:D $ast --> Str:D) { self.deparse($ast.infix) } #- Term ------------------------------------------------------------------------ multi method deparse(RakuAST::Term::Capture:D $ast --> Str:D) { Q/\/ ~ self.parenthesize($ast.source) } multi method deparse(RakuAST::Term::EmptySet:D $ --> Str:D) { $.term-empty-set } multi method deparse(RakuAST::Term::HyperWhatever:D $ --> Str:D) { $.term-hyperwhatever } multi method deparse(RakuAST::Term::Name:D $ast --> Str:D) { self.deparse($ast.name) } multi method deparse(RakuAST::Term::Named:D $ast --> Str:D) { self.xsyn('term', $ast.name) } multi method deparse(RakuAST::Term::Rand:D $ --> Str:D) { self.xsyn('term', $.term-rand) } multi method deparse(RakuAST::Term::RadixNumber:D $ast --> Str:D) { # multi-part doesn't need to be checked, as it only involves # the legality of what is put in .value. So deparsing .value # is enough ':' ~ $ast.radix ~ self.deparse($ast.value) } multi method deparse(RakuAST::Term::Reduce:D $ast --> Str:D) { my $args := $ast.args; ($ast.triangle ?? $.reduce-triangle !! $.reduce-open) ~ self.deparse($ast.infix) ~ $.reduce-close ~ ($args.defined && $args.elems == 1 ?? self.deparse($args) !! self.parenthesize($args) ) } multi method deparse(RakuAST::Term::Self:D $ --> Str:D) { self.hsyn('invocant', self.xsyn('term', $.term-self)) } multi method deparse(RakuAST::Term::TopicCall:D $ast --> Str:D) { self.deparse($ast.call) } multi method deparse(RakuAST::Term::Whatever:D $ --> Str:D) { self.hsyn('var-term', $.term-whatever) } multi method deparse(RakuAST::WhateverCode::Argument:D $ --> Str:D) { self.hsyn('var-term', $.term-whatever) } #- Ternary --------------------------------------------------------------------- multi method deparse(RakuAST::Ternary:D $ast --> Str:D) { my $heredoc := $*HEREDOC; # no place to store heredocs, make one, try again, add them at the end if nqp::istype($heredoc,Failure) { my $*TERNARY = ""; # indenting for nested ternaries $heredoc := my $*HEREDOC := my str @; my $deparsed := self.deparse($ast); return nqp::elems($heredoc) ?? $deparsed ~ $heredoc.join ~ "\n" !! $deparsed } # already have a place to store heredocs my $then := $ast.then; my $else := $ast.else; my $intern := $*TERNARY; my $nested := $intern || nqp::istype($then,RakuAST::Ternary) || nqp::istype($else,RakuAST::Ternary); my str $indent = $nested ?? "\n" ~ ($intern ~= ' ').chop # assume 1 space left of ?? !! !! ''; my str @parts = self.deparse($ast.condition), $indent, self.hsyn('ternary', $.ternary1); # helper sub for a ternary part sub deparse-part($node --> Nil) { if nqp::istype($node,RakuAST::Heredoc) { my str ($header,$rest) = self.deparse($node).split("\n",2); @parts.push($header); $heredoc.push("\n" ~ $rest.chomp); } else { @parts.push(self.deparse($node)); } } deparse-part($then); @parts.push($indent); @parts.push(self.hsyn('ternary', $.ternary2)); deparse-part($else); @parts.join } #- Trait ----------------------------------------------------------------------- multi method deparse(RakuAST::Trait::Is:D $ast --> Str:D) { my str $base = self.syn-trait($ast.IMPL-TRAIT-NAME) ~ ' ' ~ self.xsyn('trait-is', self.deparse($ast.name)); with $ast.argument { $base ~ self.deparse($_) } else { $base } } multi method deparse(RakuAST::Trait::Type:D $ast --> Str:D) { self.syn-trait($ast.IMPL-TRAIT-NAME) ~ ' ' ~ self.deparse($ast.type) } #- Type ------------------------------------------------------------------------ multi method deparse(RakuAST::Type::Capture:D $ast --> Str:D) { '::' ~ self.deparse($ast.name) } multi method deparse(RakuAST::Type::Coercion:D $ast --> Str:D) { my str $constraint = self.deparse($ast.constraint); $constraint = "" if $constraint eq 'SETTING::'; self.deparse($ast.base-type) ~ "($constraint)" } multi method deparse(RakuAST::Type::Definedness:D $ast --> Str:D) { self.deparse($ast.base-type.name) ~ ($ast.definite ?? ':D' !! ':U') } multi method deparse(RakuAST::Type::Enum:D $ast --> Str:D) { my str @parts = self.syn-typer('enum'); my str $scope = $ast.scope; @parts.unshift(self.syn-scope($scope)) if $scope && $scope ne $ast.default-scope; @parts.unshift(self.deparse($_)) with $ast.of; @parts.push(self.deparse($_)) with $ast.name; if $ast.clean-clone.traits -> @traits { @parts.push(self.deparse($_)) for @traits; } @parts.push(self.deparse($ast.term)); self.add-any-docs(@parts.join(' '), $ast.WHY) } multi method deparse(RakuAST::Type::Parameterized:D $ast --> Str:D) { my str $args = self.deparse($ast.args); self.deparse($ast.base-type) ~ ($args ?? "[$args]" !! "") } multi method deparse(RakuAST::Type::Setting:D $ast --> Str:D) { my str @parts = nqp::split('::',self.deparse($ast.name)); my str $root = @parts.shift; 'SETTING::<' ~ $root ~ '>' ~ @parts.map({ '.WHO<' ~ $_ ~ '>' }).join } multi method deparse(RakuAST::Type::Simple:D $ast --> Str:D) { self.deparse($ast.name) } multi method deparse(RakuAST::Type::Subset:D $ast --> Str:D) { my str @parts = self.syn-typer('subset'); my str $scope = $ast.scope; @parts.unshift(self.syn-scope($scope)) if $scope && $scope ne $ast.default-scope; @parts.push(self.deparse($ast.name)); @parts.push(self.deparse($_)) with $ast.of; @parts.push(self.deparse($_)) for $ast.traits; @parts.push('where ' ~ self.deparse($_)) with $ast.where; self.add-any-docs(@parts.join(' '), $ast.WHY) } #- Var ------------------------------------------------------------------------- multi method deparse(RakuAST::Var::Attribute:D $ast --> Str:D) { self.hsyn('var-attribute', $ast.name) } multi method deparse(RakuAST::Var::Compiler::File:D $ast --> Str:D) { self.hsyn('var-compile',$.var-compiler-file) } multi method deparse(RakuAST::Var::Compiler::Line:D $ast --> Str:D) { self.hsyn('var-compile', $.var-compiler-line) } multi method deparse(RakuAST::Var::Compiler::Lookup:D $ast --> Str:D) { self.hsyn('var-compile', $ast.name) } multi method deparse(RakuAST::Var::Doc:D $ast --> Str:D) { self.hsyn('var-rakudoc', '$=' ~ $ast.name) } multi method deparse(RakuAST::Var::Dynamic:D $ast --> Str:D) { self.hsyn('var-dynamic', $ast.name) } multi method deparse(RakuAST::Var::Lexical:D $ast --> Str:D) { my $name := $ast.name; self.hsyn('var-lexical', $name) } multi method deparse(RakuAST::Var::Lexical::Setting:D $ast --> Str:D) { self.hsyn('var-setting', 'SETTING::<' ~ $ast.name ~ '>') } multi method deparse(RakuAST::Var::NamedCapture:D $ast --> Str:D) { self.hsyn('cap-named', '$' ~ self.deparse($ast.index)) } multi method deparse(RakuAST::Var::Package:D $ast --> Str:D) { self.hsyn('var-package', $ast.sigil ~ self.deparse($ast.name)) } multi method deparse(RakuAST::Var::PositionalCapture:D $ast --> Str:D) { self.hsyn('cap-positional', '$' ~ $ast.index.Str) } #- VarDeclaration -------------------------------------------------------------- multi method deparse(RakuAST::VarDeclaration::Anonymous:D $ast --> Str:D) { my str $sigil = $ast.sigil; my str $scope = $ast.scope; $scope eq 'state' ?? $sigil !! self.xsyn('scope', $scope) ~ ' ' ~ $sigil } multi method deparse(RakuAST::VarDeclaration::Auto:D $ast --> Str:D) { self.deparse(RakuAST::Var::Lexical.new($ast.name)) } multi method deparse(RakuAST::VarDeclaration::Constant:D $ast --> Str:D) { my str @parts; my str $scope = $ast.scope; @parts.push(self.syn-scope($scope)) if $scope ne $ast.default-scope; @parts.push(self.syn-type($_)) with $ast.type; @parts.push(self.xsyn('scope', 'constant')); @parts.push($ast.name); if $ast.traits -> @traits { @parts.push(self.deparse($_)) for @traits; } @parts.push(self.deparse($ast.initializer).trim-leading); @parts.join(' '); } multi method deparse(RakuAST::VarDeclaration::Implicit:D $ast --> Str:D) { self.hsyn('var-implicit', $ast.name) } multi method deparse( RakuAST::VarDeclaration::Implicit::Constant:D $ast --> Str:D) { (self.hsyn('scope-my', self.xsyn('scope', 'my')), self.hsyn('scope-constant', self.xsyn('scope', 'constant')), self.hsyn('var-term', $ast.name), self.hsyn('infix', '='), $ast.value.raku ).join(' ') } multi method deparse( RakuAST::VarDeclaration::Placeholder::Named:D $ast --> Str:D) { self.hsyn('var-placeholder', .substr(0, 1) ~ ':' ~ .substr(1)) given $ast.lexical-name } multi method deparse( RakuAST::VarDeclaration::Placeholder::Positional:D $ast --> Str:D) { self.hsyn('var-placeholder', .substr(0, 1) ~ '^' ~ .substr(1)) given $ast.lexical-name } multi method deparse( RakuAST::VarDeclaration::Placeholder::SlurpyArray:D $ --> Str:D) { self.hsyn('var-placeholder', '@_') } multi method deparse( RakuAST::VarDeclaration::Placeholder::SlurpyHash:D $ --> Str:D) { self.hsyn('var-placeholder', '%_') } multi method deparse(RakuAST::VarDeclaration::Signature:D $ast --> Str:D) { my str @parts = self.syn-scope($ast.scope); @parts.push(self.syn-type($_)) with $ast.type; @parts.push('(' ~ self.deparse($ast.signature) ~ ')'); if $ast.initializer -> $initializer { @parts.push(self.deparse($initializer)); } @parts.join(' ') } multi method deparse(RakuAST::VarDeclaration::Simple:D $ast --> Str:D) { my str $scope = $ast.scope; my str @parts; @parts.push(self.syn-scope($ast.scope)); @parts.push(' '); if $ast.type -> $type { @parts.push(self.syn-type($type)); @parts.push(' '); } my str $twigil = $ast.twigil; @parts.push( self.hsyn(%twigil2type{$twigil} // 'var-lexical', $ast.name) ); if $ast.traits.grep({ nqp::not_i( nqp::istype($_,RakuAST::Trait::Will) && .type eq 'build' ) }) -> @traits { for @traits { @parts.push(' '); @parts.push(self.deparse($_)); } } if $ast.initializer -> $initializer { @parts.push(self.deparse($initializer)); } self.add-any-docs(@parts.join, $ast.WHY) } multi method deparse(RakuAST::VarDeclaration::Term:D $ast --> Str:D) { my str @parts; @parts.push(self.syn-scope($ast.scope)); @parts.push(' '); if $ast.type -> $type { @parts.push(self.syn-type($type)); @parts.push(' '); } @parts.push(Q/\/); @parts.push(self.hsyn('var-term', self.deparse($ast.name))); @parts.push(self.deparse($ast.initializer)); @parts.join } } nqp::bindhllsym('Raku', 'DEPARSE', RakuAST::Deparse); #line 1 SETTING::src/core.c/RakuAST/Raku.rakumod # This augments the RakuAST::Node class so that all of its subclasses can # generate sensible .raku output *WITHOUT* having to specify that in the # RakuAST bootstrap. # # In it, it provides a new ".raku" proto method that will catch any # unimplemented classes in a sensible way. augment class RakuAST::Node { # Allow calling .EVAL on any RakuAST::Node method EVAL(RakuAST::Node:D: *%opts) { use MONKEY-SEE-NO-EVAL; EVAL self, context => CALLER::LEXICAL::, |%opts } proto method raku(RakuAST::Node:) { CATCH { when X::Multi::NoMatch | X::Multi::Ambiguous { die "No .raku method implemented for {self.^name} objects yet"; } } if nqp::istype($*INDENT,Failure) { my $*INDENT = ""; {*} } else { {*} } } #------------------------------------------------------------------------------- # Helper subs my $spaces = ' '; our sub indent(--> Str:D) { $_ = $_ ~ $spaces given $*INDENT } our sub dedent(--> Str:D) { $_ = .chomp($spaces) given $*INDENT } our sub rakufy($value) { if nqp::istype($value,List) && $value -> @elements { if nqp::istype(@elements.are,Int) { "(@elements.join(',')" ~ (@elements == 1 ?? ',)' !! ')') } else { indent; my str $list = @elements.map({ $*INDENT ~ .raku }).join(",\n"); dedent; "(\n$list,\n$*INDENT)" } } else { nqp::istype($value,Bool) ?? ($value.defined ?? $value ?? "True" !! "False" !! 'Bool') !! $value.raku } } #------------------------------------------------------------------------------- # Private helper methods method !none() { self.^name ~ '.new' } method !literal($value) { self.^name ~ '.new(' ~ nqp::decont($value).raku ~ ')'; } method !positional($value) { indent; my str $raku = $*INDENT ~ $value.raku; dedent; self.^name ~ ".new(\n$raku\n$*INDENT)" } method !positionals(@values) { if @values { indent; my str $parts = @values.map({ $*INDENT ~ rakufy($_) }).join(",\n"); dedent; self.^name ~ ".new(\n$parts\n$*INDENT)" } else { self.^name ~ '.new()' } } method !nameds(*@names) { sub as-class(str $name, str $raku) { my class Rakufy-as { has str $.raku } Pair.new($name, Rakufy-as.new(:$raku)) } my $special := BEGIN nqp::hash( 'abbreviated', -> { :abbreviated if self.abbreviated && !self.directive }, 'adverbs', -> { my $adverbs := nqp::decont(self.adverbs); :$adverbs if $adverbs }, 'args', -> { my $args := self.args; :$args if $args && $args.args }, 'atoms', -> { my $atoms := nqp::decont(self.atoms); :$atoms if $atoms }, 'backtrack', -> { my $backtrack := self.backtrack; as-class('backtrack', $backtrack.^name) unless nqp::eqaddr($backtrack,RakuAST::Regex::Backtrack) }, 'capturing', -> { :capturing if self.capturing }, 'cells', -> { if self.cells -> @cells is copy { :@cells } }, 'colonpairs', -> { if self.colonpairs -> @colonpairs is copy { :@colonpairs } }, 'column-dividers', -> { if self.column-dividers -> $column-dividers { :$column-dividers } }, 'column-offsets', -> { if self.column-offsets -> @column-offsets is copy { :@column-offsets } }, 'config', -> { my $config := nqp::decont(self.config); :config($config.Hash) if $config }, 'directive', -> { :directive if self.directive }, 'dwim-left', -> { :dwim-left if self.dwim-left }, 'dwim-right', -> { :dwim-right if self.dwim-right }, 'elsifs', -> { my $elsifs := nqp::decont(self.elsifs); :$elsifs if $elsifs }, 'excludes-max', -> { :excludes-max if self.excludes-max }, 'excludes-min', -> { :excludes-min if self.excludes-min }, 'for', -> { :for if self.for }, 'how', -> { my $how := self.how; as-class('how', $how.^name.subst("Perl6::")) unless nqp::eqaddr($how,self.default-how) }, 'implicit-topic', -> { :implicit-topic if self.implicit-topic }, 'inverted', -> { :inverted if self.inverted }, 'labels', -> { my $labels := nqp::decont(self.labels); :$labels if $labels }, 'leading', -> { my $leading := nqp::decont(self.leading); :$leading if $leading }, 'level', -> { my $level := self.level; :$level if $level }, 'margin', -> { my $margin := self.margin; :$margin if $margin }, 'match-immediately', -> { :match-immediately if self.match-immediately }, 'meta', -> { my $meta := self.meta; :$meta if $meta }, 'module-names', -> { my $module-names := nqp::decont(self.module-names); :$module-names if $module-names }, 'negated', -> { :negated if self.negated }, 'off', -> { :off if self.off }, 'paragraphs', -> { my @paragraphs := self.paragraphs; :@paragraphs if @paragraphs.elems }, 'processors', -> { my @processors := self.processors; :@processors if @processors.elems }, 'scope', -> { my $scope := self.scope; :$scope if $scope ne self.default-scope }, 'separator', -> { my $separator := self.separator; :$separator if $separator }, 'sigil', -> { my $sigil := self.sigil; :$sigil if $sigil }, 'signature', -> { my $signature := self.signature; :$signature if $signature && ($signature.parameters.elems || $signature.returns) }, 'slurpy', -> { my $slurpy := self.slurpy; as-class('slurpy', $slurpy.^name) unless nqp::eqaddr($slurpy,RakuAST::Parameter::Slurpy) }, 'trailing', -> { my $trailing := nqp::decont(self.trailing); :$trailing if $trailing }, 'trailing-separator', -> { :trailing-separator if self.trailing-separator }, 'traits', -> { my $traits := nqp::decont(self.traits); :$traits if $traits }, 'twigil', -> { my $twigil := self.twigil; :$twigil if $twigil } ); indent; my @pairs = @names.map: -> $method { if nqp::istype($method,Pair) { $method } elsif nqp::atkey($special,$method) -> &handle { handle() } else { my $object := self."$method"(); Pair.new($method, $object) if nqp::isconcrete($object) } } @pairs.append: %_; my $format := "$*INDENT%-@pairs.map(*.key.chars).max()s => %s"; my str $args = @pairs.map({ sprintf($format, .key, rakufy(.value)) }).join(",\n"); dedent; self.^name ~ ($args ?? ".new(\n$args\n$*INDENT)" !! '.new') } method !add-WHY($raku) { (my $WHY := self.WHY) ?? $raku ~ $WHY.raku(:declarator-docs) !! $raku } #- A --------------------------------------------------------------------------- multi method raku(RakuAST::ApplyInfix:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::ApplyDottyInfix:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::ApplyListInfix:D: --> Str:D) { my str @parts = "RakuAST::ApplyListInfix.new("; indent; @parts.push: $*INDENT ~ "infix => " ~ rakufy(self.infix) ~ ","; if self.operands -> @operands { @parts.push: $*INDENT ~ "operands => " ~ rakufy(@operands); } else { @parts.push: $*INDENT ~ "operands => ()"; } dedent; @parts.push: $*INDENT ~ ")"; @parts.join("\n") } multi method raku(RakuAST::ApplyPostfix:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::ApplyPrefix:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::ArgList:D: --> Str:D) { self!positionals(self.args) } multi method raku(RakuAST::Assignment:D: --> Str:D) { self.item ?? self!literal(Pair.new("item",True)) !! self!none } #- B --------------------------------------------------------------------------- multi method raku(RakuAST::Block:D: --> Str:D) { self!add-WHY: self!nameds: } multi method raku(RakuAST::Blockoid:D: --> Str:D) { (my $statements := self.statement-list) ?? self!positional($statements) !! self!none } #- Call ------------------------------------------------------------------------ # Generic RakuAST::Call::xxx handler multi method raku(RakuAST::Call:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Call::Term:D: --> Str:D) { self!nameds: } #- Circumfix ------------------------------------------------------------------- multi method raku(RakuAST::Circumfix::ArrayComposer:D: --> Str:D) { self!positional(self.semilist) } multi method raku(RakuAST::Circumfix::HashComposer:D: --> Str:D) { self!positional(self.expression) } multi method raku(RakuAST::Circumfix::Parentheses:D: --> Str:D) { self!positional(self.semilist) } #- ColonPair ------------------------------------------------------------------- multi method raku(RakuAST::ColonPair::False:D: --> Str:D) { self!literal(self.key) } multi method raku(RakuAST::ColonPair::Number:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::ColonPair::True:D: --> Str:D) { self!literal(self.key) } multi method raku(RakuAST::ColonPair::Value:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::ColonPair::Variable:D: --> Str:D) { self!nameds: } #- Co -------------------------------------------------------------------------- multi method raku(RakuAST::CompUnit:D: :$compunit --> Str:D) { # XXX need to handle .finish-content and other arguments self!nameds: } multi method raku(RakuAST::Contextualizer:D: --> Str:D) { self!positional(self.target) } #- D --------------------------------------------------------------------------- multi method raku(RakuAST::Declaration:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Declaration::ResolvedConstant:D: --> Str:D) { self!literal(self.compile-time-value) } #- Doc ------------------------------------------------------------------------- multi method raku(RakuAST::Doc::Block:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Doc::Declarator:D: --> Str:D) { self!nameds: } multi method raku( RakuAST::Doc::Declarator:D: :$declarator-docs! --> Str:D) { self!nameds().subst( 'RakuAST::Doc::Declarator.new(', '.declarator-docs(' ) } multi method raku(RakuAST::Doc::Markup:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Doc::Paragraph:D: --> Str:D) { self!positionals(self.atoms) } #- Dot ------------------------------------------------------------------------- # Handles all of the RakuAST::DottyInfix::xxx classes multi method raku(RakuAST::DottyInfixish:D: --> Str:D) { self!none } #- E --------------------------------------------------------------------------- multi method raku(RakuAST::Expression:U: --> '') { } #- F --------------------------------------------------------------------------- multi method raku(RakuAST::FatArrow:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::FunctionInfix:D: --> Str:D) { self!positional(self.function) } #- H --------------------------------------------------------------------------- multi method raku(RakuAST::Heredoc:D: --> Str:D) { self!nameds: } #- I --------------------------------------------------------------------------- # Also for ::FlipFlop multi method raku(RakuAST::Infix:D: --> Str:D) { self!literal(self.operator) } multi method raku(RakuAST::Initializer::Assign:D: --> Str:D) { self!positional(self.expression) } multi method raku(RakuAST::Initializer::Bind:D: --> Str:D) { self!positional(self.expression) } multi method raku(RakuAST::Initializer::CallAssign:D: --> Str:D) { self!positional(self.postfixish) } #- L --------------------------------------------------------------------------- multi method raku(RakuAST::Label:D: --> Str:D) { self!nameds: } # handles all RakuAST::xxxLiteral classes multi method raku(RakuAST::Literal:D: --> Str:D) { self!literal(self.value) } #- M --------------------------------------------------------------------------- # Generic handling of all other RakuAST::MetaInfix::xxx classes multi method raku(RakuAST::MetaInfix:D: --> Str:D) { self!positional(self.infix) } multi method raku(RakuAST::MetaInfix::Hyper:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Method:D: --> Str:D) { my str @nameds = 'name'; @nameds.unshift("private") if self.private; @nameds.unshift("meta") if self.meta; @nameds.unshift("multiness") if self.multiness; @nameds.unshift("scope") if self.scope ne self.default-scope; @nameds.push("signature") if self.signature.parameters-initialized; @nameds.append: ; self!add-WHY(self!nameds(@nameds)) } #- N --------------------------------------------------------------------------- multi method raku(RakuAST::Name:D: --> Str:D) { my @parts := self.parts; if nqp::istype(@parts.are, RakuAST::Name::Part::Simple) { self.^name ~ (@parts.elems == 1 ?? ".from-identifier(@parts.head.name.raku())" !! ".from-identifier-parts(@parts.map(*.name.raku).join(','))" ) } else { self!positionals(@parts) } } multi method raku(RakuAST::Nqp:D: --> Str:D) { my @args := self.args.args; @args ?? self!positionals( (self.op,|@args) ) !! self!literal(self.op) } multi method raku(RakuAST::Nqp::Const:D: --> Str:D) { $*INDENT ~ 'RakuAST::Nqp::Const.new(' ~ self.name.raku ~ ')' } #- O --------------------------------------------------------------------------- multi method raku(RakuAST::OnlyStar:D: --> Str:D) { self!none } #- P --------------------------------------------------------------------------- multi method raku(RakuAST::Package:D: --> Str:D) { my $signature := self.body.signature; my $self := self; if self.declarator eq 'role' { $self := nqp::clone(self); my $statements := self.body.body.statement-list.statements; nqp::bindattr($self, RakuAST::Package, '$!body', $statements.elems == 1 ?? RakuAST::Block !! RakuAST::Block.new( body => RakuAST::Blockoid.new( RakuAST::StatementList.new( |$statements.head(*-1) # lose fabricated return value ) ) ) ); } self!add-WHY: $self!nameds: , (parameterization => $signature if $signature && $signature.parameters.elems) } multi method raku(RakuAST::Pragma:D: --> Str:D) { self!nameds: } #- Parameter ------------------------------------------------------------------- multi method raku(RakuAST::Parameter:D: --> Str:D) { my str @nameds; @nameds.push("type") if self.type && self.type.DEPARSE ne 'Any'; @nameds.push("names") if self.names.elems; @nameds.push("type-captures") if self.type-captures.elems; @nameds.append: < target optional slurpy traits default where sub-signature value >; self!add-WHY: self!nameds: @nameds; } # Generic handler for all RakuAST::Parameter::Slurpy::xxx classes multi method raku(RakuAST::Parameter::Slurpy:U: --> Str:D) { self.^name } multi method raku(RakuAST::ParameterTarget::Var:D: --> Str:D) { self!literal(self.name) } multi method raku(RakuAST::ParameterTarget::Term:D: --> Str:D) { self!positional(self.name) } multi method raku(RakuAST::ParameterDefaultThunk:D: --> '') { } # XXX #- Po -------------------------------------------------------------------------- multi method raku(RakuAST::PointyBlock:D: --> Str:D) { self!add-WHY: self!nameds: self.signature.parameters-initialized ?? !! } multi method raku(RakuAST::Postcircumfix::ArrayIndex:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Postcircumfix::HashIndex:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Postcircumfix::LiteralHashIndex:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Postfix:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Postfix::Power:D: --> Str:D) { self!literal(self.power) } multi method raku(RakuAST::Postfix::Vulgar:D: --> Str:D) { self!literal(self.vulgar) } multi method raku(RakuAST::Prefix:D: --> Str:D) { self!literal(self.operator) } #- Q --------------------------------------------------------------------------- multi method raku(RakuAST::QuotedRegex:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::QuotedString:D: --> Str:D) { my str @parts = "RakuAST::QuotedString.new("; indent; if self.processors -> @processors { @parts.push: $*INDENT ~ "processors => <@processors[]>,"; } @parts.push: $*INDENT ~ "segments => " ~ rakufy(self.segments); dedent; @parts.push: $*INDENT ~ ")"; @parts.join("\n") } multi method raku(RakuAST::QuoteWordsAtom:D: --> Str:D) { self!positional(self.atom) } #- Regex ----------------------------------------------------------------------- # Generic handling of all RakuAST::Regex::Anchor::xxx classes multi method raku(RakuAST::Regex::Anchor:D: --> Str:D) { self!none } multi method raku(RakuAST::Regex::Literal:D: --> Str:D) { self!literal(self.text) } multi method raku(RakuAST::Regex::Alternation:D: --> Str:D) { self!positionals(self.branches) } #- Regex::Assertion ------------------------------------------------------------ multi method raku(RakuAST::Regex::Assertion::Alias:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Assertion::Callable:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Assertion::CharClass:D: --> Str:D) { self!positionals(self.elements) } multi method raku(RakuAST::Regex::Assertion::Fail:D: --> Str:D) { self!none } multi method raku( RakuAST::Regex::Assertion::InterpolatedBlock:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Assertion::InterpolatedVar:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Assertion::Lookahead:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Assertion::Named:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Assertion::Named::Args:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Assertion::Named::RegexArg:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Assertion::Pass:D: --> Str:D) { self!none } multi method raku(RakuAST::Regex::Assertion::PredicateBlock:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Assertion::Recurse:D: --> Str:D) { self!positional(self.node) } #- Regex::B -------------------------------------------------------------------- multi method raku(RakuAST::Regex::BackReference::Positional:D: --> Str:D) { self!positional(self.index) } multi method raku(RakuAST::Regex::BackReference::Named:D: --> Str:D) { self!positional(self.name) } # Generix handling of all RakuAST::Regex::Backtrack::xxx classes multi method raku(RakuAST::Regex::Backtrack:U: --> Str:D) { self.^name } multi method raku(RakuAST::Regex::BacktrackModifiedAtom:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Block:D: --> Str:D) { self!positional(self.block) } #- Regex::C -------------------------------------------------------------------- multi method raku(RakuAST::Regex::CapturingGroup:D: --> Str:D) { self!positional(self.regex) } #- Regex::Charclass ------------------------------------------------------------ # Generic handler for most RakuAST::Regex::CharClass::xxx classes multi method raku(RakuAST::Regex::CharClass:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::CharClass::Any:D: --> Str:D) { self!none } multi method raku(RakuAST::Regex::CharClass::Nul:D: --> Str:D) { self!none } multi method raku(RakuAST::Regex::CharClass::Specified:D: --> Str:D) { self!nameds: } multi method raku( RakuAST::Regex::CharClassElement::Enumeration:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::CharClassElement::Property:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::CharClassElement::Rule:D: --> Str:D) { self!nameds: } multi method raku( RakuAST::Regex::CharClassEnumerationElement::Character:D: --> Str:D) { self!positional(self.character) } multi method raku( RakuAST::Regex::CharClassEnumerationElement::Range:D: --> Str:D) { self!nameds: } #- Regex::Co ------------------------------------------------------------------- multi method raku(RakuAST::Regex::Conjunction:D: --> Str:D) { self!positionals(self.branches) } #- Regex::D -------------------------------------------------------------------- multi method raku(RakuAST::RegexDeclaration:D: --> Str:D) { my str @nameds = 'name'; @nameds.unshift("scope") if self.scope ne self.default-scope; @nameds.push("signature") if self.signature.parameters-initialized; @nameds.append: ; self!add-WHY: self!nameds: @nameds; } #- Regex::G -------------------------------------------------------------------- multi method raku(RakuAST::Regex::Group:D: --> Str:D) { self!positional(self.regex) } #- Regex::I -------------------------------------------------------------------- # Generic handler for all RakuAST::Regex::InternalModifier::xxx classes multi method raku( RakuAST::Regex::InternalModifier:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Interpolation:D: --> Str:D) { self!nameds: } #- Regex::M -------------------------------------------------------------------- multi method raku(RakuAST::Regex::MatchFrom:D: --> Str:D) { self!none } multi method raku(RakuAST::Regex::MatchTo:D: --> Str:D) { self!none } #- Regex::N -------------------------------------------------------------------- multi method raku(RakuAST::Regex::NamedCapture:D: --> Str:D) { self!nameds: } #- Regex::Q -------------------------------------------------------------------- multi method raku(RakuAST::Regex::QuantifiedAtom:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Quantifier::BlockRange:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Quantifier::OneOrMore:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Quantifier::Range:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Quantifier::ZeroOrMore:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Quantifier::ZeroOrOne:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Regex::Quote:D: --> Str:D) { self!positional(self.quoted) } #- Regex::S -------------------------------------------------------------------- multi method raku(RakuAST::Regex::Sequence:D: --> Str:D) { self!positionals(self.terms) } multi method raku(RakuAST::Regex::SequentialAlternation:D: --> Str:D) { self!positionals(self.branches) } multi method raku(RakuAST::Regex::SequentialConjunction:D: --> Str:D) { self!positionals(self.branches) } multi method raku(RakuAST::Regex::Statement:D: --> Str:D) { self!positional(self.statement) } #- Regex::W -------------------------------------------------------------------- multi method raku(RakuAST::Regex::WithWhitespace:D: --> Str:D) { self!positional(self.regex) } #- S --------------------------------------------------------------------------- multi method raku(RakuAST::SemiList:D: --> Str:D) { self!positionals(self.statements) } multi method raku(RakuAST::Signature:D: --> Str:D) { self!nameds: } #- Statement ------------------------------------------------------------------- multi method raku(RakuAST::Statement::Catch:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Control:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Default:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Empty:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Expression:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::For:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Given:D: --> Str:D) { self!nameds: } # Handling both ::If and ::With multi method raku(RakuAST::Statement::IfWith:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Import:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Loop:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Loop::RepeatUntil:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Loop::RepeatWhile:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Loop::Until:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Loop::While:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Need:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Require:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Unless:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Use:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::When:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Whenever:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Statement::Without:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::StatementList:D: --> Str:D) { self!positionals(self.statements) } #- Statement::Modifier --------------------------------------------------------- # Generic handler for most RakuAST::StatementModifier::xxx classes multi method raku(RakuAST::StatementModifier:D: --> Str:D) { self!positional(self.expression) } multi method raku(RakuAST::StatementModifier::For::Thunk:D: --> Str:D) { self!none } #- Statement::Prefix ----------------------------------------------------------- # Generic handler for most RakuAST::StatementPrefix::xxx classes multi method raku(RakuAST::StatementPrefix:D: --> Str:D) { self!positional(self.blorst) } multi method raku(RakuAST::StatementPrefix::Phaser::Post:D: --> Str:D) { # skip the auto-generated code self!positional( self.blorst.body .statement-list.statements.head.condition-modifier.expression ) } multi method raku(RakuAST::StatementPrefix::Phaser::Pre:D: --> Str:D) { # skip the auto-generated code self!positional(self.blorst.condition-modifier.expression) } multi method raku(RakuAST::StatementPrefix::Phaser::First:D: --> Str:D) { # skip the auto-generated code self!positional(self.original-blorst) } #- Stu ------------------------------------------------------------------------- multi method raku(RakuAST::Stub:D: --> Str:D) { self!nameds: } #- Su -------------------------------------------------------------------------- multi method raku(RakuAST::Sub:D: --> Str:D) { my str @nameds = 'name'; @nameds.unshift("multiness") if self.multiness; @nameds.unshift("scope") if self.scope ne self.default-scope; @nameds.push("signature") if self.signature.parameters-initialized; @nameds.append: ; self!add-WHY: self!nameds: @nameds; } multi method raku(RakuAST::Submethod:D: --> Str:D) { my str @nameds = 'name'; @nameds.push("signature") if self.signature.parameters-initialized; @nameds.append: ; self!add-WHY: self!nameds: @nameds; } multi method raku(RakuAST::Substitution:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::SubstitutionReplacementThunk:D: --> Str:D) { self!positional(self.infix) } #- Term ------------------------------------------------------------------------ # Generic handler for some RakuAST::Term::xxx classes multi method raku(RakuAST::Term:D: --> Str:D) { self!none } multi method raku(RakuAST::Term::Capture:D: --> Str:D) { self!positional(self.source) } multi method raku(RakuAST::Term::Name:D: --> Str:D) { self!positional(self.name) } multi method raku(RakuAST::Term::Named:D: --> Str:D) { self!literal(self.name) } multi method raku(RakuAST::Term::RadixNumber:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Term::Reduce:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Term::TopicCall:D: --> Str:D) { self!positional(self.call) } #- Ternary --------------------------------------------------------------------- multi method raku(RakuAST::Ternary:D: --> Str:D) { self!nameds: } #- Trait ----------------------------------------------------------------------- multi method raku(RakuAST::Trait::Is:D: --> Str:D) { self!nameds: } # Generic handler for the RakuAST::Trait::Type classes multi method raku(RakuAST::Trait::Type:D: --> Str:D) { self!positional(self.type) } multi method raku(RakuAST::Trait::Will:D: --> Str:D) { self!nameds: } #- Type ------------------------------------------------------------------------ multi method raku(RakuAST::Type::Capture:D: --> Str:D) { self!positional(self.name) } multi method raku(RakuAST::Type::Coercion:D: --> Str:D) { self!nameds: (try self.constraint.name.canonicalize eq 'Any') ?? !! } multi method raku(RakuAST::Type::Definedness:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Type::Enum:D: --> Str:D) { self!add-WHY: self.clean-clone!nameds: } multi method raku(RakuAST::Type::Parameterized:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Type::Setting:D: --> Str:D) { self!positional(self.name) } multi method raku(RakuAST::Type::Simple:D: --> Str:D) { self!positional(self.name) } multi method raku(RakuAST::Type::Subset:D: --> Str:D) { self!add-WHY: self!nameds: } #- Var ------------------------------------------------------------------------- multi method raku(RakuAST::Var::Attribute:D: --> Str:D) { self!positional(self.name) } multi method raku(RakuAST::Var::Compiler::File:D: --> Str:D) { self!positional(self.file) } multi method raku(RakuAST::Var::Compiler::Line:D: --> Str:D) { self!positional(self.line) } multi method raku(RakuAST::Var::Compiler::Lookup:D: --> Str:D) { self!positional(self.name) } multi method raku(RakuAST::Var::Dynamic:D: --> Str:D) { self!positional(self.name) } multi method raku(RakuAST::Var::Lexical:D: --> Str:D) { my $name := self.name; self!literal($name.starts-with('$whatevercode_arg_') ?? '*' !! $name) } multi method raku(RakuAST::Var::NamedCapture:D: --> Str:D) { self!positional(self.index) } multi method raku(RakuAST::Var::Package:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::Var::Doc:D: --> Str:D) { self!positional(self.name) } multi method raku(RakuAST::Var::PositionalCapture:D: --> Str:D) { self!literal(self.index) } #- VarDeclaration -------------------------------------------------------------- multi method raku(RakuAST::VarDeclaration::Anonymous:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::VarDeclaration::Auto:D: --> Str:D) { RakuAST::Var::Lexical.new(self.name).raku } multi method raku(RakuAST::VarDeclaration::Constant:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::VarDeclaration::Implicit:D: --> Str:D) { self!nameds: } multi method raku( RakuAST::VarDeclaration::Implicit::Constant:D: --> Str:D) { self!nameds: } multi method raku( RakuAST::VarDeclaration::Placeholder::Named:D: --> Str:D) { self!positional(self.lexical-name) } multi method raku( RakuAST::VarDeclaration::Placeholder::Positional:D: --> Str:D) { self!positional(self.lexical-name) } multi method raku( RakuAST::VarDeclaration::Placeholder::SlurpyArray:D: --> Str:D) { self!none } multi method raku( RakuAST::VarDeclaration::Placeholder::SlurpyHash:D: --> Str:D) { self!none } multi method raku(RakuAST::VarDeclaration::Signature:D: --> Str:D) { self!nameds: } multi method raku(RakuAST::VarDeclaration::Simple:D: --> Str:D) { self!add-WHY: self!nameds: } multi method raku(RakuAST::VarDeclaration::Term:D: --> Str:D) { self!nameds: } } #------------------------------------------------------------------------------- # The RakuAST::Name::Part tree is *not* descendent from RakuAST::Node and # as such needs separate handling to prevent it from bleeding into the normal # .raku handling augment class RakuAST::Name::Part { proto method raku(RakuAST::Name::Part:) { CATCH { when X::Multi::NoMatch { die "No .raku method implemented for {self.^name} objects yet"; } } if nqp::istype($*INDENT,Failure) { my $*INDENT = ""; {*} } else { {*} } } #- Name::Part------------------------------------------------------------------- multi method raku(RakuAST::Name::Part::Empty:U: --> Str:D) { self.^name } multi method raku(RakuAST::Name::Part::Expression:D: --> Str:D) { my str @parts = self.^name ~ '.new('; RakuAST::Node::indent(); @parts.push: $*INDENT ~ self.expr.raku; RakuAST::Node::dedent(); @parts.push: $*INDENT ~ ")"; @parts.join("\n") } multi method raku(RakuAST::Name::Part::Simple:D: --> Str:D) { self.^name ~ '.new(' ~ self.name.raku ~ ')'; } } #------------------------------------------------------------------------------- # The RakuAST::Statement::Elsif tree is *not* descendent from RakuAST::Node # and as such needs separate handling to prevent it from bleeding into the # normal # .raku handling augment class RakuAST::Statement::Elsif { multi method raku(RakuAST::Statement::Elsif:D: --> Str:D) { my str @parts = self.^name ~ '.new('; RakuAST::Node::indent(); @parts.push: $*INDENT ~ 'condition => ' ~ self.condition.raku ~ ","; @parts.push: $*INDENT ~ 'then => ' ~ self.then.raku; RakuAST::Node::dedent(); @parts.push: $*INDENT ~ ")"; @parts.join("\n") } } #line 1 SETTING::src/core.c/RakuAST/Literalize.rakumod # This augments the RakuAST::Node class so that all of its subclasses can # generate sensible .literalize. # # The .literalize method attempts to create literal HLL objects for the # given RakuAST::Node object, and returns Nil if for some reason it is # impossible to do so. augment class RakuAST::Node { our class CannotLiteralize is Exception { } my sub alas() { CannotLiteralize.new.throw } proto method literalize(RakuAST::Node:) { CATCH { when CannotLiteralize { return CannotLiteralize; } } unless $++ { say self if %*ENV; } {*} } #- helper subs ----------------------------------------------------------------- # Return a Callable for the given infix op as string, returns # a Failure if the infix op could not be found my sub infix-op(str $op) { ::(Q/&infix:/) // ::(Q/&infix:«/ ~ $op ~ Q/»/) } # Return a Callable for the given prefix op as string, returns # a Failure if the prefix op could not be found my sub prefix-op(str $op) { ::(Q/&prefix:/) // ::(Q/&prefix:«/ ~ $op ~ Q/»/) } #- A --------------------------------------------------------------------------- multi method literalize(RakuAST::ApplyInfix:D:) { if infix-op(self.infix.operator) -> &op { my $left := self.left.literalize; my $right := self.right.literalize; nqp::istype($left,Nil) || nqp::istype($right,Nil) ?? alas !! op($left,$right) } else { alas; } } multi method literalize(RakuAST::ApplyListInfix:D:) { my str $operator = self.infix.operator; if $operator eq ',' { self.operands.map(*.literalize).List } elsif infix-op(self.infix.operator) -> &op { op(self.operands.map(*.literalize)) } else { alas; } } multi method literalize(RakuAST::ApplyPostfix:D:) { my $postfix := self.postfix; if nqp::istype($postfix,RakuAST::Postfix::Vulgar) { self.operand.literalize + $postfix.vulgar } elsif nqp::istype($postfix,RakuAST::Postfix::Power) { self.operand.literalize ** $postfix.power } else { alas; } } multi method literalize(RakuAST::ApplyPrefix:D:) { if prefix-op(self.prefix.operator) -> &op { op(self.operand.literalize) } else { alas; } } #- B --------------------------------------------------------------------------- multi method literalize(RakuAST::Block:D:) { self.body.statement-list.literalize.Map } #- Circumfix ------------------------------------------------------------------- multi method literalize(RakuAST::Circumfix::ArrayComposer:D:) { self.semilist.literalize } multi method literalize(RakuAST::Circumfix::HashComposer:D:) { self.expression.literalize.Map } multi method literalize(RakuAST::Circumfix::Parentheses:D:) { self.semilist.literalize } #- ColonPair ------------------------------------------------------------------- multi method literalize(RakuAST::ColonPair::False:D:) { Pair.new: self.key, False } multi method literalize(RakuAST::ColonPair::Number:D:) { Pair.new: self.key, self.value.compile-time-value } multi method literalize(RakuAST::ColonPair::True:D:) { Pair.new: self.key, True } multi method literalize(RakuAST::ColonPair::Value:D:) { Pair.new: self.key, self.value.literalize } #- D --------------------------------------------------------------------------- multi method literalize(RakuAST::Declaration::ResolvedConstant:D:) { self.compile-time-value } multi method literalize(RakuAST::Declaration::External:D:) { alas; } #- F --------------------------------------------------------------------------- multi method literalize(RakuAST::FatArrow:D:) { Pair.new: self.key, self.value.literalize } #- L --------------------------------------------------------------------------- # handles Int/Str/Num/Rat/ComplexLiteral multi method literalize(RakuAST::Literal:D:) { self.value } #- N --------------------------------------------------------------------------- multi method literalize(RakuAST::Node:D:) { nqp::die('literalize on ' ~ self.HOW.name(self) ~ ' NYI'); } #- Q --------------------------------------------------------------------------- multi method literalize(RakuAST::QuotedString:D:) { self.literal-value } #- S --------------------------------------------------------------------------- multi method literalize(RakuAST::SemiList:D:) { my $stmts := self.statements; my int $elems = $stmts.elems; $elems ?? $elems == 1 ?? $stmts.head.literalize !! $stmts.map(*.literalize).List !! () } #- Statement ------------------------------------------------------------------- multi method literalize(RakuAST::Statement::Expression:D:) { if self.condition-modifier // self.loop-modifier { alas; } else { self.expression.literalize } } multi method literalize(RakuAST::StatementList:D:) { my $stmts := self.statements; my int $elems = $stmts.elems; $elems ?? $elems == 1 ?? $stmts.head.literalize !! $stmts.map(*.literalize).List !! () } #- Term ------------------------------------------------------------------------ multi method literalize(RakuAST::Term::Name:D:) { my str $name = self.name.canonicalize; if $name eq 'True' { True } elsif $name eq 'False' { False } else { unless self.is-resolved { self.resolve-with($_) with $*RESOLVER; } with try self.resolution andthen .compile-time-value { $_ } else { alas; } } } multi method literalize(RakuAST::Term::RadixNumber:D:) { (self.multi-part ?? &UNBASE_BRACKET !! &UNBASE)( self.radix, self.value.literalize ) } multi method literalize(RakuAST::Type::Simple:D:) { unless self.is-resolved { self.resolve-with($_) with $*RESOLVER; } with try self.resolution { .compile-time-value } else { alas; } } #- Var ------------------------------------------------------------------------- multi method literalize(RakuAST::Var::Lexical:D:) { with self.resolution andthen try .compile-time-value { $_ } else { alas; } } multi method literalize(RakuAST::Var::Compiler::File:D:) { self.file } multi method literalize(RakuAST::Var::Compiler::Line:D:) { self.line } multi method literalize(RakuAST::VarDeclaration::Constant:D:) { self.compile-time-value } } #line 1 SETTING::src/core.c/RakuAST/LegacyPodify.rakumod # This file contains the default class for turning RakUAST::Doc::xxx # classes into legacy pod6 objects. class RakuAST::LegacyPodify { my int32 $nl = 10; # "\n" my int32 $space = 32; # " " my int32 $nbsp = 0x00A0; # NO-BREAK SPACE my int32 $nnbsp = 0x202F; # NARROW NO-BREAK SPACE my int32 $wj = 0x2060; # WORD JOINER my int32 $zwnbsp = 0xFEFF; # ZERO WIDTH NO-BREAK SPACE my int $gcprop = nqp::unipropcode("General_Category"); # basically mangle text to just single spaces my sub sanitize(str $string, :$add-space --> Str:D) { return ' ' if $string eq "\n"; # work with integers instead of characters for speed nqp::strtocodes($string,nqp::const::NORMALIZE_NFC,my int32 @input); # remove any trailing newlines my int $elems = nqp::elems(@input); nqp::while( $elems && nqp::iseq_i(nqp::atpos_i(@input,--$elems),$nl), nqp::pop_i(@input) ); return '' unless $elems = nqp::elems(@input); my int32 @output; my int32 $curr; my int32 $prev; my str $prop; my int $i = -1; # step through all codes, make the non-breaking whitespace act as # normal characters, and collapse all other consecutive whitespace # into a single space character nqp::while( nqp::islt_i(++$i,$elems), nqp::if( # for all codes nqp::iseq_i(($curr = nqp::atpos_i(@input,$i)),$nbsp) || nqp::iseq_i($curr,$nnbsp) || nqp::iseq_i($curr,$wj) || nqp::iseq_i($curr,$zwnbsp), nqp::push_i(@output,$prev = $curr), # non-breaking whitespace nqp::if( # not nb whitespace nqp::iseq_s(($prop = nqp::getuniprop_str($curr,$gcprop)),'Zs') || nqp::iseq_s($prop,'Cf') || nqp::iseq_s($prop,'Cc'), nqp::if( # all other whitespace nqp::isne_i($prev,$space), nqp::push_i(@output,$prev = $space), # after non-ws, add space ), nqp::push_i(@output,$prev = $curr) # all ok, just copy ) ) ); # add a space if there is something and were asked to add one @output.push($space) if $add-space && nqp::elems(@output); nqp::strfromcodes(@output) } # sanitize the given cell, including any handling of markup my sub table-sanitize($cell --> Str:D) { sanitize(nqp::istype($cell,RakuAST::Doc::Paragraph) ?? $cell.atoms.map({ nqp::istype($_,Str) ?? $_ !! .letter eq 'Z' ?? "" !! .Str }).join !! $cell ).trim.subst(Q/\+/, '+', :global).subst(Q/\|/, '|', :global) } # hide the outer markup my sub hide(RakuAST::Doc::Markup:D $markup) { my @atoms = $markup.atoms; given @atoms.head { nqp::istype($_,Str) ?? ($_ = $markup.opener ~ $_) !! @atoms.unshift($markup.opener) } given @atoms.tail { nqp::istype($_,Str) ?? ($_ = $_ ~ $markup.closer) !! @atoms.push($markup.closer) } nqp::istype(@atoms.are,Str) ?? @atoms.join !! @atoms.map({ nqp::istype($_,Str) ?? $_ !! .podify }).Slip } # produce list without last if last is \n my sub no-last-nl(\list) { my @parts = list; @parts.pop if nqp::istype($_,Str) && $_ eq "\n" given @parts.tail; @parts } # create podified contents for atoms method !contentify(@source) { my str @parts; my @atoms = @source.map({ nqp::istype($_,Str) ?? sanitize(.subst("\n", ' ', :g)) !! .podify # may Slip }).map({ # collect any strings if nqp::istype($_,Str) { @parts.push: $_; Empty } # something else, produce with any strings preceding elsif @parts { my str $string = @parts.join; @parts = (); ($string, $_).Slip } # just produce, already podified else { $_ } }); # collect any uncollected strings so far @atoms.push: @parts.join if @parts; # string at left needs to be trimmed left if @atoms.head <-> $_ { $_ = .trim-leading if nqp::istype($_,Str); } # return strings if just strings nqp::istype(@atoms.are,Str) ?? @atoms.join !! @atoms } proto method podify(|) {*} # Base class catcher multi method podify(RakuAST::Doc:D $ast) { NYI("Podifying $ast.^name() objects").throw } # Odd value catcher, avoiding long dispatch options in error message multi method podify(Mu:D $ast) { die "You cannot podify a $ast.^name() instance: $ast.raku()"; } multi method podify(Mu:U $ast) { die "You cannot podify a $ast.^name() type object"; } multi method podify(RakuAST::Doc::Markup:D $ast) { # This markup is allowed my str $letter = $ast.letter; # make sure we have properly podified meta data my @meta = do if $letter ne 'E' && $ast.meta -> @_ { self!contentify(@_) } # Markup is allowed if %*OK{$letter} { $letter eq 'V' ?? $ast.atoms.head.subst("\n", ' ', :g) !! Pod::FormattingCode.new( type => $letter, meta => @meta, contents => $letter eq 'C' ?? $ast.atoms.head.subst("\n", ' ', :g) !! self!contentify($ast.atoms) ) } # Meta on markup that itself is not allowed elsif @meta { my @atoms = self!contentify($ast.atoms); @atoms.unshift: nqp::istype(@atoms.head,Str) ?? $letter ~ $ast.opener ~ @atoms.shift !! $letter ~ $ast.opener; @atoms.push: nqp::istype(@atoms.tail,Str) ?? @atoms.pop ~ "|" !! "|"; @atoms.push: nqp::istype(@meta.head,Str) ?? @atoms.pop ~ @meta.shift !! @meta.shift; @atoms.append: @meta; @atoms.push: nqp::istype(@atoms.tail,Str) ?? @atoms.pop ~ $ast.closer !! $ast.closer; @atoms.List } # No meta on unallowed markup else { $ast.Str } } multi method podify(RakuAST::Doc::Paragraph:D $ast) { my @atoms := $ast.atoms; my int $left = @atoms.elems; my @contents = no-last-nl(@atoms).map: { --$left; nqp::istype($_,Str) ?? sanitize($_, :add-space($left && .ends-with("\n"))) || Empty !! self.podify($_) } my int $i; # start at 2nd element my int $elems = @contents.elems; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::istype(@contents[$i],Str) && nqp::istype(@contents[my int $j = $i - 1],Str), nqp::stmts( (@contents[$j] ~= @contents.splice($i,1).head), --$i, --$elems ) ) ); Pod::Block::Para.new(:@contents) } multi method podify(RakuAST::Doc::Block:D $ast) { # Set up dynamic lookup for allowable markup letters my %*OK := $ast.allowed-markup; # These need code of its own, as the new grammar only collects # and does not do any interpretation my str $type = $ast.type; my str $level = $ast.level; unless $level { return self.podify-table($ast) if $type eq 'table'; return self.podify-verbatim($ast, $type) if $type eq 'code' | 'input' | 'output'; return self.podify-implicit-code($ast) if $type eq 'implicit-code'; return self.podify-defn($ast) if $type eq 'defn' | 'numdefn'; } # no more special casing my $config := $ast.resolved-config; my $contents := no-last-nl($ast.paragraphs).map({ if nqp::istype($_,Str) { if sanitize(.trim-leading) -> $contents { Pod::Block::Para.new(:$contents) } } else { self.podify($_) } }).List; if $type { if $type eq 'item' | 'head' { ($type eq 'item' ?? Pod::Item !! Pod::Heading).new( :level($level ?? $level.Int !! 1), :$config, :$contents ) } elsif $type eq 'numitem' | 'numhead' { my %config = $config; %config := True; ($type eq 'numitem' ?? Pod::Item !! Pod::Heading).new( :level($level ?? $level.Int !! 1), :%config, :$contents ) } elsif $level { Pod::Block::Named.new( :name($type ~ $level), :$config, :$contents ) } # from here on without level else { $type eq 'comment' ?? Pod::Block::Comment.new( :$config, :contents([ $ast.paragraphs.head.trim-trailing ~ "\n" ]) ) !! $type eq 'config' && $ast.abbreviated ?? Pod::Config.new( :type($ast.paragraphs.head), :$config ) !! Pod::Block::Named.new(:name($type), :$config, :$contents) } } # no type means just a string else { $contents } } method podify-table(RakuAST::Doc::Block:D $ast) { my $config := $ast.resolved-config; my @rows = $ast.paragraphs.grep(RakuAST::Doc::Row); # Make sure that all rows have the same number of cells my $nr-columns := @rows.map(*.cells.elems).max; my sub spread(\cells) { cells.elems == $nr-columns ?? cells !! (cells.Slip, ("" xx $nr-columns - cells.elems).Slip) } # determine whether we have headers my $headers; with $config -> $index { $headers := @rows.splice($index, 1).head; } # some legacy sanity checks my $has-data; # flag: True if actual rows where found my $previous-was-divider; # flag: True if previous row was divider for $ast.paragraphs -> $row { if nqp::istype($row,Str) { if $previous-was-divider { $ast.sorry-ad-hoc: "Table has multiple interior row separator lines.", "dummy argument that is somehow needed"; last; } $previous-was-divider := True; } else { $has-data := True; $previous-was-divider := False; } } $ast.sorry-ad-hoc( "Table has no data.", "dummy argument that is somehow needed" ) unless $has-data; # wrap up $headers := [spread .cells.map(&table-sanitize)] with $headers; Pod::Block::Table.new( caption => $config // "", headers => $headers // [], config => $config, contents => @rows.map({ [spread .cells.map(&table-sanitize)] }) ) } method podify-verbatim(RakuAST::Doc::Block:D $ast, Str:D $type) { my $config := $ast.resolved-config; my @contents = $ast.paragraphs.map({ (nqp::istype($_,Str) ?? .split("\n", :v, :skip-empty) # assume a paragraph with string / markup atoms !! .atoms.map({ nqp::istype($_,Str) ?? .split("\n", :v, :skip-empty).Slip !! nqp::istype($_,RakuAST::Doc::Markup) ?? .podify.Slip !! .Str }) ).Slip }); # only keep one "\n" at end @contents.pop while @contents.tail eq "\n"; @contents.push("\n"); ::("Pod::Block::$type.tc()").new: :@contents, :$config } method podify-implicit-code(RakuAST::Doc::Block:D $ast) { Pod::Block::Code.new: :contents($ast.paragraphs.head.Str.trim), :config($ast.resolved-config) } method podify-defn(RakuAST::Doc::Block:D $ast) { my $term := sanitize $ast.paragraphs.head.Str; my @contents = $ast.paragraphs.skip.map: { Pod::Block::Para.new(:contents(sanitize(.Str))) } my $config := $ast.resolved-config; $config := %(|$config, :numbered) if $ast.type.starts-with('num'); Pod::Defn.new: :$term, :@contents, :$config } multi method podify(RakuAST::Doc::Declarator:D $ast, $WHEREFORE) { sub normalize(@paragraphs) { @paragraphs .map(*.lines.map({.trim if $_}).Slip) .join(' ') .trim-trailing } my $leading := %*ENV ?? $ast.leading.join("\n") !! normalize($ast.leading); my $trailing := normalize $ast.trailing; my %args; %args = $WHEREFORE; %args = $leading if $leading; %args = [$trailing] if $trailing; my $pod := Pod::Block::Declarator.new(|%args); $WHEREFORE.set_why($pod); $pod } } #line 1 SETTING::src/core.c/core_epilogue.rakumod BEGIN { # Re-parent meta-objects so they appear to be under Any. Perl6::Metamodel::ClassHOW.HOW.reparent(Perl6::Metamodel::ClassHOW, Any); Perl6::Metamodel::ConcreteRoleHOW.HOW.reparent(Perl6::Metamodel::ConcreteRoleHOW, Any); Perl6::Metamodel::CurriedRoleHOW.HOW.reparent(Perl6::Metamodel::CurriedRoleHOW, Any); Perl6::Metamodel::EnumHOW.HOW.reparent(Perl6::Metamodel::EnumHOW, Any); Perl6::Metamodel::GenericHOW.HOW.reparent(Perl6::Metamodel::GenericHOW, Any); Perl6::Metamodel::ModuleHOW.HOW.reparent(Perl6::Metamodel::ModuleHOW, Any); Perl6::Metamodel::NativeHOW.HOW.reparent(Perl6::Metamodel::NativeHOW, Any); Perl6::Metamodel::PackageHOW.HOW.reparent(Perl6::Metamodel::PackageHOW, Any); Perl6::Metamodel::ParametricRoleGroupHOW.HOW.reparent(Perl6::Metamodel::ParametricRoleGroupHOW, Any); Perl6::Metamodel::ParametricRoleHOW.HOW.reparent(Perl6::Metamodel::ParametricRoleHOW, Any); Perl6::Metamodel::SubsetHOW.HOW.reparent(Perl6::Metamodel::SubsetHOW, Any); Perl6::Metamodel::GrammarHOW.HOW.compose(Perl6::Metamodel::GrammarHOW); } my constant CORE-SETTING-REV = do { # Turn CORE-SETTING-REV into kind of an allomorph except that we cannot use the actual Allomorph class since it is # not available at the beginning of CORE compilation and this is where we need the symbol in first place. Therefore # it gets its initial value as a plain integer and it is only now as we can eventually mixin the public interface # role into it. Besides, Allomorph is a string in first place, whereas CORE-SETTING-REV must represent the internal # representation which is now an integer. my class LanguageRevision { has int $!language-revision is box_target; has Str $!p6rev; method p6rev(::?CLASS:D:) { nqp::isconcrete($!p6rev) ?? $!p6rev !! ($!p6rev := nqp::getcomp('Raku').lvs.p6rev(nqp::unbox_i(self))) } # The default Int, Numeric, and Real coercions return the object itself, but we need a fresh copy. multi method Int(::?CLASS:D:) { nqp::box_i($!language-revision, Int) } multi method Numeric(::?CLASS:D:) { nqp::box_i($!language-revision, Int) } multi method Real(::?CLASS:D:) { nqp::box_i($!language-revision, Int) } multi method Str(::?CLASS:D:) { self.p6rev } multi method gist(::?CLASS:D:) { self.p6rev } method Version(::?CLASS:D:) { nqp::getcomp('Raku').lvs.as-public-repr($!language-revision, :as-version) } } nqp::box_i(1, LanguageRevision) } Metamodel::Configuration.set_language_revision_type(CORE-SETTING-REV.WHAT); BEGIN { # Create pun at compile time as buf8 is used extensively in file I/O and module loading buf8.elems; # Mark all subs that are implementation details, as implementation detail. # In any other code, this would have been done as a trait on the actual # sub definition. But doing that in the setting *before* the Routine # class is actually a HLL thing, makes it an unCallable. So we do these # routines and methods here, at the end of setting compilation. trait_mod:($_, :implementation-detail) for &CLONE-HASH-DECONTAINERIZED, &CLONE-LIST-DECONTAINERIZED, &HYPERWHATEVER, &dd, &DUMP, &DYNAMIC, &RETURN-LIST, &SLICE_MORE_HASH, &SLICE_ONE_HASH, &THROW, &THROW-NIL, Code.^find_method("POSITIONS"), Mu.^find_method("DUMP"), Mu.^find_method("DUMP-OBJECT-ATTRS"), Mu.^find_method("DUMP-PIECES"), Mu.^find_method("WALK") ; } { # XXX TODO: https://github.com/rakudo/rakudo/issues/2433 # my $perl := BEGIN Perl.new; Rakudo::Internals.REGISTER-DYNAMIC: '$*PERL', { PROCESS::<$PERL> := Raku.new; } Rakudo::Internals.REGISTER-DYNAMIC: '$*RAKU', { PROCESS::<$RAKU> := Raku.new; } } # Cannot be added in the Uni class, as we don't have native arrays # then yet, so it must be done here as an augment. augment class Uni { multi method new(Uni: array[uint32] \codepoints) { my $uni := nqp::create(self); my int $elems = nqp::elems(codepoints); my int $i = -1; my int $code; nqp::while( nqp::islt_i(++$i,$elems), nqp::if(nqp::isgt_i($code = nqp::atpos_i(codepoints,$i), 0x10ffff) || (nqp::isle_i(0xd800, $code) && nqp::isle_i($code, 0xdfff)) || nqp::islt_i($code, 0), X::InvalidCodepoint.new(:$code).throw, nqp::push_i($uni,$code)) ); $uni } } # Subs that are DEPRECATED are moved here so that the "is DEPRECATED" trait # can be applied without bootstrapping issues. sub parse-names(Str:D \names) is DEPRECATED('uniparse') { names.uniparse } sub to-json(|c) is implementation-detail is DEPRECATED('JSON::Fast, JSON::Tiny or JSON::Pretty from https://modules.raku.org/') { Rakudo::Internals::JSON.to-json(|c); } sub from-json($text) is implementation-detail is DEPRECATED('JSON::Fast, JSON::Tiny or JSON::Pretty from https://modules.raku.org/') { Rakudo::Internals::JSON.from-json($text); } proto sub gethostname(*%) is implementation-detail {*} multi sub gethostname(--> Str:D) is DEPRECATED('$*KERNEL.hostname') { $*KERNEL.hostname } augment class Cool { # Methods that are DEPRECATED are moved here and augmented into the classes # they belong to without bootstrapping issues. method parse-names(Cool:D: --> Str:D) is DEPRECATED('uniparse') { self.uniparse } method path(Cool:D: --> IO::Path:D) is DEPRECATED('IO') { self.IO } # Allow for creating an AST out of a string, for core debugging mainly method AST(Cool:D: Mu $slang? is copy, Bool :$expression, # return the first expression Bool :$compunit, # return the whole compunit, not statement-list Mu :$grammar is copy = nqp::gethllsym('Raku','Grammar'), Mu :$actions = nqp::gethllsym('Raku','Actions'), ) { # Make sure we don't use the EVAL's MAIN context for the # currently compiling compilation unit my $*CTXSAVE; my $eval_ctx := nqp::getattr(CALLER::,PseudoStash,'$!ctx'); # Some context my $?FILES :='EVAL_' ~ Rakudo::Internals::EvalIdSource.next-id; my $*INSIDE-EVAL := 1; # Slang specified by string, go fetch it $slang = "use L10N::$slang; L10N::$slang".EVAL if nqp::istype($slang,Str); # Got a slang to mix in $grammar = $grammar.^mixin($slang) unless nqp::eqaddr(nqp::decont($slang),Mu); # Convert to RakuAST my $compiler := nqp::getcomp('Raku'); my $ast := $compiler.compile: self.Str, :outer_ctx($eval_ctx), :global(GLOBAL), :language_version($compiler.language_version), |(:optimize($_) with $compiler.cli-options), :target, :compunit_ok(1), :$grammar, :$actions; $expression ?? $ast.statement-list.statements.head.expression !! $compunit ?? $ast !! $ast.statement-list } } # Make sure all affected subclasses are aware of additions to their parents BEGIN .^compose for Str, Int, Num, Rat, Complex, IntStr, NumStr, RatStr, ComplexStr, List, Array, array, Match, Range, Seq, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, byte, num, num32, num64, str, IterationBuffer ; BEGIN Metamodel::ClassHOW.exclude_parent(Mu); {YOU_ARE_HERE} # vim: set ft=perl6 nomodifiable :

and nqp::istype(res, Pair) { Pair.new(self.elems - res.key - 1, res.value) } else { res } } else { nextsame } } method bounds() { ($!min, $!max) } proto method int-bounds(|) {*} multi method int-bounds($from is rw, $to is rw --> Bool:D) { nqp::if( $!is-int, nqp::stmts( ($from = $!min + $!excludes-min), ($to = $!max - $!excludes-max), True ), nqp::if( nqp::istype($!min,Real) && $!min.floor == $!min && nqp::istype($!max,Real) && nqp::istype($!min.Int, Int) # exclude NaN and Infs, who will fail() here && nqp::istype($!max.Int, Int), nqp::stmts( ($from = $!min.floor + $!excludes-min), ($to = $!max.floor - ($!excludes-max && $!max.Int == $!max)), True ), False ) ) } multi method int-bounds() { $!is-int ?? ($!min + $!excludes-min, $!max - $!excludes-max) !! nqp::istype($!min,Real) && $!min.floor == $!min && nqp::istype($!max,Real) && nqp::istype($!min.Int, Int) # exclude NaN and Infs, who will fail() here && nqp::istype($!max.Int, Int) ?? ($!min.floor + $!excludes-min, $!max.floor - ($!excludes-max && $!max.Int == $!max)) !! "Cannot determine integer bounds".Failure } method fmt(|c) { self.list.fmt(|c) } multi method Str(Range:D:) { $!min === -Inf && $!max === Inf ?? "*{'^' if $!excludes-min}..{'^' if $!excludes-max}*" !! $!min === -Inf ?? "*{'^' if $!excludes-min}..{'^' if $!excludes-max}$!max" !! $!max === Inf ?? "{$!min}{'^' if $!excludes-min}..{'^' if $!excludes-max}*" !! self.list.Str } my sub IS-COMPARABLE(&client-cmp, Mu $topic, Mu $endpoint, $what-endpoint) { unless &client-cmp.cando($topic, $endpoint) { X::Range::Incomparable.new(:$topic, :$endpoint, :$what-endpoint).throw } } multi method ACCEPTS(Range:D \SELF: Junction:D $topic) { $topic.THREAD: { SELF.ACCEPTS($_) } } multi method ACCEPTS(Range:D: Cool:D \got) { nqp::if( $!is-int && nqp::istype(got, Int), nqp::if(got >= $!min + $!excludes-min, got <= $!max - $!excludes-max), nqp::if( (nqp::istype($!min, Numeric) && nqp::istype($!max, Numeric)), nqp::stmts( (my \got-num = nqp::if(nqp::istype(got, Numeric), got, got.Numeric(:fail-or-nil))), nqp::if( nqp::istype(got-num, Nil), False, nqp::if( nqp::if($!excludes-min, got-num > $!min, got-num >= $!min), nqp::if($!excludes-max, got-num < $!max, got-num <= $!max)))), nqp::if( nqp::if($!excludes-min, got after $!min, not got before $!min), nqp::if($!excludes-max, got before $!max, not got after $!max)))) } multi method ACCEPTS(Range:D: Complex:D \got) { nqp::istype(($_ := got.Real), Failure) ?? False !! nextwith $_ } multi method ACCEPTS(Range:D: Range:D \topic) { nqp::istype($!min, Numeric) ?? # RHS is a numeric range, use numeric comparators try { (topic.min > $!min || topic.min == $!min && !(!topic.excludes-min && $!excludes-min)) && (topic.max < $!max || topic.max == $!max && !(!topic.excludes-max && $!excludes-max)) } // False # don't explode on failures to coerce to numerics !! # RHS is a stringy range, use stringy comparators (topic.min gt $!min || topic.min eq $!min && !(!topic.excludes-min && $!excludes-min)) && (topic.max lt $!max || topic.max eq $!max && !(!topic.excludes-max && $!excludes-max)) } multi method ACCEPTS(Range:D: Any \topic) { (topic cmp $!min) > -(!$!excludes-min) and (topic cmp $!max) < +(!$!excludes-max) } multi method ACCEPTS(Range:D: Mu \topic) { # Generally speaking, Mu is not a comparable type. Neither any of its children unless specific multi-candidates # of &infix: are provided by user code. In this case try to go slow path. # XXX This still doesn't work with junctions because with threading this method is invoked from Junction's # namespace. my &client-cmp := CLIENT::LEXICAL::{'&infix:'}; IS-COMPARABLE(&client-cmp, topic, $!min, 'minimum'); IS-COMPARABLE(&client-cmp, topic, $!max, 'maximum'); (&client-cmp(topic, $!min) > -(!$!excludes-min) and &client-cmp(topic, $!max) < +(!$!excludes-max)) } method ASSIGN-POS(Range:D: |) { X::Assignment::RO.new(value => self).throw } multi method AT-POS(Range:D: uint $pos) { $!is-int ?? self.EXISTS-POS($pos) ?? $!min + $!excludes-min + $pos !! Nil !! self.list.AT-POS($pos); } multi method AT-POS(Range:D: Int:D $pos) { $!is-int ?? self.EXISTS-POS($pos) ?? $!min + $!excludes-min + $pos !! $pos < 0 ?? X::OutOfRange.new( :what($*INDEX // 'Index'), :got($pos), :range<0..^Inf> ).Failure !! Nil !! self.list.AT-POS($pos) } multi method raku(Range:D:) { if $!is-int && $!min == 0 && nqp::not_i($!excludes-min) && $!excludes-max { "^$!max" } else { my $parts := nqp::list_s($!min.raku); nqp::push_s($parts,'^') if $!excludes-min; nqp::push_s($parts,'..'); nqp::push_s($parts,'^') if $!excludes-max; nqp::push_s($parts,$!max.raku); nqp::join('',$parts) } } proto method roll(|) {*} my class RollWhatever does Iterator { has $!min; has $!elems; method !SET-SELF(\min,\elems) { $!min := nqp::decont(min); $!elems := nqp::decont(elems); self } method new(\b,\e) { nqp::create(self)!SET-SELF(b,e) } method pull-one() { $!min + nqp::rand_I($!elems, Int) } method is-lazy(--> True) { } method is-deterministic(--> False) { } } my class RollN does Iterator { has $!min; has $!elems; has Int $!todo; method !SET-SELF(\min,\elems,\todo) { $!min := nqp::decont(min); $!elems := nqp::decont(elems); $!todo = todo; self } method new(\m,\e,\t) { nqp::create(self)!SET-SELF(m,e,t) } method pull-one() { $!todo-- ?? $!min + nqp::rand_I($!elems, Int) !! IterationEnd } method push-all(\target --> IterationEnd) { target.push($!min + nqp::rand_I($!elems, Int)) while $!todo--; } method is-deterministic(--> False) { } } multi method roll(Range:D: Whatever) { (my \elems := self.elems) ?? $!is-int ?? Seq.new(RollWhatever.new($!min + $!excludes-min,elems)) !! self.list.roll(*) !! Seq.new(Rakudo::Iterator.Empty) } multi method roll(Range:D:) { $!is-int ?? (my \elems := $!max - $!excludes-max - $!min - $!excludes-min + 1) > 0 ?? $!min + $!excludes-min + nqp::rand_I(elems,Int) !! Nil !! self.elems ?? self.list.roll !! Nil } multi method roll(Int(Cool) $todo) { (my \elems := self.elems) ?? $!is-int ?? Seq.new(RollN.new($!min + $!excludes-min,elems,0 max $todo)) !! self.list.roll($todo) !! Seq.new(Rakudo::Iterator.Empty) } my class PickN does Iterator { has $!min; has $!elems; has Int $!todo; has $!seen; method !SET-SELF(\min,\elems,\todo) { $!min := nqp::decont(min); $!elems := nqp::decont(elems); $!todo = todo; $!seen := nqp::hash(); self } method new(\m,\e,\t) { nqp::create(self)!SET-SELF(m,e,t) } method pull-one() { my Int $value; my str $key; if $!todo { repeat { $value = $!min + nqp::rand_I($!elems, Int); $key = nqp::tostr_I(nqp::decont($value)); } while nqp::existskey($!seen,$key); $!todo = $!todo - 1; nqp::bindkey($!seen,$key,1); $value } else { IterationEnd } } method push-all(\target --> IterationEnd) { my str $key; while $!todo { my Int $value = $!min + nqp::rand_I($!elems, Int); $key = nqp::tostr_I(nqp::decont($value)); unless nqp::existskey($!seen,$key) { target.push($value); $!todo = $!todo - 1; nqp::bindkey($!seen,$key,1); } } } method is-deterministic(--> False) { } } multi method pick() { self.roll } multi method pick(Whatever) { self.elems ?? self.list.pick(*) !! Seq.new(Rakudo::Iterator.Empty) } multi method pick(Int(Cool) $todo) { (my \elems := self.elems) ?? $!is-int && elems > 3 * $todo # heuristic for sparse lookup ?? Seq.new(PickN.new($!min + $!excludes-min,elems,0 max $todo)) !! self.list.pick($todo) !! Seq.new(Rakudo::Iterator.Empty) } method Capture(Range:D:) { \( :$!min, :$!max, excludes-min => self.excludes-min, excludes-max => self.excludes-max, infinite => self.infinite, is-int => self.is-int) } multi method Numeric(Range:D:) { $!is-int ?? self.elems !! nqp::istype($!min,Numeric) && nqp::istype($!max,Numeric) ?? do { my $diff = 0 max $!max - $!min - $!excludes-min; my $floor = $diff.floor; $floor + 1 - ($floor == $diff ?? $!excludes-max !! 0) } !! self.flat.elems } 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 } multi method sum(Range:D:) { self.int-bounds(my $start, my $stop) ?? ($start + $stop) * (0 max $stop - $start + 1) div 2 !! $!min == -Inf ?? $!max == Inf ?? NaN !! -Inf !! $!max == Inf ?? Inf !! nextsame } method rand() { fail "Can only get a random value on Real values, did you mean .pick?" unless nqp::istype($!min,Real) && nqp::istype($!max,Real); fail "Can only get a random value from numeric values" if $!min === NaN || $!max === NaN; fail "Can not get a random value from an infinite range" if $!min === -Inf || $!max === Inf; my $range = $!max - $!min; fail "Can only get a random value if the range is positive" unless $range > 0; my $value = 0; if $!excludes-min || $!excludes-max { if $!excludes-min { if $!excludes-max { $value = $range.rand while $value+$!min == $!min || $value+$!min == $!max; } else { $value = $range.rand while $value+$!min == $!min; } } else { # $!excludes-max repeat { $value = $range.rand } while $value+$!min == $!max; } } else { $value = $range.rand } $value + $!min; } method in-range($got, $what?) { self.ACCEPTS($got) || X::OutOfRange.new(:what($what // 'Value'),:got($got.raku),:range(self.gist)).throw } multi method minmax(Range:D:) { $!is-int ?? self.int-bounds !! $!excludes-min || $!excludes-max ?? "Cannot return minmax on Range with excluded ends".Failure !! ($!min,$!max) } } augment class Range { method Inf-Inf() is implementation-detail { BEGIN Range.new(Inf, -Inf) } } proto sub infix:<..>($, $, *%) is pure {*} multi sub infix:<..>(Int:D $min, Int:D $max) { my $range := nqp::create(Range); nqp::bindattr($range,Range,'$!min',$min); nqp::bindattr($range,Range,'$!max',$max); nqp::bindattr_i($range,Range,'$!is-int',1); $range } multi sub infix:<..>($min, $max) { Range.new($min, $max) } proto sub infix:<^..>($, $, *%) is pure {*} multi sub infix:<^..>(Int:D $min, Int:D $max) { my $range := nqp::create(Range); nqp::bindattr($range,Range,'$!min',$min); nqp::bindattr($range,Range,'$!max',$max); nqp::bindattr_i($range,Range,'$!excludes-min',1); nqp::bindattr_i($range,Range,'$!is-int',1); $range } multi sub infix:<^..>($min, $max) { Range.new($min, $max, :excludes-min) } proto sub infix:<..^>($, $, *%) is pure {*} multi sub infix:<..^>(Int:D $min, Int:D $max) { my $range := nqp::create(Range); nqp::bindattr($range,Range,'$!min',$min); nqp::bindattr($range,Range,'$!max',$max); nqp::bindattr_i($range,Range,'$!excludes-max',1); nqp::bindattr_i($range,Range,'$!is-int',1); $range } multi sub infix:<..^>($min, $max) { Range.new($min, $max, :excludes-max) } proto sub infix:<^..^>($, $, *%) is pure {*} multi sub infix:<^..^>(Int:D $min, Int:D $max) { my $range := nqp::create(Range); nqp::bindattr($range,Range,'$!min',$min); nqp::bindattr($range,Range,'$!max',$max); nqp::bindattr_i($range,Range,'$!excludes-min',1); nqp::bindattr_i($range,Range,'$!excludes-max',1); nqp::bindattr_i($range,Range,'$!is-int',1); $range } multi sub infix:<^..^>($min, $max) { Range.new($min, $max, :excludes-min, :excludes-max) } proto sub prefix:<^>($, *%) is pure {*} multi sub prefix:<^>(Int:D $max) { my $range := nqp::create(Range); nqp::bindattr($range,Range,'$!min',0); nqp::bindattr($range,Range,'$!max',$max); nqp::bindattr_i($range,Range,'$!excludes-max',1); nqp::bindattr_i($range,Range,'$!is-int',1); $range } multi sub prefix:<^>($max) { Range.new(0, $max.Numeric, :excludes-max) } multi sub infix:(Range:D \a, Range:D \b --> Bool:D) { nqp::hllbool( nqp::eqaddr(nqp::decont(a),nqp::decont(b)) || (nqp::eqaddr(a.WHAT,b.WHAT) && a.min eqv b.min && a.max eqv b.max && nqp::iseq_i( nqp::getattr_i(nqp::decont(a),Range,'$!excludes-min'), nqp::getattr_i(nqp::decont(b),Range,'$!excludes-min') ) && nqp::iseq_i( nqp::getattr_i(nqp::decont(a),Range,'$!excludes-max'), nqp::getattr_i(nqp::decont(b),Range,'$!excludes-max') )) ) } multi sub infix:<+>(Range:D \r, Real:D $v) { r.new: r.min + $v, r.max + $v, :excludes-min(r.excludes-min), :excludes-max(r.excludes-max) } multi sub infix:<+>(Real:D $v, Range:D \r) { r.new: $v + r.min, $v + r.max, :excludes-min(r.excludes-min), :excludes-max(r.excludes-max) } multi sub infix:<->(Range:D \r, Real:D $v) { r.new: r.min - $v, r.max - $v, :excludes-min(r.excludes-min), :excludes-max(r.excludes-max) } multi sub infix:<*>(Range:D \r, Real:D $v) { r.new: r.min * $v, r.max * $v, :excludes-min(r.excludes-min), :excludes-max(r.excludes-max) } multi sub infix:<*>(Real:D $v, Range:D \r) { r.new: $v * r.min, $v * r.max, :excludes-min(r.excludes-min), :excludes-max(r.excludes-max) } multi sub infix:(Range:D \r, Real:D $v) { r.new: r.min / $v, r.max / $v, :excludes-min(r.excludes-min), :excludes-max(r.excludes-max) } multi sub infix:(Range:D \a, Range:D \b) { a.min cmp b.min || a.excludes-min cmp b.excludes-min || a.max cmp b.max || b.excludes-max cmp a.excludes-max } multi sub infix:(Num(Real) $a, Range:D \b) { ($a..$a) cmp b } multi sub infix:(Range:D \a, Num(Real) $b) { a cmp ($b..$b) } multi sub infix:(Positional \a, Range:D \b) { a cmp b.list } multi sub infix:(Range:D \a, Positional \b) { a.list cmp b } #line 1 SETTING::src/core.c/List.rakumod # 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) ) } #line 1 SETTING::src/core.c/Array/Element.rakumod my class Array::Element { method access(\SELF, \pos, %adverbs, $adverb, $value) { my $lookup := Rakudo::Internals.ADVERBS_AND_NAMED_TO_DISPATCH_INDEX( %adverbs, $adverb, $value ); nqp::if( nqp::istype($lookup,X::Adverb), nqp::stmts( ($lookup.what = "element access"), ($lookup.source = try { SELF.VAR.name } // SELF.^name), $lookup.Failure ), Rakudo::Internals.ACCESS-ELEMENT-DISPATCH-CLASS( $lookup ).element(SELF,pos) ) } method access-any(\SELF, \pos, %adverbs, $adverb, $value) { my $lookup := Rakudo::Internals.ADVERBS_AND_NAMED_TO_DISPATCH_INDEX( %adverbs, $adverb, $value ); nqp::if( nqp::istype($lookup,X::Adverb), nqp::stmts( ($lookup.what = "element access"), ($lookup.source = try { SELF.VAR.name } // SELF.^name), $lookup.Failure ), Rakudo::Internals.ACCESS-ELEMENT-ANY-DISPATCH-CLASS( $lookup ).element(SELF,pos) ) } } # Classes that take an Int position my class Array::Element::Access::none { method element(\SELF,\pos) { SELF.AT-POS(pos) } } my class Array::Element::Access::kv { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) ?? (pos,SELF.AT-POS(pos)) !! () } } my class Array::Element::Access::not-kv { method element(\SELF,\pos) { (pos,SELF.AT-POS(pos)) } } my class Array::Element::Access::p { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) ?? Pair.new(pos,SELF.AT-POS(pos)) !! () } } my class Array::Element::Access::not-p { method element(\SELF,\pos) { Pair.new(pos,SELF.AT-POS(pos)) } } my class Array::Element::Access::k { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) ?? pos !! () } } my class Array::Element::Access::not-k { method element(\SELF,\pos) { pos } } my class Array::Element::Access::v { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) ?? nqp::decont(SELF.AT-POS(pos)) !! () } } my class Array::Element::Access::exists { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) } } my class Array::Element::Access::exists-kv { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) ?? (pos,True) !! () } } my class Array::Element::Access::exists-not-kv { method element(\SELF,\pos) { (pos,SELF.EXISTS-POS(pos)) } } my class Array::Element::Access::exists-p { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) ?? Pair.new(pos,True) !! () } } my class Array::Element::Access::exists-not-p { method element(\SELF,\pos) { Pair.new(pos,SELF.EXISTS-POS(pos)) } } my class Array::Element::Access::exists-delete { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos) { SELF.DELETE-POS(pos); True } else { False } } } my class Array::Element::Access::exists-delete-kv { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos) { SELF.DELETE-POS(pos); (pos,True) } else { () } } } my class Array::Element::Access::exists-delete-not-kv { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos) { SELF.DELETE-POS(pos); (pos,True) } else { (pos,False) } } } my class Array::Element::Access::exists-delete-p { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos) { SELF.DELETE-POS(pos); Pair.new(pos,True) } else { () } } } my class Array::Element::Access::exists-delete-not-p { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos) { SELF.DELETE-POS(pos); Pair.new(pos,True) } else { Pair.new(pos,False) } } } my class Array::Element::Access::not-exists { method element(\SELF,\pos) { !SELF.EXISTS-POS(pos) } } my class Array::Element::Access::not-exists-kv { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) ?? (pos,False) !! () } } my class Array::Element::Access::not-exists-not-kv { method element(\SELF,\pos) { (pos,!SELF.EXISTS-POS(pos)) } } my class Array::Element::Access::not-exists-p { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) ?? Pair.new(pos,False) !! () } } my class Array::Element::Access::not-exists-not-p { method element(\SELF,\pos) { Pair.new(pos,!SELF.EXISTS-POS(pos)) } } my class Array::Element::Access::not-exists-delete { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos) { SELF.DELETE-POS(pos); False } else { True } } } my class Array::Element::Access::not-exists-delete-kv { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos) { SELF.DELETE-POS(pos); (pos,False) } else { () } } } my class Array::Element::Access::not-exists-delete-not-kv { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos) { SELF.DELETE-POS(pos); (pos,False) } else { (pos,True) } } } my class Array::Element::Access::not-exists-delete-p { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos) { SELF.DELETE-POS(pos); Pair.new(pos,False) } else { () } } } my class Array::Element::Access::not-exists-delete-not-p { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos) { SELF.DELETE-POS(pos); Pair.new(pos,False) } else { Pair.new(pos,True) } } } my class Array::Element::Access::delete { method element(\SELF,\pos) { SELF.DELETE-POS(pos) } } my class Array::Element::Access::delete-kv { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) ?? (pos,SELF.DELETE-POS(pos)) !! () } } my class Array::Element::Access::delete-not-kv { method element(\SELF,\pos) { (pos,SELF.DELETE-POS(pos)) } } my class Array::Element::Access::delete-p { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) ?? Pair.new(pos,SELF.DELETE-POS(pos)) !! () } } my class Array::Element::Access::delete-not-p { method element(\SELF,\pos) { Pair.new(pos,SELF.DELETE-POS(pos)) } } my class Array::Element::Access::delete-k { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos) { SELF.DELETE-POS(pos); pos } else { () } } } my class Array::Element::Access::delete-not-k { method element(\SELF,\pos) { SELF.DELETE-POS(pos) if SELF.EXISTS-POS(pos); pos } } my class Array::Element::Access::delete-v { method element(\SELF,\pos) { SELF.EXISTS-POS(pos) ?? SELF.DELETE-POS(pos) !! () } } # Classes that take an Any position my class Array::Element::Access::none-any { method element(\SELF,\pos) { SELF.AT-POS(pos.Int) } } my class Array::Element::Access::kv-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) ?? (pos,SELF.AT-POS(pos.Int)) !! () } } my class Array::Element::Access::not-kv-any { method element(\SELF,\pos) { (pos,SELF.AT-POS(pos.Int)) } } my class Array::Element::Access::p-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) ?? Pair.new(pos,SELF.AT-POS(pos.Int)) !! () } } my class Array::Element::Access::not-p-any { method element(\SELF,\pos) { Pair.new(pos,SELF.AT-POS(pos.Int)) } } my class Array::Element::Access::k-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) ?? pos !! () } } my class Array::Element::Access::not-k-any { method element(\SELF,\pos) { pos } } my class Array::Element::Access::v-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) ?? nqp::decont(SELF.AT-POS(pos.Int)) !! () } } my class Array::Element::Access::exists-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) } } my class Array::Element::Access::exists-kv-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) ?? (pos,True) !! () } } my class Array::Element::Access::exists-not-kv-any { method element(\SELF,\pos) { (pos,SELF.EXISTS-POS(pos.Int)) } } my class Array::Element::Access::exists-p-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) ?? Pair.new(pos,True) !! () } } my class Array::Element::Access::exists-not-p-any { method element(\SELF,\pos) { Pair.new(pos,SELF.EXISTS-POS(pos.Int)) } } my class Array::Element::Access::exists-delete-any { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos.Int) { SELF.DELETE-POS(pos.Int); True } else { False } } } my class Array::Element::Access::exists-delete-kv-any { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos.Int) { SELF.DELETE-POS(pos.Int); (pos,True) } else { () } } } my class Array::Element::Access::exists-delete-not-kv-any { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos.Int) { SELF.DELETE-POS(pos.Int); (pos,True) } else { (pos,False) } } } my class Array::Element::Access::exists-delete-p-any { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos.Int) { SELF.DELETE-POS(pos.Int); Pair.new(pos,True) } else { () } } } my class Array::Element::Access::exists-delete-not-p-any { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos.Int) { SELF.DELETE-POS(pos.Int); Pair.new(pos,True) } else { Pair.new(pos,False) } } } my class Array::Element::Access::not-exists-any { method element(\SELF,\pos) { !SELF.EXISTS-POS(pos.Int) } } my class Array::Element::Access::not-exists-kv-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) ?? (pos,False) !! () } } my class Array::Element::Access::not-exists-not-kv-any { method element(\SELF,\pos) { (pos,!SELF.EXISTS-POS(pos.Int)) } } my class Array::Element::Access::not-exists-p-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) ?? Pair.new(pos,False) !! () } } my class Array::Element::Access::not-exists-not-p-any { method element(\SELF,\pos) { Pair.new(pos,!SELF.EXISTS-POS(pos.Int)) } } my class Array::Element::Access::not-exists-delete-any { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos.Int) { SELF.DELETE-POS(pos.Int); False } else { True } } } my class Array::Element::Access::not-exists-delete-kv-any { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos.Int) { SELF.DELETE-POS(pos.Int); (pos,False) } else { () } } } my class Array::Element::Access::not-exists-delete-not-kv-any { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos.Int) { SELF.DELETE-POS(pos.Int); (pos,False) } else { (pos,True) } } } my class Array::Element::Access::not-exists-delete-p-any { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos.Int) { SELF.DELETE-POS(pos.Int); Pair.new(pos,False) } else { () } } } my class Array::Element::Access::not-exists-delete-not-p-any { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos.Int) { SELF.DELETE-POS(pos.Int); Pair.new(pos,False) } else { Pair.new(pos,True) } } } my class Array::Element::Access::delete-any { method element(\SELF,\pos) { SELF.DELETE-POS(pos.Int) } } my class Array::Element::Access::delete-kv-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) ?? (pos,SELF.DELETE-POS(pos.Int)) !! () } } my class Array::Element::Access::delete-not-kv-any { method element(\SELF,\pos) { (pos,SELF.DELETE-POS(pos.Int)) } } my class Array::Element::Access::delete-p-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) ?? Pair.new(pos,SELF.DELETE-POS(pos.Int)) !! () } } my class Array::Element::Access::delete-not-p-any { method element(\SELF,\pos) { Pair.new(pos,SELF.DELETE-POS(pos.Int)) } } my class Array::Element::Access::delete-k-any { method element(\SELF,\pos) { if SELF.EXISTS-POS(pos.Int) { SELF.DELETE-POS(pos.Int); pos } else { () } } } my class Array::Element::Access::delete-not-k-any { method element(\SELF,\pos) { SELF.DELETE-POS(pos.Int) if SELF.EXISTS-POS(pos.Int); pos } } my class Array::Element::Access::delete-v-any { method element(\SELF,\pos) { SELF.EXISTS-POS(pos.Int) ?? SELF.DELETE-POS(pos.Int) !! () } } #line 1 SETTING::src/core.c/Array/Slice.rakumod #- start of generated part of array slice access ------------------------------- #- Generated on 2021-02-22T20:46:50+01:00 by tools/build/makeARRAY_SLICE_ACCESS.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE # no actionable adverbs my class Array::Slice::Access::none is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,$!iterable.AT-POS(pos)); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,$!iterable.AT-POS(pos)) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :kv my class Array::Slice::Access::kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,$!iterable.AT-POS(pos)); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,$!iterable.AT-POS(pos)); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!kv my class Array::Slice::Access::not-kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,pos); nqp::push($!result,$!iterable.AT-POS(pos)); } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,$!iterable.AT-POS(pos)); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :p my class Array::Slice::Access::p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,Pair.new(pos,$!iterable.AT-POS(pos))) if $!iterable.EXISTS-POS(pos); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,Pair.new(pos,$!iterable.AT-POS(pos))) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!p my class Array::Slice::Access::not-p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,Pair.new(pos,$!iterable.AT-POS(pos))); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,Pair.new(pos,$!iterable.AT-POS(pos))) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :k my class Array::Slice::Access::k is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,pos) if $!iterable.EXISTS-POS(pos); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,pos) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!k my class Array::Slice::Access::not-k is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,pos); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,pos) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :v my class Array::Slice::Access::v is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,$!iterable.AT-POS(pos)) if $!iterable.EXISTS-POS(pos); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,$!iterable.AT-POS(pos)) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :exists my class Array::Slice::Access::exists is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,$!iterable.EXISTS-POS(pos)); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,True) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :exists:kv my class Array::Slice::Access::exists-kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,True); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,True); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :exists:!kv my class Array::Slice::Access::exists-not-kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,pos); nqp::push($!result,$!iterable.EXISTS-POS(pos)); } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,True); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :exists:p my class Array::Slice::Access::exists-p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,Pair.new(pos,True)) if $!iterable.EXISTS-POS(pos); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,Pair.new(pos,True)) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :exists:!p my class Array::Slice::Access::exists-not-p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,Pair.new(pos,$!iterable.EXISTS-POS(pos))); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,Pair.new(pos,True)) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :exists:delete my class Array::Slice::Access::exists-delete is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,True); } else { nqp::push($!result,False); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,True); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :exists:delete:kv my class Array::Slice::Access::exists-delete-kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,pos); nqp::push($!result,True); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,pos); nqp::push($!result,True); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :exists:delete:!kv my class Array::Slice::Access::exists-delete-not-kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,pos); self!delete(pos) if nqp::push($!result,$!iterable.EXISTS-POS(pos)); } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,pos); nqp::push($!result,True); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :exists:delete:p my class Array::Slice::Access::exists-delete-p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,Pair.new(pos,True)); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,Pair.new(pos,True)); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :exists:delete:!p my class Array::Slice::Access::exists-delete-not-p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,Pair.new(pos,True)); } else { nqp::push($!result,Pair.new(pos,False)); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,Pair.new(pos,True)); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!exists my class Array::Slice::Access::not-exists is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,!$!iterable.EXISTS-POS(pos)); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,False) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!exists:kv my class Array::Slice::Access::not-exists-kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,False); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,False); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!exists:!kv my class Array::Slice::Access::not-exists-not-kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,pos); nqp::push($!result,!$!iterable.EXISTS-POS(pos)); } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,False); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!exists:p my class Array::Slice::Access::not-exists-p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,Pair.new(pos,False)) if $!iterable.EXISTS-POS(pos); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,Pair.new(pos,False)) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!exists:!p my class Array::Slice::Access::not-exists-not-p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,Pair.new(pos,!$!iterable.EXISTS-POS(pos))); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,Pair.new(pos,False)) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!exists:delete my class Array::Slice::Access::not-exists-delete is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,False); } else { nqp::push($!result,True); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,False); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!exists:delete:kv my class Array::Slice::Access::not-exists-delete-kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,pos); nqp::push($!result,False); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,pos); nqp::push($!result,False); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!exists:delete:!kv my class Array::Slice::Access::not-exists-delete-not-kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,pos); self!delete(pos) unless nqp::push($!result,!$!iterable.EXISTS-POS(pos)); } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,pos); nqp::push($!result,False); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!exists:delete:p my class Array::Slice::Access::not-exists-delete-p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,Pair.new(pos,False)); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,Pair.new(pos,False)); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :!exists:delete:!p my class Array::Slice::Access::not-exists-delete-not-p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,Pair.new(pos,False)); } else { nqp::push($!result,Pair.new(pos,True)); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,Pair.new(pos,False)); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :delete my class Array::Slice::Access::delete is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,self!delete(pos)); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,self!delete(pos)) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :delete:kv my class Array::Slice::Access::delete-kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,self!delete(pos)); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,self!delete(pos)); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :delete:kv my class Array::Slice::Access::delete-not-kv is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,pos); nqp::push($!result,self!delete(pos)); } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,pos); nqp::push($!result,self!delete(pos)); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :delete:p my class Array::Slice::Access::delete-p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,Pair.new(pos,self!delete(pos))) if $!iterable.EXISTS-POS(pos); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,Pair.new(pos,self!delete(pos))) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :delete:!p my class Array::Slice::Access::delete-not-p is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,Pair.new(pos,self!delete(pos))); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,Pair.new(pos,self!delete(pos))) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :delete:k my class Array::Slice::Access::delete-k is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,pos); } } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,pos); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :delete:!k my class Array::Slice::Access::delete-not-k is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { self!delete(pos) if $!iterable.EXISTS-POS(pos); nqp::push($!result,pos); } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { self!delete(pos); nqp::push($!result,pos); } else { $!done = 1; } } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } # :delete:v my class Array::Slice::Access::delete-v is implementation-detail { has $!result; has $!elems; has $!iterable; has int $!done; method !accept(\pos --> Nil) { nqp::push($!result,self!delete(pos)) if $!iterable.EXISTS-POS(pos); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,self!delete(pos)) !! ($!done = 1); } method !SET-SELF(\iterable) { $!result := nqp::create(IterationBuffer); $!elems := nqp::null; $!iterable := iterable; self } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } method new(\iterable) { nqp::create(self)!SET-SELF(iterable) } # Helper method for deleting elements, making sure that the total number # of elements is fixed from *before* the first deletion, so that relative # positions such as *-1 will continue to refer to the same position, # even if the last element of an array was removed (which shortens the # array). method !delete(\pos) { $!elems := $!iterable.elems if nqp::isnull($!elems); $!iterable.DELETE-POS(pos) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); # We're only done on this level, not generally $!done = 0; } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept($real) !! self!handle-nonInt($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my $real := (pos)(self!elems)),Int) ?? self!accept-lazy($real) !! self!handle-nonInt-lazy($real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of array slice access --------------------------------- #- start of generated part of array slice assignment --------------------------- #- Generated on 2021-02-23T11:58:40+01:00 by tools/build/makeARRAY_SLICE_ASSIGN.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE # no actionable adverbs my class Array::Slice::Assign::none is implementation-detail { has $!result; # IterationBuffer with result has $!lhs; # IterationBuffer with containers has $!rhs; # IterationBuffer with values has $!iterable; # Iterable to assign to has $!elems; # Number of elements in iterable has $!values; # Iterator producing values to assign has int $!done; # flag to indicate we're done method !accept(\pos --> Nil) { nqp::push($!result,nqp::push($!lhs,$!iterable.AT-POS(pos))); nqp::push($!rhs,$!values.pull-one); } method !accept-lazy(\pos --> Nil) { if $!iterable.EXISTS-POS(pos) { nqp::push($!result,nqp::push($!lhs,$!iterable.AT-POS(pos))); nqp::push($!rhs,$!values.pull-one); } else { $!done = 1; } } method !SET-SELF(\iterable, \values) { $!result := nqp::create(IterationBuffer); $!lhs := nqp::create(IterationBuffer); $!rhs := nqp::create(IterationBuffer); $!iterable := iterable; $!elems := nqp::null; $!values := values; self } method new(\iterable, \values) { nqp::create(self)!SET-SELF(iterable, values) } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { # Set up alternate result handling my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my \real := (pos)(self!elems)),Int) ?? self!accept(real) !! self!handle-nonInt(real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my \real := (pos)(self!elems)),Int) ?? self!accept-lazy(real) !! self!handle-nonInt-lazy(real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method assign-slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Do the actual assignments until there's nothing to assign anymore my $lhs := $!lhs; my $rhs := $!rhs; nqp::while( nqp::elems($lhs), nqp::assign(nqp::shift($lhs),nqp::shift($rhs)) ); $!result.List } } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of array slice assignment ----------------------------- #- start of generated part of array slice binding ------------------------------ #- Generated on 2021-02-23T11:56:12+01:00 by tools/build/makeARRAY_SLICE_BIND.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE # no actionable adverbs my class Array::Slice::Bind::none is implementation-detail { has $!result; # IterationBuffer with result has $!iterable; # Iterable to assign to has $!elems; # Number of elements in iterable has $!values; # Iterator producing values to assign has int $!done; # flag to indicate we're done method !accept(\pos --> Nil) { nqp::push($!result,$!iterable.BIND-POS(pos,$!values.pull-one)); } method !accept-lazy(\pos --> Nil) { $!iterable.EXISTS-POS(pos) ?? nqp::push($!result,$!iterable.BIND-POS(pos,$!values.pull-one)) !! ($!done = 1); } method !SET-SELF(\iterable, \values) { $!result := nqp::create(IterationBuffer); $!iterable := iterable; $!elems := nqp::null; $!values := values; self } method new(\iterable, \values) { nqp::create(self)!SET-SELF(iterable, values) } method !elems() { nqp::ifnull($!elems,$!elems := $!iterable.elems) } # Handle iterator in the generated positions: this will add a List # with the elements pointed to by the iterator to the result. Because # these positions can also be non-Int, some trickery needs to be # done to allow this being called recursively. method !handle-iterator(\iterator) { # basically push the current result on a stack my int $mark = nqp::elems($!result); # Lazy iterators should halt as soon as a non-existing element is seen if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept-lazy(pos), self!handle-nonInt-lazy(pos) ) ); } # Fast path for non-lazy iterators else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } # Take what was added and push it as a List if nqp::isgt_i(nqp::elems($!result),$mark) { # Set up alternate result handling my $buffer; if $mark { $buffer := nqp::slice($!result,$mark,nqp::sub_i(nqp::elems($!result),1)); nqp::setelems($!result,$mark); } else { $buffer := $!result; $!result := nqp::create(IterationBuffer); } nqp::push($!result,$buffer.List); } } # Handle anything non-integer in the generated positions eagerly method !handle-nonInt(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my \real := (pos)(self!elems)),Int) ?? self!accept(real) !! self!handle-nonInt(real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept(pos.Int) } # Handle anything non-integer in the generated positions lazily method !handle-nonInt-lazy(\pos) { nqp::istype(pos,Iterable) ?? nqp::iscont(pos) ?? self!accept-lazy(pos.Int) !! self!handle-iterator(pos.iterator) !! nqp::istype(pos,Callable) ?? nqp::istype((my \real := (pos)(self!elems)),Int) ?? self!accept-lazy(real) !! self!handle-nonInt-lazy(real) !! nqp::istype(pos,Whatever) ?? self!handle-iterator( Rakudo::Iterator.IntRange(0,nqp::sub_i(self!elems,1)) ) !! self!accept-lazy(pos.Int) } # The actual building of the result method bind-slice(\iterator) { if iterator.is-lazy { nqp::until( $!done || nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } else { nqp::until( nqp::eqaddr((my \pos := iterator.pull-one),IterationEnd), nqp::if( nqp::istype(pos,Int), self!accept(pos), self!handle-nonInt(pos) ) ); } $!result.List } } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of array slice binding -------------------------------- #line 1 SETTING::src/core.c/array_slice.rakumod # all sub postcircumfix [] candidates here please proto sub postcircumfix:<[ ]>($, |) is nodal {*} multi sub postcircumfix:<[ ]>( \SELF, Any:U $type, |c ) { die "Unable to call postcircumfix {try SELF.VAR.name}[ $type.gist() ] with a type object\n" ~ "Indexing requires a defined object"; } multi sub postcircumfix:<[ ]>(Failure:D \SELF, Any:D \pos, *%_) { SELF } # @a[Int 1] multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos) is raw { SELF.AT-POS(pos) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, Mu \assignee) is raw { SELF.ASSIGN-POS(pos, assignee); } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, Mu :$BIND! is raw) is raw { SELF.BIND-POS(pos, $BIND); } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$delete!) is raw { $delete ?? SELF.DELETE-POS(pos) !! SELF.AT-POS(pos) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$delete!, *%_) is raw { Array::Element.access(SELF, pos, %_, 'delete', $delete) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$exists!) is raw { $exists ?? SELF.EXISTS-POS(pos) !! !SELF.EXISTS-POS(pos) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$exists!, *%_) is raw { Array::Element.access(SELF, pos, %_, 'exists', $exists) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$kv!) is raw { $kv ?? (SELF.EXISTS-POS(pos) ?? (pos, SELF.AT-POS(pos)) !! ()) !! (pos, SELF.AT-POS(pos)) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$kv!, *%_) is raw { Array::Element.access(SELF, pos, %_, 'kv', $kv) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$p!) is raw { $p ?? (SELF.EXISTS-POS(pos) ?? Pair.new(pos, SELF.AT-POS(pos)) !! ()) !! Pair.new(pos, SELF.AT-POS(pos)) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$p!, *%_) is raw { Array::Element.access(SELF, pos, %_, 'p', $p) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$k!) is raw { $k ?? (SELF.EXISTS-POS(pos) ?? pos !! ()) !! pos } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$k!, *%_) is raw { Array::Element.access(SELF, pos, %_, 'k', $k) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$v!) is raw { $v ?? (SELF.EXISTS-POS(pos) ?? nqp::decont(SELF.AT-POS(pos)) !! ()) !! SELF.AT-POS(pos) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$v!, *%_) is raw { Array::Element.access(SELF, pos, %_, 'v', $v) } # @a[$x] multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos) is raw { SELF.AT-POS(pos.Int) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, Mu \assignee) is raw { SELF.ASSIGN-POS(pos.Int, assignee) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, Mu :$BIND! is raw) is raw { SELF.BIND-POS(pos.Int, $BIND) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, :$delete!) is raw { $delete ?? SELF.DELETE-POS(pos.Int) !! SELF.AT-POS(pos.Int) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, :$delete!, *%_) is raw { Array::Element.access-any(SELF, pos, %_, 'delete', $delete) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, :$exists!) is raw { $exists ?? SELF.EXISTS-POS(pos.Int) !! !SELF.EXISTS-POS(pos.Int) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, :$exists!, *%_) is raw { Array::Element.access-any(SELF, pos, %_, 'exists', $exists) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, :$kv!) is raw { $kv ?? (SELF.EXISTS-POS(pos.Int) ?? (pos, SELF.AT-POS(pos.Int)) !! ()) !! (pos, SELF.AT-POS(pos.Int)) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, :$kv!, *%_) is raw { Array::Element.access-any(SELF, pos, %_, 'kv', $kv) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, :$p!) is raw { $p ?? (SELF.EXISTS-POS(pos.Int) ?? Pair.new(pos, SELF.AT-POS(pos.Int)) !! ()) !! Pair.new(pos, SELF.AT-POS(pos.Int)) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, :$p!, *%_) is raw { Array::Element.access-any(SELF, pos, %_, 'p', $p) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, :$k!) is raw { $k ?? (SELF.EXISTS-POS(pos.Int) ?? pos !! ()) !! pos } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, :$k!, *%_) is raw { Array::Element.access-any(SELF, pos, %_, 'k', $k) } multi sub postcircumfix:<[ ]>(\SELF, Any:D \pos, :$v!) is raw { $v ?? (SELF.EXISTS-POS(pos.Int) ?? nqp::decont(SELF.AT-POS(pos.Int)) !! ()) !! SELF.AT-POS(pos.Int) } multi sub postcircumfix:<[ ]>(\SELF, Int:D \pos, :$v!, *%_) is raw { Array::Element.access-any(SELF, pos, %_, 'v', $v) } # @a[@i] multi sub postcircumfix:<[ ]>(\SELF, Iterable:D \positions, *%_) is raw { nqp::iscont(positions) # MMD is not behaving itself so we do this by hand ?? postcircumfix:<[ ]>(SELF, positions.Int, |%_) !! nqp::isconcrete(my $storage := nqp::getattr(%_,Map,'$!storage')) && nqp::elems($storage) ?? Rakudo::Internals.SLICE_POSITIONS_WITH_ADVERBS( SELF, positions, %_ ) !! Array::Slice::Access::none.new(SELF).slice(positions.iterator) } multi sub postcircumfix:<[ ]>(\SELF, Iterable:D \positions, \values) is raw { # MMD is not behaving itself so we do this by hand. nqp::iscont(positions) ?? postcircumfix:<[ ]>(SELF, positions.Int, values) !! Array::Slice::Assign::none.new( SELF, Rakudo::Iterator.TailWith(values.iterator, Nil) ).assign-slice(positions.iterator) } multi sub postcircumfix:<[ ]>(\SELF, Iterable:D \positions, :$BIND! is raw) is raw { # MMD is not behaving itself so we do this by hand. nqp::iscont(positions) ?? postcircumfix:<[ ]>(SELF, positions.Int, :$BIND) !! Array::Slice::Bind::none.new( SELF, Rakudo::Iterator.TailWith($BIND.iterator, Nil) ).bind-slice(positions.iterator) } # @a[->{}] multi sub postcircumfix:<[ ]>(\SELF, Callable:D $block) is raw { my $*INDEX := 'Effective index'; nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? SELF.AT-POS(pos) !! SELF[pos] } multi sub postcircumfix:<[ ]>(\SELF, Callable:D $block, Mu \assignee) is raw { my $*INDEX := 'Effective index'; nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? SELF.ASSIGN-POS(pos,assignee) !! (SELF[pos] = assignee) } multi sub postcircumfix:<[ ]>(\SELF, Callable:D $block, :$BIND!) is raw { X::Bind::Slice.new(type => SELF.WHAT).throw; } multi sub postcircumfix:<[ ]>(\SELF, Callable:D $block, :$delete!) is raw { my $*INDEX := 'Effective index'; nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? $delete ?? SELF.DELETE-POS(pos) !! SELF.AT-POS(pos) !! $delete ?? (SELF[pos]:delete) !! SELF[pos] } multi sub postcircumfix:<[ ]>(\SELF, Callable:D $block, :$delete!, *%_) is raw { my $*INDEX := 'Effective index'; nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? Array::Element.access-any(SELF, pos, %_, 'delete', $delete) !! postcircumfix:<[ ]>(SELF, pos, :$delete, |%_) } multi sub postcircumfix:<[ ]>(\SELF, Callable:D $block, :$exists!) is raw { my $*INDEX := 'Effective index'; nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? $exists ?? SELF.EXISTS-POS(pos) !! !SELF.EXISTS-POS(pos) !! (SELF[pos]:$exists) } multi sub postcircumfix:<[ ]>(\SELF, Callable:D $block, :$exists!, *%_) is raw { my $*INDEX := 'Effective index'; nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? Array::Element.access-any(SELF, pos, %_, 'exists', $exists) !! postcircumfix:<[ ]>(SELF, pos, :$exists, |%_) } multi sub postcircumfix:<[ ]>(\SELF,Callable:D $block, :$kv!) is raw { nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? $kv ?? SELF.EXISTS-POS(pos) ?? (pos,SELF.AT-POS(pos)) !! () !! (pos,SELF.AT-POS(pos)) !! (SELF[pos]:$kv) } multi sub postcircumfix:<[ ]>(\SELF,Callable:D $block, :$kv!, *%_) is raw { my $*INDEX := 'Effective index'; nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? Array::Element.access-any(SELF, pos, %_, 'kv', $kv) !! postcircumfix:<[ ]>(SELF, pos, :$kv, |%_) } multi sub postcircumfix:<[ ]>(\SELF,Callable:D $block, :$p!) is raw { nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? $p ?? SELF.EXISTS-POS(pos) ?? Pair.new(pos,SELF.AT-POS(pos)) !! () !! Pair.new(pos,SELF.AT-POS(pos)) !! (SELF[pos]:$p) } multi sub postcircumfix:<[ ]>(\SELF,Callable:D $block, :$p!, *%_) is raw { my $*INDEX := 'Effective index'; nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? Array::Element.access-any(SELF, pos, %_, 'p', $p) !! postcircumfix:<[ ]>(SELF, pos, :$p, |%_) } multi sub postcircumfix:<[ ]>(\SELF,Callable:D $block, :$k!) is raw { nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? $k ?? SELF.EXISTS-POS(pos) ?? pos !! () !! pos !! (SELF[pos]:$k) } multi sub postcircumfix:<[ ]>(\SELF,Callable:D $block, :$k!, *%_) is raw { my $*INDEX := 'Effective index'; nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? Array::Element.access-any(SELF, pos, %_, 'k', $k) !! postcircumfix:<[ ]>(SELF, pos, :$k, |%_) } multi sub postcircumfix:<[ ]>(\SELF,Callable:D $block, :$v!) is raw { nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? $v ?? SELF.EXISTS-POS(pos) ?? SELF.AT-POS(pos) !! () !! SELF.AT-POS(pos) !! (SELF[pos]:$v) } multi sub postcircumfix:<[ ]>(\SELF,Callable:D $block, :$v!, *%_) is raw { my $*INDEX := 'Effective index'; nqp::istype((my \pos := $block.POSITIONS(SELF)),Int) ?? Array::Element.access-any(SELF, pos, %_, 'v', $v) !! postcircumfix:<[ ]>(SELF, pos, :$v, |%_) } # @a[*] multi sub postcircumfix:<[ ]>(\SELF, Whatever:D, *%_) is raw { nqp::if( nqp::isconcrete(my $storage := nqp::getattr(%_,Map,'$!storage')) && nqp::elems($storage), Rakudo::Internals.SLICE_WITH_ADVERBS(SELF, 'whatever slice', %_), nqp::stmts( # fast path SELF.iterator.push-all(my $buffer := nqp::create(IterationBuffer)), $buffer.List ) ) } multi sub postcircumfix:<[ ]>( \SELF, Whatever:D, Mu \assignee ) is raw { SELF[^SELF.elems] = assignee; } multi sub postcircumfix:<[ ]>(\SELF, Whatever:D, :$BIND!) is raw { X::Bind::Slice.new(type => SELF.WHAT).throw; } # @a[**] multi sub postcircumfix:<[ ]>(\SELF, HyperWhatever:D $, *%adv) is raw { NYI('HyperWhatever in array index').throw; } multi sub postcircumfix:<[ ]>(\SELF, HyperWhatever:D $, Mu \assignee) is raw { NYI('HyperWhatever in array index').throw; } # @a[] multi sub postcircumfix:<[ ]>(\SELF, *%_) is raw { nqp::isconcrete(my $storage := nqp::getattr(%_,Map,'$!storage')) && nqp::elems($storage) ?? Rakudo::Internals.SLICE_WITH_ADVERBS(SELF, 'zen slice', %_) !! nqp::decont(SELF) # Just the thing, please } multi sub postcircumfix:<[ ]>(\SELF, :$BIND!) is raw { X::Bind::ZenSlice.new(type => SELF.WHAT).throw; } #line 1 SETTING::src/core.c/array_multislice.rakumod # all sub postcircumfix [;] candidates here please proto sub postcircumfix:<[; ]>($, $, Mu $?, *%) is nodal {*} sub MD-ARRAY-SLICE-ONE-POSITION( \SELF, \indices, \idx, int $dim, \target ) is raw is implementation-detail { my int $next-dim = $dim + 1; if $next-dim < indices.elems { if nqp::istype(idx, Iterable) && !nqp::iscont(idx) { for idx { MD-ARRAY-SLICE-ONE-POSITION(SELF, indices, $_, $dim, target) } } elsif nqp::istype(idx, Int) { MD-ARRAY-SLICE-ONE-POSITION(SELF.AT-POS(idx), indices, indices.AT-POS($next-dim), $next-dim, target) } elsif nqp::istype(idx, Whatever) { for ^SELF.elems { MD-ARRAY-SLICE-ONE-POSITION(SELF.AT-POS($_), indices, indices.AT-POS($next-dim), $next-dim, target) } } elsif nqp::istype(idx, Callable) { MD-ARRAY-SLICE-ONE-POSITION(SELF, indices, idx.(|(SELF.elems xx (idx.count == Inf ?? 1 !! idx.count))), $dim, target); } else { MD-ARRAY-SLICE-ONE-POSITION(SELF.AT-POS(idx.Int), indices, indices.AT-POS($next-dim), $next-dim, target) } } else { if nqp::istype(idx, Iterable) && !nqp::iscont(idx) { for idx { MD-ARRAY-SLICE-ONE-POSITION(SELF, indices, $_, $dim, target) } } elsif nqp::istype(idx, Int) { nqp::push(target, SELF.AT-POS(idx)) } elsif nqp::istype(idx, Whatever) { for ^SELF.elems { nqp::push(target, SELF.AT-POS($_)) } } elsif nqp::istype(idx, Callable) { nqp::push(target, SELF.AT-POS(idx.(|(SELF.elems xx (idx.count == Inf ?? 1 !! idx.count))))) } else { nqp::push(target, SELF.AT-POS(idx.Int)) } } } sub MD-ARRAY-SLICE(\SELF, @indices) is raw is implementation-detail { my \target = nqp::create(IterationBuffer); MD-ARRAY-SLICE-ONE-POSITION(SELF, @indices, @indices.AT-POS(0), 0, target); target.List } multi sub postcircumfix:<[; ]>(\SELF, @indices) is raw { my \indices := nqp::getattr(@indices,List,'$!reified'); my int $elems = nqp::elems(indices); my int $i = -1; my \idxs := nqp::list_i; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::istype((my $index = nqp::atpos(indices,$i)),Int), nqp::push_i(idxs,$index), # it's an Int, use that nqp::if( nqp::istype($index,Numeric), nqp::push_i(idxs,$index.Int), # can be safely coerced to Int nqp::if( nqp::istype($index,Str), nqp::if( nqp::istype((my \coerced := $index.Int),Failure), coerced.throw, # alas, not numeric, bye! nqp::push_i(idxs,coerced) # could be coerced to Int ), (return-rw MD-ARRAY-SLICE(SELF,@indices)) # alas, slow path needed ) ) ) ); nqp::if( # we have all Ints nqp::iseq_i($elems,2), SELF.AT-POS( # fast pathing [n;n] nqp::atpos_i(idxs,0), nqp::atpos_i(idxs,1) ), nqp::if( nqp::iseq_i($elems,3), SELF.AT-POS( # fast pathing [n;n;n] nqp::atpos_i(idxs,0), nqp::atpos_i(idxs,1), nqp::atpos_i(idxs,2) ), SELF.AT-POS(|@indices) # alas >3 dims, slow path ) ) } multi sub postcircumfix:<[; ]>(\SELF, @indices, Mu \assignee) is raw { my int $elems = @indices.elems; # reifies my \indices := nqp::getattr(@indices,List,'$!reified'); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems) && nqp::istype(nqp::atpos(indices,$i),Int), nqp::null ); nqp::if( nqp::islt_i($i,$elems), (MD-ARRAY-SLICE(SELF,@indices) = assignee), nqp::if( nqp::iseq_i($elems,2), SELF.ASSIGN-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), assignee ), nqp::if( nqp::iseq_i($elems,3), SELF.ASSIGN-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), nqp::atpos(indices,2), assignee ), SELF.ASSIGN-POS(|@indices,assignee) ) ) ) } multi sub postcircumfix:<[; ]>(\SELF, @indices, :$BIND!) is raw { my int $elems = @indices.elems; # reifies my \indices := nqp::getattr(@indices,List,'$!reified'); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems) && nqp::istype(nqp::atpos(indices,$i),Int), nqp::null ); nqp::if( nqp::islt_i($i,$elems), X::Bind::Slice.new(type => SELF.WHAT).throw, nqp::if( nqp::iseq_i($elems,2), SELF.BIND-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), $BIND ), nqp::if( nqp::iseq_i($elems,3), SELF.BIND-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), nqp::atpos(indices,2), $BIND ), SELF.BIND-POS(|@indices, $BIND) ) ) ) } multi sub postcircumfix:<[; ]>(\SELF, @indices, :$delete!) is raw { nqp::if( $delete, nqp::stmts( (my int $elems = @indices.elems), # reifies (my \indices := nqp::getattr(@indices,List,'$!reified')), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::istype(nqp::atpos(indices,$i),Int), nqp::null ), nqp::if( nqp::islt_i($i,$elems), NYI(':delete on multi-dimensional slices'), nqp::if( nqp::iseq_i($elems,2), SELF.DELETE-POS( nqp::atpos(indices,0), nqp::atpos(indices,1) ), nqp::if( nqp::iseq_i($elems,3), SELF.DELETE-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), nqp::atpos(indices,2) ), SELF.DELETE-POS(|@indices) ) ) ) ), postcircumfix:<[; ]>(SELF, @indices) ) } multi sub postcircumfix:<[; ]>(\SELF, @indices, :$exists!) is raw { nqp::if( $exists, nqp::stmts( (my int $elems = @indices.elems), # reifies (my \indices := nqp::getattr(@indices,List,'$!reified')), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::istype(nqp::atpos(indices,$i),Int), nqp::null ), nqp::if( nqp::islt_i($i,$elems), NYI(':exists on multi-dimensional slices'), nqp::if( nqp::iseq_i($elems,2), SELF.EXISTS-POS( nqp::atpos(indices,0), nqp::atpos(indices,1) ), nqp::if( nqp::iseq_i($elems,3), SELF.EXISTS-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), nqp::atpos(indices,2) ), SELF.EXISTS-POS(|@indices) ) ) ) ), postcircumfix:<[; ]>(SELF, @indices) ) } multi sub postcircumfix:<[; ]>(\SELF, @indices, :$kv!) is raw { nqp::if( $kv, nqp::stmts( (my int $elems = @indices.elems), # reifies (my \indices := nqp::getattr(@indices,List,'$!reified')), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::istype(nqp::atpos(indices,$i),Int), nqp::null ), nqp::if( nqp::islt_i($i,$elems), NYI(':kv on multi-dimensional slices'), nqp::if( nqp::iseq_i($elems,2), nqp::if( SELF.EXISTS-POS( nqp::atpos(indices,0), nqp::atpos(indices,1) ), (@indices, SELF.AT-POS( nqp::atpos(indices,0), nqp::atpos(indices,1) )), () ), nqp::if( nqp::iseq_i($elems,3), nqp::if( SELF.EXISTS-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), nqp::atpos(indices,2) ), (@indices, SELF.AT-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), nqp::atpos(indices,2) )), () ), nqp::if( SELF.EXISTS-POS(|@indices), (@indices, SELF.AT-POS(|@indices)), () ) ) ) ) ), postcircumfix:<[; ]>(SELF, @indices) ) } multi sub postcircumfix:<[; ]>(\SELF, @indices, :$p!) is raw { nqp::if( $p, nqp::stmts( (my int $elems = @indices.elems), # reifies (my \indices := nqp::getattr(@indices,List,'$!reified')), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::istype(nqp::atpos(indices,$i),Int), nqp::null ), nqp::if( nqp::islt_i($i,$elems), NYI(':p on multi-dimensional slices'), nqp::if( nqp::iseq_i($elems,2), nqp::if( SELF.EXISTS-POS( nqp::atpos(indices,0), nqp::atpos(indices,1) ), Pair.new(@indices, SELF.AT-POS( nqp::atpos(indices,0), nqp::atpos(indices,1) )), () ), nqp::if( nqp::iseq_i($elems,3), nqp::if( SELF.EXISTS-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), nqp::atpos(indices,2) ), Pair.new(@indices, SELF.AT-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), nqp::atpos(indices,2) )), () ), nqp::if( SELF.EXISTS-POS(|@indices), Pair.new(@indices, SELF.AT-POS(|@indices)), () ) ) ) ) ), postcircumfix:<[; ]>(SELF, @indices) ) } multi sub postcircumfix:<[; ]>(\SELF, @indices, :$k!) is raw { nqp::if( $k, nqp::stmts( (my int $elems = @indices.elems), # reifies (my \indices := nqp::getattr(@indices,List,'$!reified')), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::istype(nqp::atpos(indices,$i),Int), nqp::null ), nqp::if( nqp::islt_i($i,$elems), NYI(':k on multi-dimensional slices'), nqp::if( nqp::iseq_i($elems,2), nqp::if( SELF.EXISTS-POS( nqp::atpos(indices,0), nqp::atpos(indices,1) ), @indices, () ), nqp::if( nqp::iseq_i($elems,3), nqp::if( SELF.EXISTS-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), nqp::atpos(indices,2) ), @indices, () ), nqp::if( SELF.EXISTS-POS(|@indices), @indices, () ) ) ) ) ), postcircumfix:<[; ]>(SELF, @indices) ) } multi sub postcircumfix:<[; ]>(\SELF, @indices, :$v!) is raw { nqp::if( $v, nqp::stmts( (my int $elems = @indices.elems), # reifies (my \indices := nqp::getattr(@indices,List,'$!reified')), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::istype(nqp::atpos(indices,$i),Int), nqp::null ), nqp::if( nqp::islt_i($i,$elems), NYI(':v on multi-dimensional slices'), nqp::if( nqp::iseq_i($elems,2), nqp::if( SELF.EXISTS-POS( nqp::atpos(indices,0), nqp::atpos(indices,1) ), nqp::decont(SELF.AT-POS( nqp::atpos(indices,0), nqp::atpos(indices,1) )), () ), nqp::if( nqp::iseq_i($elems,3), nqp::if( SELF.EXISTS-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), nqp::atpos(indices,2) ), nqp::decont(SELF.AT-POS( nqp::atpos(indices,0), nqp::atpos(indices,1), nqp::atpos(indices,2) )), () ), nqp::if( SELF.EXISTS-POS(|@indices), nqp::decont(SELF.AT-POS(|@indices)), () ) ) ) ) ), postcircumfix:<[; ]>(SELF, @indices) ) } #line 1 SETTING::src/core.c/Slip.rakumod # A Slip is a kind of List that is immediately incorporated into an iteration # or another List. Other than that, it's a totally normal List. my class Slip { # is List # XXX this makes an empty Slip undefined? multi method defined (Slip:D: --> Bool:D) { self.Bool } multi method Slip(Slip:D:) { self } multi method raku(Slip:D: --> Str:D) { nqp::if( nqp::eqaddr(self,Empty), 'Empty', nqp::stmts( (my str $guts = callsame), nqp::if( nqp::eqat($guts,'$',0), # we're itemized nqp::concat('$(slip',nqp::concat(nqp::substr($guts,1),')')), nqp::concat('slip',$guts) ) ) ) } multi method List(Slip:D: --> List:D) { my $list := nqp::create(List); nqp::bindattr($list,List,'$!todo',nqp::getattr(self,List,'$!todo')) if nqp::isconcrete(nqp::getattr(self,List,'$!todo')); nqp::bindattr($list,List,'$!reified',nqp::getattr(self,List,'$!reified')) if nqp::isconcrete(nqp::getattr(self,List,'$!reified')); $list } # shortcutting methods for better performance on Empty multi method are(Slip:D:) { nqp::eqaddr(self,Empty) ?? Nil !! nextsame } multi method batch(Slip:D:) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method batch(Slip:D: $) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method deepmap(Slip:D: &) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method duckmap(Slip:D: &) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method map(Slip:D: &) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method first(Slip:D: &) { nqp::eqaddr(self,Empty) ?? Nil !! nextsame } multi method grep(Slip:D: &) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method head(Slip:D:) { nqp::eqaddr(self,Empty) ?? Nil !! nextsame } multi method head(Slip:D: $) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } method join(Slip:D: |) { nqp::eqaddr(self,Empty) ?? "" !! nextsame } multi method maxpairs(Slip:D:) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method minpairs(Slip:D:) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method nodemap(Slip:D: &) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method pairup(Slip:D:) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method repeated(Slip:D: |) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method rotor(Slip:D: |) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method skip(Slip:D:) { nqp::eqaddr(self,Empty) ?? Nil !! nextsame } multi method skip(Slip:D: $) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method sort(Slip:D:) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method sort(Slip:D: &) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method squish(Slip:D: |) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method tail(Slip:D:) { nqp::eqaddr(self,Empty) ?? Nil !! nextsame } multi method tail(Slip:D: $) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method toggle(Slip:D: &) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } multi method unique(Slip:D: |) { nqp::eqaddr(self,Empty) ?? Empty !! nextsame } } # The slip(...) function creates a Slip. proto sub slip(|) {*} multi sub slip(--> Empty) { } multi sub slip(@args --> Slip:D) { @args.Slip } multi sub slip(+args --> Slip:D) { args.Slip } #line 1 SETTING::src/core.c/Array/Typed.rakumod my role Array::Typed[::TValue] does Positional[TValue] { proto method new(|) {*} multi method new(:$shape!) { set-descriptor( nqp::defined($shape) ?? self.set-shape($shape) !! Metamodel::EnumHOW.ACCEPTS($shape.HOW) ?? self.set-shape($shape.^elems) !! nqp::create(self) ) } multi method new() { set-descriptor(nqp::create(self)) } multi method new(\values, :$shape!) { set-descriptor( nqp::defined($shape) ?? self.set-shape($shape) !! Metamodel::EnumHOW.ACCEPTS($shape.HOW) ?? self.set-shape($shape.^elems) !! nqp::create(self) ).STORE(values) } multi method new(\values) { set-descriptor(nqp::create(self)).STORE(values) } multi method new(**@values is raw, :$shape!) { set-descriptor( nqp::defined($shape) ?? self.set-shape($shape) !! Metamodel::EnumHOW.ACCEPTS($shape.HOW) ?? self.set-shape($shape.^elems) !! nqp::create(self) ).STORE(@values) } multi method new(**@values is raw) { set-descriptor(nqp::create(self)).STORE(@values) } sub set-descriptor(\list) is raw { nqp::bindattr(list,Array,'$!descriptor', ContainerDescriptor.new(:of(TValue), :default(TValue)) ); list } method !out-of-range(int $got) { X::OutOfRange.new(:what($*INDEX // 'Index'),:$got,:range<0..^Inf>).Failure } # must have a proto here to hide the candidates in Array # otherwise we could bind any value to the Array proto method BIND-POS(|) {*} # these BIND-POSses are identical to Array's, except for bindval multi method BIND-POS(Array:D: uint $pos, TValue \bindval) is raw { nqp::if( nqp::isconcrete( my $reified := nqp::getattr(self,List,'$!reified') ), nqp::if( nqp::isge_i($pos,nqp::elems($reified)) && nqp::isconcrete(nqp::getattr(self,List,'$!todo')), nqp::getattr(self,List,'$!todo').reify-at-least( nqp::add_i($pos,1)), ), ($reified := nqp::bindattr( self,List,'$!reified',nqp::create(IterationBuffer) )) ); nqp::bindpos($reified,$pos,bindval) } # because this is a very hot path, we copied the code from the int candidate multi method BIND-POS(Array:D: Int:D $pos, TValue \bindval) is raw { nqp::if( nqp::islt_i($pos,0), self!out-of-range($pos), nqp::stmts( nqp::if( nqp::isconcrete( my $reified := nqp::getattr(self,List,'$!reified') ), nqp::if( nqp::isge_i($pos,nqp::elems($reified)) && nqp::isconcrete(nqp::getattr(self,List,'$!todo')), nqp::getattr(self,List,'$!todo').reify-at-least( nqp::add_i($pos,1) ) ), ($reified := nqp::bindattr( self,List,'$!reified',nqp::create(IterationBuffer) )) ), nqp::bindpos($reified,$pos,bindval) ) ) } multi method raku(::?CLASS:D:) { my $type := (try TValue.raku) // nqp::getattr(self,Array,'$!descriptor').of.^name; my $raku := self.map({ nqp::isconcrete($_) ?? .raku(:arglist) !! $type }).join(', '); 'Array[' ~ $type ~ '].new(' ~ $raku ~ ')' } } #line 1 SETTING::src/core.c/Array/Shaped.rakumod # for our tantrums my class X::Assignment::ArrayShapeMismatch { ... }; my class X::NotEnoughDimensions { ... }; my role Array::Shaped does Rakudo::Internals::ShapedArrayCommon { has $.shape; multi method new(::?CLASS:D:) { nqp::istype(self,Array::Typed) ?? Array[self.of].new(:$!shape) !! Array.new(:$!shape) } # Handle dimensions > 3 or more indices than dimensions. # If dimensions <= 3, then custom AT-POS should have caught # correct number of indices already. multi method AT-POS(::?CLASS:D: **@indices) is raw { my \reified := nqp::getattr(self,List,'$!reified'); nqp::if( nqp::islt_i( @indices.elems, # reifies (my int $numdims = nqp::numdimensions(reified)) ), NYI("Partially dimensioned views of shaped arrays").throw, nqp::stmts( (my \indices := nqp::getattr(@indices,List,'$!reified')), (my \idxs := nqp::list_i), nqp::while( # native index list nqp::isge_i(--$numdims,0), nqp::push_i(idxs,nqp::shift(indices)) ), (my \element := nqp::ifnull( nqp::atposnd(reified,idxs), # found it nqp::p6scalarfromdesc( ContainerDescriptor::BindArrayPosND.new( nqp::getattr(self, Array, '$!descriptor'), reified, idxs ) ) )), nqp::if( nqp::elems(indices), element.AT-POS(|@indices), # index further element # we're done! ) ) ) } multi method ASSIGN-POS(::?CLASS:D: **@indices-value) { my \value := @indices-value.pop; # reifies my \indices := nqp::getattr(@indices-value,List,'$!reified'); my \reified := nqp::getattr(self,List,'$!reified'); nqp::if( nqp::isge_i( (my int $numind = nqp::elems(indices)), (my int $numdims = nqp::numdimensions(reified)) ), nqp::stmts( # more than enough indices (my \idxs := nqp::list_i), nqp::while( # native index list nqp::isge_i(--$numdims,0), nqp::push_i(idxs,nqp::shift(indices)) ), (my \element := nqp::ifnull( nqp::atposnd(reified,idxs), # found it! nqp::bindposnd(reified,idxs, # create new scalar nqp::p6scalarfromdesc( nqp::getattr(self,Array,'$!descriptor'))) )), nqp::if( nqp::elems(indices), element.AT-POS(|@indices-value), # go deeper element # this is it ) = value # and assign ), X::NotEnoughDimensions.new( # too few indices operation => 'assign to', got-dimensions => $numind, needed-dimensions => $numdims ).throw ) } multi method EXISTS-POS(::?CLASS:D: **@indices --> Bool:D) { nqp::hllbool( nqp::stmts( (my int $numind = @indices.elems), # reifies (my \indices := nqp::getattr(@indices,List,'$!reified')), (my \reified := nqp::getattr(self,List,'$!reified')), (my \dims := nqp::dimensions(reified)), (my int $i = -1), nqp::if( nqp::isge_i( $numind, (my int $numdims = nqp::numdimensions(reified)), ), nqp::stmts( # same or more indices (my \idxs := nqp::list_i), nqp::while( nqp::islt_i(++$i,$numind) # still indices left && nqp::islt_i( # within range? (my $idx = nqp::shift(indices)), nqp::atpos_i(dims,$i)), nqp::push_i(idxs,$idx) ), nqp::if( nqp::iseq_i($i,$numind) && nqp::not_i( nqp::isnull(nqp::atposnd(reified,idxs))), nqp::unless( # base pos exists nqp::not_i(nqp::elems(indices)), nqp::atposnd(reified,idxs).EXISTS-POS(|@indices) ) ) ), nqp::stmts( # fewer inds than dims nqp::while( nqp::islt_i(++$i,$numind) && nqp::islt_i( nqp::atpos(indices,$i), nqp::atpos_i(dims,$i)), nqp::null ), nqp::iseq_i($i,$numind) # all clear or oor ) ) ) ) } proto method DELETE-POS(|) {*} multi method DELETE-POS(::?CLASS:U: |c) { self.Any::DELETE-POS(|c) } multi method DELETE-POS(::?CLASS:D:) is raw { die "Must specify at least one index with DELETE-POS" } multi method DELETE-POS(::?CLASS:D: **@indices) { my int $numind = @indices.elems; # reifies my \indices := nqp::getattr(@indices,List,'$!reified'); my \reified := nqp::getattr(self,List,'$!reified'); my int $i = -1; nqp::if( nqp::isge_i( $numind, (my int $numdims = nqp::numdimensions(reified)), ), nqp::stmts( # same or more indices (my \idxs := nqp::list_i), nqp::while( nqp::islt_i(++$i,$numind), # still indices left nqp::push_i(idxs,nqp::shift(indices)), ), nqp::if( nqp::isnull(my \value := nqp::atposnd(reified,idxs)), Nil, # nothing here nqp::if( nqp::elems(indices), value.DELETE-POS(|@indices), # delete at deeper level nqp::stmts( # found it, nullify here nqp::bindposnd(reified,idxs,nqp::null), value ) ) ) ), X::NotEnoughDimensions.new( # fewer inds than dims operation => 'delete from', got-dimensions => $numind, needed-dimensions => $numdims ).throw ) } proto method BIND-POS(|) is raw {*} multi method BIND-POS(::?CLASS:U: |c) is raw { self.Any::BIND-POS(|c) } multi method BIND-POS(::?CLASS:D:) { die "Must specify at least one index and a value with BIND-POS" } multi method BIND-POS(::?CLASS:D: $) { die "Must specify at least one index and a value with BIND-POS" } multi method BIND-POS(::?CLASS:D: **@indices) is raw { my \value := nqp::decont(@indices.pop); # reifies my \indices := nqp::getattr(@indices,List,'$!reified'); my \reified := nqp::getattr(self,List,'$!reified'); my int $i = -1; nqp::if( nqp::isge_i( (my int $numind = nqp::elems(indices)), (my int $numdims = nqp::numdimensions(reified)), ), nqp::stmts( # same or more indices (my \idxs := nqp::list_i), nqp::while( nqp::islt_i(++$i,$numind), # still indices left nqp::push_i(idxs,nqp::shift(indices)) ), nqp::if( nqp::elems(indices), nqp::atposnd(reified,idxs) # bind at deeper level .BIND-POS(|@indices,value), nqp::bindposnd(reified,idxs,value) # found it, bind here ) ), X::NotEnoughDimensions.new( # fewer inds than dims operation => 'bind to', got-dimensions => $numind, needed-dimensions => $numdims ).throw ) } my class MemCopy does Rakudo::Iterator::ShapeLeaf { has $!from; has $!desc; method !INIT(Mu \to, Mu \from) { $!from := nqp::getattr(from,List,'$!reified'); $!desc := nqp::getattr(from,Array,'$!descriptor'); self!SET-SELF(to) } method new(Mu \to, Mu \from) { nqp::create(self)!INIT(to,from) } method result(--> Nil) { nqp::ifnull( nqp::atposnd($!list,$!indices), nqp::bindposnd($!list,$!indices, nqp::p6scalarfromdesc($!desc)) ) = nqp::ifnull( nqp::atposnd($!from,$!indices), nqp::p6scalarfromdesc($!desc) ) } } sub MEMCPY(Mu \to, Mu \from) { MemCopy.new(to,from).sink-all } my class IntCopy does Rakudo::Iterator::ShapeLeaf { has $!from; method !INIT(Mu \to, Mu \from) { $!from := from; self!SET-SELF(to) } method new(Mu \to, Mu \from) { nqp::create(self)!INIT(to,from) } method result(--> Nil) { nqp::ifnull( nqp::atposnd($!list,$!indices), nqp::bindposnd($!list,$!indices,nqp::p6scalarfromdesc(Mu)) ) = nqp::multidimref_i($!from,$!indices) } } sub INTCPY(Mu \to, Mu \from) { IntCopy.new(to,from).sink-all } my class NumCopy does Rakudo::Iterator::ShapeLeaf { has $!from; method !INIT(Mu \to, Mu \from) { $!from := from; self!SET-SELF(to) } method new(Mu \to, Mu \from) { nqp::create(self)!INIT(to,from) } method result(--> Nil) { nqp::ifnull( nqp::atposnd($!list,$!indices), nqp::bindposnd($!list,$!indices,nqp::p6scalarfromdesc(Mu)) ) = nqp::multidimref_n($!from,$!indices) } } sub NUMCPY(Mu \to, Mu \from) { NumCopy.new(to,from).sink-all } method !RE-INITIALIZE(::?CLASS:D: --> Nil) { nqp::bindattr( # this is a yucky way to re-init, but it works self,List,'$!reified', nqp::getattr(self.new(:shape(self.shape)),List,'$!reified') ) } proto method STORE(::?CLASS:D: |) {*} multi method STORE(::?CLASS:D: ::?CLASS:D \in, :$INITIALIZE) { nqp::if( in.shape eqv self.shape, nqp::stmts( nqp::unless($INITIALIZE,self!RE-INITIALIZE), MEMCPY(self,in), # VM-supported memcpy-like thing? self ), X::Assignment::ArrayShapeMismatch.new( source-shape => in.shape, target-shape => self.shape ).throw ) } multi method STORE(::?CLASS:D: array:D \in, :$INITIALIZE) { nqp::if( in.shape eqv self.shape, nqp::stmts( nqp::unless($INITIALIZE,self!RE-INITIALIZE), nqp::if( nqp::istype(in.of,Int), INTCPY(self,in), # copy from native int NUMCPY(self,in) # copy from native num ), self ), X::Assignment::ArrayShapeMismatch.new( source-shape => in.shape, target-shape => self.shape ).throw ) } my class StoreIterable does Rakudo::Iterator::ShapeBranch { has $!iterators; has $!desc; method !INIT(\to,\from) { self!SET-SELF(to); $!desc := nqp::getattr(to,Array,'$!descriptor'); $!iterators := nqp::setelems( nqp::list(from.iterator), nqp::add_i($!maxdim,1) ); self } method new(\to,\from) { nqp::create(self)!INIT(to,from) } method done(--> Nil) { nqp::unless( # verify lowest nqp::atpos($!iterators,0).is-lazy # finite iterator || nqp::eqaddr( # and something there nqp::atpos($!iterators,0).pull-one,IterationEnd), nqp::atposnd($!list,$!indices) # boom! ) } method process(--> Nil) { my int $i = $!level; nqp::while( nqp::isle_i(++$i,$!maxdim), nqp::if( nqp::eqaddr((my $item := # exhausted ? nqp::atpos($!iterators,nqp::sub_i($i,1)).pull-one), IterationEnd ), nqp::bindpos($!iterators,$i, # add an empty one Rakudo::Iterator.Empty), nqp::if( # is it an iterator? nqp::istype($item,Iterable) && nqp::isconcrete($item), nqp::bindpos($!iterators,$i,$item.iterator), X::Assignment::ToShaped.new(shape => self.dims).throw ) ) ); my $iter := nqp::atpos($!iterators,$!maxdim); nqp::until( # loop over highest dim nqp::eqaddr((my $pulled := $iter.pull-one),IterationEnd) || nqp::isgt_i(nqp::atpos_i($!indices,$!maxdim),$!maxind), nqp::stmts( (nqp::ifnull( # containerize if needed nqp::atposnd($!list,$!indices), nqp::bindposnd($!list,$!indices, nqp::p6scalarfromdesc($!desc)) ) = $pulled), nqp::bindpos_i($!indices,$!maxdim, # increment index nqp::add_i(nqp::atpos_i($!indices,$!maxdim),1)) ) ); nqp::unless( nqp::eqaddr($pulled,IterationEnd) # if not exhausted || nqp::isle_i( # and index too high nqp::atpos_i($!indices,$!maxdim),$!maxind) || $iter.is-lazy, # and not lazy nqp::atposnd($!list,$!indices) # error ) } } multi method STORE(::?CLASS:D: Iterable:D \in, :$INITIALIZE) { self!RE-INITIALIZE unless $INITIALIZE; StoreIterable.new(self,in).sink-all; self } my class StoreIterator does Rakudo::Iterator::ShapeLeaf { has Mu $!iterator; has Mu $!desc; method !INIT(\list,\iterator) { $!iterator := iterator; $!desc := nqp::getattr(list,Array,'$!descriptor'); self!SET-SELF(list) } method new(\list,\iter) { nqp::create(self)!INIT(list,iter) } method result(--> Nil) { nqp::unless( nqp::eqaddr( (my \pulled := $!iterator.pull-one),IterationEnd), nqp::ifnull( nqp::atposnd($!list,$!indices), nqp::bindposnd($!list,$!indices, nqp::p6scalarfromdesc($!desc)) ) = pulled ) } } multi method STORE(::?CLASS:D: Iterator:D $iterator, :$INITIALIZE) { self!RE-INITIALIZE unless $INITIALIZE; StoreIterator.new(self,$iterator).sink-all; self } multi method STORE(::?CLASS:D: Mu \item --> Nil) { X::Assignment::ToShaped.new(shape => self.shape).throw } my class KV does Rakudo::Iterator::ShapeLeaf { has int $!on-key; method result() is raw { nqp::if( ($!on-key = nqp::not_i($!on-key)), nqp::stmts( (my \result := self.indices), (nqp::bindpos_i($!indices,$!maxdim, # back 1 for next nqp::sub_i(nqp::atpos_i($!indices,$!maxdim),1))), result ), nqp::atposnd($!list,$!indices) ) } # needs its own push-all since it fiddles with $!indices method push-all(\target --> IterationEnd) { nqp::until( nqp::eqaddr((my \pulled := self.pull-one),IterationEnd), target.push(pulled) ) } } multi method kv(::?CLASS:D:) { Seq.new(KV.new(self)) } my class Pairs does Rakudo::Iterator::ShapeLeaf { has Mu $!desc; method !INIT(\list) { $!desc := nqp::getattr(list,Array,'$!descriptor'); self!SET-SELF(list) } method new(Mu \list) { nqp::create(self)!INIT(list) } method result() { Pair.new( self.indices, nqp::ifnull( nqp::atposnd($!list,$!indices), # By the time the block gets executed, the $!indices # may be at the next iteration already or even reset # because we reached the end. So we need to make # a copy of the indices now. nqp::p6scalarfromdesc(ContainerDescriptor::BindArrayPosND.new( $!desc, $!list, nqp::clone($!indices))) ) ) } } multi method pairs(::?CLASS:D:) { Seq.new(Pairs.new(self)) } my class AntiPairs does Rakudo::Iterator::ShapeLeaf { method result() { Pair.new(nqp::atposnd($!list,$!indices),self.indices) } } multi method antipairs(::?CLASS:D:) { Seq.new(AntiPairs.new(self)) } multi method List(::?CLASS:D: --> List:D) { my \buf := nqp::create(IterationBuffer); self.iterator.push-all(buf); buf.List } multi method Array(::?CLASS:D: --> Array:D) { my @Array := nqp::eqaddr(self.of,Mu) ?? Array.new !! Array[self.of].new; self.iterator.push-all(@Array); @Array } my class Iterate does Rakudo::Iterator::ShapeLeaf { has Mu $!desc; method !INIT(\list) { $!desc := nqp::getattr(list,Array,'$!descriptor'); self!SET-SELF(list) } method new(Mu \list) { nqp::create(self)!INIT(list) } method result() is raw { nqp::ifnull( nqp::atposnd($!list,$!indices), # By the time the block gets executed, the $!indices # may be at the next iteration already or even reset # because we reached the end. So we need to make # a copy of the indices now. nqp::p6scalarfromdesc(ContainerDescriptor::BindArrayPosND.new( $!desc, $!list, nqp::clone($!indices))) ) } } method iterator(::?CLASS:D: --> Iterator:D) { Iterate.new(self) } # A shaped array isn't lazy, these methods don't need to go looking # into the "todo". method eager() { self } multi method sum(::?CLASS:D:) { self.Any::sum } multi method elems(::?CLASS:D:) { nqp::elems(nqp::getattr(self,List,'$!reified')) } method clone(::?CLASS:D:) { my \obj := nqp::create(self); nqp::bindattr(obj,Array,'$!descriptor', nqp::getattr(self,Array,'$!descriptor')); nqp::bindattr(obj,::?CLASS,'$!shape', nqp::getattr(self,::?CLASS,'$!shape')); obj.STORE(self); obj } } #line 1 SETTING::src/core.c/Array/Shaped1.rakumod my role Array::Shaped1 does Array::Shaped { multi method AT-POS(::?CLASS:D: uint $one) is raw { nqp::ifnull( nqp::atpos( nqp::getattr(self,List,'$!reified'), $one), AT-POS-CONTAINER(self, $one) ) } multi method AT-POS(::?CLASS:D: Int:D $one) is raw { nqp::ifnull( nqp::atpos( nqp::getattr(self,List,'$!reified'), $one), AT-POS-CONTAINER(self, $one) ) } sub AT-POS-CONTAINER(\array, uint $one) is raw { nqp::p6scalarfromdesc( ContainerDescriptor::BindArrayPos.new( nqp::getattr(array,Array,'$!descriptor'), nqp::getattr(array,List,'$!reified'), $one ) ) } multi method ASSIGN-POS(::?CLASS:D: uint $one, \value) { my \reified := nqp::getattr(self,List,'$!reified'); nqp::ifnull( nqp::atpos(reified,$one), nqp::bindpos( reified, $one, nqp::p6scalarfromdesc(nqp::getattr(self,Array,'$!descriptor'))) ) = value } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, \value) { my \reified := nqp::getattr(self,List,'$!reified'); nqp::ifnull( nqp::atpos(reified,$one), nqp::bindpos( reified, $one, nqp::p6scalarfromdesc(nqp::getattr(self,Array,'$!descriptor'))) ) = value } multi method EXISTS-POS(::?CLASS:D: uint $one --> Bool:D) { my \reified := nqp::getattr(self,List,'$!reified'); nqp::hllbool( nqp::islt_i($one,nqp::elems(reified)) && nqp::not_i(nqp::isnull(nqp::atpos(reified,$one))) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one --> Bool:D) { my \reified := nqp::getattr(self,List,'$!reified'); nqp::hllbool( nqp::islt_i($one,nqp::elems(reified)) && nqp::not_i(nqp::isnull(nqp::atpos(reified,$one))) ) } multi method DELETE-POS(::?CLASS:D: uint $one) is raw { my \reified := nqp::getattr(self,List,'$!reified'); nqp::if( nqp::isnull(my \value := nqp::atpos(reified,$one)), Nil, nqp::stmts( nqp::bindpos(reified,$one,nqp::null), value ) ) } multi method DELETE-POS(::?CLASS:D: Int:D $one) is raw { my \reified := nqp::getattr(self,List,'$!reified'); nqp::if( nqp::isnull(my \value := nqp::atpos(reified,$one)), Nil, nqp::stmts( nqp::bindpos(reified,$one,nqp::null), value ) ) } multi method BIND-POS(::?CLASS:D: uint $one, \value) { nqp::bindpos(nqp::getattr(self,List,'$!reified'),$one,value) } multi method BIND-POS(::?CLASS:D: Int:D $one, \value) { nqp::bindpos(nqp::getattr(self,List,'$!reified'),$one,value) } method !RE-INITIALIZE(::?CLASS:D:) { my \list := nqp::getattr(self,List,'$!reified'); nqp::bind( # rebind newly created list list, nqp::bindattr( self,List,'$!reified', nqp::setelems(nqp::create(list),nqp::elems(list)) ) ) } proto method STORE(::?CLASS:D: |) {*} multi method STORE(::?CLASS:D: ::?CLASS:D \from-array) { my \to := nqp::getattr(self,List,'$!reified'); my \from := nqp::getattr(from-array,List,'$!reified'); nqp::if( nqp::iseq_i( (my uint $elems = nqp::elems(to)),nqp::elems(from)), nqp::stmts( (my \desc := nqp::getattr(self,Array,'$!descriptor')), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), # always create a new container in case the from list # contains containers already existing in the to list # e.g. after having done a .reverse or .rotate nqp::bindpos(to,$i,nqp::p6scalarfromdesc(desc)) = nqp::atpos(from,$i) ), self ), X::Assignment::ArrayShapeMismatch.new( source-shape => from-array.shape, target-shape => self.shape ).throw ) } multi method STORE(::?CLASS:D: Iterable:D \in, :$INITIALIZE) { my \list := $INITIALIZE ?? nqp::getattr(self,List,'$!reified') !! self!RE-INITIALIZE; my \desc := nqp::getattr(self,Array,'$!descriptor'); my \iter := in.iterator; my int $i = -1; my uint $elems = nqp::elems(list); nqp::until( nqp::eqaddr((my \pulled := iter.pull-one),IterationEnd) || nqp::iseq_i(++$i,$elems), nqp::ifnull( nqp::atpos(list,$i), nqp::bindpos(list,$i,nqp::p6scalarfromdesc(desc)) ) = pulled ); nqp::atpos(list,$i) # too many values on non-lazy iter, error unless nqp::islt_i($i,$elems) || iter.is-lazy; self } multi method STORE(::?CLASS:D: Mu \item, :$INITIALIZE) { my \list := $INITIALIZE ?? nqp::getattr(self,List,'$!reified') !! self!RE-INITIALIZE; nqp::bindpos(list,0, nqp::p6scalarfromdesc(nqp::getattr(self,Array,'$!descriptor')) ) = item; self } multi method keys(::?CLASS:D:) { Seq.new(Rakudo::Iterator.IntRange(0,self.shape.AT-POS(0) - 1)) } multi method kv(::?CLASS:D:) { Seq.new(Rakudo::Iterator.KeyValue(self.iterator)) } multi method pairs(::?CLASS:D:) { Seq.new(Rakudo::Iterator.Pairs(self.iterator)) } multi method antipairs(::?CLASS:D:) { Seq.new(Rakudo::Iterator.AntiPair(self.iterator)) } my class Iterate does PredictiveIterator { has Mu $!reified; has Mu $!desc; has int $!pos; method !SET-SELF(Mu \list) { $!reified := nqp::getattr(list,List,'$!reified'); $!desc := nqp::getattr(list,Array,'$!descriptor'); $!pos = -1; self } method new(Mu \list) { nqp::create(self)!SET-SELF(list) } method pull-one() is raw { nqp::if( nqp::islt_i(++$!pos,nqp::elems($!reified)), nqp::ifnull( nqp::atpos($!reified,$!pos), nqp::p6scalarfromdesc(ContainerDescriptor::BindArrayPos.new( $!desc, $!reified, $!pos)) ), IterationEnd ) } method skip-one() { nqp::islt_i(++$!pos,nqp::elems($!reified)) } method push-all(\target --> IterationEnd) { my uint $elems = nqp::elems($!reified); my int $i = $!pos; nqp::while( nqp::islt_i(++$i,$elems), target.push( nqp::ifnull( nqp::atpos($!reified,$i), nqp::p6scalarfromdesc(ContainerDescriptor::BindArrayPos.new( $!desc, $!reified, $i)) ) ) ); $!pos = $i; # mark as done } method count-only(--> Int:D) { nqp::p6box_i( nqp::elems($!reified) - $!pos - nqp::islt_i($!pos,nqp::elems($!reified) ) ) } method sink-all(--> IterationEnd) { $!pos = nqp::elems($!reified) } } method iterator(::?CLASS:D: --> Iterator:D) { Iterate.new(self) } method reverse(::?CLASS:D: --> Seq:D) is nodal { Seq.new: nqp::elems(nqp::getattr(self,List,'$!reified')) ?? Rakudo::Iterator.ReifiedReverse( self, nqp::getattr(self,Array,'$!descriptor')) !! Rakudo::Iterator.Empty } method rotate(::?CLASS:D: Int(Cool) $rotate = 1 --> Seq:D) is nodal { Seq.new: Rakudo::Iterator.ReifiedRotate( $rotate, self, nqp::getattr(self,Array,'$!descriptor') ) } multi method sum(::?CLASS:D:) { self.List::sum } } #line 1 SETTING::src/core.c/Array/Shaped2.rakumod my role Array::Shaped2 does Array::Shaped { multi method AT-POS(::?CLASS:D: uint $one, uint $two) is raw { nqp::ifnull( nqp::atpos2d(nqp::getattr(self,List,'$!reified'),$one,$two), AT-POS-CONTAINER(self, $one, $two) ) } multi method AT-POS(::?CLASS:D: Int:D $one, Int:D $two) is raw { nqp::ifnull( nqp::atpos2d(nqp::getattr(self,List,'$!reified'),$one,$two), AT-POS-CONTAINER(self, $one, $two) ) } sub AT-POS-CONTAINER(\array, uint $one, uint $two) is raw { nqp::p6scalarfromdesc( ContainerDescriptor::BindArrayPos2D.new( nqp::getattr(array,Array,'$!descriptor'), nqp::getattr(array,List,'$!reified'), $one, $two ) ) } multi method ASSIGN-POS(::?CLASS:D: uint $one, uint $two, \value) { my \reified := nqp::getattr(self,List,'$!reified'); nqp::ifnull( nqp::atpos2d(reified,$one,$two), nqp::bindpos2d(reified,$one,$two, nqp::p6scalarfromdesc(nqp::getattr(self,Array,'$!descriptor'))) ) = value } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Int:D $two, \value) { my \reified := nqp::getattr(self,List,'$!reified'); nqp::ifnull( nqp::atpos2d(reified,$one,$two), nqp::bindpos2d(reified,$one,$two, nqp::p6scalarfromdesc(nqp::getattr(self,Array,'$!descriptor'))) ) = value } multi method EXISTS-POS(::?CLASS:D: uint $one, uint $two --> Bool:D) { my \reified := nqp::getattr(self,List,'$!reified'); my \dims := nqp::dimensions(reified); nqp::hllbool( nqp::islt_i($one,nqp::atpos_i(dims,0)) && nqp::islt_i($two,nqp::atpos_i(dims,1)) && nqp::not_i(nqp::isnull(nqp::atpos2d(reified,$one,$two))) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one, Int:D $two --> Bool:D) { my \reified := nqp::getattr(self,List,'$!reified'); my \dims := nqp::dimensions(reified); nqp::hllbool( nqp::islt_i($one,nqp::atpos_i(dims,0)) && nqp::islt_i($two,nqp::atpos_i(dims,1)) && nqp::not_i(nqp::isnull(nqp::atpos2d(reified,$one,$two))) ) } multi method DELETE-POS(::?CLASS:D: uint $one, uint $two) is raw { my \reified := nqp::getattr(self,List,'$!reified'); nqp::if( nqp::isnull(my \value := nqp::atpos2d(reified,$one,$two)), Nil, nqp::stmts( nqp::bindpos2d(reified,$one,$two,nqp::null), value ) ) } multi method DELETE-POS(::?CLASS:D: Int:D $one, Int:D $two) is raw { my \reified := nqp::getattr(self,List,'$!reified'); nqp::if( nqp::isnull(my \value := nqp::atpos2d(reified,$one,$two)), Nil, nqp::stmts( nqp::bindpos2d(reified,$one,$two,nqp::null), value ) ) } multi method BIND-POS(::?CLASS:D: uint $one, uint $two, \value) { nqp::bindpos2d(nqp::getattr(self,List,'$!reified'),$one,$two,value) } multi method BIND-POS(::?CLASS:D: Int:D $one, Int:D $two, \value) { nqp::bindpos2d(nqp::getattr(self,List,'$!reified'),$one,$two,value) } } #line 1 SETTING::src/core.c/Array/Shaped3.rakumod my role Array::Shaped3 does Array::Shaped { multi method AT-POS(::?CLASS:D: uint $one, uint $two, uint $three) is raw { nqp::ifnull( nqp::atpos3d(nqp::getattr(self,List,'$!reified'),$one,$two,$three), AT-POS-CONTAINER(self, $one, $two, $three) ) } multi method AT-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three ) is raw { nqp::ifnull( nqp::atpos3d(nqp::getattr(self,List,'$!reified'),$one,$two,$three), AT-POS-CONTAINER(self, $one, $two, $three) ) } sub AT-POS-CONTAINER(\array, uint $one, uint $two, uint $three) is raw { nqp::p6scalarfromdesc( ContainerDescriptor::BindArrayPos3D.new( nqp::getattr(array,Array,'$!descriptor'), nqp::getattr(array,List,'$!reified'), $one, $two, $three ) ) } multi method ASSIGN-POS(::?CLASS:D: uint $one, uint $two, uint $three, \value ) { my \reified := nqp::getattr(self,List,'$!reified'); nqp::ifnull( nqp::atpos3d(reified,$one,$two,$three), nqp::bindpos3d(reified,$one,$two,$three, nqp::p6scalarfromdesc(nqp::getattr(self,Array,'$!descriptor'))) ) = value } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three, \value ) { my \reified := nqp::getattr(self,List,'$!reified'); nqp::ifnull( nqp::atpos3d(reified,$one,$two,$three), nqp::bindpos3d(reified,$one,$two,$three, nqp::p6scalarfromdesc(nqp::getattr(self,Array,'$!descriptor'))) ) = value } multi method EXISTS-POS(::?CLASS:D: uint $one, uint $two, uint $three --> Bool:D) { my \reified := nqp::getattr(self,List,'$!reified'); my \dims := nqp::dimensions(reified); nqp::hllbool( nqp::islt_i($one,nqp::atpos_i(dims,0)) && nqp::islt_i($two,nqp::atpos_i(dims,1)) && nqp::islt_i($three,nqp::atpos_i(dims,2)) && nqp::not_i( nqp::isnull(nqp::atpos3d(reified,$one,$two,$three)) ) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three --> Bool:D) { my \reified := nqp::getattr(self,List,'$!reified'); my \dims := nqp::dimensions(reified); nqp::hllbool( nqp::islt_i($one,nqp::atpos_i(dims,0)) && nqp::islt_i($two,nqp::atpos_i(dims,1)) && nqp::islt_i($three,nqp::atpos_i(dims,2)) && nqp::not_i( nqp::isnull(nqp::atpos3d(reified,$one,$two,$three)) ) ) } multi method DELETE-POS(::?CLASS:D: uint $one, uint $two, uint $three ) is raw { my \reified := nqp::getattr(self,List,'$!reified'); nqp::if( nqp::isnull(my \value := nqp::atpos3d(reified,$one,$two,$three)), Nil, nqp::stmts( nqp::bindpos3d(reified,$one,$two,$three,nqp::null), value ) ) } multi method DELETE-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three ) is raw { my \reified := nqp::getattr(self,List,'$!reified'); nqp::if( nqp::isnull(my \value := nqp::atpos3d(reified,$one,$two,$three)), Nil, nqp::stmts( nqp::bindpos3d(reified,$one,$two,$three,nqp::null), value ) ) } multi method BIND-POS(::?CLASS:D: uint $one, uint $two, uint $three, \value ) { nqp::bindpos3d( nqp::getattr(self,List,'$!reified'),$one,$two,$three,value ) } multi method BIND-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three, \value ) { nqp::bindpos3d( nqp::getattr(self,List,'$!reified'),$one,$two,$three,value ) } } #line 1 SETTING::src/core.c/Array.rakumod # for our tantrums my class X::TypeCheck { ... }; my class X::TypeCheck::Splice { ... } my class X::Subscript::Negative { ... }; # An Array is a List that ensures every item added to it is in a Scalar # container. It also supports push, pop, shift, unshift, splice, BIND-POS, # and so forth. my class Array { # declared in BOOTSTRAP # class Array is List # has Mu $!descriptor; my class ArrayReificationTarget { has $!target; has $!descriptor; method new(\target, Mu \descriptor) { nqp::bindattr((my \rt = nqp::create(self)),self,'$!target',target); nqp::p6bindattrinvres(rt,self,'$!descriptor',descriptor) } method push(Mu \value --> Nil) { nqp::push($!target, nqp::p6scalarwithvalue($!descriptor, value)); } method append(IterationBuffer:D \buffer --> Nil) { nqp::while( nqp::elems(buffer), nqp::push($!target, nqp::p6scalarwithvalue($!descriptor,nqp::shift(buffer)) ) ); } } my class ListReificationTarget { has $!target; method new(\target) { nqp::p6bindattrinvres(nqp::create(self), self, '$!target', target); } method push(Mu \value --> Nil) { nqp::push($!target,nqp::decont(value)); } method append(IterationBuffer:D \buffer --> Nil) { nqp::splice($!target,buffer,nqp::elems($!target),0) } } multi method clone(Array:D: --> Array:D) { my \iter := self.iterator; my \result := nqp::p6bindattrinvres( nqp::create(self), Array, '$!descriptor', nqp::isnull($!descriptor) ?? (nqp::null) !! nqp::clone($!descriptor) ); nqp::if( nqp::eqaddr( IterationEnd, iter.push-until-lazy: my \target := ArrayReificationTarget.new( (my \buffer := nqp::create(IterationBuffer)), nqp::clone($!descriptor))), nqp::p6bindattrinvres(result, List, '$!reified', buffer), nqp::stmts( nqp::bindattr(result, List, '$!reified', buffer), nqp::bindattr((my \todo := nqp::create(List::Reifier)), List::Reifier,'$!current-iter', iter), nqp::bindattr(todo, List::Reifier,'$!reified', buffer), nqp::bindattr(todo, List::Reifier,'$!reification-target', target), nqp::p6bindattrinvres(result, List, '$!todo', todo) ) ) } my class Todo does Iterator { has int $!i; has $!array; has $!reified; has $!todo; has $!descriptor; method !SET-SELF(\array) { $!i = -1; $!array := array; $!reified := nqp::ifnull( nqp::getattr( array,List,'$!reified'), nqp::bindattr(array,List,'$!reified', nqp::create(IterationBuffer)) ); $!todo := nqp::getattr(array,List, '$!todo'); $!descriptor := nqp::getattr(array,Array,'$!descriptor'); self } method new(\array) { nqp::create(self)!SET-SELF(array) } method pull-one() is raw { nqp::ifnull( nqp::atpos($!reified,++$!i), nqp::if( nqp::islt_i($!i,nqp::elems($!reified)), self!hole($!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), # cannot be nqp::null self!done ), IterationEnd ) ) ) } method !hole(int $i) is raw { nqp::p6scalarfromcertaindesc( ContainerDescriptor::BindArrayPos.new($!descriptor,$!reified,$i) ) } method !done() is raw { $!todo := nqp::bindattr($!array,List,'$!todo',Mu); IterationEnd } method push-until-lazy(\target) { nqp::if( nqp::isconcrete($!todo), nqp::stmts( (my int $elems = $!todo.reify-until-lazy), (my int $i = $!i), # lexicals faster than attributes nqp::while( # doesn't sink nqp::islt_i(++$i,$elems), target.push(nqp::atpos($!reified,$i)) ), nqp::if( $!todo.fully-reified, nqp::stmts( ($!i = $i), self!done ), nqp::stmts( ($!i = nqp::sub_i($elems,1)), Mu ) ) ), nqp::stmts( ($elems = nqp::elems($!reified)), ($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), IterationEnd ) ) } method is-lazy() { $!todo.DEFINITE && $!todo.is-lazy } } multi method iterator(Array:D: --> Iterator:D) { nqp::isconcrete(nqp::getattr(self,List,'$!todo')) ?? Todo.new(self) # something to iterate over !! nqp::isconcrete(nqp::getattr(self,List,'$!reified')) ?? Rakudo::Iterator.ReifiedArray( # everything is already there self, nqp::getattr(self,Array,'$!descriptor') ) !! Rakudo::Iterator.Empty # nothing now or in the future } method from-iterator(Array:U: Iterator $iter --> Array:D) { nqp::if( nqp::eqaddr( $iter.push-until-lazy( my \target := ArrayReificationTarget.new( (my \buffer := nqp::create(IterationBuffer)), BEGIN nqp::getcurhllsym('default_cont_spec') ) ), IterationEnd ), nqp::p6bindattrinvres(nqp::create(self),List,'$!reified',buffer), nqp::stmts( nqp::bindattr((my \result := nqp::create(self)), List,'$!reified',buffer), nqp::bindattr((my \todo := nqp::create(List::Reifier)), List::Reifier,'$!current-iter',$iter), nqp::bindattr(todo, List::Reifier,'$!reified',buffer), nqp::bindattr(todo, List::Reifier,'$!reification-target',target), nqp::p6bindattrinvres(result,List,'$!todo',todo) ) ) } method from-list(Array:U: Mu \list --> Array:D) { my \params := nqp::getattr(list,List,'$!reified'); my int $elems = list.elems; # reifies my int $i = -1; my \reified := nqp::create(IterationBuffer); nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos( reified, $i, nqp::p6scalarwithvalue( (BEGIN nqp::getcurhllsym('default_cont_spec')), nqp::decont(nqp::atpos(params,$i)) ) ) ); nqp::p6bindattrinvres(nqp::create(Array),List,'$!reified',reified) } # handle non-straightforward shapes method !difficult-shape(\shape --> Array:D) { nqp::if( Metamodel::EnumHOW.ACCEPTS(shape.HOW), self.set-shape(shape.^elems), nqp::stmts( warn("Ignoring [{ shape.^name }] as shape specification, did you mean 'my { shape.^name } @foo' ?"), nqp::create(self) ) ) } proto method new(|) {*} multi method new(Array: :$shape! --> Array:D) { nqp::isconcrete($shape) ?? self.set-shape($shape) !! self!difficult-shape($shape) } multi method new(Array: --> Array:D) { nqp::create(self) } multi method new(Array: \values, :$shape! --> Array:D) { (nqp::isconcrete($shape) ?? self.set-shape($shape) !! self!difficult-shape($shape) ).STORE(values) } multi method new(Array: \values --> Array:D) { nqp::create(self).STORE(values) } multi method new(Array: **@values is raw, :$shape! --> Array:D) { (nqp::isconcrete($shape) ?? self.set-shape($shape) !! self!difficult-shape($shape) ).STORE(@values) } multi method new(Array: **@values is raw --> Array:D) { nqp::create(self).STORE(@values) } proto method STORE(Array:D: |) {*} multi method STORE(Array:D: Iterable:D \iterable --> Array:D) { $!descriptor := $!descriptor.next if nqp::eqaddr($!descriptor.WHAT, ContainerDescriptor::UninitializedAttribute); my \buffer = nqp::create(IterationBuffer); nqp::if( nqp::iscont(iterable), nqp::stmts( # only a single element nqp::push( buffer, nqp::p6scalarwithvalue($!descriptor,iterable) ), nqp::bindattr(self,List,'$!todo',Mu) ), nqp::if( # a real iterator with N elems nqp::eqaddr( (my \iter = iterable.iterator).push-until-lazy( (my \target = ArrayReificationTarget.new( buffer,nqp::decont($!descriptor) )) ), IterationEnd ), nqp::bindattr(self,List,'$!todo',Mu), # exhausted nqp::stmts( # still left to do nqp::bindattr(self,List,'$!todo', my \todo = nqp::create(List::Reifier)), nqp::bindattr(todo,List::Reifier,'$!reified',buffer), nqp::bindattr(todo,List::Reifier,'$!current-iter',iter), nqp::bindattr(todo,List::Reifier,'$!reification-target',target), ) ) ); nqp::p6bindattrinvres(self,List,'$!reified',buffer) } multi method STORE(Array:D: QuantHash:D \qh --> Array:D) { $!descriptor := $!descriptor.next if nqp::eqaddr($!descriptor.WHAT, ContainerDescriptor::UninitializedAttribute); my \buffer = nqp::create(IterationBuffer); nqp::iscont(qh) ?? nqp::push(buffer,nqp::p6scalarwithvalue($!descriptor,qh)) !! qh.iterator.push-all( ArrayReificationTarget.new(buffer,nqp::decont($!descriptor)) ); nqp::bindattr(self,List,'$!todo',Mu); # exhausted nqp::p6bindattrinvres(self,List,'$!reified',buffer) } multi method STORE(Array:D: Mu \item --> Array:D) { $!descriptor := $!descriptor.next if nqp::eqaddr($!descriptor.WHAT, ContainerDescriptor::UninitializedAttribute); nqp::push( (my \buffer = nqp::create(IterationBuffer)), nqp::p6scalarwithvalue($!descriptor, item) ); nqp::bindattr(self,List,'$!todo',Mu); nqp::p6bindattrinvres(self,List,'$!reified',buffer) } method reification-target(Array:D: --> ArrayReificationTarget:D) { ArrayReificationTarget.new( nqp::getattr(self, List, '$!reified'), nqp::decont($!descriptor)) } multi method Slip(Array:D: --> Slip:D) { # A Slip-With-Descripto is a special kind of Slip that also has a # descriptor to be able to generate containers for null elements that # have type and default information. my class Slip-With-Descriptor is Slip { has $!descriptor; method iterator() { Rakudo::Iterator.ReifiedArray(self,$!descriptor) } multi method AT-POS(Int:D $pos) { nqp::ifnull( nqp::atpos(nqp::getattr(self,List,'$!reified'),$pos), nqp::p6scalarfromcertaindesc( ContainerDescriptor::BindArrayPos.new( $!descriptor, nqp::getattr(self,List,'$!reified'), $pos ) ) ) } method default() { $!descriptor.default } } BEGIN Slip-With-Descriptor.^set_name("Slip"); nqp::isconcrete(nqp::getattr(self,List,'$!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. Make a Slip that shares our reified buffer # but that will fill in default values for nulls. !! nqp::isconcrete(nqp::getattr(self,List,'$!reified')) ?? nqp::p6bindattrinvres( nqp::p6bindattrinvres( nqp::create(Slip-With-Descriptor), Slip-With-Descriptor, '$!descriptor', $!descriptor ), List, '$!reified', nqp::clone(nqp::getattr(self,List,'$!reified')) ) !! nqp::create(Slip) } method FLATTENABLE_LIST() is implementation-detail { nqp::if( nqp::isconcrete(nqp::getattr(self,List,'$!todo')), nqp::stmts( nqp::getattr(self,List,'$!todo').reify-all, nqp::getattr(self,List,'$!reified') ), nqp::if( nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')), nqp::stmts( nqp::if( (my int $elems = nqp::elems($reified)), nqp::stmts( (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::isnull(nqp::atpos($reified,$i)), nqp::bindpos( $reified, $i, nqp::p6scalarfromcertaindesc($!descriptor) ) ) ) ) ), nqp::getattr(self,List,'$!reified') ), nqp::bindattr(self,List,'$!reified',nqp::create(IterationBuffer)) ) ) } multi method flat(Array:U:) { self } multi method flat(Array:D:) { Seq.new(self.iterator) } method reverse(Array:D: --> Seq:D) is nodal { self.is-lazy # reifies ?? self.fail-iterator-cannot-be-lazy('.reverse') !! Seq.new: nqp::getattr(self,List,'$!reified') ?? Rakudo::Iterator.ReifiedReverse(self, $!descriptor) !! 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: nqp::getattr(self,List,'$!reified') ?? Rakudo::Iterator.ReifiedRotate($rotate, self, $!descriptor) !! Rakudo::Iterator.Empty } multi method List(Array:D: :$view --> List:D) { # :view is implementation-detail nqp::if( self.is-lazy, # can't make a List self.throw-iterator-cannot-be-lazy('.List'), nqp::if( # all reified nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')), nqp::if( $view, # assume no change in array $reified.List, nqp::stmts( # make cow copy (my int $elems = nqp::elems($reified)), (my $cow := nqp::setelems(nqp::create(IterationBuffer),$elems)), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos($cow,$i,nqp::ifnull(nqp::decont(nqp::atpos($reified,$i)),Nil)), ), $cow.List ) ), nqp::create(List) # was empty, is empty ) ) } method shape(Array: --> List:D) { (*,) } # should probably be Array:D: multi method AT-POS(Array:D: uint $pos) is raw { nqp::ifnull( nqp::if( nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')), nqp::atpos($reified, $pos), # can also be null nqp::null ), self!AT_POS_SLOW($pos) ) } multi method AT-POS(Array:D: Int:D $pos) is raw { nqp::ifnull( nqp::if( nqp::bitand_i( nqp::isge_i($pos,0), nqp::isconcrete( my $reified := nqp::getattr(self,List,'$!reified') ) ), nqp::atpos($reified, $pos), # can also be null nqp::null ), self!AT_POS_SLOW($pos) ) } # handle any lookup that is not simple method !AT_POS_SLOW(int $pos) is raw { nqp::if( nqp::islt_i($pos, 0), self!INDEX_OOR($pos), nqp::if( nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')), nqp::if( nqp::islt_i($pos,nqp::elems($reified)), self!AT_POS_CONTAINER($pos), # it's a hole nqp::if( # too far out, try reifying nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')), nqp::stmts( $todo.reify-at-least(nqp::add_i($pos,1)), nqp::ifnull( nqp::atpos($reified,$pos), # reified ok self!AT_POS_CONTAINER($pos) # reifier didn't reach ) ), self!AT_POS_CONTAINER($pos) # create an outlander ) ), # no reified, implies no todo nqp::stmts( # create reified nqp::bindattr(self,List,'$!reified',nqp::create(IterationBuffer)), self!AT_POS_CONTAINER($pos) # create an outlander ) ) ) } method !AT_POS_CONTAINER(int $pos) is raw { my $desc := $!descriptor; my $scalar := nqp::create(Scalar); nqp::bindattr($scalar, Scalar, '$!value', nqp::isnull($desc) ?? Any !! nqp::getattr($desc, ContainerDescriptor, '$!default')); nqp::bindattr($scalar, Scalar, '$!descriptor', ContainerDescriptor::BindArrayPos.new( $desc, nqp::getattr(self,List,'$!reified'), $pos)); $scalar } multi method ASSIGN-POS(Array:D: uint $pos, Mu \assignee) is raw { nqp::bitand_i( nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')), nqp::not_i(nqp::isconcrete(nqp::getattr(self,List,'$!todo'))), ) ?? nqp::p6assign( nqp::ifnull( nqp::atpos($reified, $pos), nqp::bindpos( $reified, $pos, nqp::p6bindattrinvres( nqp::create(Scalar),Scalar,'$!descriptor',$!descriptor ) ) ), nqp::decont(assignee) ) !! self!ASSIGN_POS_SLOW_PATH($pos, assignee) } multi method ASSIGN-POS(Array:D: Int:D $pos, Mu \assignee) is raw { nqp::isge_i($pos,0) ?? nqp::bitand_i( # should refer to uint candidate when that inlines nqp::isconcrete(my \reified := nqp::getattr(self,List,'$!reified')), nqp::not_i(nqp::isconcrete(nqp::getattr(self,List,'$!todo'))), ) ?? nqp::p6assign( nqp::ifnull( nqp::atpos(reified, $pos), nqp::bindpos( reified, $pos, nqp::p6bindattrinvres( nqp::create(Scalar),Scalar,'$!descriptor',$!descriptor ) ) ), nqp::decont(assignee) ) !! self!ASSIGN_POS_SLOW_PATH($pos, assignee) !! self!INDEX_OOR($pos) } method !ASSIGN_POS_SLOW_PATH(Array:D: int $pos, Mu \assignee) is raw { my \reified := nqp::getattr(self,List,'$!reified'); nqp::p6assign( nqp::if( nqp::isconcrete(reified), nqp::ifnull( nqp::atpos(reified,$pos), nqp::if( nqp::islt_i($pos,nqp::elems(reified)), # it's a hole nqp::bindpos( reified, $pos, nqp::p6bindattrinvres(nqp::create(Scalar), Scalar, '$!descriptor', $!descriptor) ), nqp::if( nqp::isconcrete(my \todo := nqp::getattr(self,List,'$!todo')), nqp::stmts( # can reify todo.reify-at-least(nqp::add_i($pos,1)), nqp::ifnull( nqp::atpos(reified,$pos), # reified nqp::bindpos( # outlander reified, $pos, nqp::p6bindattrinvres(nqp::create(Scalar), Scalar, '$!descriptor', $!descriptor) ) ) ), nqp::bindpos( # outlander without todo reified, $pos, nqp::p6bindattrinvres(nqp::create(Scalar), Scalar, '$!descriptor', $!descriptor) ) ) ) ), nqp::bindpos( # new outlander without reified nqp::bindattr(self,List,'$!reified',nqp::create(IterationBuffer)), $pos, nqp::p6bindattrinvres(nqp::create(Scalar), Scalar, '$!descriptor', $!descriptor) ) ), nqp::decont(assignee) ) } multi method BIND-POS(Array:D: uint $pos, Mu \bindval) is raw { nqp::if( nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')), nqp::if( nqp::isge_i($pos, nqp::elems($reified)) && nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')), $todo.reify-at-least(nqp::add_i($pos,1)), ), ($reified := nqp::bindattr( self,List,'$!reified',nqp::create(IterationBuffer) )) ); nqp::bindpos($reified,$pos,bindval) } multi method BIND-POS(Array:D: Int:D $pos, Mu \bindval) is raw { nqp::if( nqp::islt_i($pos,0), self!INDEX_OOR($pos), nqp::stmts( # should refer to uint candidate when that inlines nqp::if( nqp::isconcrete( my $reified := nqp::getattr(self,List,'$!reified') ), nqp::if( nqp::isge_i($pos, nqp::elems($reified)) && nqp::isconcrete( my $todo := nqp::getattr(self,List,'$!todo') ), $todo.reify-at-least(nqp::add_i($pos,1)), ), ($reified := nqp::bindattr( self,List,'$!reified',nqp::create(IterationBuffer) )) ), nqp::bindpos($reified,$pos,bindval) ) ) } method !remove-nulls-from-end(uint $from --> Nil) { my int $i = $from; nqp::unless( nqp::isconcrete(nqp::getattr(self,List,'$!todo')), nqp::stmts( (my $reified := nqp::getattr(self,List,'$!reified')), nqp::while( (nqp::isge_i(--$i,0) && nqp::not_i(nqp::existspos($reified,$i))), nqp::null ), nqp::setelems($reified,nqp::add_i($i,1)) ) ) } multi method DELETE-POS(Array:D: uint $pos) is raw { nqp::if( nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')), nqp::stmts( nqp::if( nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')), $todo.reify-at-least(nqp::add_i($pos,1)), ), nqp::if( nqp::isle_i( # something to delete $pos,my int $end = nqp::sub_i(nqp::elems($reified),1)), nqp::stmts( (my $value := nqp::ifnull( # save the value nqp::atpos($reified,$pos), self.default )), nqp::bindpos($reified,$pos,nqp::null), # remove this one nqp::if( nqp::iseq_i($pos,$end), self!remove-nulls-from-end($pos) ), $value # value, if any ), self.default # outlander ), ), self.default # no elements ) } multi method DELETE-POS(Array:D: Int:D $pos) is raw { nqp::if( nqp::islt_i($pos,0), self!INDEX_OOR($pos), nqp::if( # should refer to the uint candidate when that inlines nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')), nqp::stmts( nqp::if( nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')), $todo.reify-at-least(nqp::add_i($pos,1)), ), nqp::if( nqp::isle_i( # something to delete $pos,my int $end = nqp::sub_i(nqp::elems($reified),1)), nqp::stmts( (my $value := nqp::ifnull( # save the value nqp::atpos($reified,$pos), self.default )), nqp::bindpos($reified,$pos,nqp::null), # remove this one nqp::if( nqp::iseq_i($pos,$end), self!remove-nulls-from-end($pos) ), $value # value, if any ), self.default # outlander ), ), self.default # no elements ) ) } method !INDEX_OOR($pos) { X::OutOfRange.new( :what($*INDEX // 'Index'), :got($pos), :range<0..^Inf> ).Failure } # MUST have a separate Slip variant to have it slip multi method push(Array:D: Slip \value --> Array:D) { self.is-lazy ?? self.throw-iterator-cannot-be-lazy('push to') !! self!append-list(value) } multi method push(Array:D: \value --> Array:D) { nqp::if( self.is-lazy, self.throw-iterator-cannot-be-lazy('push to'), nqp::stmts( nqp::push( nqp::if( nqp::isconcrete(nqp::getattr(self,List,'$!reified')), nqp::getattr(self,List,'$!reified'), nqp::bindattr(self,List,'$!reified', nqp::create(IterationBuffer)) ), nqp::p6scalarwithvalue($!descriptor,value) ), self ) ) } multi method push(Array:D: **@values is raw --> Array:D) { self.is-lazy ?? self.throw-iterator-cannot-be-lazy('push to') !! self!append-list(@values) } multi method append(Array:D: \value --> Array:D) { nqp::if( self.is-lazy, self.throw-iterator-cannot-be-lazy('append to'), nqp::if( (nqp::iscont(value) || nqp::not_i(nqp::istype(value, Iterable))), nqp::stmts( nqp::push( nqp::if( nqp::isconcrete(nqp::getattr(self,List,'$!reified')), nqp::getattr(self,List,'$!reified'), nqp::bindattr(self,List,'$!reified', nqp::create(IterationBuffer)) ), nqp::p6scalarwithvalue($!descriptor,value) ), self ), self!append-list(value.list) ) ) } multi method append(Array:D: **@values is raw --> Array:D) { self.is-lazy ?? self.throw-iterator-cannot-be-lazy('append to') !! self!append-list(@values) } method !append-list(Array:D: @values --> Array:D) { nqp::if( nqp::eqaddr( @values.iterator.push-until-lazy( ArrayReificationTarget.new( nqp::if( nqp::isconcrete(nqp::getattr(self,List,'$!reified')), nqp::getattr(self,List,'$!reified'), nqp::bindattr(self,List,'$!reified', nqp::create(IterationBuffer)) ), nqp::decont($!descriptor) ) ), IterationEnd ), self, self.throw-iterator-cannot-be-lazy('push') ) } multi method unshift(Array:D: Slip \value --> Array:D) { self!prepend-list(value) } multi method unshift(Array:D: \value --> Array:D) { nqp::stmts( nqp::unshift( nqp::if( nqp::isconcrete(nqp::getattr(self,List,'$!reified')), nqp::getattr(self,List,'$!reified'), nqp::bindattr(self,List,'$!reified', nqp::create(IterationBuffer)) ), nqp::p6scalarwithvalue($!descriptor,value) ), self ) } multi method unshift(Array:D: **@values is raw --> Array:D) { self!prepend-list(@values) } multi method prepend(Array:D: \value --> Array:D) { nqp::if( (nqp::iscont(value) || nqp::not_i(nqp::istype(value, Iterable))), nqp::stmts( nqp::unshift( nqp::if( nqp::isconcrete(nqp::getattr(self,List,'$!reified')), nqp::getattr(self,List,'$!reified'), nqp::bindattr(self,List,'$!reified', nqp::create(IterationBuffer)) ), nqp::p6scalarwithvalue($!descriptor,value) ), self ), self!prepend-list(value.list) ) } multi method prepend(Array:D: **@values is raw --> Array:D) { self!prepend-list(@values) } method !prepend-list(Array:D: @values --> Array:D) { nqp::if( nqp::isconcrete(nqp::getattr(self,List,'$!reified')), nqp::splice(nqp::getattr(self,List,'$!reified'), # prepend existing nqp::stmts( @values.iterator.push-all( ArrayReificationTarget.new( (my $containers := nqp::create(IterationBuffer)), nqp::decont($!descriptor) ) ), $containers ), 0, 0 ), @values.iterator.push-all( # no list yet, make this it ArrayReificationTarget.new( nqp::bindattr(self,List,'$!reified', nqp::create(IterationBuffer)), nqp::decont($!descriptor) ) ) ); self } method pop(Array:D:) is nodal { my $reified := nqp::getattr(self,List,'$!reified'); self.is-lazy ?? self.fail-iterator-cannot-be-lazy('pop from') !! nqp::isconcrete($reified) && nqp::elems($reified) ?? nqp::pop($reified) !! self.fail-cannot-be-empty('pop') } method shift(Array:D:) is nodal { nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')) && nqp::elems($reified) ?? nqp::ifnull( # handle holes nqp::shift($reified), Nil ) !! nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')) && $todo.reify-at-least(1) ?? nqp::shift($reified) !! self.fail-cannot-be-empty('shift') } my $empty := nqp::create(IterationBuffer); # splicing in without values #------ splice() candidates multi method splice(Array:D \SELF: --> Array:D) { nqp::if( nqp::isconcrete(nqp::getattr(SELF,List,'$!reified')), nqp::stmts( (my $result := nqp::create(SELF)), nqp::bindattr($result,Array,'$!descriptor',$!descriptor), nqp::stmts( # transplant the internals nqp::bindattr($result,List,'$!reified', nqp::getattr(SELF,List,'$!reified')), nqp::if( nqp::isconcrete(nqp::getattr(SELF,List,'$!todo')), nqp::bindattr($result,List,'$!todo', nqp::getattr(SELF,List,'$!todo')), ) ), (SELF = nqp::create(SELF)), # XXX this preserves $!descriptor ?? $result ), nqp::p6bindattrinvres( # nothing to return, so create new one nqp::create(SELF),Array,'$!descriptor',$!descriptor) ) } #------ splice(offset) candidates multi method splice(Array:D: Whatever $ --> Array:D) { nqp::p6bindattrinvres( # nothing to return, so create new one nqp::create(self),Array,'$!descriptor',$!descriptor) } multi method splice(Array:D: Callable:D $offset --> Array:D) { self.splice($offset(self.elems)) } multi method splice(Array:D: Int:D $offset --> Array:D) { nqp::if( $offset, nqp::if( nqp::islt_i(nqp::unbox_i($offset),0), self!splice-offset-fail($offset), nqp::if( nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')), nqp::if( nqp::isge_i( $todo.reify-at-least($offset),nqp::unbox_i($offset)), self!splice-offset(nqp::unbox_i($offset)), self!splice-offset-fail($offset) ), nqp::if( nqp::isconcrete(nqp::getattr(self,List,'$!reified')) && nqp::isge_i( nqp::elems(nqp::getattr(self,List,'$!reified')), nqp::unbox_i($offset) ), self!splice-offset(nqp::unbox_i($offset)), self!splice-offset-fail($offset) ) ) ), self.splice # offset 0, take the quick route out ) } method !splice-offset(Array:D: int $offset --> Array:D) { my $reified := nqp::getattr(self,List,'$!reified'); my uint $elems = nqp::elems($reified); my $result:= nqp::create(self); nqp::unless( nqp::iseq_i($offset,$elems), nqp::stmts( nqp::bindattr($result,List,'$!reified',nqp::slice($reified,$offset,-1)), nqp::splice( $reified, $empty, $offset, nqp::sub_i(nqp::elems($reified),$offset) ), ) ); nqp::p6bindattrinvres($result,Array,'$!descriptor',$!descriptor) } method !splice-offset-fail(Array:D: $got) { X::OutOfRange.new( :what('Offset argument to splice'), :$got, :range("0..{self.elems}") ).throw } #------ splice(offset,size) candidates multi method splice(Array:D: Whatever $, Whatever $ --> Array:D) { nqp::p6bindattrinvres( # nothing to return, so create new one nqp::create(self),Array,'$!descriptor',$!descriptor) } multi method splice(Array:D: Whatever $, Int:D $size --> Array:D) { self.splice(self.elems,$size) } multi method splice(Array:D: Whatever $, Callable:D $size --> Array:D) { my int $elems = self.elems; self.splice($elems,$size(nqp::sub_i($elems,$elems))); } multi method splice(Array:D: Callable:D $offset, Callable:D $size --> Array:D) { my int $elems = self.elems; my int $from = $offset($elems); self.splice($from,$size(nqp::sub_i($elems,$from))) } multi method splice(Array:D: Callable:D $offset, Whatever $ --> Array:D) { self.splice($offset(self.elems)) } multi method splice(Array:D: Callable:D $offset, Int:D $size --> Array:D) { self.splice($offset(self.elems),$size) } multi method splice(Array:D: Int:D $offset, Whatever $ --> Array:D) { self.splice($offset) } multi method splice(Array:D: Int:D $offset, Callable:D $size --> Array:D) { self.splice($offset,$size(self.elems - $offset)) } multi method splice(Array:D: Int:D $offset, Int:D $size --> Array:D) { nqp::if( nqp::islt_i(nqp::unbox_i($offset),0), self!splice-offset-fail($offset), nqp::if( nqp::islt_i(nqp::unbox_i($size),0), self!splice-size-fail($size,$offset), nqp::if( nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')), nqp::if( nqp::isge_i( $todo.reify-at-least( nqp::add_i(nqp::unbox_i($offset),nqp::unbox_i($size)) ),nqp::unbox_i($offset)), self!splice-offset-size( nqp::unbox_i($offset),nqp::unbox_i($size)), self!splice-size-fail($size,$offset) ), nqp::if( nqp::isconcrete(nqp::getattr(self,List,'$!reified')), nqp::if( nqp::isge_i( nqp::elems(nqp::getattr(self,List,'$!reified')), nqp::unbox_i($offset)), self!splice-offset-size( nqp::unbox_i($offset),nqp::unbox_i($size)), self!splice-size-fail($size,$offset) ), nqp::if( nqp::iseq_i(nqp::unbox_i($offset),0), nqp::p6bindattrinvres( # nothing to return, create new nqp::create(self),Array,'$!descriptor',$!descriptor), self!splice-offset-fail($offset) ) ) ) ) ) } method !splice-offset-size(Array:D: int $offset,int $size --> Array:D) { my $result := self!splice-save($offset,$size,my int $removed); nqp::splice( nqp::getattr(self,List,'$!reified'),$empty,$offset,$removed ); $result } method !splice-save(Array:D: int $offset,int $size, \removed --> Array:D) { my $reified := nqp::getattr(self,List,'$!reified'); my $result:= nqp::create(self); nqp::if( (removed = nqp::if( nqp::isgt_i(nqp::add_i($offset,$size),nqp::elems($reified)), nqp::sub_i(nqp::elems($reified),$offset), $size )), nqp::bindattr( $result, List, '$!reified', nqp::slice($reified,$offset,nqp::sub_i(nqp::add_i($offset,removed),1)) ) ); nqp::p6bindattrinvres($result,Array,'$!descriptor',$!descriptor) } method !splice-size-fail(Array:D: $got,$offset) { $offset > self.elems ?? self!splice-offset-fail($offset) !! X::OutOfRange.new( :what('Size argument to splice'), :$got, :range("0..^{self.elems - $offset}") ).throw } #------ splice(offset,size,array) candidates # we have these 9 multies to avoid infiniloop when incorrect types are # given to $offset/$size. Other attempts to resolve this showed 30%+ # performance decreases multi method splice(Array:D: Whatever $offset, Whatever $size, **@new --> Array:D) { self.splice($offset, $size, @new) } multi method splice(Array:D: Whatever $offset, Callable:D $size, **@new --> Array:D) { self.splice($offset, $size, @new) } multi method splice(Array:D: Whatever $offset, Int:D $size, **@new --> Array:D) { self.splice($offset, $size, @new) } multi method splice(Array:D: Callable:D $offset, Whatever $size, **@new --> Array:D) { self.splice($offset, $size, @new) } multi method splice(Array:D: Callable:D $offset, Callable:D $size, **@new --> Array:D) { self.splice($offset, $size, @new) } multi method splice(Array:D: Callable:D $offset, Int:D $size, **@new --> Array:D) { self.splice($offset, $size, @new) } multi method splice(Array:D: Int:D $offset, Whatever $size, **@new --> Array:D) { self.splice($offset, $size, @new) } multi method splice(Array:D: Int:D $offset, Callable:D $size, **@new --> Array:D) { self.splice($offset, $size, @new) } multi method splice(Array:D: Int:D $offset, Int:D $size, **@new --> Array:D) { self.splice($offset, $size, @new) } multi method splice(Array:D: Whatever $, Whatever $, @new --> Array:D) { self.splice(self.elems,0,@new) } multi method splice(Array:D: Whatever $, Int:D $size, @new --> Array:D) { self.splice(self.elems,$size,@new) } multi method splice(Array:D: Whatever $, Callable:D $size, @new --> Array:D) { my int $elems = self.elems; self.splice($elems,$size(nqp::sub_i($elems,$elems)),@new); } multi method splice(Array:D: Callable:D $offset, Callable:D $size, @new --> Array:D) { my int $elems = self.elems; my int $from = $offset($elems); self.splice($from,$size(nqp::sub_i($elems,$from)),@new) } multi method splice(Array:D: Callable:D $offset, Whatever $, @new --> Array:D) { my int $elems = self.elems; my int $from = $offset($elems); self.splice($from,nqp::sub_i($elems,$from),@new) } multi method splice(Array:D: Callable:D $offset, Int:D $size, @new --> Array:D) { self.splice($offset(self.elems),$size,@new) } multi method splice(Array:D: Int:D $offset, Whatever $, @new --> Array:D) { self.splice($offset,self.elems - $offset,@new) } multi method splice(Array:D: Int:D $offset, Callable:D $size, @new --> Array:D) { self.splice($offset,$size(self.elems - $offset),@new) } multi method splice(Array:D: Int:D $offset, Int:D $size, @new --> Array:D) { nqp::if( nqp::islt_i(nqp::unbox_i($offset),0), self!splice-offset-fail($offset), nqp::if( nqp::islt_i(nqp::unbox_i($size),0), self!splice-size-fail($size,$offset), nqp::if( nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')), nqp::if( nqp::isge_i( $todo.reify-at-least( nqp::add_i(nqp::unbox_i($offset),nqp::unbox_i($size)) ),nqp::unbox_i($offset)), self!splice-offset-size-new( nqp::unbox_i($offset),nqp::unbox_i($size),@new), self!splice-size-fail($size,$offset) ), nqp::if( nqp::isge_i( nqp::elems(nqp::if( nqp::isconcrete(nqp::getattr(self,List,'$!reified')), nqp::getattr(self,List,'$!reified'), nqp::bindattr(self,List,'$!reified', nqp::create(IterationBuffer)) )), nqp::unbox_i($offset), ), self!splice-offset-size-new( nqp::unbox_i($offset),nqp::unbox_i($size),@new), self!splice-offset-fail($offset) ) ) ) ) } method !splice-offset-size-new(Array:D: int $offset,int $size,@new --> Array:D) { nqp::if( nqp::eqaddr(@new.iterator.push-until-lazy( (my $new := nqp::create(IterationBuffer))),IterationEnd), nqp::if( # reified all values to splice in (nqp::isnull($!descriptor) || nqp::eqaddr(self.of,Mu)), nqp::stmts( # no typecheck needed (my $result := self!splice-save($offset,$size,my int $removed)), nqp::splice( nqp::getattr(self,List,'$!reified'),$new,$offset,$removed), $result ), nqp::stmts( # typecheck the values first (my $expected := self.of), (my int $elems = nqp::elems($new)), (my int $i = -1), nqp::while( (nqp::islt_i(++$i,$elems) && nqp::istype(nqp::atpos($new,$i),$expected)), nqp::null ), nqp::if( nqp::islt_i($i,$elems), # exited loop because of wrong type X::TypeCheck::Splice.new( :action, :got(nqp::atpos($new,$i).WHAT), :$expected ).throw, nqp::stmts( ($result := self!splice-save($offset,$size,$removed)), nqp::splice( nqp::getattr(self,List,'$!reified'),$new,$offset,$removed), $result ) ) ) ), self.throw-iterator-cannot-be-lazy('splice in') ) } multi method tail(Array:D: $n) { nqp::if( nqp::isconcrete(nqp::getattr(self,List,'$!todo')), self.Any::tail($n), Seq.new( nqp::if( nqp::isconcrete( my $reified := nqp::getattr(self,List,'$!reified') ) && nqp::elems($reified), nqp::stmts( (my $iterator := Rakudo::Iterator.ReifiedArray( self, nqp::getattr(self,Array,'$!descriptor') )), nqp::if( nqp::istype($n,Callable) && 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) ) ), $iterator ), Rakudo::Iterator.Empty ) ) ) } proto method grab(|) {*} multi method grab(Array:D:) { self.is-lazy ?? self.throw-iterator-cannot-be-lazy('grab from') # can't make List !! self.elems # reifies ?? self.GRAB_ONE !! Nil } multi method grab(Array:D: Callable:D $calculate) { self.grab($calculate(self.elems)) } multi method grab(Array:D: Whatever --> Seq:D) { self.grab(Inf) } my class GrabN does Iterator { has $!array; has int $!count; method !SET-SELF(\array,\count) { my int $elems = nqp::elems(nqp::getattr(array,List,'$!reified')); $!array := array; nqp::if( count == Inf, ($!count = $elems), nqp::if( nqp::isgt_i(($!count = count.Int),$elems), ($!count = $elems) ) ); self } method new(\a,\c) { nqp::create(self)!SET-SELF(a,c) } method pull-one() { nqp::if( $!count && nqp::elems(nqp::getattr($!array,List,'$!reified')), nqp::stmts( --$!count, $!array.GRAB_ONE ), IterationEnd ) } method is-deterministic(--> False) { } } multi method grab(Array:D: \count --> Seq:D) { Seq.new( self.elems # reifies ?? GrabN.new(self,count) !! Rakudo::Iterator.Empty ) } method GRAB_ONE(Array:D:) is implementation-detail { my $reified := nqp::getattr(self,List,'$!reified'); my $value := nqp::atpos( $reified, (my int $pos = nqp::floor_n(nqp::rand_n(nqp::elems($reified)))), ); nqp::splice($reified,$empty,$pos,1); $value } # introspection method name() { $!descriptor.name } proto method of() {*} multi method of(Array:U:) { Mu } multi method of(Array:D:) { $!descriptor.of } method default() { $!descriptor.default } method dynamic() { $!descriptor.dynamic.Bool } multi method raku(Array:D \SELF: --> Str:D) { SELF.rakuseen('Array', { '$' x nqp::iscont(SELF) # self is always deconted ~ '[' ~ self.map({nqp::decont($_).raku}).join(', ') ~ ',' x ((try self.elems // Inf) == 1 && nqp::istype(self.AT-POS(0),Iterable)) ~ ']' }) } multi method WHICH(Array:D: --> ObjAt:D) { self.Mu::WHICH } my constant \dim2role = nqp::list(Array::Shaped,Array::Shaped1,Array::Shaped2,Array::Shaped3); proto method set-shape(|) is implementation-detail {*} multi method set-shape(Whatever) is raw { nqp::create(self.WHAT) } multi method set-shape(\shape) is raw { self.set-shape(shape.List) } multi method set-shape(List:D \shape) is raw { my int $dims = shape.elems; # reifies my $reified := nqp::getattr(nqp::decont(shape),List,'$!reified'); # just a list with Whatever, so no shape if nqp::iseq_i($dims,1) && nqp::istype(nqp::atpos($reified,0),Whatever) { nqp::create(self.WHAT) } # we haz dimensions elsif $dims { my $what := self.WHAT.^mixin( nqp::atpos(dim2role,nqp::isle_i($dims,3) && $dims) ); $what.^set_name(self.^name) # correct name if needed if nqp::isne_s($what.^name,self.^name); my $array := nqp::p6bindattrinvres( nqp::create($what),List,'$!reified', Rakudo::Internals.SHAPED-ARRAY-STORAGE(shape,nqp::knowhow,Mu) ); nqp::p6bindattrinvres($array,$what,'$!shape',nqp::decont(shape)) } # flatland else { X::NotEnoughDimensions.new( operation => 'create', got-dimensions => 0, needed-dimensions => '', ).throw } } my class LTHandle { has Mu $!reified; has Mu $!todo; has Mu $!descriptor; } method TEMP-LET-LOCALIZE() is raw is implementation-detail { my \handle = nqp::create(LTHandle); nqp::bindattr(handle, LTHandle, '$!reified', nqp::getattr(self, List, '$!reified')); nqp::bindattr(handle, LTHandle, '$!todo', nqp::getattr(self, List, '$!todo')); nqp::bindattr(handle, LTHandle, '$!descriptor', nqp::getattr(self, Array, '$!descriptor')); self.STORE: self.clone; handle } method TEMP-LET-RESTORE(\handle --> Nil) is implementation-detail { nqp::bindattr(self, List, '$!reified', nqp::getattr(handle, LTHandle, '$!reified')); nqp::bindattr(self, List, '$!todo', nqp::getattr(handle, LTHandle, '$!todo')); nqp::bindattr(self, Array, '$!descriptor', nqp::getattr(handle, LTHandle, '$!descriptor')); } method ^parameterize(Mu:U \arr, Mu \of) { if nqp::isconcrete(of) { die "Can not parameterize {arr.^name} with {of.raku}" } else { my $what := arr.^mixin(Array::Typed[of]); # needs to be done in COMPOSE phaser when that works $what.^set_name("{arr.^name}[{of.^name}]"); $what } } } #line 1 SETTING::src/core.c/array_operators.rakumod # The [...] term creates an Array. proto sub circumfix:<[ ]>(Mu $?, *%) {*} multi sub circumfix:<[ ]>() { nqp::create(Array) } multi sub circumfix:<[ ]>(Iterable:D \iterable) { nqp::if( nqp::iscont(iterable), Rakudo::Internals.Array-with-one-elem(Mu, iterable), nqp::if( nqp::istype(iterable,List) && nqp::isfalse(iterable.is-lazy), Array.from-list(iterable), Array.from-iterator(iterable.iterator) ) ) } multi sub circumfix:<[ ]>(Mu \x) { # really only for [$foo] Rakudo::Internals.Array-with-one-elem(Mu, x) } proto sub pop($, *%) {*} multi sub pop(@a) is raw { @a.pop } proto sub shift($, *%) {*} multi sub shift(@a) is raw { @a.shift } proto sub push($, |) {*} multi sub push(\a, \b ) { a.push: b } multi sub push(\a, **@b is raw) { a.push: |@b } proto sub append($, |) {*} multi sub append(\a, \b ) { a.append: b } multi sub append(\a, **@b is raw) { a.append: @b } proto sub unshift($, |) {*} multi sub unshift(\a, \b ) { a.unshift: b } multi sub unshift(\a, **@b is raw) { a.unshift: |@b } proto sub prepend($, |) {*} multi sub prepend(\a, \b ) { a.prepend: b } multi sub prepend(\a, **@b is raw) { a.prepend: @b } proto sub splice($, |) {*} multi sub splice(@arr, |c) { @arr.splice(|c) } #line 1 SETTING::src/core.c/native_array.rakumod my class X::Delete { ... } my class X::MustBeParametric { ... } my class X::TooManyDimensions { ... } my class X::TypeCheck::Assignment { ... } my class array does Iterable does Positional { multi method new(array:) { self!create } multi method new(array: @v) { self!create.STORE(@v) } multi method new(array: **@v) { self!create.STORE(@v) } multi method new(array: :$shape!) { self!create-ws($shape) } multi method new(array: @v, :$shape!) { self!create-ws($shape).STORE(@v) } multi method new(array: **@v, :$shape!) { self!create-ws($shape).STORE(@v) } method !create() { nqp::isnull(nqp::typeparameterized(self)) ?? X::MustBeParametric.new(:type(self)).throw !! nqp::create(self) } method !create-ws($shape) { nqp::isnull(nqp::typeparameterized(self)) ?? X::MustBeParametric.new(:type(self)).throw !! nqp::isconcrete($shape) ?? self.set-shape($shape) !! Metamodel::EnumHOW.ACCEPTS($shape.HOW) ?? self.set-shape($shape.^elems) !! nqp::create(self) } proto method STORE(array:D: |) {*} multi method STORE(array:D: *@values) { self.STORE(@values) } multi method push(array:D: **@values) { self.append(@values) } multi method append(array:D: *@values) { self.append(@values) } multi method unshift(array:D: **@values) { self.unshift(@values) } multi method prepend(array:D: *@values) { self.unshift(@values) } multi method head(array:D: Int:D $n) { my int $end = $n - 1; nqp::isge_i($end,nqp::elems(self)) ?? nqp::clone(self) !! nqp::slice(self,0,$end) } multi method head(array:D: Callable:D $calculator) { my int $end = $calculator(nqp::elems(self)) - 1; nqp::islt_i($end,0) ?? nqp::create(self.WHAT) !! nqp::slice(self,0,$end) } multi method tail(array:D: Int:D $n) { my int $start = nqp::elems(self) - $n; nqp::islt_i($start,0) ?? nqp::clone(self) !! nqp::slice(self,$start,nqp::sub_i(nqp::elems(self),1)) } multi method tail(array:D: Callable:D $calculator) { my int $start = $calculator(nqp::elems(self)); nqp::isge_i($start,nqp::elems(self)) ?? nqp::create(self.WHAT) !! nqp::slice(self,$start,nqp::sub_i(nqp::elems(self),1)) } sub INDEX_OUT_OF_RANGE(Int:D $got --> Nil) { X::OutOfRange.new(what => "Index", :$got, range => "0..^Inf").throw } sub EQV_DIMENSIONS(Mu \one, Mu \two) is raw { nqp::iseq_i( # much faster than one.shape eqv two.shape (my int $dims = nqp::elems( my $onedims := nqp::dimensions(one) )), nqp::elems(my $twodims := nqp::dimensions(two)) ) && nqp::stmts( (my int $i = -1), nqp::while( nqp::islt_i(++$i,$dims) && nqp::iseq_i( nqp::atpos_i($onedims,$i), nqp::atpos_i($twodims,$i) ), nqp::null ), nqp::iseq_i($i,$dims) ) } sub SPLICE_OFFSET_OUT_OF_RANGE(int $got, int $end) { X::OutOfRange.new( :what("Offset argument to splice"), :$got, :range("0..$end") ).Failure } sub SPLICE_SIZE_OUT_OF_RANGE(int $got, int $end) { Failure.new(X::OutOfRange.new( :what("Size argument to splice"), :$got, :range("0..^$end") )) } sub CLONE_SLICE(\array, int $offset, int $size) { nqp::if( nqp::isgt_i($offset,(my int $elems = nqp::elems(array))) || nqp::islt_i($offset,0), SPLICE_OFFSET_OUT_OF_RANGE($offset, $elems), nqp::if( nqp::islt_i($size,0), SPLICE_SIZE_OUT_OF_RANGE($size, $elems - $offset), nqp::if( nqp::iseq_i($offset,$elems) || nqp::iseq_i($size,0), nqp::create(array), nqp::if( nqp::isge_i( (my int $end = nqp::sub_i(nqp::add_i($offset,$size),1)), $elems ), nqp::slice(array,$offset,-1), nqp::slice(array,$offset,$end) ) ) ) ) } role strarray[::T] does Positional[T] is array_type(T) { #- start of generated part of strarray role ----------------------------------- #- Generated on 2022-05-21T12:11:45+02:00 by tools/build/makeNATIVE_ARRAY.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi method grep(strarray:D: Str:D $needle, :$k, :$kv, :$p, :$v --> Seq:D) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(IterationBuffer); if $k { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_s(nqp::atpos_s(self,$i),$needle), nqp::push($result,nqp::clone($i)) ) ); } elsif $kv { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_s(nqp::atpos_s(self,$i),$needle), nqp::stmts( nqp::push($result,nqp::clone($i)), nqp::push($result,$needle) ) ) ); } elsif $p { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_s(nqp::atpos_s(self,$i),$needle), nqp::push($result,Pair.new($i,$needle)) ) ); } else { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_s(nqp::atpos_s(self,$i),$needle), nqp::push($result,$needle) ) ); } $result.Seq } multi method head(strarray:D:) { nqp::atpos_s(self,0) } multi method tail(strarray:D:) { nqp::atpos_s(self,nqp::sub_i(nqp::elems(self),1)) } multi method first(strarray:D: Str:D $needle, :$k, :$kv, :$p, :$v) { my int $i = -1; my uint $elems = nqp::elems(self); nqp::while( nqp::islt_i(++$i,$elems) && nqp::isne_s(nqp::atpos_s(self,$i),$needle), nqp::null() ); nqp::iseq_i($i,$elems) ?? Nil !! $k ?? $i !! $kv ?? ($i,$needle) !! $p ?? Pair.new($i,$needle) !! $needle } multi method unique(strarray:D:) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(array[self.of]); my $seen := nqp::hash; nqp::while( nqp::islt_i(++$i,$elems), nqp::unless( nqp::existskey( $seen, (my str $key = ( my str $value = nqp::atpos_s(self,$i) )) ), nqp::stmts( nqp::push_s($result,$value), nqp::bindkey($seen,$key,1), ) ) ); $result } multi method repeated(strarray:D:) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(array[self.of]); my $seen := nqp::hash; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::existskey( $seen, (my str $key = ( my str $value = nqp::atpos_s(self,$i) )) ), nqp::push_s($result,$value), nqp::bindkey($seen,$key,1) ) ); $result } multi method squish(strarray:D:) { if nqp::elems(self) -> int $elems { my $result := nqp::create(array[self.of]); my str $last = nqp::push_s($result,nqp::atpos_s(self,0)); my uint $i; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::isne_s(nqp::atpos_s(self,$i),$last), nqp::push_s($result,$last = nqp::atpos_s(self,$i)) ) ); $result } else { self } } multi method AT-POS(strarray:D: uint $idx --> str) is raw { nqp::atposref_s(self,$idx) } multi method AT-POS(strarray:D: Int:D $idx --> str) is raw { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::atposref_s(self,$idx) } multi method ASSIGN-POS(strarray:D: uint $idx, str $value --> str) { nqp::bindpos_s(self, $idx, $value) } multi method ASSIGN-POS(strarray:D: Int:D $idx, str $value --> str) { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::bindpos_s(self, $idx, $value) } multi method ASSIGN-POS(strarray:D: uint $idx, Str:D $value --> str) { nqp::bindpos_s(self, $idx, $value) } multi method ASSIGN-POS(strarray:D: Int:D $idx, Str:D $value --> str) { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::bindpos_s(self, $idx, $value) } multi method ASSIGN-POS(strarray:D: Any $idx, Mu \value --> Nil) { X::TypeCheck.new( operation => "assignment to str array element #$idx", got => value, expected => T, ).throw; } multi method STORE(strarray:D: $value --> strarray:D) { nqp::setelems(self,1); nqp::bindpos_s(self, 0, nqp::unbox_s($value)); self } multi method STORE(strarray:D: strarray:D \values --> strarray:D) { nqp::setelems(self,nqp::elems(values)); nqp::splice(self,values,0,nqp::elems(values)) } multi method STORE(strarray:D: Seq:D $seq --> strarray:D) { nqp::if( (my $iterator := $seq.iterator).is-lazy, self.throw-iterator-cannot-be-lazy('store'), nqp::stmts( nqp::setelems(self,0), $iterator.push-all(self), self ) ) } multi method STORE(strarray:D: List:D \values --> strarray:D) { my uint $elems = values.elems; # reifies my $reified := nqp::getattr(values,List,'$!reified'); nqp::setelems(self, $elems); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_s(self,$i, nqp::if( nqp::isnull(nqp::atpos($reified,$i)), "", nqp::unbox_s(nqp::atpos($reified,$i)) ) ) ); self } multi method STORE(strarray:D: @values --> strarray:D) { my uint $elems = @values.elems; # reifies nqp::setelems(self, $elems); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_s(self, $i, nqp::unbox_s(@values.AT-POS($i))) ); self } multi method push(strarray:D: str $value --> strarray:D) { nqp::push_s(self, $value); self } multi method push(strarray:D: Str:D $value --> strarray:D) { nqp::push_s(self, $value); self } multi method push(strarray:D: Mu \value --> Nil) { X::TypeCheck.new( operation => 'push to str array', got => value, expected => T, ).throw; } multi method append(strarray:D: str $value --> strarray:D) { nqp::push_s(self, $value); self } multi method append(strarray:D: Str:D $value --> strarray:D) { nqp::push_s(self, $value); self } multi method append(strarray:D: strarray:D $values --> strarray:D) is default { nqp::splice(self,$values,nqp::elems(self),0) } multi method append(strarray:D: @values --> strarray:D) { return self.fail-iterator-cannot-be-lazy('.append') if @values.is-lazy; nqp::push_s(self, $_) for flat @values; self } method pop(strarray:D: --> str) { nqp::elems(self) ?? nqp::pop_s(self) !! self.throw-cannot-be-empty('pop') } method shift(strarray:D: --> str) { nqp::elems(self) ?? nqp::shift_s(self) !! self.throw-cannot-be-empty('shift') } multi method unshift(strarray:D: str $value --> strarray:D) { nqp::unshift_s(self, $value); self } multi method unshift(strarray:D: Str:D $value --> strarray:D) { nqp::unshift_s(self, $value); self } multi method unshift(strarray:D: @values --> strarray:D) { return self.fail-iterator-cannot-be-lazy('.unshift') if @values.is-lazy; nqp::unshift_s(self, @values.pop) while @values; self } multi method unshift(strarray:D: Mu \value --> Nil) { X::TypeCheck.new( operation => 'unshift to str array', got => value, expected => T, ).throw; } my $empty_s := nqp::list_s; multi method splice(strarray:D: --> strarray:D) { my $splice := nqp::clone(self); nqp::setelems(self,0); $splice } multi method splice(strarray:D: Int:D $got --> strarray:D) { nqp::if( nqp::islt_i($got,0) || nqp::isgt_i( (my uint $offset = $got), (my uint $elems = nqp::elems(self)) ), X::OutOfRange.new( :what('Offset argument to splice'), :$got, :range("0..$elems") ).Failure, nqp::if( nqp::iseq_i($offset,$elems), nqp::create(self.WHAT), nqp::stmts( (my $slice := nqp::slice(self,$offset,-1)), nqp::splice( self, $empty_s, $offset, nqp::sub_i($elems,$offset) ), $slice ) ) ) } multi method splice(strarray:D: Int:D $offset, Int:D $size --> strarray:D) { nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice(self,$empty_s,$offset,$size) ); $slice } multi method splice(strarray:D: Int:D $offset, Int:D $size, strarray:D \values --> strarray:D) { nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice( self, nqp::if(nqp::eqaddr(self,values),nqp::clone(values),values), $offset, $size ) ); $slice } multi method splice(strarray:D: Int:D $offset, Int:D $size, Seq:D $seq --> strarray:D) { nqp::if( $seq.is-lazy, self.throw-iterator-cannot-be-lazy('.splice'), nqp::stmts( nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice(self,nqp::create(self).STORE($seq),$offset,$size) ), $slice ) ) } multi method splice(strarray:D: $offset=0, $size=Whatever, *@values --> strarray:D) { return self.fail-iterator-cannot-be-lazy('splice in') if @values.is-lazy; my int $elems = nqp::elems(self); # XXX execution error on next line if uint my int $o = nqp::istype($offset,Callable) ?? $offset($elems) !! nqp::istype($offset,Whatever) ?? $elems !! $offset.Int; my int $s = nqp::istype($size,Callable) ?? $size($elems - $o) !! !defined($size) || nqp::istype($size,Whatever) ?? $elems - ($o min $elems) !! $size.Int; unless nqp::istype( (my $splice := CLONE_SLICE(self,$o,$s)), Failure ) { my $splicees := nqp::create(self); nqp::push_s($splicees, @values.shift) while @values; nqp::splice(self,$splicees,$o,$s); } $splice } multi method min(strarray:D:) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my str $min = nqp::atpos_s(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::islt_s(nqp::atpos_s(self,$i),$min), ($min = nqp::atpos_s(self,$i)) ) ), $min ), Inf ) } multi method max(strarray:D:) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my str $max = nqp::atpos_s(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::isgt_s(nqp::atpos_s(self,$i),$max), ($max = nqp::atpos_s(self,$i)) ) ), $max ), -Inf ) } multi method minmax(strarray:D: --> Range:D) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my str $min = my str $max = nqp::atpos_s(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::islt_s(nqp::atpos_s(self,$i),$min), ($min = nqp::atpos_s(self,$i)), nqp::if( nqp::isgt_s(nqp::atpos_s(self,$i),$max), ($max = nqp::atpos_s(self,$i)) ) ) ), Range.new($min,$max) ), Range.Inf-Inf ) } method iterator(strarray:D: --> PredictiveIterator:D) { Rakudo::Iterator.native_s(self) } method Seq(strarray:D: --> Seq:D) { Seq.new(Rakudo::Iterator.native_s(self)) } method reverse(strarray:D: --> strarray:D) is nodal { nqp::stmts( (my uint $elems = nqp::elems(self)), (my int $last = nqp::sub_i($elems,1)), (my int $i = -1), (my $to := nqp::clone(self)), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_s($to,nqp::sub_i($last,$i), nqp::atpos_s(self,$i)) ), $to ) } method rotate(strarray:D: Int(Cool) $rotate = 1 --> strarray:D) is nodal { my int $elems = nqp::elems(self); my $to := nqp::clone(self); my int $i = -1; my int $j = nqp::mod_i( nqp::sub_i(nqp::sub_i($elems,1),$rotate), $elems ); $j = nqp::add_i($j,$elems) if nqp::islt_i($j,0); nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_s( $to, ($j = nqp::mod_i(nqp::add_i($j,1),$elems)), nqp::atpos_s(self,$i) ), ); $to } multi method sort(strarray:D: --> strarray:D) { Rakudo::Sorting.MERGESORT-str(nqp::clone(self)) } multi method ACCEPTS(strarray:D: strarray:D \o --> Bool:D) { nqp::hllbool( nqp::unless( nqp::eqaddr(self,my $other := nqp::decont(o)), nqp::if( nqp::iseq_i( (my uint $elems = nqp::elems(self)), nqp::elems($other) ), nqp::stmts( (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::iseq_s( nqp::atpos_s(self,$i), nqp::atpos_s($other,$i) ), nqp::null ), nqp::iseq_i($i,$elems) ) ) ) ) } proto method grab(|) {*} multi method grab(strarray:D: --> str) { nqp::elems(self) ?? self.GRAB_ONE !! Nil } multi method grab(strarray:D: Callable:D $calculate --> str) { self.grab($calculate(nqp::elems(self))) } multi method grab(strarray:D: Whatever --> Seq:D) { self.grab(Inf) } my class GrabN does Iterator { has $!array; has uint $!count; method !SET-SELF(\array,\count) { nqp::stmts( (my uint $elems = nqp::elems(array)), ($!array := array), nqp::if( count == Inf, ($!count = $elems), nqp::if( nqp::isgt_i(($!count = count.Int),$elems), ($!count = $elems) ) ), self ) } method new(\a,\c) { nqp::create(self)!SET-SELF(a,c) } method pull-one() { nqp::if( $!count && nqp::elems($!array), nqp::stmts( --$!count, $!array.GRAB_ONE ), IterationEnd ) } method is-deterministic(--> False) { } } multi method grab(strarray:D: \count --> Seq:D) { Seq.new( nqp::elems(self) ?? GrabN.new(self,count) !! Rakudo::Iterator.Empty ) } method GRAB_ONE(strarray:D: --> str) is implementation-detail { nqp::stmts( (my $value := nqp::atpos_s( self, (my uint $pos = nqp::floor_n(nqp::rand_n(nqp::elems(self)))) )), nqp::splice(self,$empty_s,$pos,1), $value ) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of strarray role ------------------------------------- method join(strarray:D: $delim = '') { my str $empty = ""; my int $elems = nqp::elems(self); my int $i = -1; nqp::bindpos_s(self,$i,$empty) if nqp::isnull_s(nqp::atposref_s(self,$i)) while nqp::islt_i(++$i,$elems); nqp::join($delim.Str,self) } method raku(strarray:D: --> Str:D) { my $parts := nqp::list_s; my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems(self)), nqp::push_s($parts,nqp::if( nqp::isnull_s(my $str := nqp::atpos_s(self,$i)), '""', $str.raku )) ); nqp::concat('array[', nqp::concat(T.^name, nqp::concat('].new(', nqp::concat(nqp::join(', ',$parts),')') ) ) ) } } role intarray[::T] does Positional[T] is array_type(T) { #- start of generated part of intarray role ----------------------------------- #- Generated on 2022-05-21T12:11:45+02:00 by tools/build/makeNATIVE_ARRAY.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi method grep(intarray:D: Int:D $needle, :$k, :$kv, :$p, :$v --> Seq:D) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(IterationBuffer); if $k { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_i(nqp::atpos_i(self,$i),$needle), nqp::push($result,nqp::clone($i)) ) ); } elsif $kv { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_i(nqp::atpos_i(self,$i),$needle), nqp::stmts( nqp::push($result,nqp::clone($i)), nqp::push($result,$needle) ) ) ); } elsif $p { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_i(nqp::atpos_i(self,$i),$needle), nqp::push($result,Pair.new($i,$needle)) ) ); } else { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_i(nqp::atpos_i(self,$i),$needle), nqp::push($result,$needle) ) ); } $result.Seq } multi method head(intarray:D:) { nqp::atpos_i(self,0) } multi method tail(intarray:D:) { nqp::atpos_i(self,nqp::sub_i(nqp::elems(self),1)) } multi method first(intarray:D: Int:D $needle, :$k, :$kv, :$p, :$v) { my int $i = -1; my uint $elems = nqp::elems(self); nqp::while( nqp::islt_i(++$i,$elems) && nqp::isne_i(nqp::atpos_i(self,$i),$needle), nqp::null() ); nqp::iseq_i($i,$elems) ?? Nil !! $k ?? $i !! $kv ?? ($i,$needle) !! $p ?? Pair.new($i,$needle) !! $needle } multi method unique(intarray:D:) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(array[self.of]); my $seen := nqp::hash; nqp::while( nqp::islt_i(++$i,$elems), nqp::unless( nqp::existskey( $seen, (my str $key = nqp::coerce_is( my int $value = nqp::atpos_i(self,$i) )) ), nqp::stmts( nqp::push_i($result,$value), nqp::bindkey($seen,$key,1), ) ) ); $result } multi method repeated(intarray:D:) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(array[self.of]); my $seen := nqp::hash; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::existskey( $seen, (my str $key = nqp::coerce_is( my int $value = nqp::atpos_i(self,$i) )) ), nqp::push_i($result,$value), nqp::bindkey($seen,$key,1) ) ); $result } multi method squish(intarray:D:) { if nqp::elems(self) -> int $elems { my $result := nqp::create(array[self.of]); my int $last = nqp::push_i($result,nqp::atpos_i(self,0)); my uint $i; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::isne_i(nqp::atpos_i(self,$i),$last), nqp::push_i($result,$last = nqp::atpos_i(self,$i)) ) ); $result } else { self } } multi method AT-POS(intarray:D: uint $idx --> int) is raw { nqp::atposref_i(self,$idx) } multi method AT-POS(intarray:D: Int:D $idx --> int) is raw { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::atposref_i(self,$idx) } multi method ASSIGN-POS(intarray:D: uint $idx, int $value --> int) { nqp::bindpos_i(self, $idx, $value) } multi method ASSIGN-POS(intarray:D: Int:D $idx, int $value --> int) { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::bindpos_i(self, $idx, $value) } multi method ASSIGN-POS(intarray:D: uint $idx, Int:D $value --> int) { nqp::bindpos_i(self, $idx, $value) } multi method ASSIGN-POS(intarray:D: Int:D $idx, Int:D $value --> int) { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::bindpos_i(self, $idx, $value) } multi method ASSIGN-POS(intarray:D: Any $idx, Mu \value --> Nil) { X::TypeCheck.new( operation => "assignment to int array element #$idx", got => value, expected => T, ).throw; } multi method STORE(intarray:D: $value --> intarray:D) { nqp::setelems(self,1); nqp::bindpos_i(self, 0, nqp::unbox_i($value)); self } multi method STORE(intarray:D: intarray:D \values --> intarray:D) { nqp::setelems(self,nqp::elems(values)); nqp::splice(self,values,0,nqp::elems(values)) } multi method STORE(intarray:D: Seq:D $seq --> intarray:D) { nqp::if( (my $iterator := $seq.iterator).is-lazy, self.throw-iterator-cannot-be-lazy('store'), nqp::stmts( nqp::setelems(self,0), $iterator.push-all(self), self ) ) } multi method STORE(intarray:D: List:D \values --> intarray:D) { my uint $elems = values.elems; # reifies my $reified := nqp::getattr(values,List,'$!reified'); nqp::setelems(self, $elems); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_i(self,$i, nqp::if( nqp::isnull(nqp::atpos($reified,$i)), 0, nqp::unbox_i(nqp::atpos($reified,$i)) ) ) ); self } multi method STORE(intarray:D: @values --> intarray:D) { my uint $elems = @values.elems; # reifies nqp::setelems(self, $elems); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_i(self, $i, nqp::unbox_i(@values.AT-POS($i))) ); self } multi method push(intarray:D: int $value --> intarray:D) { nqp::push_i(self, $value); self } multi method push(intarray:D: Int:D $value --> intarray:D) { nqp::push_i(self, $value); self } multi method push(intarray:D: Mu \value --> Nil) { X::TypeCheck.new( operation => 'push to int array', got => value, expected => T, ).throw; } multi method append(intarray:D: int $value --> intarray:D) { nqp::push_i(self, $value); self } multi method append(intarray:D: Int:D $value --> intarray:D) { nqp::push_i(self, $value); self } multi method append(intarray:D: intarray:D $values --> intarray:D) is default { nqp::splice(self,$values,nqp::elems(self),0) } multi method append(intarray:D: @values --> intarray:D) { return self.fail-iterator-cannot-be-lazy('.append') if @values.is-lazy; nqp::push_i(self, $_) for flat @values; self } method pop(intarray:D: --> int) { nqp::elems(self) ?? nqp::pop_i(self) !! self.throw-cannot-be-empty('pop') } method shift(intarray:D: --> int) { nqp::elems(self) ?? nqp::shift_i(self) !! self.throw-cannot-be-empty('shift') } multi method unshift(intarray:D: int $value --> intarray:D) { nqp::unshift_i(self, $value); self } multi method unshift(intarray:D: Int:D $value --> intarray:D) { nqp::unshift_i(self, $value); self } multi method unshift(intarray:D: @values --> intarray:D) { return self.fail-iterator-cannot-be-lazy('.unshift') if @values.is-lazy; nqp::unshift_i(self, @values.pop) while @values; self } multi method unshift(intarray:D: Mu \value --> Nil) { X::TypeCheck.new( operation => 'unshift to int array', got => value, expected => T, ).throw; } my $empty_i := nqp::list_i; multi method splice(intarray:D: --> intarray:D) { my $splice := nqp::clone(self); nqp::setelems(self,0); $splice } multi method splice(intarray:D: Int:D $got --> intarray:D) { nqp::if( nqp::islt_i($got,0) || nqp::isgt_i( (my uint $offset = $got), (my uint $elems = nqp::elems(self)) ), X::OutOfRange.new( :what('Offset argument to splice'), :$got, :range("0..$elems") ).Failure, nqp::if( nqp::iseq_i($offset,$elems), nqp::create(self.WHAT), nqp::stmts( (my $slice := nqp::slice(self,$offset,-1)), nqp::splice( self, $empty_i, $offset, nqp::sub_i($elems,$offset) ), $slice ) ) ) } multi method splice(intarray:D: Int:D $offset, Int:D $size --> intarray:D) { nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice(self,$empty_i,$offset,$size) ); $slice } multi method splice(intarray:D: Int:D $offset, Int:D $size, intarray:D \values --> intarray:D) { nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice( self, nqp::if(nqp::eqaddr(self,values),nqp::clone(values),values), $offset, $size ) ); $slice } multi method splice(intarray:D: Int:D $offset, Int:D $size, Seq:D $seq --> intarray:D) { nqp::if( $seq.is-lazy, self.throw-iterator-cannot-be-lazy('.splice'), nqp::stmts( nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice(self,nqp::create(self).STORE($seq),$offset,$size) ), $slice ) ) } multi method splice(intarray:D: $offset=0, $size=Whatever, *@values --> intarray:D) { return self.fail-iterator-cannot-be-lazy('splice in') if @values.is-lazy; my int $elems = nqp::elems(self); # XXX execution error on next line if uint my int $o = nqp::istype($offset,Callable) ?? $offset($elems) !! nqp::istype($offset,Whatever) ?? $elems !! $offset.Int; my int $s = nqp::istype($size,Callable) ?? $size($elems - $o) !! !defined($size) || nqp::istype($size,Whatever) ?? $elems - ($o min $elems) !! $size.Int; unless nqp::istype( (my $splice := CLONE_SLICE(self,$o,$s)), Failure ) { my $splicees := nqp::create(self); nqp::push_i($splicees, @values.shift) while @values; nqp::splice(self,$splicees,$o,$s); } $splice } multi method min(intarray:D:) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my int $min = nqp::atpos_i(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::islt_i(nqp::atpos_i(self,$i),$min), ($min = nqp::atpos_i(self,$i)) ) ), $min ), Inf ) } multi method max(intarray:D:) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my int $max = nqp::atpos_i(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::isgt_i(nqp::atpos_i(self,$i),$max), ($max = nqp::atpos_i(self,$i)) ) ), $max ), -Inf ) } multi method minmax(intarray:D: --> Range:D) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my int $min = my int $max = nqp::atpos_i(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::islt_i(nqp::atpos_i(self,$i),$min), ($min = nqp::atpos_i(self,$i)), nqp::if( nqp::isgt_i(nqp::atpos_i(self,$i),$max), ($max = nqp::atpos_i(self,$i)) ) ) ), Range.new($min,$max) ), Range.Inf-Inf ) } method iterator(intarray:D: --> PredictiveIterator:D) { Rakudo::Iterator.native_i(self) } method Seq(intarray:D: --> Seq:D) { Seq.new(Rakudo::Iterator.native_i(self)) } method reverse(intarray:D: --> intarray:D) is nodal { nqp::stmts( (my uint $elems = nqp::elems(self)), (my int $last = nqp::sub_i($elems,1)), (my int $i = -1), (my $to := nqp::clone(self)), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_i($to,nqp::sub_i($last,$i), nqp::atpos_i(self,$i)) ), $to ) } method rotate(intarray:D: Int(Cool) $rotate = 1 --> intarray:D) is nodal { my int $elems = nqp::elems(self); my $to := nqp::clone(self); my int $i = -1; my int $j = nqp::mod_i( nqp::sub_i(nqp::sub_i($elems,1),$rotate), $elems ); $j = nqp::add_i($j,$elems) if nqp::islt_i($j,0); nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_i( $to, ($j = nqp::mod_i(nqp::add_i($j,1),$elems)), nqp::atpos_i(self,$i) ), ); $to } multi method sort(intarray:D: --> intarray:D) { Rakudo::Sorting.MERGESORT-int(nqp::clone(self)) } multi method ACCEPTS(intarray:D: intarray:D \o --> Bool:D) { nqp::hllbool( nqp::unless( nqp::eqaddr(self,my $other := nqp::decont(o)), nqp::if( nqp::iseq_i( (my uint $elems = nqp::elems(self)), nqp::elems($other) ), nqp::stmts( (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::iseq_i( nqp::atpos_i(self,$i), nqp::atpos_i($other,$i) ), nqp::null ), nqp::iseq_i($i,$elems) ) ) ) ) } proto method grab(|) {*} multi method grab(intarray:D: --> int) { nqp::elems(self) ?? self.GRAB_ONE !! Nil } multi method grab(intarray:D: Callable:D $calculate --> int) { self.grab($calculate(nqp::elems(self))) } multi method grab(intarray:D: Whatever --> Seq:D) { self.grab(Inf) } my class GrabN does Iterator { has $!array; has uint $!count; method !SET-SELF(\array,\count) { nqp::stmts( (my uint $elems = nqp::elems(array)), ($!array := array), nqp::if( count == Inf, ($!count = $elems), nqp::if( nqp::isgt_i(($!count = count.Int),$elems), ($!count = $elems) ) ), self ) } method new(\a,\c) { nqp::create(self)!SET-SELF(a,c) } method pull-one() { nqp::if( $!count && nqp::elems($!array), nqp::stmts( --$!count, $!array.GRAB_ONE ), IterationEnd ) } method is-deterministic(--> False) { } } multi method grab(intarray:D: \count --> Seq:D) { Seq.new( nqp::elems(self) ?? GrabN.new(self,count) !! Rakudo::Iterator.Empty ) } method GRAB_ONE(intarray:D: --> int) is implementation-detail { nqp::stmts( (my $value := nqp::atpos_i( self, (my uint $pos = nqp::floor_n(nqp::rand_n(nqp::elems(self)))) )), nqp::splice(self,$empty_i,$pos,1), $value ) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of intarray role ------------------------------------- multi method chrs(intarray:D: --> Str:D) { my int $i = -1; my int $elems = nqp::elems(self); my $result := nqp::setelems(nqp::list_s,$elems); nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_s($result,$i,nqp::chr(nqp::atpos_i(self,$i))) ); nqp::join("",$result) } multi method sum(intarray:D: :$wrap) { nqp::if( (my int $elems = nqp::elems(self)), nqp::stmts( (my int $i), nqp::if( $wrap, nqp::stmts( (my int $sum = nqp::atpos_i(self,0)), nqp::while( nqp::islt_i(++$i,$elems), $sum = nqp::add_i($sum,nqp::atpos_i(self,$i)) ), $sum ), nqp::stmts( (my Int $Sum = nqp::atpos_i(self,0)), nqp::while( nqp::islt_i(++$i,$elems), $Sum = $Sum + nqp::atpos_i(self,$i) ), $Sum ) ) ), 0 ) } method join(intarray:D: $delim = '') { my int $elems = nqp::elems(self); my $list := nqp::setelems(nqp::list_s,$elems); my int $i = -1; nqp::bindpos_s($list,$i, nqp::tostr_I(nqp::p6box_i(nqp::atpos_i(self,$i)))) while nqp::islt_i(++$i,$elems); nqp::join($delim.Str,$list) } multi method STORE(intarray:D: Range:D \range) { nqp::if( range.is-int, nqp::stmts( (my int $val = nqp::add_i( nqp::getattr(range,Range,'$!min'), nqp::getattr_i(range,Range,'$!excludes-min') )), (my int $max = nqp::sub_i( nqp::getattr(range,Range,'$!max'), nqp::getattr_i(range,Range,'$!excludes-max') )), nqp::setelems(self,0), # make sure we start from scratch ($val = nqp::sub_i($val,1)), nqp::while( nqp::isle_i(++$val,$max), nqp::push_i(self,$val) ), self ), X::AdHoc.new( payload => "Can only initialize an int array with an int Range" ).throw ) } } role uintarray[::T] does Positional[T] is array_type(T) { #- start of generated part of uintarray role ----------------------------------- #- Generated on 2022-05-21T12:11:45+02:00 by tools/build/makeNATIVE_ARRAY.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi method grep(uintarray:D: Int:D $needle, :$k, :$kv, :$p, :$v --> Seq:D) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(IterationBuffer); if $k { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_i(nqp::atpos_u(self,$i),$needle), nqp::push($result,nqp::clone($i)) ) ); } elsif $kv { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_i(nqp::atpos_u(self,$i),$needle), nqp::stmts( nqp::push($result,nqp::clone($i)), nqp::push($result,$needle) ) ) ); } elsif $p { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_i(nqp::atpos_u(self,$i),$needle), nqp::push($result,Pair.new($i,$needle)) ) ); } else { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_i(nqp::atpos_u(self,$i),$needle), nqp::push($result,$needle) ) ); } $result.Seq } multi method head(uintarray:D:) { nqp::atpos_u(self,0) } multi method tail(uintarray:D:) { nqp::atpos_u(self,nqp::sub_i(nqp::elems(self),1)) } multi method first(uintarray:D: Int:D $needle, :$k, :$kv, :$p, :$v) { my int $i = -1; my uint $elems = nqp::elems(self); nqp::while( nqp::islt_i(++$i,$elems) && nqp::isne_i(nqp::atpos_u(self,$i),$needle), nqp::null() ); nqp::iseq_i($i,$elems) ?? Nil !! $k ?? $i !! $kv ?? ($i,$needle) !! $p ?? Pair.new($i,$needle) !! $needle } multi method unique(uintarray:D:) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(array[self.of]); my $seen := nqp::hash; nqp::while( nqp::islt_i(++$i,$elems), nqp::unless( nqp::existskey( $seen, (my str $key = nqp::coerce_us( my uint $value = nqp::atpos_u(self,$i) )) ), nqp::stmts( nqp::push_i($result,$value), nqp::bindkey($seen,$key,1), ) ) ); $result } multi method repeated(uintarray:D:) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(array[self.of]); my $seen := nqp::hash; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::existskey( $seen, (my str $key = nqp::coerce_us( my uint $value = nqp::atpos_u(self,$i) )) ), nqp::push_i($result,$value), nqp::bindkey($seen,$key,1) ) ); $result } multi method squish(uintarray:D:) { if nqp::elems(self) -> int $elems { my $result := nqp::create(array[self.of]); my uint $last = nqp::push_i($result,nqp::atpos_u(self,0)); my uint $i; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::isne_i(nqp::atpos_u(self,$i),$last), nqp::push_i($result,$last = nqp::atpos_u(self,$i)) ) ); $result } else { self } } multi method AT-POS(uintarray:D: uint $idx --> uint) is raw { nqp::atposref_u(self,$idx) } multi method AT-POS(uintarray:D: Int:D $idx --> uint) is raw { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::atposref_u(self,$idx) } multi method ASSIGN-POS(uintarray:D: uint $idx, uint $value --> uint) { nqp::bindpos_u(self, $idx, $value) } multi method ASSIGN-POS(uintarray:D: Int:D $idx, uint $value --> uint) { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::bindpos_u(self, $idx, $value) } multi method ASSIGN-POS(uintarray:D: uint $idx, Int:D $value --> uint) { nqp::bindpos_u(self, $idx, $value) } multi method ASSIGN-POS(uintarray:D: Int:D $idx, Int:D $value --> uint) { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::bindpos_u(self, $idx, $value) } multi method ASSIGN-POS(uintarray:D: Any $idx, Mu \value --> Nil) { X::TypeCheck.new( operation => "assignment to uint array element #$idx", got => value, expected => T, ).throw; } multi method STORE(uintarray:D: $value --> uintarray:D) { nqp::setelems(self,1); nqp::bindpos_u(self, 0, nqp::unbox_u($value)); self } multi method STORE(uintarray:D: uintarray:D \values --> uintarray:D) { nqp::setelems(self,nqp::elems(values)); nqp::splice(self,values,0,nqp::elems(values)) } multi method STORE(uintarray:D: Seq:D $seq --> uintarray:D) { nqp::if( (my $iterator := $seq.iterator).is-lazy, self.throw-iterator-cannot-be-lazy('store'), nqp::stmts( nqp::setelems(self,0), $iterator.push-all(self), self ) ) } multi method STORE(uintarray:D: List:D \values --> uintarray:D) { my uint $elems = values.elems; # reifies my $reified := nqp::getattr(values,List,'$!reified'); nqp::setelems(self, $elems); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_u(self,$i, nqp::if( nqp::isnull(nqp::atpos($reified,$i)), 0, nqp::unbox_u(nqp::atpos($reified,$i)) ) ) ); self } multi method STORE(uintarray:D: @values --> uintarray:D) { my uint $elems = @values.elems; # reifies nqp::setelems(self, $elems); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_u(self, $i, nqp::unbox_u(@values.AT-POS($i))) ); self } multi method push(uintarray:D: uint $value --> uintarray:D) { nqp::push_i(self, $value); self } multi method push(uintarray:D: Int:D $value --> uintarray:D) { nqp::push_i(self, $value); self } multi method push(uintarray:D: Mu \value --> Nil) { X::TypeCheck.new( operation => 'push to uint array', got => value, expected => T, ).throw; } multi method append(uintarray:D: uint $value --> uintarray:D) { nqp::push_i(self, $value); self } multi method append(uintarray:D: Int:D $value --> uintarray:D) { nqp::push_i(self, $value); self } multi method append(uintarray:D: uintarray:D $values --> uintarray:D) is default { nqp::splice(self,$values,nqp::elems(self),0) } multi method append(uintarray:D: @values --> uintarray:D) { return self.fail-iterator-cannot-be-lazy('.append') if @values.is-lazy; nqp::push_i(self, $_) for flat @values; self } method pop(uintarray:D: --> uint) { nqp::elems(self) ?? nqp::pop_i(self) !! self.throw-cannot-be-empty('pop') } method shift(uintarray:D: --> uint) { nqp::elems(self) ?? nqp::shift_i(self) !! self.throw-cannot-be-empty('shift') } multi method unshift(uintarray:D: uint $value --> uintarray:D) { nqp::unshift_i(self, $value); self } multi method unshift(uintarray:D: Int:D $value --> uintarray:D) { nqp::unshift_i(self, $value); self } multi method unshift(uintarray:D: @values --> uintarray:D) { return self.fail-iterator-cannot-be-lazy('.unshift') if @values.is-lazy; nqp::unshift_i(self, @values.pop) while @values; self } multi method unshift(uintarray:D: Mu \value --> Nil) { X::TypeCheck.new( operation => 'unshift to uint array', got => value, expected => T, ).throw; } my $empty_u := nqp::list_i; multi method splice(uintarray:D: --> uintarray:D) { my $splice := nqp::clone(self); nqp::setelems(self,0); $splice } multi method splice(uintarray:D: Int:D $got --> uintarray:D) { nqp::if( nqp::islt_i($got,0) || nqp::isgt_i( (my uint $offset = $got), (my uint $elems = nqp::elems(self)) ), X::OutOfRange.new( :what('Offset argument to splice'), :$got, :range("0..$elems") ).Failure, nqp::if( nqp::iseq_i($offset,$elems), nqp::create(self.WHAT), nqp::stmts( (my $slice := nqp::slice(self,$offset,-1)), nqp::splice( self, $empty_u, $offset, nqp::sub_i($elems,$offset) ), $slice ) ) ) } multi method splice(uintarray:D: Int:D $offset, Int:D $size --> uintarray:D) { nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice(self,$empty_u,$offset,$size) ); $slice } multi method splice(uintarray:D: Int:D $offset, Int:D $size, uintarray:D \values --> uintarray:D) { nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice( self, nqp::if(nqp::eqaddr(self,values),nqp::clone(values),values), $offset, $size ) ); $slice } multi method splice(uintarray:D: Int:D $offset, Int:D $size, Seq:D $seq --> uintarray:D) { nqp::if( $seq.is-lazy, self.throw-iterator-cannot-be-lazy('.splice'), nqp::stmts( nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice(self,nqp::create(self).STORE($seq),$offset,$size) ), $slice ) ) } multi method splice(uintarray:D: $offset=0, $size=Whatever, *@values --> uintarray:D) { return self.fail-iterator-cannot-be-lazy('splice in') if @values.is-lazy; my int $elems = nqp::elems(self); # XXX execution error on next line if uint my int $o = nqp::istype($offset,Callable) ?? $offset($elems) !! nqp::istype($offset,Whatever) ?? $elems !! $offset.Int; my int $s = nqp::istype($size,Callable) ?? $size($elems - $o) !! !defined($size) || nqp::istype($size,Whatever) ?? $elems - ($o min $elems) !! $size.Int; unless nqp::istype( (my $splice := CLONE_SLICE(self,$o,$s)), Failure ) { my $splicees := nqp::create(self); nqp::push_i($splicees, @values.shift) while @values; nqp::splice(self,$splicees,$o,$s); } $splice } multi method min(uintarray:D:) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my uint $min = nqp::atpos_u(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::islt_i(nqp::atpos_u(self,$i),$min), ($min = nqp::atpos_u(self,$i)) ) ), $min ), Inf ) } multi method max(uintarray:D:) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my uint $max = nqp::atpos_u(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::isgt_i(nqp::atpos_u(self,$i),$max), ($max = nqp::atpos_u(self,$i)) ) ), $max ), -Inf ) } multi method minmax(uintarray:D: --> Range:D) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my uint $min = my uint $max = nqp::atpos_u(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::islt_i(nqp::atpos_u(self,$i),$min), ($min = nqp::atpos_u(self,$i)), nqp::if( nqp::isgt_i(nqp::atpos_u(self,$i),$max), ($max = nqp::atpos_u(self,$i)) ) ) ), Range.new($min,$max) ), Range.Inf-Inf ) } method iterator(uintarray:D: --> PredictiveIterator:D) { Rakudo::Iterator.native_u(self) } method Seq(uintarray:D: --> Seq:D) { Seq.new(Rakudo::Iterator.native_u(self)) } method reverse(uintarray:D: --> uintarray:D) is nodal { nqp::stmts( (my uint $elems = nqp::elems(self)), (my int $last = nqp::sub_i($elems,1)), (my int $i = -1), (my $to := nqp::clone(self)), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_u($to,nqp::sub_i($last,$i), nqp::atpos_u(self,$i)) ), $to ) } method rotate(uintarray:D: Int(Cool) $rotate = 1 --> uintarray:D) is nodal { my int $elems = nqp::elems(self); my $to := nqp::clone(self); my int $i = -1; my int $j = nqp::mod_i( nqp::sub_i(nqp::sub_i($elems,1),$rotate), $elems ); $j = nqp::add_i($j,$elems) if nqp::islt_i($j,0); nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_u( $to, ($j = nqp::mod_i(nqp::add_i($j,1),$elems)), nqp::atpos_u(self,$i) ), ); $to } multi method sort(uintarray:D: --> uintarray:D) { Rakudo::Sorting.MERGESORT-uint(nqp::clone(self)) } multi method ACCEPTS(uintarray:D: uintarray:D \o --> Bool:D) { nqp::hllbool( nqp::unless( nqp::eqaddr(self,my $other := nqp::decont(o)), nqp::if( nqp::iseq_i( (my uint $elems = nqp::elems(self)), nqp::elems($other) ), nqp::stmts( (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::iseq_i( nqp::atpos_u(self,$i), nqp::atpos_u($other,$i) ), nqp::null ), nqp::iseq_i($i,$elems) ) ) ) ) } proto method grab(|) {*} multi method grab(uintarray:D: --> uint) { nqp::elems(self) ?? self.GRAB_ONE !! Nil } multi method grab(uintarray:D: Callable:D $calculate --> uint) { self.grab($calculate(nqp::elems(self))) } multi method grab(uintarray:D: Whatever --> Seq:D) { self.grab(Inf) } my class GrabN does Iterator { has $!array; has uint $!count; method !SET-SELF(\array,\count) { nqp::stmts( (my uint $elems = nqp::elems(array)), ($!array := array), nqp::if( count == Inf, ($!count = $elems), nqp::if( nqp::isgt_i(($!count = count.Int),$elems), ($!count = $elems) ) ), self ) } method new(\a,\c) { nqp::create(self)!SET-SELF(a,c) } method pull-one() { nqp::if( $!count && nqp::elems($!array), nqp::stmts( --$!count, $!array.GRAB_ONE ), IterationEnd ) } method is-deterministic(--> False) { } } multi method grab(uintarray:D: \count --> Seq:D) { Seq.new( nqp::elems(self) ?? GrabN.new(self,count) !! Rakudo::Iterator.Empty ) } method GRAB_ONE(uintarray:D: --> uint) is implementation-detail { nqp::stmts( (my $value := nqp::atpos_u( self, (my uint $pos = nqp::floor_n(nqp::rand_n(nqp::elems(self)))) )), nqp::splice(self,$empty_u,$pos,1), $value ) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of uintarray role ------------------------------------- multi method chrs(uintarray:D: --> Str:D) { my int $i = -1; my int $elems = nqp::elems(self); my $result := nqp::setelems(nqp::list_s,$elems); nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_s($result,$i,nqp::chr(nqp::atpos_u(self,$i))) ); nqp::join("",$result) } multi method sum(uintarray:D: :$wrap) { nqp::if( (my int $elems = nqp::elems(self)), nqp::stmts( (my int $i), nqp::if( $wrap, nqp::stmts( (my int $sum = nqp::atpos_u(self,0)), nqp::while( nqp::islt_i(++$i,$elems), $sum = nqp::add_i($sum,nqp::atpos_u(self,$i)) ), $sum ), nqp::stmts( (my Int $Sum = nqp::atpos_u(self,0)), nqp::while( nqp::islt_i(++$i,$elems), $Sum = $Sum + nqp::atpos_u(self,$i) ), $Sum ) ) ), 0 ) } method join(uintarray:D: $delim = '') { my int $elems = nqp::elems(self); my $list := nqp::setelems(nqp::list_s,$elems); my int $i = -1; nqp::bindpos_s($list,$i, nqp::tostr_I(nqp::p6box_i(nqp::atpos_u(self,$i)))) while nqp::islt_i(++$i,$elems); nqp::join($delim.Str,$list) } multi method STORE(uintarray:D: Range:D \range) { nqp::if( range.is-int, nqp::stmts( (my int $val = nqp::add_i( nqp::getattr(range,Range,'$!min'), nqp::getattr_i(range,Range,'$!excludes-min') )), (my int $max = nqp::sub_i( nqp::getattr(range,Range,'$!max'), nqp::getattr_i(range,Range,'$!excludes-max') )), nqp::setelems(self,0), # make sure we start from scratch ($val = nqp::sub_i($val,1)), nqp::while( nqp::isle_i(++$val,$max), nqp::push_i(self,$val) ), self ), X::AdHoc.new( payload => "Can only initialize an int array with an int Range" ).throw ) } } role numarray[::T] does Positional[T] is array_type(T) { #- start of generated part of numarray role ----------------------------------- #- Generated on 2022-05-21T12:11:45+02:00 by tools/build/makeNATIVE_ARRAY.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi method grep(numarray:D: Num:D $needle, :$k, :$kv, :$p, :$v --> Seq:D) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(IterationBuffer); if $k { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_n(nqp::atpos_n(self,$i),$needle), nqp::push($result,nqp::clone($i)) ) ); } elsif $kv { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_n(nqp::atpos_n(self,$i),$needle), nqp::stmts( nqp::push($result,nqp::clone($i)), nqp::push($result,$needle) ) ) ); } elsif $p { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_n(nqp::atpos_n(self,$i),$needle), nqp::push($result,Pair.new($i,$needle)) ) ); } else { nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::iseq_n(nqp::atpos_n(self,$i),$needle), nqp::push($result,$needle) ) ); } $result.Seq } multi method head(numarray:D:) { nqp::atpos_n(self,0) } multi method tail(numarray:D:) { nqp::atpos_n(self,nqp::sub_i(nqp::elems(self),1)) } multi method first(numarray:D: Num:D $needle, :$k, :$kv, :$p, :$v) { my int $i = -1; my uint $elems = nqp::elems(self); nqp::while( nqp::islt_i(++$i,$elems) && nqp::isne_n(nqp::atpos_n(self,$i),$needle), nqp::null() ); nqp::iseq_i($i,$elems) ?? Nil !! $k ?? $i !! $kv ?? ($i,$needle) !! $p ?? Pair.new($i,$needle) !! $needle } multi method unique(numarray:D:) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(array[self.of]); my $seen := nqp::hash; nqp::while( nqp::islt_i(++$i,$elems), nqp::unless( nqp::existskey( $seen, (my str $key = nqp::coerce_ns( my num $value = nqp::atpos_n(self,$i) )) ), nqp::stmts( nqp::push_n($result,$value), nqp::bindkey($seen,$key,1), ) ) ); $result } multi method repeated(numarray:D:) { my int $i = -1; my uint $elems = nqp::elems(self); my $result := nqp::create(array[self.of]); my $seen := nqp::hash; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::existskey( $seen, (my str $key = nqp::coerce_ns( my num $value = nqp::atpos_n(self,$i) )) ), nqp::push_n($result,$value), nqp::bindkey($seen,$key,1) ) ); $result } multi method squish(numarray:D:) { if nqp::elems(self) -> int $elems { my $result := nqp::create(array[self.of]); my num $last = nqp::push_n($result,nqp::atpos_n(self,0)); my uint $i; nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::isne_n(nqp::atpos_n(self,$i),$last), nqp::push_n($result,$last = nqp::atpos_n(self,$i)) ) ); $result } else { self } } multi method AT-POS(numarray:D: uint $idx --> num) is raw { nqp::atposref_n(self,$idx) } multi method AT-POS(numarray:D: Int:D $idx --> num) is raw { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::atposref_n(self,$idx) } multi method ASSIGN-POS(numarray:D: uint $idx, num $value --> num) { nqp::bindpos_n(self, $idx, $value) } multi method ASSIGN-POS(numarray:D: Int:D $idx, num $value --> num) { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::bindpos_n(self, $idx, $value) } multi method ASSIGN-POS(numarray:D: uint $idx, Num:D $value --> num) { nqp::bindpos_n(self, $idx, $value) } multi method ASSIGN-POS(numarray:D: Int:D $idx, Num:D $value --> num) { $idx < 0 ?? INDEX_OUT_OF_RANGE($idx) !! nqp::bindpos_n(self, $idx, $value) } multi method ASSIGN-POS(numarray:D: Any $idx, Mu \value --> Nil) { X::TypeCheck.new( operation => "assignment to num array element #$idx", got => value, expected => T, ).throw; } multi method STORE(numarray:D: $value --> numarray:D) { nqp::setelems(self,1); nqp::bindpos_n(self, 0, nqp::unbox_n($value)); self } multi method STORE(numarray:D: numarray:D \values --> numarray:D) { nqp::setelems(self,nqp::elems(values)); nqp::splice(self,values,0,nqp::elems(values)) } multi method STORE(numarray:D: Seq:D $seq --> numarray:D) { nqp::if( (my $iterator := $seq.iterator).is-lazy, self.throw-iterator-cannot-be-lazy('store'), nqp::stmts( nqp::setelems(self,0), $iterator.push-all(self), self ) ) } multi method STORE(numarray:D: List:D \values --> numarray:D) { my uint $elems = values.elems; # reifies my $reified := nqp::getattr(values,List,'$!reified'); nqp::setelems(self, $elems); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_n(self,$i, nqp::if( nqp::isnull(nqp::atpos($reified,$i)), 0e0, nqp::unbox_n(nqp::atpos($reified,$i)) ) ) ); self } multi method STORE(numarray:D: @values --> numarray:D) { my uint $elems = @values.elems; # reifies nqp::setelems(self, $elems); my int $i = -1; nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_n(self, $i, nqp::unbox_n(@values.AT-POS($i))) ); self } multi method push(numarray:D: num $value --> numarray:D) { nqp::push_n(self, $value); self } multi method push(numarray:D: Num:D $value --> numarray:D) { nqp::push_n(self, $value); self } multi method push(numarray:D: Mu \value --> Nil) { X::TypeCheck.new( operation => 'push to num array', got => value, expected => T, ).throw; } multi method append(numarray:D: num $value --> numarray:D) { nqp::push_n(self, $value); self } multi method append(numarray:D: Num:D $value --> numarray:D) { nqp::push_n(self, $value); self } multi method append(numarray:D: numarray:D $values --> numarray:D) is default { nqp::splice(self,$values,nqp::elems(self),0) } multi method append(numarray:D: @values --> numarray:D) { return self.fail-iterator-cannot-be-lazy('.append') if @values.is-lazy; nqp::push_n(self, $_) for flat @values; self } method pop(numarray:D: --> num) { nqp::elems(self) ?? nqp::pop_n(self) !! self.throw-cannot-be-empty('pop') } method shift(numarray:D: --> num) { nqp::elems(self) ?? nqp::shift_n(self) !! self.throw-cannot-be-empty('shift') } multi method unshift(numarray:D: num $value --> numarray:D) { nqp::unshift_n(self, $value); self } multi method unshift(numarray:D: Num:D $value --> numarray:D) { nqp::unshift_n(self, $value); self } multi method unshift(numarray:D: @values --> numarray:D) { return self.fail-iterator-cannot-be-lazy('.unshift') if @values.is-lazy; nqp::unshift_n(self, @values.pop) while @values; self } multi method unshift(numarray:D: Mu \value --> Nil) { X::TypeCheck.new( operation => 'unshift to num array', got => value, expected => T, ).throw; } my $empty_n := nqp::list_n; multi method splice(numarray:D: --> numarray:D) { my $splice := nqp::clone(self); nqp::setelems(self,0); $splice } multi method splice(numarray:D: Int:D $got --> numarray:D) { nqp::if( nqp::islt_i($got,0) || nqp::isgt_i( (my uint $offset = $got), (my uint $elems = nqp::elems(self)) ), X::OutOfRange.new( :what('Offset argument to splice'), :$got, :range("0..$elems") ).Failure, nqp::if( nqp::iseq_i($offset,$elems), nqp::create(self.WHAT), nqp::stmts( (my $slice := nqp::slice(self,$offset,-1)), nqp::splice( self, $empty_n, $offset, nqp::sub_i($elems,$offset) ), $slice ) ) ) } multi method splice(numarray:D: Int:D $offset, Int:D $size --> numarray:D) { nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice(self,$empty_n,$offset,$size) ); $slice } multi method splice(numarray:D: Int:D $offset, Int:D $size, numarray:D \values --> numarray:D) { nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice( self, nqp::if(nqp::eqaddr(self,values),nqp::clone(values),values), $offset, $size ) ); $slice } multi method splice(numarray:D: Int:D $offset, Int:D $size, Seq:D $seq --> numarray:D) { nqp::if( $seq.is-lazy, self.throw-iterator-cannot-be-lazy('.splice'), nqp::stmts( nqp::unless( nqp::istype( (my $slice := CLONE_SLICE(self,$offset,$size)), Failure ), nqp::splice(self,nqp::create(self).STORE($seq),$offset,$size) ), $slice ) ) } multi method splice(numarray:D: $offset=0, $size=Whatever, *@values --> numarray:D) { return self.fail-iterator-cannot-be-lazy('splice in') if @values.is-lazy; my int $elems = nqp::elems(self); # XXX execution error on next line if uint my int $o = nqp::istype($offset,Callable) ?? $offset($elems) !! nqp::istype($offset,Whatever) ?? $elems !! $offset.Int; my int $s = nqp::istype($size,Callable) ?? $size($elems - $o) !! !defined($size) || nqp::istype($size,Whatever) ?? $elems - ($o min $elems) !! $size.Int; unless nqp::istype( (my $splice := CLONE_SLICE(self,$o,$s)), Failure ) { my $splicees := nqp::create(self); nqp::push_n($splicees, @values.shift) while @values; nqp::splice(self,$splicees,$o,$s); } $splice } multi method min(numarray:D:) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my num $min = nqp::atpos_n(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::islt_n(nqp::atpos_n(self,$i),$min), ($min = nqp::atpos_n(self,$i)) ) ), $min ), Inf ) } multi method max(numarray:D:) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my num $max = nqp::atpos_n(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::isgt_n(nqp::atpos_n(self,$i),$max), ($max = nqp::atpos_n(self,$i)) ) ), $max ), -Inf ) } multi method minmax(numarray:D: --> Range:D) { nqp::if( (my uint $elems = nqp::elems(self)), nqp::stmts( (my uint $i), (my num $min = my num $max = nqp::atpos_n(self,0)), nqp::while( nqp::islt_i(++$i,$elems), nqp::if( nqp::islt_n(nqp::atpos_n(self,$i),$min), ($min = nqp::atpos_n(self,$i)), nqp::if( nqp::isgt_n(nqp::atpos_n(self,$i),$max), ($max = nqp::atpos_n(self,$i)) ) ) ), Range.new($min,$max) ), Range.Inf-Inf ) } method iterator(numarray:D: --> PredictiveIterator:D) { Rakudo::Iterator.native_n(self) } method Seq(numarray:D: --> Seq:D) { Seq.new(Rakudo::Iterator.native_n(self)) } method reverse(numarray:D: --> numarray:D) is nodal { nqp::stmts( (my uint $elems = nqp::elems(self)), (my int $last = nqp::sub_i($elems,1)), (my int $i = -1), (my $to := nqp::clone(self)), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_n($to,nqp::sub_i($last,$i), nqp::atpos_n(self,$i)) ), $to ) } method rotate(numarray:D: Int(Cool) $rotate = 1 --> numarray:D) is nodal { my int $elems = nqp::elems(self); my $to := nqp::clone(self); my int $i = -1; my int $j = nqp::mod_i( nqp::sub_i(nqp::sub_i($elems,1),$rotate), $elems ); $j = nqp::add_i($j,$elems) if nqp::islt_i($j,0); nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_n( $to, ($j = nqp::mod_i(nqp::add_i($j,1),$elems)), nqp::atpos_n(self,$i) ), ); $to } multi method sort(numarray:D: --> numarray:D) { Rakudo::Sorting.MERGESORT-num(nqp::clone(self)) } multi method ACCEPTS(numarray:D: numarray:D \o --> Bool:D) { nqp::hllbool( nqp::unless( nqp::eqaddr(self,my $other := nqp::decont(o)), nqp::if( nqp::iseq_i( (my uint $elems = nqp::elems(self)), nqp::elems($other) ), nqp::stmts( (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems) && nqp::iseq_n( nqp::atpos_n(self,$i), nqp::atpos_n($other,$i) ), nqp::null ), nqp::iseq_i($i,$elems) ) ) ) ) } proto method grab(|) {*} multi method grab(numarray:D: --> num) { nqp::elems(self) ?? self.GRAB_ONE !! Nil } multi method grab(numarray:D: Callable:D $calculate --> num) { self.grab($calculate(nqp::elems(self))) } multi method grab(numarray:D: Whatever --> Seq:D) { self.grab(Inf) } my class GrabN does Iterator { has $!array; has uint $!count; method !SET-SELF(\array,\count) { nqp::stmts( (my uint $elems = nqp::elems(array)), ($!array := array), nqp::if( count == Inf, ($!count = $elems), nqp::if( nqp::isgt_i(($!count = count.Int),$elems), ($!count = $elems) ) ), self ) } method new(\a,\c) { nqp::create(self)!SET-SELF(a,c) } method pull-one() { nqp::if( $!count && nqp::elems($!array), nqp::stmts( --$!count, $!array.GRAB_ONE ), IterationEnd ) } method is-deterministic(--> False) { } } multi method grab(numarray:D: \count --> Seq:D) { Seq.new( nqp::elems(self) ?? GrabN.new(self,count) !! Rakudo::Iterator.Empty ) } method GRAB_ONE(numarray:D: --> num) is implementation-detail { nqp::stmts( (my $value := nqp::atpos_n( self, (my uint $pos = nqp::floor_n(nqp::rand_n(nqp::elems(self)))) )), nqp::splice(self,$empty_n,$pos,1), $value ) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of numarray role ------------------------------------- multi method sum(numarray:D:) { nqp::if( (my int $elems = nqp::elems(self)), nqp::stmts( (my num $sum = nqp::atpos_n(self,0)), (my int $i), nqp::while( nqp::islt_i(++$i,$elems), $sum = nqp::add_n($sum,nqp::atpos_n(self,$i)) ), $sum ), 0e0 ) } multi method STORE(numarray:D: Range:D $range) { my num $val = $range.min; $val = $val + 1 if $range.excludes-min; my num $max = $range.max; $max = $max - 1 if $range.excludes-max; fail X::Cannot::Lazy.new(:action,:what(self.^name)) if $val == -Inf || $max == Inf; nqp::setelems(self, ($max - $val + 1).Int ); my int $i; while $val <= $max { nqp::bindpos_n(self, $i, $val); $val = $val + 1; $i = $i + 1; } self } } role shapedarray does Rakudo::Internals::ShapedArrayCommon { method BIND-POS(|) { X::Bind.new(target => 'a natively typed shaped array').throw } method DELETE-POS(|) { X::Delete.new(target => 'a natively typed shaped array').throw } method shape() { my $idims := nqp::dimensions(self); my $odims := nqp::create(IterationBuffer); nqp::while( nqp::elems($idims), nqp::push($odims,nqp::shift_i($idims)) ); $odims.List } multi method EXISTS-POS(::?CLASS:D: **@indices) { nqp::hllbool( nqp::stmts( (my int $numdims = nqp::numdimensions(self)), (my int $numind = @indices.elems), # reifies (my $indices := nqp::getattr(@indices,List,'$!reified')), nqp::if( nqp::isle_i($numind,$numdims), nqp::stmts( (my $dims := nqp::dimensions(self)), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$numind) && nqp::isge_i(nqp::atpos($indices,$i),0) && nqp::islt_i( nqp::atpos($indices,$i), nqp::atpos_i($dims,$i) ), nqp::null ), nqp::iseq_i($i,$numind) ) ) ) ) } proto method STORE(|) {*} multi method STORE(::?CLASS:D: Mu \item) { X::Assignment::ToShaped.new(shape => self.shape).throw } } #- start of generated part of shapedintarray role ----------------------------- #- Generated on 2022-02-16T12:09:03+01:00 by ./tools/build/makeNATIVE_SHAPED_ARRAY.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE role shapedintarray does shapedarray { multi method AT-POS(::?CLASS:D: **@indices --> int) is raw { nqp::if( nqp::iseq_i( (my int $numdims = nqp::numdimensions(self)), (my int $numind = @indices.elems), # reifies ), nqp::stmts( (my $indices := nqp::getattr(@indices,List,'$!reified')), (my $idxs := nqp::list_i), nqp::while( # native index list nqp::isge_i(--$numdims,0), nqp::push_i($idxs,nqp::shift($indices)) ), nqp::multidimref_i(self,$idxs) ), nqp::if( nqp::isgt_i($numind,$numdims), X::TooManyDimensions.new( operation => 'access', got-dimensions => $numind, needed-dimensions => $numdims ).throw, NYI("Partially dimensioned views of shaped arrays").throw ) ) } multi method ASSIGN-POS(::?CLASS:D: **@indices --> int) { nqp::stmts( (my int $value = @indices.pop), nqp::if( nqp::iseq_i( (my int $numdims = nqp::numdimensions(self)), (my int $numind = @indices.elems), # reifies ), nqp::stmts( (my $indices := nqp::getattr(@indices,List,'$!reified')), (my $idxs := nqp::list_i), nqp::while( # native index list nqp::isge_i(--$numdims,0), nqp::push_i($idxs,nqp::shift($indices)) ), nqp::bindposnd_i(self, $idxs, $value) ), nqp::if( nqp::isgt_i($numind,$numdims), X::TooManyDimensions, X::NotEnoughDimensions ).new( operation => 'assign to', got-dimensions => $numind, needed-dimensions => $numdims ).throw ) ) } my class NATCPY-int does Rakudo::Iterator::ShapeLeaf { has Mu $!from; method !INIT(Mu \to, Mu \from) { nqp::stmts( ($!from := from), self!SET-SELF(to) ) } method new(Mu \to, Mu \from) { nqp::create(self)!INIT(to,from) } method result(--> Nil) { nqp::bindposnd_i($!list,$!indices, nqp::multidimref_i($!from,$!indices)) } } sub NATCPY(Mu \to, Mu \from) is raw { NATCPY-int.new(to,from).sink-all; to } my class OBJCPY-int does Rakudo::Iterator::ShapeLeaf { has Mu $!from; method !INIT(Mu \to, Mu \from) { nqp::stmts( ($!from := nqp::getattr(from,List,'$!reified')), self!SET-SELF(to) ) } method new(Mu \to, Mu \from) { nqp::create(self)!INIT(to,from) } method result(--> Nil) { nqp::bindposnd_i($!list,$!indices, nqp::atposnd($!from,$!indices)) } } sub OBJCPY(Mu \to, Mu \from) is raw { OBJCPY-int.new(to,from).sink-all; to } my class ITERCPY-int does Rakudo::Iterator::ShapeBranch { has $!iterators; method !INIT(\to,\from) { nqp::stmts( self!SET-SELF(to), ($!iterators := nqp::setelems( nqp::list(from.iterator), nqp::add_i($!maxdim,1) )), self ) } method new(\to,\from) { nqp::create(self)!INIT(to,from) } method done(--> Nil) { nqp::unless( # verify lowest nqp::atpos($!iterators,0).is-lazy # finite iterator || nqp::eqaddr( # and something there nqp::atpos($!iterators,0).pull-one,IterationEnd), nqp::atposnd_i($!list,$!indices) # boom! ) } method process(--> Nil) { nqp::stmts( (my int $i = $!level), nqp::while( nqp::isle_i(++$i,$!maxdim), nqp::if( nqp::eqaddr((my \item := # exhausted ? nqp::atpos($!iterators,nqp::sub_i($i,1)).pull-one), IterationEnd ), nqp::bindpos($!iterators,$i, # add an empty one Rakudo::Iterator.Empty), nqp::if( # is it an iterator? nqp::istype(item,Iterable) && nqp::isconcrete(item), nqp::bindpos($!iterators,$i,item.iterator), X::Assignment::ToShaped.new(shape => $!dims).throw ) ) ), (my \iter := nqp::atpos($!iterators,$!maxdim)), nqp::until( # loop over highest dim nqp::eqaddr((my \pulled := iter.pull-one),IterationEnd) || nqp::isgt_i(nqp::atpos_i($!indices,$!maxdim),$!maxind), nqp::stmts( nqp::bindposnd_i($!list,$!indices,pulled), nqp::bindpos_i($!indices,$!maxdim, # increment index nqp::add_i(nqp::atpos_i($!indices,$!maxdim),1)) ) ), nqp::unless( nqp::eqaddr(pulled,IterationEnd) # if not exhausted || nqp::isle_i( # and index too high nqp::atpos_i($!indices,$!maxdim),$!maxind) || iter.is-lazy, # and not lazy nqp::atposnd_i($!list,$!indices) # boom! ) ) } } sub ITERCPY(Mu \to, Mu \from) is raw { ITERCPY-int.new(to,from).sink-all; to } multi method STORE(::?CLASS:D: ::?CLASS:D \from) { EQV_DIMENSIONS(self,from) ?? NATCPY(self,from) !! X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw } multi method STORE(::?CLASS:D: array:D \from) { nqp::if( nqp::istype(from.of,Int), nqp::if( EQV_DIMENSIONS(self,from), NATCPY(self,from), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ), X::TypeCheck::Assignment.new( symbol => self.^name ~ '[' ~ self.shape.join(';') ~ ']', expected => Int, got => from.of ).throw ) } multi method STORE(::?CLASS:D: Iterable:D \from) { nqp::if( nqp::can(from,'shape'), nqp::if( from.shape eqv self.shape, OBJCPY(self,from), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ), ITERCPY(self,from) ) } my class Iterate-int does Rakudo::Iterator::ShapeLeaf { method result() is raw { nqp::multidimref_i($!list,nqp::clone($!indices)) } } method iterator(::?CLASS:D: --> Iterate-int:D) { Iterate-int.new(self) } my class KV-int does Rakudo::Iterator::ShapeLeaf { has int $!on-key; method result() is raw { nqp::if( ($!on-key = nqp::not_i($!on-key)), nqp::stmts( (my \result := self.indices), (nqp::bindpos_i($!indices,$!maxdim, # back 1 for next nqp::sub_i(nqp::atpos_i($!indices,$!maxdim),1))), result ), nqp::multidimref_i($!list,nqp::clone($!indices)) ) } # needs its own push-all since it fiddles with $!indices method push-all(\target --> IterationEnd) { nqp::until( nqp::eqaddr((my \pulled := self.pull-one),IterationEnd), target.push(pulled) ) } } multi method kv(::?CLASS:D: --> Seq:D) { Seq.new(KV-int.new(self)) } my class Pairs-int does Rakudo::Iterator::ShapeLeaf { method result() { Pair.new( self.indices, nqp::multidimref_i($!list,nqp::clone($!indices)) ) } } multi method pairs(::?CLASS:D: --> Seq:D) { Seq.new(Pairs-int.new(self)) } my class Antipairs-int does Rakudo::Iterator::ShapeLeaf { method result() { Pair.new(nqp::atposnd_i($!list,$!indices),self.indices) } } multi method antipairs(::?CLASS:D: --> Seq:D) { Seq.new(Antipairs-int.new(self)) } } # end of shapedintarray role role shaped1intarray does shapedintarray { multi method AT-POS(::?CLASS:D: int \one --> int) is raw { nqp::atposref_i(self,one) } multi method AT-POS(::?CLASS:D: Int:D $one --> int) is raw { nqp::atposref_i(self,$one) } multi method ASSIGN-POS(::?CLASS:D: int \one, int \value --> int) { nqp::bindpos_i(self,one,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, int \value --> int) { nqp::bindpos_i(self,$one,value) } multi method ASSIGN-POS(::?CLASS:D: int \one, Int:D \value --> int) { nqp::bindpos_i(self,one,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Int:D \value --> int) { nqp::bindpos_i(self,$one,value) } multi method EXISTS-POS(::?CLASS:D: int \one --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::islt_i(one,nqp::elems(self)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::islt_i($one,nqp::elems(self)) ) } multi method STORE(::?CLASS:D: ::?CLASS:D \from) { nqp::if( nqp::iseq_i((my int $elems = nqp::elems(self)),nqp::elems(from)), nqp::stmts( (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_i(self,$i,nqp::atpos_i(from,$i)) ), self ), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ) } multi method STORE(::?CLASS:D: Iterable:D \in) { my \iter := Rakudo::Iterator.TailWith(in.iterator,0); my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems(self)), nqp::bindpos_i(self,$i,iter.pull-one) ); # too many values? then throw by just accessing out of range nqp::atpos_i(list,$i) unless iter.exhausted; self } multi method STORE(::?CLASS:D: Int:D \item) { nqp::bindpos_i(self,0,item); self } my class Iterate-int does PredictiveIterator { has Mu $!list; has int $!pos; method !SET-SELF(Mu \list) { nqp::stmts( ($!list := list), ($!pos = -1), self ) } method new(Mu \list) { nqp::create(self)!SET-SELF(list) } method pull-one() is raw { nqp::islt_i(++$!pos,nqp::elems($!list)) ?? nqp::atposref_i($!list,$!pos) !! IterationEnd } method skip-one() { nqp::islt_i(++$!pos,nqp::elems($!list)) } method push-all(\target --> IterationEnd) { nqp::stmts( (my int $elems = nqp::elems($!list)), (my int $pos = $!pos), nqp::while( nqp::islt_i(++$pos,$elems), target.push(nqp::atpos_i($!list,$pos)) ), ($!pos = $pos) ) } method count-only(--> Int:D) { nqp::p6box_i( nqp::elems($!list) - $!pos - nqp::islt_i($!pos,nqp::elems($!list)) ) } method sink-all(--> IterationEnd) { $!pos = nqp::elems($!list) } } method iterator(::?CLASS:D: --> Iterate-int:D) { Iterate-int.new(self) } multi method kv(::?CLASS:D: --> Seq:D) { my int $i = -1; my int $elems = nqp::add_i(nqp::elems(self),nqp::elems(self)); Seq.new(Rakudo::Iterator.Callable({ nqp::if( nqp::islt_i(++$i,$elems), nqp::if( nqp::bitand_i($i,1), nqp::atposref_i(self,nqp::bitshiftr_i($i,1)), nqp::bitshiftr_i($i,1) ), IterationEnd ) })) } multi method pairs(::?CLASS:D: --> Seq:D) { my int $i = -1; my int $elems = nqp::elems(self); Seq.new(Rakudo::Iterator.Callable({ nqp::islt_i(++$i,$elems) ?? Pair.new($i,nqp::atposref_i(self,$i)) !! IterationEnd })) } multi method antipairs(::?CLASS:D: --> Seq:D) { Seq.new(Rakudo::Iterator.AntiPair(self.iterator)) } method reverse(::?CLASS:D: --> ::?CLASS:D) is nodal { nqp::stmts( (my int $elems = nqp::elems(self)), (my int $last = nqp::sub_i($elems,1)), (my int $i = -1), (my $to := nqp::clone(self)), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_i($to,nqp::sub_i($last,$i), nqp::atpos_i(self,$i)) ), $to ) } method rotate(::?CLASS:D: Int(Cool) $rotate = 1 --> ::?CLASS:D) is nodal { nqp::stmts( (my int $elems = nqp::elems(self)), (my $to := nqp::clone(self)), (my int $i = -1), (my int $j = nqp::mod_i(nqp::sub_i(nqp::sub_i($elems,1),$rotate),$elems)), nqp::if(nqp::islt_i($j,0),($j = nqp::add_i($j,$elems))), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_i( $to, ($j = nqp::mod_i(nqp::add_i($j,1),$elems)), nqp::atpos_i(self,$i) ), ), $to ) } } # end of shaped1intarray role role shaped2intarray does shapedintarray { multi method AT-POS(::?CLASS:D: int \one, int \two --> int) is raw { nqp::multidimref_i(self,nqp::list_i(one, two)) } multi method AT-POS(::?CLASS:D: Int:D $one, Int:D $two --> int) is raw { nqp::multidimref_i(self,nqp::list_i($one, $two)) } multi method ASSIGN-POS(::?CLASS:D: int \one, int \two, Int:D \value --> int) { nqp::bindpos2d_i(self,one,two,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D \value --> int) { nqp::bindpos2d_i(self,$one,$two,value) } multi method EXISTS-POS(::?CLASS:D: int \one, int \two --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::isge_i(two,0) && nqp::islt_i(one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i(two,nqp::atpos_i(nqp::dimensions(self),1)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one, Int:D $two --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::isge_i($two,0) && nqp::islt_i($one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i($two,nqp::atpos_i(nqp::dimensions(self),1)) ) } } # end of shaped2intarray role role shaped3intarray does shapedintarray { multi method AT-POS(::?CLASS:D: int \one, int \two, int \three --> int) is raw { nqp::multidimref_i(self,nqp::list_i(one, two, three)) } multi method AT-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three --> int) is raw { nqp::multidimref_i(self,nqp::list_i($one, $two, $three)) } multi method ASSIGN-POS(::?CLASS:D: int \one, int \two, int \three, Int:D \value --> int) { nqp::bindpos3d_i(self,one,two,three,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three, Int:D \value --> int) { nqp::bindpos3d_i(self,$one,$two,$three,value) } multi method EXISTS-POS(::?CLASS:D: int \one, int \two, int \three --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::isge_i(two,0) && nqp::isge_i(three,0) && nqp::islt_i(one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i(two,nqp::atpos_i(nqp::dimensions(self),1)) && nqp::islt_i(three,nqp::atpos_i(nqp::dimensions(self),2)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::isge_i($two,0) && nqp::isge_i($three,0) && nqp::islt_i($one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i($two,nqp::atpos_i(nqp::dimensions(self),1)) && nqp::islt_i($three,nqp::atpos_i(nqp::dimensions(self),2)) ) } } # end of shaped3intarray role #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of shapedintarray role ------------------------------- #- start of generated part of shapeduintarray role ----------------------------- #- Generated on 2022-02-16T12:09:03+01:00 by ./tools/build/makeNATIVE_SHAPED_ARRAY.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE role shapeduintarray does shapedarray { multi method AT-POS(::?CLASS:D: **@indices --> uint) is raw { nqp::if( nqp::iseq_i( (my int $numdims = nqp::numdimensions(self)), (my int $numind = @indices.elems), # reifies ), nqp::stmts( (my $indices := nqp::getattr(@indices,List,'$!reified')), (my $idxs := nqp::list_i), nqp::while( # native index list nqp::isge_i(--$numdims,0), nqp::push_i($idxs,nqp::shift($indices)) ), nqp::multidimref_u(self,$idxs) ), nqp::if( nqp::isgt_i($numind,$numdims), X::TooManyDimensions.new( operation => 'access', got-dimensions => $numind, needed-dimensions => $numdims ).throw, NYI("Partially dimensioned views of shaped arrays").throw ) ) } multi method ASSIGN-POS(::?CLASS:D: **@indices --> uint) { nqp::stmts( (my uint $value = @indices.pop), nqp::if( nqp::iseq_i( (my int $numdims = nqp::numdimensions(self)), (my int $numind = @indices.elems), # reifies ), nqp::stmts( (my $indices := nqp::getattr(@indices,List,'$!reified')), (my $idxs := nqp::list_i), nqp::while( # native index list nqp::isge_i(--$numdims,0), nqp::push_i($idxs,nqp::shift($indices)) ), nqp::bindposnd_u(self, $idxs, $value) ), nqp::if( nqp::isgt_i($numind,$numdims), X::TooManyDimensions, X::NotEnoughDimensions ).new( operation => 'assign to', got-dimensions => $numind, needed-dimensions => $numdims ).throw ) ) } my class NATCPY-uint does Rakudo::Iterator::ShapeLeaf { has Mu $!from; method !INIT(Mu \to, Mu \from) { nqp::stmts( ($!from := from), self!SET-SELF(to) ) } method new(Mu \to, Mu \from) { nqp::create(self)!INIT(to,from) } method result(--> Nil) { nqp::bindposnd_u($!list,$!indices, nqp::multidimref_u($!from,$!indices)) } } sub NATCPY(Mu \to, Mu \from) is raw { NATCPY-uint.new(to,from).sink-all; to } my class OBJCPY-uint does Rakudo::Iterator::ShapeLeaf { has Mu $!from; method !INIT(Mu \to, Mu \from) { nqp::stmts( ($!from := nqp::getattr(from,List,'$!reified')), self!SET-SELF(to) ) } method new(Mu \to, Mu \from) { nqp::create(self)!INIT(to,from) } method result(--> Nil) { nqp::bindposnd_u($!list,$!indices, nqp::atposnd($!from,$!indices)) } } sub OBJCPY(Mu \to, Mu \from) is raw { OBJCPY-uint.new(to,from).sink-all; to } my class ITERCPY-uint does Rakudo::Iterator::ShapeBranch { has $!iterators; method !INIT(\to,\from) { nqp::stmts( self!SET-SELF(to), ($!iterators := nqp::setelems( nqp::list(from.iterator), nqp::add_i($!maxdim,1) )), self ) } method new(\to,\from) { nqp::create(self)!INIT(to,from) } method done(--> Nil) { nqp::unless( # verify lowest nqp::atpos($!iterators,0).is-lazy # finite iterator || nqp::eqaddr( # and something there nqp::atpos($!iterators,0).pull-one,IterationEnd), nqp::atposnd_u($!list,$!indices) # boom! ) } method process(--> Nil) { nqp::stmts( (my int $i = $!level), nqp::while( nqp::isle_i(++$i,$!maxdim), nqp::if( nqp::eqaddr((my \item := # exhausted ? nqp::atpos($!iterators,nqp::sub_i($i,1)).pull-one), IterationEnd ), nqp::bindpos($!iterators,$i, # add an empty one Rakudo::Iterator.Empty), nqp::if( # is it an iterator? nqp::istype(item,Iterable) && nqp::isconcrete(item), nqp::bindpos($!iterators,$i,item.iterator), X::Assignment::ToShaped.new(shape => $!dims).throw ) ) ), (my \iter := nqp::atpos($!iterators,$!maxdim)), nqp::until( # loop over highest dim nqp::eqaddr((my \pulled := iter.pull-one),IterationEnd) || nqp::isgt_i(nqp::atpos_i($!indices,$!maxdim),$!maxind), nqp::stmts( nqp::bindposnd_u($!list,$!indices,pulled), nqp::bindpos_i($!indices,$!maxdim, # increment index nqp::add_i(nqp::atpos_i($!indices,$!maxdim),1)) ) ), nqp::unless( nqp::eqaddr(pulled,IterationEnd) # if not exhausted || nqp::isle_i( # and index too high nqp::atpos_i($!indices,$!maxdim),$!maxind) || iter.is-lazy, # and not lazy nqp::atposnd_u($!list,$!indices) # boom! ) ) } } sub ITERCPY(Mu \to, Mu \from) is raw { ITERCPY-uint.new(to,from).sink-all; to } multi method STORE(::?CLASS:D: ::?CLASS:D \from) { EQV_DIMENSIONS(self,from) ?? NATCPY(self,from) !! X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw } multi method STORE(::?CLASS:D: array:D \from) { nqp::if( nqp::istype(from.of,UInt), nqp::if( EQV_DIMENSIONS(self,from), NATCPY(self,from), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ), X::TypeCheck::Assignment.new( symbol => self.^name ~ '[' ~ self.shape.join(';') ~ ']', expected => UInt, got => from.of ).throw ) } multi method STORE(::?CLASS:D: Iterable:D \from) { nqp::if( nqp::can(from,'shape'), nqp::if( from.shape eqv self.shape, OBJCPY(self,from), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ), ITERCPY(self,from) ) } my class Iterate-uint does Rakudo::Iterator::ShapeLeaf { method result() is raw { nqp::multidimref_u($!list,nqp::clone($!indices)) } } method iterator(::?CLASS:D: --> Iterate-uint:D) { Iterate-uint.new(self) } my class KV-uint does Rakudo::Iterator::ShapeLeaf { has int $!on-key; method result() is raw { nqp::if( ($!on-key = nqp::not_i($!on-key)), nqp::stmts( (my \result := self.indices), (nqp::bindpos_i($!indices,$!maxdim, # back 1 for next nqp::sub_i(nqp::atpos_i($!indices,$!maxdim),1))), result ), nqp::multidimref_u($!list,nqp::clone($!indices)) ) } # needs its own push-all since it fiddles with $!indices method push-all(\target --> IterationEnd) { nqp::until( nqp::eqaddr((my \pulled := self.pull-one),IterationEnd), target.push(pulled) ) } } multi method kv(::?CLASS:D: --> Seq:D) { Seq.new(KV-uint.new(self)) } my class Pairs-uint does Rakudo::Iterator::ShapeLeaf { method result() { Pair.new( self.indices, nqp::multidimref_u($!list,nqp::clone($!indices)) ) } } multi method pairs(::?CLASS:D: --> Seq:D) { Seq.new(Pairs-uint.new(self)) } my class Antipairs-uint does Rakudo::Iterator::ShapeLeaf { method result() { Pair.new(nqp::atposnd_u($!list,$!indices),self.indices) } } multi method antipairs(::?CLASS:D: --> Seq:D) { Seq.new(Antipairs-uint.new(self)) } } # end of shapeduintarray role role shaped1uintarray does shapeduintarray { multi method AT-POS(::?CLASS:D: int \one --> uint) is raw { nqp::atposref_u(self,one) } multi method AT-POS(::?CLASS:D: Int:D $one --> uint) is raw { nqp::atposref_u(self,$one) } multi method ASSIGN-POS(::?CLASS:D: int \one, uint \value --> uint) { nqp::bindpos_u(self,one,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, uint \value --> uint) { nqp::bindpos_u(self,$one,value) } multi method ASSIGN-POS(::?CLASS:D: int \one, UInt:D \value --> uint) { nqp::bindpos_u(self,one,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, UInt:D \value --> uint) { nqp::bindpos_u(self,$one,value) } multi method EXISTS-POS(::?CLASS:D: int \one --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::islt_i(one,nqp::elems(self)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::islt_i($one,nqp::elems(self)) ) } multi method STORE(::?CLASS:D: ::?CLASS:D \from) { nqp::if( nqp::iseq_i((my int $elems = nqp::elems(self)),nqp::elems(from)), nqp::stmts( (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_u(self,$i,nqp::atpos_u(from,$i)) ), self ), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ) } multi method STORE(::?CLASS:D: Iterable:D \in) { my \iter := Rakudo::Iterator.TailWith(in.iterator,0); my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems(self)), nqp::bindpos_u(self,$i,iter.pull-one) ); # too many values? then throw by just accessing out of range nqp::atpos_u(list,$i) unless iter.exhausted; self } multi method STORE(::?CLASS:D: UInt:D \item) { nqp::bindpos_u(self,0,item); self } my class Iterate-uint does PredictiveIterator { has Mu $!list; has int $!pos; method !SET-SELF(Mu \list) { nqp::stmts( ($!list := list), ($!pos = -1), self ) } method new(Mu \list) { nqp::create(self)!SET-SELF(list) } method pull-one() is raw { nqp::islt_i(++$!pos,nqp::elems($!list)) ?? nqp::atposref_u($!list,$!pos) !! IterationEnd } method skip-one() { nqp::islt_i(++$!pos,nqp::elems($!list)) } method push-all(\target --> IterationEnd) { nqp::stmts( (my int $elems = nqp::elems($!list)), (my int $pos = $!pos), nqp::while( nqp::islt_i(++$pos,$elems), target.push(nqp::atpos_u($!list,$pos)) ), ($!pos = $pos) ) } method count-only(--> Int:D) { nqp::p6box_i( nqp::elems($!list) - $!pos - nqp::islt_i($!pos,nqp::elems($!list)) ) } method sink-all(--> IterationEnd) { $!pos = nqp::elems($!list) } } method iterator(::?CLASS:D: --> Iterate-uint:D) { Iterate-uint.new(self) } multi method kv(::?CLASS:D: --> Seq:D) { my int $i = -1; my int $elems = nqp::add_i(nqp::elems(self),nqp::elems(self)); Seq.new(Rakudo::Iterator.Callable({ nqp::if( nqp::islt_i(++$i,$elems), nqp::if( nqp::bitand_i($i,1), nqp::atposref_u(self,nqp::bitshiftr_i($i,1)), nqp::bitshiftr_i($i,1) ), IterationEnd ) })) } multi method pairs(::?CLASS:D: --> Seq:D) { my int $i = -1; my int $elems = nqp::elems(self); Seq.new(Rakudo::Iterator.Callable({ nqp::islt_i(++$i,$elems) ?? Pair.new($i,nqp::atposref_u(self,$i)) !! IterationEnd })) } multi method antipairs(::?CLASS:D: --> Seq:D) { Seq.new(Rakudo::Iterator.AntiPair(self.iterator)) } method reverse(::?CLASS:D: --> ::?CLASS:D) is nodal { nqp::stmts( (my int $elems = nqp::elems(self)), (my int $last = nqp::sub_i($elems,1)), (my int $i = -1), (my $to := nqp::clone(self)), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_u($to,nqp::sub_i($last,$i), nqp::atpos_u(self,$i)) ), $to ) } method rotate(::?CLASS:D: Int(Cool) $rotate = 1 --> ::?CLASS:D) is nodal { nqp::stmts( (my int $elems = nqp::elems(self)), (my $to := nqp::clone(self)), (my int $i = -1), (my int $j = nqp::mod_i(nqp::sub_i(nqp::sub_i($elems,1),$rotate),$elems)), nqp::if(nqp::islt_i($j,0),($j = nqp::add_i($j,$elems))), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_u( $to, ($j = nqp::mod_i(nqp::add_i($j,1),$elems)), nqp::atpos_u(self,$i) ), ), $to ) } } # end of shaped1uintarray role role shaped2uintarray does shapeduintarray { multi method AT-POS(::?CLASS:D: int \one, int \two --> uint) is raw { nqp::multidimref_u(self,nqp::list_i(one, two)) } multi method AT-POS(::?CLASS:D: Int:D $one, Int:D $two --> uint) is raw { nqp::multidimref_u(self,nqp::list_i($one, $two)) } multi method ASSIGN-POS(::?CLASS:D: int \one, int \two, UInt:D \value --> uint) { nqp::bindpos2d_u(self,one,two,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Int:D $two, UInt:D \value --> uint) { nqp::bindpos2d_u(self,$one,$two,value) } multi method EXISTS-POS(::?CLASS:D: int \one, int \two --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::isge_i(two,0) && nqp::islt_i(one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i(two,nqp::atpos_i(nqp::dimensions(self),1)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one, Int:D $two --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::isge_i($two,0) && nqp::islt_i($one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i($two,nqp::atpos_i(nqp::dimensions(self),1)) ) } } # end of shaped2uintarray role role shaped3uintarray does shapeduintarray { multi method AT-POS(::?CLASS:D: int \one, int \two, int \three --> uint) is raw { nqp::multidimref_u(self,nqp::list_i(one, two, three)) } multi method AT-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three --> uint) is raw { nqp::multidimref_u(self,nqp::list_i($one, $two, $three)) } multi method ASSIGN-POS(::?CLASS:D: int \one, int \two, int \three, UInt:D \value --> uint) { nqp::bindpos3d_u(self,one,two,three,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three, UInt:D \value --> uint) { nqp::bindpos3d_u(self,$one,$two,$three,value) } multi method EXISTS-POS(::?CLASS:D: int \one, int \two, int \three --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::isge_i(two,0) && nqp::isge_i(three,0) && nqp::islt_i(one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i(two,nqp::atpos_i(nqp::dimensions(self),1)) && nqp::islt_i(three,nqp::atpos_i(nqp::dimensions(self),2)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::isge_i($two,0) && nqp::isge_i($three,0) && nqp::islt_i($one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i($two,nqp::atpos_i(nqp::dimensions(self),1)) && nqp::islt_i($three,nqp::atpos_i(nqp::dimensions(self),2)) ) } } # end of shaped3uintarray role #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of shapeduintarray role ------------------------------- #- start of generated part of shapednumarray role ----------------------------- #- Generated on 2022-02-16T12:09:03+01:00 by ./tools/build/makeNATIVE_SHAPED_ARRAY.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE role shapednumarray does shapedarray { multi method AT-POS(::?CLASS:D: **@indices --> num) is raw { nqp::if( nqp::iseq_i( (my int $numdims = nqp::numdimensions(self)), (my int $numind = @indices.elems), # reifies ), nqp::stmts( (my $indices := nqp::getattr(@indices,List,'$!reified')), (my $idxs := nqp::list_i), nqp::while( # native index list nqp::isge_i(--$numdims,0), nqp::push_i($idxs,nqp::shift($indices)) ), nqp::multidimref_n(self,$idxs) ), nqp::if( nqp::isgt_i($numind,$numdims), X::TooManyDimensions.new( operation => 'access', got-dimensions => $numind, needed-dimensions => $numdims ).throw, NYI("Partially dimensioned views of shaped arrays").throw ) ) } multi method ASSIGN-POS(::?CLASS:D: **@indices --> num) { nqp::stmts( (my num $value = @indices.pop), nqp::if( nqp::iseq_i( (my int $numdims = nqp::numdimensions(self)), (my int $numind = @indices.elems), # reifies ), nqp::stmts( (my $indices := nqp::getattr(@indices,List,'$!reified')), (my $idxs := nqp::list_i), nqp::while( # native index list nqp::isge_i(--$numdims,0), nqp::push_i($idxs,nqp::shift($indices)) ), nqp::bindposnd_n(self, $idxs, $value) ), nqp::if( nqp::isgt_i($numind,$numdims), X::TooManyDimensions, X::NotEnoughDimensions ).new( operation => 'assign to', got-dimensions => $numind, needed-dimensions => $numdims ).throw ) ) } my class NATCPY-num does Rakudo::Iterator::ShapeLeaf { has Mu $!from; method !INIT(Mu \to, Mu \from) { nqp::stmts( ($!from := from), self!SET-SELF(to) ) } method new(Mu \to, Mu \from) { nqp::create(self)!INIT(to,from) } method result(--> Nil) { nqp::bindposnd_n($!list,$!indices, nqp::multidimref_n($!from,$!indices)) } } sub NATCPY(Mu \to, Mu \from) is raw { NATCPY-num.new(to,from).sink-all; to } my class OBJCPY-num does Rakudo::Iterator::ShapeLeaf { has Mu $!from; method !INIT(Mu \to, Mu \from) { nqp::stmts( ($!from := nqp::getattr(from,List,'$!reified')), self!SET-SELF(to) ) } method new(Mu \to, Mu \from) { nqp::create(self)!INIT(to,from) } method result(--> Nil) { nqp::bindposnd_n($!list,$!indices, nqp::atposnd($!from,$!indices)) } } sub OBJCPY(Mu \to, Mu \from) is raw { OBJCPY-num.new(to,from).sink-all; to } my class ITERCPY-num does Rakudo::Iterator::ShapeBranch { has $!iterators; method !INIT(\to,\from) { nqp::stmts( self!SET-SELF(to), ($!iterators := nqp::setelems( nqp::list(from.iterator), nqp::add_i($!maxdim,1) )), self ) } method new(\to,\from) { nqp::create(self)!INIT(to,from) } method done(--> Nil) { nqp::unless( # verify lowest nqp::atpos($!iterators,0).is-lazy # finite iterator || nqp::eqaddr( # and something there nqp::atpos($!iterators,0).pull-one,IterationEnd), nqp::atposnd_n($!list,$!indices) # boom! ) } method process(--> Nil) { nqp::stmts( (my int $i = $!level), nqp::while( nqp::isle_i(++$i,$!maxdim), nqp::if( nqp::eqaddr((my \item := # exhausted ? nqp::atpos($!iterators,nqp::sub_i($i,1)).pull-one), IterationEnd ), nqp::bindpos($!iterators,$i, # add an empty one Rakudo::Iterator.Empty), nqp::if( # is it an iterator? nqp::istype(item,Iterable) && nqp::isconcrete(item), nqp::bindpos($!iterators,$i,item.iterator), X::Assignment::ToShaped.new(shape => $!dims).throw ) ) ), (my \iter := nqp::atpos($!iterators,$!maxdim)), nqp::until( # loop over highest dim nqp::eqaddr((my \pulled := iter.pull-one),IterationEnd) || nqp::isgt_i(nqp::atpos_i($!indices,$!maxdim),$!maxind), nqp::stmts( nqp::bindposnd_n($!list,$!indices,pulled), nqp::bindpos_i($!indices,$!maxdim, # increment index nqp::add_i(nqp::atpos_i($!indices,$!maxdim),1)) ) ), nqp::unless( nqp::eqaddr(pulled,IterationEnd) # if not exhausted || nqp::isle_i( # and index too high nqp::atpos_i($!indices,$!maxdim),$!maxind) || iter.is-lazy, # and not lazy nqp::atposnd_n($!list,$!indices) # boom! ) ) } } sub ITERCPY(Mu \to, Mu \from) is raw { ITERCPY-num.new(to,from).sink-all; to } multi method STORE(::?CLASS:D: ::?CLASS:D \from) { EQV_DIMENSIONS(self,from) ?? NATCPY(self,from) !! X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw } multi method STORE(::?CLASS:D: array:D \from) { nqp::if( nqp::istype(from.of,Num), nqp::if( EQV_DIMENSIONS(self,from), NATCPY(self,from), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ), X::TypeCheck::Assignment.new( symbol => self.^name ~ '[' ~ self.shape.join(';') ~ ']', expected => Num, got => from.of ).throw ) } multi method STORE(::?CLASS:D: Iterable:D \from) { nqp::if( nqp::can(from,'shape'), nqp::if( from.shape eqv self.shape, OBJCPY(self,from), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ), ITERCPY(self,from) ) } my class Iterate-num does Rakudo::Iterator::ShapeLeaf { method result() is raw { nqp::multidimref_n($!list,nqp::clone($!indices)) } } method iterator(::?CLASS:D: --> Iterate-num:D) { Iterate-num.new(self) } my class KV-num does Rakudo::Iterator::ShapeLeaf { has int $!on-key; method result() is raw { nqp::if( ($!on-key = nqp::not_i($!on-key)), nqp::stmts( (my \result := self.indices), (nqp::bindpos_i($!indices,$!maxdim, # back 1 for next nqp::sub_i(nqp::atpos_i($!indices,$!maxdim),1))), result ), nqp::multidimref_n($!list,nqp::clone($!indices)) ) } # needs its own push-all since it fiddles with $!indices method push-all(\target --> IterationEnd) { nqp::until( nqp::eqaddr((my \pulled := self.pull-one),IterationEnd), target.push(pulled) ) } } multi method kv(::?CLASS:D: --> Seq:D) { Seq.new(KV-num.new(self)) } my class Pairs-num does Rakudo::Iterator::ShapeLeaf { method result() { Pair.new( self.indices, nqp::multidimref_n($!list,nqp::clone($!indices)) ) } } multi method pairs(::?CLASS:D: --> Seq:D) { Seq.new(Pairs-num.new(self)) } my class Antipairs-num does Rakudo::Iterator::ShapeLeaf { method result() { Pair.new(nqp::atposnd_n($!list,$!indices),self.indices) } } multi method antipairs(::?CLASS:D: --> Seq:D) { Seq.new(Antipairs-num.new(self)) } } # end of shapednumarray role role shaped1numarray does shapednumarray { multi method AT-POS(::?CLASS:D: int \one --> num) is raw { nqp::atposref_n(self,one) } multi method AT-POS(::?CLASS:D: Int:D $one --> num) is raw { nqp::atposref_n(self,$one) } multi method ASSIGN-POS(::?CLASS:D: int \one, num \value --> num) { nqp::bindpos_n(self,one,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, num \value --> num) { nqp::bindpos_n(self,$one,value) } multi method ASSIGN-POS(::?CLASS:D: int \one, Num:D \value --> num) { nqp::bindpos_n(self,one,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Num:D \value --> num) { nqp::bindpos_n(self,$one,value) } multi method EXISTS-POS(::?CLASS:D: int \one --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::islt_i(one,nqp::elems(self)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::islt_i($one,nqp::elems(self)) ) } multi method STORE(::?CLASS:D: ::?CLASS:D \from) { nqp::if( nqp::iseq_i((my int $elems = nqp::elems(self)),nqp::elems(from)), nqp::stmts( (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_n(self,$i,nqp::atpos_n(from,$i)) ), self ), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ) } multi method STORE(::?CLASS:D: Iterable:D \in) { my \iter := Rakudo::Iterator.TailWith(in.iterator,0e0); my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems(self)), nqp::bindpos_n(self,$i,iter.pull-one) ); # too many values? then throw by just accessing out of range nqp::atpos_n(list,$i) unless iter.exhausted; self } multi method STORE(::?CLASS:D: Num:D \item) { nqp::bindpos_n(self,0,item); self } my class Iterate-num does PredictiveIterator { has Mu $!list; has int $!pos; method !SET-SELF(Mu \list) { nqp::stmts( ($!list := list), ($!pos = -1), self ) } method new(Mu \list) { nqp::create(self)!SET-SELF(list) } method pull-one() is raw { nqp::islt_i(++$!pos,nqp::elems($!list)) ?? nqp::atposref_n($!list,$!pos) !! IterationEnd } method skip-one() { nqp::islt_i(++$!pos,nqp::elems($!list)) } method push-all(\target --> IterationEnd) { nqp::stmts( (my int $elems = nqp::elems($!list)), (my int $pos = $!pos), nqp::while( nqp::islt_i(++$pos,$elems), target.push(nqp::atpos_n($!list,$pos)) ), ($!pos = $pos) ) } method count-only(--> Int:D) { nqp::p6box_i( nqp::elems($!list) - $!pos - nqp::islt_i($!pos,nqp::elems($!list)) ) } method sink-all(--> IterationEnd) { $!pos = nqp::elems($!list) } } method iterator(::?CLASS:D: --> Iterate-num:D) { Iterate-num.new(self) } multi method kv(::?CLASS:D: --> Seq:D) { my int $i = -1; my int $elems = nqp::add_i(nqp::elems(self),nqp::elems(self)); Seq.new(Rakudo::Iterator.Callable({ nqp::if( nqp::islt_i(++$i,$elems), nqp::if( nqp::bitand_i($i,1), nqp::atposref_n(self,nqp::bitshiftr_i($i,1)), nqp::bitshiftr_i($i,1) ), IterationEnd ) })) } multi method pairs(::?CLASS:D: --> Seq:D) { my int $i = -1; my int $elems = nqp::elems(self); Seq.new(Rakudo::Iterator.Callable({ nqp::islt_i(++$i,$elems) ?? Pair.new($i,nqp::atposref_n(self,$i)) !! IterationEnd })) } multi method antipairs(::?CLASS:D: --> Seq:D) { Seq.new(Rakudo::Iterator.AntiPair(self.iterator)) } method reverse(::?CLASS:D: --> ::?CLASS:D) is nodal { nqp::stmts( (my int $elems = nqp::elems(self)), (my int $last = nqp::sub_i($elems,1)), (my int $i = -1), (my $to := nqp::clone(self)), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_n($to,nqp::sub_i($last,$i), nqp::atpos_n(self,$i)) ), $to ) } method rotate(::?CLASS:D: Int(Cool) $rotate = 1 --> ::?CLASS:D) is nodal { nqp::stmts( (my int $elems = nqp::elems(self)), (my $to := nqp::clone(self)), (my int $i = -1), (my int $j = nqp::mod_i(nqp::sub_i(nqp::sub_i($elems,1),$rotate),$elems)), nqp::if(nqp::islt_i($j,0),($j = nqp::add_i($j,$elems))), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_n( $to, ($j = nqp::mod_i(nqp::add_i($j,1),$elems)), nqp::atpos_n(self,$i) ), ), $to ) } } # end of shaped1numarray role role shaped2numarray does shapednumarray { multi method AT-POS(::?CLASS:D: int \one, int \two --> num) is raw { nqp::multidimref_n(self,nqp::list_i(one, two)) } multi method AT-POS(::?CLASS:D: Int:D $one, Int:D $two --> num) is raw { nqp::multidimref_n(self,nqp::list_i($one, $two)) } multi method ASSIGN-POS(::?CLASS:D: int \one, int \two, Num:D \value --> num) { nqp::bindpos2d_n(self,one,two,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Int:D $two, Num:D \value --> num) { nqp::bindpos2d_n(self,$one,$two,value) } multi method EXISTS-POS(::?CLASS:D: int \one, int \two --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::isge_i(two,0) && nqp::islt_i(one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i(two,nqp::atpos_i(nqp::dimensions(self),1)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one, Int:D $two --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::isge_i($two,0) && nqp::islt_i($one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i($two,nqp::atpos_i(nqp::dimensions(self),1)) ) } } # end of shaped2numarray role role shaped3numarray does shapednumarray { multi method AT-POS(::?CLASS:D: int \one, int \two, int \three --> num) is raw { nqp::multidimref_n(self,nqp::list_i(one, two, three)) } multi method AT-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three --> num) is raw { nqp::multidimref_n(self,nqp::list_i($one, $two, $three)) } multi method ASSIGN-POS(::?CLASS:D: int \one, int \two, int \three, Num:D \value --> num) { nqp::bindpos3d_n(self,one,two,three,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three, Num:D \value --> num) { nqp::bindpos3d_n(self,$one,$two,$three,value) } multi method EXISTS-POS(::?CLASS:D: int \one, int \two, int \three --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::isge_i(two,0) && nqp::isge_i(three,0) && nqp::islt_i(one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i(two,nqp::atpos_i(nqp::dimensions(self),1)) && nqp::islt_i(three,nqp::atpos_i(nqp::dimensions(self),2)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::isge_i($two,0) && nqp::isge_i($three,0) && nqp::islt_i($one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i($two,nqp::atpos_i(nqp::dimensions(self),1)) && nqp::islt_i($three,nqp::atpos_i(nqp::dimensions(self),2)) ) } } # end of shaped3numarray role #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of shapednumarray role ------------------------------- #- start of generated part of shapedstrarray role ----------------------------- #- Generated on 2022-02-16T12:09:03+01:00 by ./tools/build/makeNATIVE_SHAPED_ARRAY.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE role shapedstrarray does shapedarray { multi method AT-POS(::?CLASS:D: **@indices --> str) is raw { nqp::if( nqp::iseq_i( (my int $numdims = nqp::numdimensions(self)), (my int $numind = @indices.elems), # reifies ), nqp::stmts( (my $indices := nqp::getattr(@indices,List,'$!reified')), (my $idxs := nqp::list_i), nqp::while( # native index list nqp::isge_i(--$numdims,0), nqp::push_i($idxs,nqp::shift($indices)) ), nqp::multidimref_s(self,$idxs) ), nqp::if( nqp::isgt_i($numind,$numdims), X::TooManyDimensions.new( operation => 'access', got-dimensions => $numind, needed-dimensions => $numdims ).throw, NYI("Partially dimensioned views of shaped arrays").throw ) ) } multi method ASSIGN-POS(::?CLASS:D: **@indices --> str) { nqp::stmts( (my str $value = @indices.pop), nqp::if( nqp::iseq_i( (my int $numdims = nqp::numdimensions(self)), (my int $numind = @indices.elems), # reifies ), nqp::stmts( (my $indices := nqp::getattr(@indices,List,'$!reified')), (my $idxs := nqp::list_i), nqp::while( # native index list nqp::isge_i(--$numdims,0), nqp::push_i($idxs,nqp::shift($indices)) ), nqp::bindposnd_s(self, $idxs, $value) ), nqp::if( nqp::isgt_i($numind,$numdims), X::TooManyDimensions, X::NotEnoughDimensions ).new( operation => 'assign to', got-dimensions => $numind, needed-dimensions => $numdims ).throw ) ) } my class NATCPY-str does Rakudo::Iterator::ShapeLeaf { has Mu $!from; method !INIT(Mu \to, Mu \from) { nqp::stmts( ($!from := from), self!SET-SELF(to) ) } method new(Mu \to, Mu \from) { nqp::create(self)!INIT(to,from) } method result(--> Nil) { nqp::bindposnd_s($!list,$!indices, nqp::multidimref_s($!from,$!indices)) } } sub NATCPY(Mu \to, Mu \from) is raw { NATCPY-str.new(to,from).sink-all; to } my class OBJCPY-str does Rakudo::Iterator::ShapeLeaf { has Mu $!from; method !INIT(Mu \to, Mu \from) { nqp::stmts( ($!from := nqp::getattr(from,List,'$!reified')), self!SET-SELF(to) ) } method new(Mu \to, Mu \from) { nqp::create(self)!INIT(to,from) } method result(--> Nil) { nqp::bindposnd_s($!list,$!indices, nqp::atposnd($!from,$!indices)) } } sub OBJCPY(Mu \to, Mu \from) is raw { OBJCPY-str.new(to,from).sink-all; to } my class ITERCPY-str does Rakudo::Iterator::ShapeBranch { has $!iterators; method !INIT(\to,\from) { nqp::stmts( self!SET-SELF(to), ($!iterators := nqp::setelems( nqp::list(from.iterator), nqp::add_i($!maxdim,1) )), self ) } method new(\to,\from) { nqp::create(self)!INIT(to,from) } method done(--> Nil) { nqp::unless( # verify lowest nqp::atpos($!iterators,0).is-lazy # finite iterator || nqp::eqaddr( # and something there nqp::atpos($!iterators,0).pull-one,IterationEnd), nqp::atposnd_s($!list,$!indices) # boom! ) } method process(--> Nil) { nqp::stmts( (my int $i = $!level), nqp::while( nqp::isle_i(++$i,$!maxdim), nqp::if( nqp::eqaddr((my \item := # exhausted ? nqp::atpos($!iterators,nqp::sub_i($i,1)).pull-one), IterationEnd ), nqp::bindpos($!iterators,$i, # add an empty one Rakudo::Iterator.Empty), nqp::if( # is it an iterator? nqp::istype(item,Iterable) && nqp::isconcrete(item), nqp::bindpos($!iterators,$i,item.iterator), X::Assignment::ToShaped.new(shape => $!dims).throw ) ) ), (my \iter := nqp::atpos($!iterators,$!maxdim)), nqp::until( # loop over highest dim nqp::eqaddr((my \pulled := iter.pull-one),IterationEnd) || nqp::isgt_i(nqp::atpos_i($!indices,$!maxdim),$!maxind), nqp::stmts( nqp::bindposnd_s($!list,$!indices,pulled), nqp::bindpos_i($!indices,$!maxdim, # increment index nqp::add_i(nqp::atpos_i($!indices,$!maxdim),1)) ) ), nqp::unless( nqp::eqaddr(pulled,IterationEnd) # if not exhausted || nqp::isle_i( # and index too high nqp::atpos_i($!indices,$!maxdim),$!maxind) || iter.is-lazy, # and not lazy nqp::atposnd_s($!list,$!indices) # boom! ) ) } } sub ITERCPY(Mu \to, Mu \from) is raw { ITERCPY-str.new(to,from).sink-all; to } multi method STORE(::?CLASS:D: ::?CLASS:D \from) { EQV_DIMENSIONS(self,from) ?? NATCPY(self,from) !! X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw } multi method STORE(::?CLASS:D: array:D \from) { nqp::if( nqp::istype(from.of,Str), nqp::if( EQV_DIMENSIONS(self,from), NATCPY(self,from), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ), X::TypeCheck::Assignment.new( symbol => self.^name ~ '[' ~ self.shape.join(';') ~ ']', expected => Str, got => from.of ).throw ) } multi method STORE(::?CLASS:D: Iterable:D \from) { nqp::if( nqp::can(from,'shape'), nqp::if( from.shape eqv self.shape, OBJCPY(self,from), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ), ITERCPY(self,from) ) } my class Iterate-str does Rakudo::Iterator::ShapeLeaf { method result() is raw { nqp::multidimref_s($!list,nqp::clone($!indices)) } } method iterator(::?CLASS:D: --> Iterate-str:D) { Iterate-str.new(self) } my class KV-str does Rakudo::Iterator::ShapeLeaf { has int $!on-key; method result() is raw { nqp::if( ($!on-key = nqp::not_i($!on-key)), nqp::stmts( (my \result := self.indices), (nqp::bindpos_i($!indices,$!maxdim, # back 1 for next nqp::sub_i(nqp::atpos_i($!indices,$!maxdim),1))), result ), nqp::multidimref_s($!list,nqp::clone($!indices)) ) } # needs its own push-all since it fiddles with $!indices method push-all(\target --> IterationEnd) { nqp::until( nqp::eqaddr((my \pulled := self.pull-one),IterationEnd), target.push(pulled) ) } } multi method kv(::?CLASS:D: --> Seq:D) { Seq.new(KV-str.new(self)) } my class Pairs-str does Rakudo::Iterator::ShapeLeaf { method result() { Pair.new( self.indices, nqp::multidimref_s($!list,nqp::clone($!indices)) ) } } multi method pairs(::?CLASS:D: --> Seq:D) { Seq.new(Pairs-str.new(self)) } my class Antipairs-str does Rakudo::Iterator::ShapeLeaf { method result() { Pair.new(nqp::atposnd_s($!list,$!indices),self.indices) } } multi method antipairs(::?CLASS:D: --> Seq:D) { Seq.new(Antipairs-str.new(self)) } } # end of shapedstrarray role role shaped1strarray does shapedstrarray { multi method AT-POS(::?CLASS:D: int \one --> str) is raw { nqp::atposref_s(self,one) } multi method AT-POS(::?CLASS:D: Int:D $one --> str) is raw { nqp::atposref_s(self,$one) } multi method ASSIGN-POS(::?CLASS:D: int \one, str \value --> str) { nqp::bindpos_s(self,one,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, str \value --> str) { nqp::bindpos_s(self,$one,value) } multi method ASSIGN-POS(::?CLASS:D: int \one, Str:D \value --> str) { nqp::bindpos_s(self,one,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Str:D \value --> str) { nqp::bindpos_s(self,$one,value) } multi method EXISTS-POS(::?CLASS:D: int \one --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::islt_i(one,nqp::elems(self)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::islt_i($one,nqp::elems(self)) ) } multi method STORE(::?CLASS:D: ::?CLASS:D \from) { nqp::if( nqp::iseq_i((my int $elems = nqp::elems(self)),nqp::elems(from)), nqp::stmts( (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_s(self,$i,nqp::atpos_s(from,$i)) ), self ), X::Assignment::ArrayShapeMismatch.new( source-shape => from.shape, target-shape => self.shape ).throw ) } multi method STORE(::?CLASS:D: Iterable:D \in) { my \iter := Rakudo::Iterator.TailWith(in.iterator,""); my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems(self)), nqp::bindpos_s(self,$i,iter.pull-one) ); # too many values? then throw by just accessing out of range nqp::atpos_s(list,$i) unless iter.exhausted; self } multi method STORE(::?CLASS:D: Str:D \item) { nqp::bindpos_s(self,0,item); self } my class Iterate-str does PredictiveIterator { has Mu $!list; has int $!pos; method !SET-SELF(Mu \list) { nqp::stmts( ($!list := list), ($!pos = -1), self ) } method new(Mu \list) { nqp::create(self)!SET-SELF(list) } method pull-one() is raw { nqp::islt_i(++$!pos,nqp::elems($!list)) ?? nqp::atposref_s($!list,$!pos) !! IterationEnd } method skip-one() { nqp::islt_i(++$!pos,nqp::elems($!list)) } method push-all(\target --> IterationEnd) { nqp::stmts( (my int $elems = nqp::elems($!list)), (my int $pos = $!pos), nqp::while( nqp::islt_i(++$pos,$elems), target.push(nqp::atpos_s($!list,$pos)) ), ($!pos = $pos) ) } method count-only(--> Int:D) { nqp::p6box_i( nqp::elems($!list) - $!pos - nqp::islt_i($!pos,nqp::elems($!list)) ) } method sink-all(--> IterationEnd) { $!pos = nqp::elems($!list) } } method iterator(::?CLASS:D: --> Iterate-str:D) { Iterate-str.new(self) } multi method kv(::?CLASS:D: --> Seq:D) { my int $i = -1; my int $elems = nqp::add_i(nqp::elems(self),nqp::elems(self)); Seq.new(Rakudo::Iterator.Callable({ nqp::if( nqp::islt_i(++$i,$elems), nqp::if( nqp::bitand_i($i,1), nqp::atposref_s(self,nqp::bitshiftr_i($i,1)), nqp::bitshiftr_i($i,1) ), IterationEnd ) })) } multi method pairs(::?CLASS:D: --> Seq:D) { my int $i = -1; my int $elems = nqp::elems(self); Seq.new(Rakudo::Iterator.Callable({ nqp::islt_i(++$i,$elems) ?? Pair.new($i,nqp::atposref_s(self,$i)) !! IterationEnd })) } multi method antipairs(::?CLASS:D: --> Seq:D) { Seq.new(Rakudo::Iterator.AntiPair(self.iterator)) } method reverse(::?CLASS:D: --> ::?CLASS:D) is nodal { nqp::stmts( (my int $elems = nqp::elems(self)), (my int $last = nqp::sub_i($elems,1)), (my int $i = -1), (my $to := nqp::clone(self)), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_s($to,nqp::sub_i($last,$i), nqp::atpos_s(self,$i)) ), $to ) } method rotate(::?CLASS:D: Int(Cool) $rotate = 1 --> ::?CLASS:D) is nodal { nqp::stmts( (my int $elems = nqp::elems(self)), (my $to := nqp::clone(self)), (my int $i = -1), (my int $j = nqp::mod_i(nqp::sub_i(nqp::sub_i($elems,1),$rotate),$elems)), nqp::if(nqp::islt_i($j,0),($j = nqp::add_i($j,$elems))), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos_s( $to, ($j = nqp::mod_i(nqp::add_i($j,1),$elems)), nqp::atpos_s(self,$i) ), ), $to ) } } # end of shaped1strarray role role shaped2strarray does shapedstrarray { multi method AT-POS(::?CLASS:D: int \one, int \two --> str) is raw { nqp::multidimref_s(self,nqp::list_i(one, two)) } multi method AT-POS(::?CLASS:D: Int:D $one, Int:D $two --> str) is raw { nqp::multidimref_s(self,nqp::list_i($one, $two)) } multi method ASSIGN-POS(::?CLASS:D: int \one, int \two, Str:D \value --> str) { nqp::bindpos2d_s(self,one,two,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Int:D $two, Str:D \value --> str) { nqp::bindpos2d_s(self,$one,$two,value) } multi method EXISTS-POS(::?CLASS:D: int \one, int \two --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::isge_i(two,0) && nqp::islt_i(one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i(two,nqp::atpos_i(nqp::dimensions(self),1)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one, Int:D $two --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::isge_i($two,0) && nqp::islt_i($one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i($two,nqp::atpos_i(nqp::dimensions(self),1)) ) } } # end of shaped2strarray role role shaped3strarray does shapedstrarray { multi method AT-POS(::?CLASS:D: int \one, int \two, int \three --> str) is raw { nqp::multidimref_s(self,nqp::list_i(one, two, three)) } multi method AT-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three --> str) is raw { nqp::multidimref_s(self,nqp::list_i($one, $two, $three)) } multi method ASSIGN-POS(::?CLASS:D: int \one, int \two, int \three, Str:D \value --> str) { nqp::bindpos3d_s(self,one,two,three,value) } multi method ASSIGN-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three, Str:D \value --> str) { nqp::bindpos3d_s(self,$one,$two,$three,value) } multi method EXISTS-POS(::?CLASS:D: int \one, int \two, int \three --> Bool:D) { nqp::hllbool( nqp::isge_i(one,0) && nqp::isge_i(two,0) && nqp::isge_i(three,0) && nqp::islt_i(one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i(two,nqp::atpos_i(nqp::dimensions(self),1)) && nqp::islt_i(three,nqp::atpos_i(nqp::dimensions(self),2)) ) } multi method EXISTS-POS(::?CLASS:D: Int:D $one, Int:D $two, Int:D $three --> Bool:D) { nqp::hllbool( nqp::isge_i($one,0) && nqp::isge_i($two,0) && nqp::isge_i($three,0) && nqp::islt_i($one,nqp::atpos_i(nqp::dimensions(self),0)) && nqp::islt_i($two,nqp::atpos_i(nqp::dimensions(self),1)) && nqp::islt_i($three,nqp::atpos_i(nqp::dimensions(self),2)) ) } } # end of shaped3strarray role #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of generated part of shapedstrarray role ------------------------------- method ^parameterize(Mu:U \arr, Mu \t) { if nqp::isconcrete(t) { return "Can not parameterize {arr.^name} with {t.raku}"; } my $t := nqp::decont(t); my int $kind = nqp::objprimspec($t); my $what; if $kind == 1 { $what := arr.^mixin(intarray[$t]); } elsif $kind == 2 { $what := arr.^mixin(numarray[$t]); } elsif $kind == 3 { $what := arr.^mixin(strarray[$t]); } elsif $kind >= 4 && $kind <= 6 { $what := arr.^mixin(intarray[$t]); } elsif $kind >= 7 && $kind <= 10 { $what := arr.^mixin(uintarray[$t]); } else { return "Can only parameterize array with a native type, not {t.^name}"; } $what.^set_name("{arr.^name}[{t.^name}]"); $what } # poor man's 3x4 matrix constant typedim2role := nqp::list(nqp::null, nqp::list(shapedintarray,shaped1intarray,shaped2intarray,shaped3intarray), nqp::list(shapednumarray,shaped1numarray,shaped2numarray,shaped3numarray), nqp::list(shapedstrarray,shaped1strarray,shaped2strarray,shaped3strarray), nqp::null,nqp::null,nqp::null,nqp::null,nqp::null,nqp::null, nqp::list(shapeduintarray,shaped1uintarray,shaped2uintarray,shaped3uintarray), ); proto method set-shape(|) is implementation-detail {*} multi method set-shape(Whatever) is raw { nqp::create(self.WHAT) } multi method set-shape(\shape) is raw { self.set-shape(shape.List) } multi method set-shape(List:D \shape) is raw { my int $dims = shape.elems; # reifies my $reified := nqp::getattr(nqp::decont(shape),List,'$!reified'); # just a list with Whatever, so no shape if nqp::iseq_i($dims,1) && nqp::istype(nqp::atpos($reified,0),Whatever) { nqp::create(self.WHAT) } elsif $dims { # Calculate new meta-object (probably hitting caches in most cases). my \shaped-type = self.WHAT.^mixin( nqp::atpos( nqp::atpos(typedim2role,nqp::objprimspec(my \T = self.of)), nqp::isle_i($dims,3) && $dims ) ); shaped-type.^set_name(self.WHAT.^name) # set name if needed if nqp::isne_s(shaped-type.^name,self.WHAT.^name); # Allocate array storage for this shape, based on calculated type. Rakudo::Internals.SHAPED-ARRAY-STORAGE(shape,shaped-type.HOW,T) } else { X::NotEnoughDimensions.new( operation => 'create', got-dimensions => $dims, needed-dimensions => '', ).throw } } method BIND-POS(|) { X::Bind.new(target => 'a natively typed array').throw } method DELETE-POS(|) { X::Delete.new(target => 'a natively typed array').throw } proto method ASSIGN-POS(|) {*} # Hide candidates from Any multi method ASSIGN-POS(Any:U \SELF: \pos, Mu \assignee) { # auto-viv SELF.AT-POS(pos) = assignee; } multi method ASSIGN-POS(Any:D: Any:U \pos, Mu \assignee) { # undefined idx die "Cannot use '{pos.^name}' as an index"; } multi method EXISTS-POS(array:D: int $idx) { $idx >= 0 && $idx < nqp::elems(self) } multi method EXISTS-POS(array:D: Int:D $idx) { $idx >= 0 && $idx < nqp::elems(self) } multi method Bool(array:D:) { nqp::hllbool(nqp::elems(self)) } multi method Numeric(array:D:) { nqp::elems(self) } multi method Str(array:D:) { self.join(' ') } multi method elems(array:D:) { nqp::elems(self) } method shape() { (*,) } proto method Real(|) {*} multi method Real(array:D:) { nqp::elems(self) } proto method Int(|) {*} multi method Int(array:D:) { nqp::elems(self) } multi method end(array:D:) { nqp::elems(self) - 1 } method eager() { self } method flat() { Seq.new(self.iterator) } method list() { List.from-iterator(self.iterator) } method sink(--> Nil) { } multi method gist(array:D:) { '[' ~ self.map(-> $elem { given ++$ { when 101 { '...' } when 102 { last } default { $elem.gist } } } ).join(' ') ~ ']'; } multi method raku(array:D:) { 'array[' ~ self.of.raku ~ '].new(' ~ self.map(*.raku).join(', ') ~ ')' } method FLATTENABLE_LIST() { self } method FLATTENABLE_HASH() { nqp::hash() } method iterator() { nqp::die('iterator must be provided by native array parameterization role') } method out_of_range(array:D \SELF: int $index) { X::OutOfRange.new( :what('Index'), :got($index), :range("0..{nqp::elems(SELF)}") ).Failure } } multi sub postcircumfix:<[ ]>(array:D \SELF, Range:D \range ) is raw { nqp::if( nqp::iscont(range), SELF.AT-POS(range.Int), # range in a container nqp::if( nqp::getattr_i(range,Range,'$!is-int'), nqp::if( # we have an integer range nqp::islt_i( (my int $min = nqp::add_i( nqp::getattr(range,Range,'$!min'), nqp::getattr_i(range,Range,'$!excludes-min') )), 0 ), SELF.out_of_range($min), # starts too low nqp::if( # start in range nqp::isgt_i( $min, (my int $max = nqp::sub_i( nqp::getattr(range,Range,'$!max'), nqp::getattr_i(range,Range,'$!excludes-max') )) ), nqp::create(SELF), # wrong order, empty! nqp::if( # correct order nqp::islt_i($max,nqp::elems(SELF)), nqp::slice(SELF,$min,$max), # end in range, slice! nqp::setelems( # end not in range nqp::if( nqp::islt_i($min,nqp::elems(SELF)), nqp::slice(SELF,$min,-1), # start in range nqp::create(SELF) # start not in range ), nqp::add_i(nqp::sub_i($max,$min),1) ) ) ) ), postcircumfix:<[ ]>(SELF, range.list) ) ) } #- start of postcircumfix candidates of strarray ------------------------------- #- Generated on 2022-04-20T21:09:40+02:00 by tools/build/makeNATIVE_CANDIDATES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, uint $pos ) is raw { nqp::atposref_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Int:D $pos ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! nqp::atposref_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, uint $pos, Str:D \assignee ) is raw { nqp::bindpos_s(nqp::decont(SELF),$pos,assignee) } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Int:D $pos, Str:D \assignee ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! nqp::bindpos_s(nqp::decont(SELF),$pos,assignee); } multi sub postcircumfix:<[ ]>( array::strarray:D, Int:D, :$BIND! ) { X::Bind.new(target => 'a native str array').throw } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Int:D $pos, :$exists!, *%_ ) { my int $state = nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))); my $value := nqp::hllbool($exists ?? $state !! nqp::not_i($state)); $state ?? nqp::elems(my $adverbs := nqp::getattr(%_,Map,'$!storage')) ?? nqp::atkey($adverbs,'kv') ?? ($pos,$value) !! nqp::atkey($adverbs,'p') ?? Pair.new($pos,$value) !! X::Adverb.new( what => "slice", source => "a native str array", nogo => ('exists', |%_.keys).sort ).Failure !! $value !! $value } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Int:D $pos, :$delete!, *%_ ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $delete ?? X::Delete.new(target => 'a native str array').throw !! nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? postcircumfix:<[ ]>(SELF, $pos, |%_) !! nqp::atposref_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Int:D $pos, :$kv! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $kv ?? nqp::list($pos,nqp::atpos_s(nqp::decont(SELF),$pos)) !! nqp::atposref_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Int:D $pos, :$p! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $p ?? Pair.new($pos,nqp::atpos_s(nqp::decont(SELF),$pos)) !! nqp::atposref_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Int:D $pos, :$k! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $k ?? $pos !! nqp::atposref_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Int:D $pos, :$v! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $v ?? nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))) ?? nqp::list(nqp::atpos_s(nqp::decont(SELF),$pos)) !! () !! nqp::atpos_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Callable:D $pos ) is raw { nqp::istype((my $got := $pos.POSITIONS(SELF)),Int) ?? nqp::islt_i($got,0) ?? X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw !! nqp::atposref_s(nqp::decont(SELF),$got) !! postcircumfix:<[ ]>(SELF, $got) } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Iterable:D $pos is rw ) is raw { nqp::islt_i((my int $got = $pos.Int),0) ?? X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw !! nqp::atposref_s(nqp::decont(SELF),$got) } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Iterable:D $pos ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my str @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_s(@result,nqp::atpos_s($self,$got)), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when slicing a native str array".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Iterable:D $pos, array::strarray:D $values ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my int $i = -1; my str @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_s( @result, nqp::bindpos_s($self,$got,nqp::atpos_s($values,++$i)) ), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when assigning to a native str array slice".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Iterable:D $pos, \values ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my $values := Rakudo::Iterator.TailWith(values.iterator,""); my str @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_s( @result, nqp::bindpos_s( $self, $got, $values.pull-one.Str ) ), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when assigning to a native str array slice".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::strarray:D \SELF, Whatever ) { nqp::decont(SELF) } multi sub infix:(array::strarray:D \a, array::strarray:D \b) { my int $elems-a = nqp::elems(a); my int $elems-b = nqp::elems(b); my int $elems = nqp::islt_i($elems-a,$elems-b) ?? $elems-a !! $elems-b; my int $i = -1; nqp::until( nqp::isge_i(++$i,$elems) || (my $res = nqp::cmp_s(nqp::atpos_s(a,$i),nqp::atpos_s(b,$i))), nqp::null ); ORDER($res || nqp::cmp_i($elems-a,$elems-b)) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of postcircumfix candidates of strarray --------------------------------- #- start of postcircumfix candidates of numarray ------------------------------- #- Generated on 2022-04-20T21:09:40+02:00 by tools/build/makeNATIVE_CANDIDATES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, uint $pos ) is raw { nqp::atposref_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Int:D $pos ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! nqp::atposref_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, uint $pos, Num:D \assignee ) is raw { nqp::bindpos_n(nqp::decont(SELF),$pos,assignee) } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Int:D $pos, Num:D \assignee ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! nqp::bindpos_n(nqp::decont(SELF),$pos,assignee); } multi sub postcircumfix:<[ ]>( array::numarray:D, Int:D, :$BIND! ) { X::Bind.new(target => 'a native num array').throw } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Int:D $pos, :$exists!, *%_ ) { my int $state = nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))); my $value := nqp::hllbool($exists ?? $state !! nqp::not_i($state)); $state ?? nqp::elems(my $adverbs := nqp::getattr(%_,Map,'$!storage')) ?? nqp::atkey($adverbs,'kv') ?? ($pos,$value) !! nqp::atkey($adverbs,'p') ?? Pair.new($pos,$value) !! X::Adverb.new( what => "slice", source => "a native num array", nogo => ('exists', |%_.keys).sort ).Failure !! $value !! $value } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Int:D $pos, :$delete!, *%_ ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $delete ?? X::Delete.new(target => 'a native num array').throw !! nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? postcircumfix:<[ ]>(SELF, $pos, |%_) !! nqp::atposref_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Int:D $pos, :$kv! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $kv ?? nqp::list($pos,nqp::atpos_n(nqp::decont(SELF),$pos)) !! nqp::atposref_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Int:D $pos, :$p! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $p ?? Pair.new($pos,nqp::atpos_n(nqp::decont(SELF),$pos)) !! nqp::atposref_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Int:D $pos, :$k! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $k ?? $pos !! nqp::atposref_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Int:D $pos, :$v! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $v ?? nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))) ?? nqp::list(nqp::atpos_n(nqp::decont(SELF),$pos)) !! () !! nqp::atpos_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Callable:D $pos ) is raw { nqp::istype((my $got := $pos.POSITIONS(SELF)),Int) ?? nqp::islt_i($got,0) ?? X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw !! nqp::atposref_n(nqp::decont(SELF),$got) !! postcircumfix:<[ ]>(SELF, $got) } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Iterable:D $pos is rw ) is raw { nqp::islt_i((my int $got = $pos.Int),0) ?? X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw !! nqp::atposref_n(nqp::decont(SELF),$got) } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Iterable:D $pos ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my num @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_n(@result,nqp::atpos_n($self,$got)), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when slicing a native num array".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Iterable:D $pos, array::numarray:D $values ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my int $i = -1; my num @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_n( @result, nqp::bindpos_n($self,$got,nqp::atpos_n($values,++$i)) ), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when assigning to a native num array slice".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Iterable:D $pos, \values ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my $values := Rakudo::Iterator.TailWith(values.iterator,0e0); my num @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_n( @result, nqp::bindpos_n( $self, $got, $values.pull-one.Num ) ), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when assigning to a native num array slice".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::numarray:D \SELF, Whatever ) { nqp::decont(SELF) } multi sub infix:(array::numarray:D \a, array::numarray:D \b) { my int $elems-a = nqp::elems(a); my int $elems-b = nqp::elems(b); my int $elems = nqp::islt_i($elems-a,$elems-b) ?? $elems-a !! $elems-b; my int $i = -1; nqp::until( nqp::isge_i(++$i,$elems) || (my $res = nqp::cmp_n(nqp::atpos_n(a,$i),nqp::atpos_n(b,$i))), nqp::null ); ORDER($res || nqp::cmp_i($elems-a,$elems-b)) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of postcircumfix candidates of numarray --------------------------------- #- start of postcircumfix candidates of intarray ------------------------------- #- Generated on 2022-04-20T21:09:40+02:00 by tools/build/makeNATIVE_CANDIDATES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, uint $pos ) is raw { nqp::atposref_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Int:D $pos ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! nqp::atposref_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, uint $pos, Int:D \assignee ) is raw { nqp::bindpos_i(nqp::decont(SELF),$pos,assignee) } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Int:D $pos, Int:D \assignee ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! nqp::bindpos_i(nqp::decont(SELF),$pos,assignee); } multi sub postcircumfix:<[ ]>( array::intarray:D, Int:D, :$BIND! ) { X::Bind.new(target => 'a native int array').throw } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Int:D $pos, :$exists!, *%_ ) { my int $state = nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))); my $value := nqp::hllbool($exists ?? $state !! nqp::not_i($state)); $state ?? nqp::elems(my $adverbs := nqp::getattr(%_,Map,'$!storage')) ?? nqp::atkey($adverbs,'kv') ?? ($pos,$value) !! nqp::atkey($adverbs,'p') ?? Pair.new($pos,$value) !! X::Adverb.new( what => "slice", source => "a native int array", nogo => ('exists', |%_.keys).sort ).Failure !! $value !! $value } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Int:D $pos, :$delete!, *%_ ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $delete ?? X::Delete.new(target => 'a native int array').throw !! nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? postcircumfix:<[ ]>(SELF, $pos, |%_) !! nqp::atposref_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Int:D $pos, :$kv! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $kv ?? nqp::list($pos,nqp::atpos_i(nqp::decont(SELF),$pos)) !! nqp::atposref_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Int:D $pos, :$p! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $p ?? Pair.new($pos,nqp::atpos_i(nqp::decont(SELF),$pos)) !! nqp::atposref_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Int:D $pos, :$k! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $k ?? $pos !! nqp::atposref_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Int:D $pos, :$v! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $v ?? nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))) ?? nqp::list(nqp::atpos_i(nqp::decont(SELF),$pos)) !! () !! nqp::atpos_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Callable:D $pos ) is raw { nqp::istype((my $got := $pos.POSITIONS(SELF)),Int) ?? nqp::islt_i($got,0) ?? X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw !! nqp::atposref_i(nqp::decont(SELF),$got) !! postcircumfix:<[ ]>(SELF, $got) } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Iterable:D $pos is rw ) is raw { nqp::islt_i((my int $got = $pos.Int),0) ?? X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw !! nqp::atposref_i(nqp::decont(SELF),$got) } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Iterable:D $pos ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my int @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_i(@result,nqp::atpos_i($self,$got)), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when slicing a native int array".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Iterable:D $pos, array::intarray:D $values ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my int $i = -1; my int @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_i( @result, nqp::bindpos_i($self,$got,nqp::atpos_i($values,++$i)) ), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when assigning to a native int array slice".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Iterable:D $pos, \values ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my $values := Rakudo::Iterator.TailWith(values.iterator,0); my int @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_i( @result, nqp::bindpos_i( $self, $got, $values.pull-one.Int ) ), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when assigning to a native int array slice".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::intarray:D \SELF, Whatever ) { nqp::decont(SELF) } multi sub infix:(array::intarray:D \a, array::intarray:D \b) { my int $elems-a = nqp::elems(a); my int $elems-b = nqp::elems(b); my int $elems = nqp::islt_i($elems-a,$elems-b) ?? $elems-a !! $elems-b; my int $i = -1; nqp::until( nqp::isge_i(++$i,$elems) || (my $res = nqp::cmp_i(nqp::atpos_i(a,$i),nqp::atpos_i(b,$i))), nqp::null ); ORDER($res || nqp::cmp_i($elems-a,$elems-b)) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of postcircumfix candidates of intarray --------------------------------- #- start of postcircumfix candidates of uintarray ------------------------------- #- Generated on 2022-04-20T21:09:40+02:00 by tools/build/makeNATIVE_CANDIDATES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, uint $pos ) is raw { nqp::atposref_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Int:D $pos ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! nqp::atposref_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, uint $pos, UInt:D \assignee ) is raw { nqp::bindpos_u(nqp::decont(SELF),$pos,assignee) } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Int:D $pos, UInt:D \assignee ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! nqp::bindpos_u(nqp::decont(SELF),$pos,assignee); } multi sub postcircumfix:<[ ]>( array::uintarray:D, Int:D, :$BIND! ) { X::Bind.new(target => 'a native uint array').throw } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Int:D $pos, :$exists!, *%_ ) { my int $state = nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))); my $value := nqp::hllbool($exists ?? $state !! nqp::not_i($state)); $state ?? nqp::elems(my $adverbs := nqp::getattr(%_,Map,'$!storage')) ?? nqp::atkey($adverbs,'kv') ?? ($pos,$value) !! nqp::atkey($adverbs,'p') ?? Pair.new($pos,$value) !! X::Adverb.new( what => "slice", source => "a native uint array", nogo => ('exists', |%_.keys).sort ).Failure !! $value !! $value } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Int:D $pos, :$delete!, *%_ ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $delete ?? X::Delete.new(target => 'a native uint array').throw !! nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? postcircumfix:<[ ]>(SELF, $pos, |%_) !! nqp::atposref_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Int:D $pos, :$kv! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $kv ?? nqp::list($pos,nqp::atpos_u(nqp::decont(SELF),$pos)) !! nqp::atposref_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Int:D $pos, :$p! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $p ?? Pair.new($pos,nqp::atpos_u(nqp::decont(SELF),$pos)) !! nqp::atposref_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Int:D $pos, :$k! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $k ?? $pos !! nqp::atposref_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Int:D $pos, :$v! ) is raw { nqp::islt_i($pos,0) ?? X::OutOfRange.new(:what, :got($pos), :range<0..^Inf>).throw !! $v ?? nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))) ?? nqp::list(nqp::atpos_u(nqp::decont(SELF),$pos)) !! () !! nqp::atpos_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Callable:D $pos ) is raw { nqp::istype((my $got := $pos.POSITIONS(SELF)),Int) ?? nqp::islt_i($got,0) ?? X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw !! nqp::atposref_u(nqp::decont(SELF),$got) !! postcircumfix:<[ ]>(SELF, $got) } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Iterable:D $pos is rw ) is raw { nqp::islt_i((my int $got = $pos.Int),0) ?? X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw !! nqp::atposref_u(nqp::decont(SELF),$got) } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Iterable:D $pos ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my uint @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_i(@result,nqp::atpos_u($self,$got)), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when slicing a native uint array".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Iterable:D $pos, array::uintarray:D $values ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my int $i = -1; my uint @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_i( @result, nqp::bindpos_u($self,$got,nqp::atpos_u($values,++$i)) ), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when assigning to a native uint array slice".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Iterable:D $pos, \values ) is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my $values := Rakudo::Iterator.TailWith(values.iterator,0); my uint @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::if( nqp::istype( (my $got = nqp::if( nqp::istype($pulled,Callable), $pulled.POSITIONS($self), $pulled )), Int ) && nqp::isge_i($got,0), nqp::push_i( @result, nqp::bindpos_u( $self, $got, $values.pull-one.UInt ) ), nqp::if( nqp::istype($got,Int), X::OutOfRange.new(:what, :$got, :range<0..^Inf>).throw, (die "Cannot handle {$got.raku} as an index in an Iterable when assigning to a native uint array slice".naive-word-wrapper) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::uintarray:D \SELF, Whatever ) { nqp::decont(SELF) } multi sub infix:(array::uintarray:D \a, array::uintarray:D \b) { my int $elems-a = nqp::elems(a); my int $elems-b = nqp::elems(b); my int $elems = nqp::islt_i($elems-a,$elems-b) ?? $elems-a !! $elems-b; my int $i = -1; nqp::until( nqp::isge_i(++$i,$elems) || (my $res = nqp::cmp_i(nqp::atpos_u(a,$i),nqp::atpos_u(b,$i))), nqp::null ); ORDER($res || nqp::cmp_i($elems-a,$elems-b)) } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of postcircumfix candidates of uintarray --------------------------------- #- start of shaped1 postcircumfix candidates of strarray ----------------------- #- Generated on 2022-04-20T21:08:52+02:00 by tools/build/makeNATIVE_SHAPED1_CANDIDATES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi sub postcircumfix:<[ ]>( array::shaped1strarray:D \SELF, Int:D $pos ) is default is raw { nqp::atposref_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1strarray:D \SELF, Int:D $pos, Str:D \assignee ) is default is raw { nqp::bindpos_s(nqp::decont(SELF),$pos,assignee) } multi sub postcircumfix:<[ ]>( array::shaped1strarray:D \SELF, Int:D $pos, :$exists!, *%_ ) is default { my int $state = nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))); my $value := nqp::hllbool($exists ?? $state !! nqp::not_i($state)); $state ?? nqp::elems(my $adverbs := nqp::getattr(%_,Map,'$!storage')) ?? nqp::atkey($adverbs,'kv') ?? ($pos,$value) !! nqp::atkey($adverbs,'p') ?? Pair.new($pos,$value) !! X::Adverb.new( what => "slice", source => "native shaped1 str array", nogo => ('exists', |%_.keys).sort ).Failure !! $value !! $value } multi sub postcircumfix:<[ ]>( array::shaped1strarray:D \SELF, Int:D $pos, :$kv! ) is default is raw { $kv ?? nqp::list($pos,nqp::atpos_s(nqp::decont(SELF),$pos)) !! nqp::atposref_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1strarray:D \SELF, Int:D $pos, :$p! ) is default is raw { $p ?? Pair.new($pos,nqp::atpos_s(nqp::decont(SELF),$pos)) !! nqp::atposref_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1strarray:D \SELF, Int:D $pos, :$k! ) is default is raw { $k ?? $pos !! nqp::atposref_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1strarray:D \SELF, Int:D $pos, :$v! ) is default is raw { $v ?? nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))) ?? nqp::list(nqp::atpos_s(nqp::decont(SELF),$pos)) !! () !! nqp::atpos_s(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1strarray:D \SELF, Iterable:D $pos is rw ) is default is raw { nqp::atposref_s(nqp::decont(SELF),$pos.Int) } multi sub postcircumfix:<[ ]>( array::shaped1strarray:D \SELF, Callable:D $pos ) is default is raw { nqp::atposref_s( nqp::decont(SELF), $pos(nqp::elems(nqp::decont(SELF))) ) } multi sub postcircumfix:<[ ]>( array::shaped1strarray:D \SELF, Iterable:D $pos ) is default is raw { my $self := nqp::decont(SELF); my $iterator := $pos.iterator; my str @result; nqp::until( nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), nqp::push_s( @result, nqp::atpos_s( $self, nqp::if( nqp::istype($pulled,Callable), $pulled(nqp::elems($self)), $pulled.Int ) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::shaped1strarray:D \SELF, Iterable:D $pos, array::strarray:D $values ) is default is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my int $i = -1; my str @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::push_s( @result, nqp::bindpos_s( $self, nqp::if( nqp::istype($pulled,Callable), $pulled(nqp::elems($self)), $pulled.Int ), nqp::atpos_s($values,++$i) ) ) ); @result } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of shaped1 postcircumfix candidates of strarray ------------------------- #- start of shaped1 postcircumfix candidates of intarray ----------------------- #- Generated on 2022-04-20T21:08:52+02:00 by tools/build/makeNATIVE_SHAPED1_CANDIDATES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi sub postcircumfix:<[ ]>( array::shaped1intarray:D \SELF, Int:D $pos ) is default is raw { nqp::atposref_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1intarray:D \SELF, Int:D $pos, Int:D \assignee ) is default is raw { nqp::bindpos_i(nqp::decont(SELF),$pos,assignee) } multi sub postcircumfix:<[ ]>( array::shaped1intarray:D \SELF, Int:D $pos, :$exists!, *%_ ) is default { my int $state = nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))); my $value := nqp::hllbool($exists ?? $state !! nqp::not_i($state)); $state ?? nqp::elems(my $adverbs := nqp::getattr(%_,Map,'$!storage')) ?? nqp::atkey($adverbs,'kv') ?? ($pos,$value) !! nqp::atkey($adverbs,'p') ?? Pair.new($pos,$value) !! X::Adverb.new( what => "slice", source => "native shaped1 int array", nogo => ('exists', |%_.keys).sort ).Failure !! $value !! $value } multi sub postcircumfix:<[ ]>( array::shaped1intarray:D \SELF, Int:D $pos, :$kv! ) is default is raw { $kv ?? nqp::list($pos,nqp::atpos_i(nqp::decont(SELF),$pos)) !! nqp::atposref_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1intarray:D \SELF, Int:D $pos, :$p! ) is default is raw { $p ?? Pair.new($pos,nqp::atpos_i(nqp::decont(SELF),$pos)) !! nqp::atposref_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1intarray:D \SELF, Int:D $pos, :$k! ) is default is raw { $k ?? $pos !! nqp::atposref_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1intarray:D \SELF, Int:D $pos, :$v! ) is default is raw { $v ?? nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))) ?? nqp::list(nqp::atpos_i(nqp::decont(SELF),$pos)) !! () !! nqp::atpos_i(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1intarray:D \SELF, Iterable:D $pos is rw ) is default is raw { nqp::atposref_i(nqp::decont(SELF),$pos.Int) } multi sub postcircumfix:<[ ]>( array::shaped1intarray:D \SELF, Callable:D $pos ) is default is raw { nqp::atposref_i( nqp::decont(SELF), $pos(nqp::elems(nqp::decont(SELF))) ) } multi sub postcircumfix:<[ ]>( array::shaped1intarray:D \SELF, Iterable:D $pos ) is default is raw { my $self := nqp::decont(SELF); my $iterator := $pos.iterator; my int @result; nqp::until( nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), nqp::push_i( @result, nqp::atpos_i( $self, nqp::if( nqp::istype($pulled,Callable), $pulled(nqp::elems($self)), $pulled.Int ) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::shaped1intarray:D \SELF, Iterable:D $pos, array::intarray:D $values ) is default is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my int $i = -1; my int @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::push_i( @result, nqp::bindpos_i( $self, nqp::if( nqp::istype($pulled,Callable), $pulled(nqp::elems($self)), $pulled.Int ), nqp::atpos_i($values,++$i) ) ) ); @result } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of shaped1 postcircumfix candidates of intarray ------------------------- #- start of shaped1 postcircumfix candidates of uintarray ----------------------- #- Generated on 2022-04-20T21:08:52+02:00 by tools/build/makeNATIVE_SHAPED1_CANDIDATES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi sub postcircumfix:<[ ]>( array::shaped1uintarray:D \SELF, Int:D $pos ) is default is raw { nqp::atposref_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1uintarray:D \SELF, Int:D $pos, UInt:D \assignee ) is default is raw { nqp::bindpos_u(nqp::decont(SELF),$pos,assignee) } multi sub postcircumfix:<[ ]>( array::shaped1uintarray:D \SELF, Int:D $pos, :$exists!, *%_ ) is default { my int $state = nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))); my $value := nqp::hllbool($exists ?? $state !! nqp::not_i($state)); $state ?? nqp::elems(my $adverbs := nqp::getattr(%_,Map,'$!storage')) ?? nqp::atkey($adverbs,'kv') ?? ($pos,$value) !! nqp::atkey($adverbs,'p') ?? Pair.new($pos,$value) !! X::Adverb.new( what => "slice", source => "native shaped1 uint array", nogo => ('exists', |%_.keys).sort ).Failure !! $value !! $value } multi sub postcircumfix:<[ ]>( array::shaped1uintarray:D \SELF, Int:D $pos, :$kv! ) is default is raw { $kv ?? nqp::list($pos,nqp::atpos_u(nqp::decont(SELF),$pos)) !! nqp::atposref_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1uintarray:D \SELF, Int:D $pos, :$p! ) is default is raw { $p ?? Pair.new($pos,nqp::atpos_u(nqp::decont(SELF),$pos)) !! nqp::atposref_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1uintarray:D \SELF, Int:D $pos, :$k! ) is default is raw { $k ?? $pos !! nqp::atposref_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1uintarray:D \SELF, Int:D $pos, :$v! ) is default is raw { $v ?? nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))) ?? nqp::list(nqp::atpos_u(nqp::decont(SELF),$pos)) !! () !! nqp::atpos_u(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1uintarray:D \SELF, Iterable:D $pos is rw ) is default is raw { nqp::atposref_u(nqp::decont(SELF),$pos.Int) } multi sub postcircumfix:<[ ]>( array::shaped1uintarray:D \SELF, Callable:D $pos ) is default is raw { nqp::atposref_u( nqp::decont(SELF), $pos(nqp::elems(nqp::decont(SELF))) ) } multi sub postcircumfix:<[ ]>( array::shaped1uintarray:D \SELF, Iterable:D $pos ) is default is raw { my $self := nqp::decont(SELF); my $iterator := $pos.iterator; my uint @result; nqp::until( nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), nqp::push_i( @result, nqp::atpos_u( $self, nqp::if( nqp::istype($pulled,Callable), $pulled(nqp::elems($self)), $pulled.Int ) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::shaped1uintarray:D \SELF, Iterable:D $pos, array::uintarray:D $values ) is default is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my int $i = -1; my uint @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::push_i( @result, nqp::bindpos_u( $self, nqp::if( nqp::istype($pulled,Callable), $pulled(nqp::elems($self)), $pulled.Int ), nqp::atpos_u($values,++$i) ) ) ); @result } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of shaped1 postcircumfix candidates of uintarray ------------------------- #- start of shaped1 postcircumfix candidates of numarray ----------------------- #- Generated on 2022-04-20T21:08:52+02:00 by tools/build/makeNATIVE_SHAPED1_CANDIDATES.raku #- PLEASE DON'T CHANGE ANYTHING BELOW THIS LINE multi sub postcircumfix:<[ ]>( array::shaped1numarray:D \SELF, Int:D $pos ) is default is raw { nqp::atposref_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1numarray:D \SELF, Int:D $pos, Num:D \assignee ) is default is raw { nqp::bindpos_n(nqp::decont(SELF),$pos,assignee) } multi sub postcircumfix:<[ ]>( array::shaped1numarray:D \SELF, Int:D $pos, :$exists!, *%_ ) is default { my int $state = nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))); my $value := nqp::hllbool($exists ?? $state !! nqp::not_i($state)); $state ?? nqp::elems(my $adverbs := nqp::getattr(%_,Map,'$!storage')) ?? nqp::atkey($adverbs,'kv') ?? ($pos,$value) !! nqp::atkey($adverbs,'p') ?? Pair.new($pos,$value) !! X::Adverb.new( what => "slice", source => "native shaped1 num array", nogo => ('exists', |%_.keys).sort ).Failure !! $value !! $value } multi sub postcircumfix:<[ ]>( array::shaped1numarray:D \SELF, Int:D $pos, :$kv! ) is default is raw { $kv ?? nqp::list($pos,nqp::atpos_n(nqp::decont(SELF),$pos)) !! nqp::atposref_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1numarray:D \SELF, Int:D $pos, :$p! ) is default is raw { $p ?? Pair.new($pos,nqp::atpos_n(nqp::decont(SELF),$pos)) !! nqp::atposref_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1numarray:D \SELF, Int:D $pos, :$k! ) is default is raw { $k ?? $pos !! nqp::atposref_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1numarray:D \SELF, Int:D $pos, :$v! ) is default is raw { $v ?? nqp::isge_i($pos,0) && nqp::islt_i($pos,nqp::elems(nqp::decont(SELF))) ?? nqp::list(nqp::atpos_n(nqp::decont(SELF),$pos)) !! () !! nqp::atpos_n(nqp::decont(SELF),$pos) } multi sub postcircumfix:<[ ]>( array::shaped1numarray:D \SELF, Iterable:D $pos is rw ) is default is raw { nqp::atposref_n(nqp::decont(SELF),$pos.Int) } multi sub postcircumfix:<[ ]>( array::shaped1numarray:D \SELF, Callable:D $pos ) is default is raw { nqp::atposref_n( nqp::decont(SELF), $pos(nqp::elems(nqp::decont(SELF))) ) } multi sub postcircumfix:<[ ]>( array::shaped1numarray:D \SELF, Iterable:D $pos ) is default is raw { my $self := nqp::decont(SELF); my $iterator := $pos.iterator; my num @result; nqp::until( nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), nqp::push_n( @result, nqp::atpos_n( $self, nqp::if( nqp::istype($pulled,Callable), $pulled(nqp::elems($self)), $pulled.Int ) ) ) ); @result } multi sub postcircumfix:<[ ]>( array::shaped1numarray:D \SELF, Iterable:D $pos, array::numarray:D $values ) is default is raw { my $self := nqp::decont(SELF); my $indices := $pos.iterator; my int $i = -1; my num @result; nqp::until( nqp::eqaddr((my $pulled := $indices.pull-one),IterationEnd), nqp::push_n( @result, nqp::bindpos_n( $self, nqp::if( nqp::istype($pulled,Callable), $pulled(nqp::elems($self)), $pulled.Int ), nqp::atpos_n($values,++$i) ) ) ); @result } #- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE #- end of shaped1 postcircumfix candidates of numarray ------------------------- multi sub elems(array:D \a) { nqp::elems(a) } multi sub end(array:D \a) { nqp::sub_i(nqp::elems(a),1) } #line 1 SETTING::src/core.c/Pair.rakumod my class Pair does Associative { has $.key is default(Nil); has $.value is rw is default(Nil); has ObjAt $!WHICH; proto method new(|) {*} # This candidate is needed because it currently JITs better multi method new(Pair: Str:D $key, Mu \value) { my \p := nqp::p6bindattrinvres( nqp::create(self),Pair,'$!key',$key); nqp::bindattr(p,Pair,'$!value',value); p } multi method new(Pair: Mu \key, Mu \value) { my \p := nqp::p6bindattrinvres( nqp::create(self),Pair,'$!key',nqp::decont(key)); nqp::bindattr(p,Pair,'$!value',value); p } multi method new(Pair: Mu :$key!, Mu :$value!) { my \p := nqp::p6bindattrinvres( nqp::create(self),Pair,'$!key',$key); nqp::bindattr(p,Pair,'$!value',$value); p } multi method clone(Pair:D:) { nqp::p6bindattrinvres(self.Mu::clone, Pair, '$!WHICH', nqp::null) } multi method WHICH(Pair:D: --> ObjAt:D) { nqp::isconcrete($!WHICH) ?? $!WHICH !! self!WHICH } method !WHICH() { $!WHICH := nqp::if( nqp::iscont($!value) || nqp::not_i(nqp::istype((my $VALUE := $!value.WHICH),ValueObjAt)), self.Mu::WHICH, nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Pair), 'Pair|', nqp::concat(self.^name,'|') ), nqp::sha1(nqp::concat(nqp::concat($!key.WHICH,"\0"),$VALUE)) ), ValueObjAt ) ) } multi method ACCEPTS(Pair:D: %h) { $!value.ACCEPTS(%h.AT-KEY($!key)); } multi method ACCEPTS(Pair:D: Pair:D $p) { $!key.ACCEPTS(nqp::getattr($p,Pair,'$!key')) && $!value.ACCEPTS(nqp::getattr($p,Pair,'$!value')) } multi method ACCEPTS(Pair:D: Mu $other) { nqp::can($other,(my $method := $!key.Str)) ?? ($other."$method"().Bool === $!value.Bool) !! X::Method::NotFound.new( invocant => $other, method => $method, typename => $other.^name, addendum => "Or did you try to smartmatch against a Pair specifically? If so, then the key of the Pair should be a valid method name, not '$method'." ).throw } method Pair() { self } method antipair(Pair:D:) { self.new($!value,$!key) } method freeze(Pair:D:) { $!value := nqp::decont($!value) } method iterator(Pair:D:) { Rakudo::Iterator.OneValue(self) } multi method keys(Pair:D:) { Seq.new(Rakudo::Iterator.OneValue($!key)) } multi method kv(Pair:D:) { Seq.new(Rakudo::Iterator.TwoValues($!key,$!value)) } multi method values(Pair:D:) { Seq.new(Rakudo::Iterator.OneValue($!value)) } multi method pairs(Pair:D:) { Seq.new(Rakudo::Iterator.OneValue(self)) } multi method antipairs(Pair:D:) { Seq.new(Rakudo::Iterator.OneValue(self.new($!value,$!key))) } multi method invert(Pair:D:) { Seq.new(Rakudo::Iterator.Invert(self.iterator)) } multi method Str(Pair:D:) { $!key ~ "\t" ~ $!value } multi method gist(Pair:D:) { self.gistseen('Pair', { nqp::istype($!key, Pair) ?? '(' ~ $!key.gist ~ ') => ' ~ $!value.gist !! $!key.gist ~ ' => ' ~ $!value.gist; }) } proto sub allowed-as-bare-key(|) {*} multi sub allowed-as-bare-key(Mu \key --> False) { } multi sub allowed-as-bare-key(Str:D $key) { my int $i; my int $pos; while $i < nqp::chars($key) { return False # starts with numeric if nqp::iscclass(nqp::const::CCLASS_NUMERIC,$key,$i); $pos = nqp::findnotcclass( nqp::const::CCLASS_WORD,$key,$i,nqp::chars($key) ); if $pos == nqp::chars($key) { return True; # reached end ok } elsif nqp::eqat($key,'-',$pos) || nqp::eqat($key,"'",$pos) { return False if $pos == $i # - or ' at start || $pos == nqp::chars($key) - 1; # - or ' at end } else { return False; # not a word char } $i = $pos + 1; # more to parse } False # the empty string } multi method raku(Pair:D: :$arglist = False) { self.rakuseen: self.^name, { nqp::isconcrete($!key) ?? nqp::istype($!key,Str) && nqp::not_i($arglist) && allowed-as-bare-key($!key) ?? nqp::eqaddr($!value,True) || nqp::eqaddr($!value,False) ?? nqp::concat(':', nqp::concat(nqp::x('!',nqp::not_i($!value)), $!key)) !! nqp::concat(':', nqp::concat($!key, nqp::concat('(', nqp::concat($!value.raku, ')')))) !! nqp::istype($!key,Numeric) && nqp::not_i( nqp::istype($!key,Num) && nqp::isnanorinf($!key) ) ?? nqp::concat($!key.raku, nqp::concat(' => ', $!value.raku)) !! nqp::istype($!key,Pair) ?? nqp::concat('(', nqp::concat($!key.raku, nqp::concat(') => ', $!value.raku))) !! nqp::concat($!key.raku, nqp::concat(' => ', $!value.raku)) !! nqp::concat('(', nqp::concat($!key.^name, nqp::concat(') => ', $!value.raku))) } } multi method fmt(Pair:D: Cool:D $format = "%s\t%s" --> Str:D) { sprintf($format, $!key, $!value); } multi method AT-KEY(Pair:D: $key) { $key eq $!key ?? $!value !! Nil } multi method EXISTS-KEY(Pair:D: $key) { $key eq $!key } method FLATTENABLE_LIST() is implementation-detail { nqp::list() } method FLATTENABLE_HASH() is implementation-detail { nqp::hash($!key.Str, $!value) } } multi sub infix:(Pair:D $a, Pair:D $b --> Bool:D) { nqp::hllbool( nqp::eqaddr($a,$b) || (nqp::eqaddr($a.WHAT,$b.WHAT) && $a.key eqv $b.key && $a.value eqv $b.value) ) } multi sub infix:(Pair:D $a, Pair:D $b) { nqp::eqaddr((my $cmp := $a.key cmp $b.key),Order::Same) ?? ($a.value cmp $b.value) !! $cmp } proto sub infix:«=>»(Mu, Mu, *%) is pure {*} multi sub infix:«=>»(Mu $key, Mu \value) { Pair.new($key, value) } # 21D2 RIGHTWARDS DOUBLE ARROW ⇒ my constant &infix:<⇒> := &infix:«=>»; proto sub pair(Mu, Mu, *%) is pure {*} multi sub pair(Mu \key, Mu \value) { Pair.new(key, value) } #line 1 SETTING::src/core.c/Map.rakumod my class X::Hash::Store::OddNumber { ... } my class Map does Iterable does Associative { # declared in BOOTSTRAP # my class Map is Iterable is Cool # has Mu $!storage; # Calling self.new for the arguments case ensures that the right # descriptor will be added for typed hashes. multi method new(Map: --> Map:D) { nqp::create(self) } multi method new(Map: *@args --> Map:D) { self.new.STORE(@args, :INITIALIZE) } multi method contains(Map:D: \needle) { my $name := self.^name; warn "Applying '.contains' to a $name will look at its .Str representation. Did you mean '$name\{needle}:exists'?".naive-word-wrapper; self.Str.contains(needle) } multi method index(Map:D: \needle) { my $name := self.^name; warn "Applying '.index' to a $name will look at its .Str representation. Did you mean '$name\{needle}:exists'?".naive-word-wrapper; self.Str.index(needle) } multi method Map(Map:) { self } multi method Hash(Map:U:) { Hash } multi method Hash(Map:D: --> Hash:D) { if nqp::iterator($!storage) -> \iter { my \hash := nqp::create(Hash); my \storage := nqp::bindattr(hash,Map,'$!storage',nqp::hash); my \descriptor := BEGIN nqp::getcurhllsym('default_cont_spec'); nqp::while( iter, nqp::bindkey( storage, nqp::iterkey_s(nqp::shift(iter)), nqp::p6scalarwithvalue( descriptor, nqp::decont(nqp::iterval(iter))) ) ); hash } else { nqp::create(Hash) } } multi method Bool(Map:D: --> Bool:D) { nqp::hllbool(nqp::elems($!storage)); } method elems(Map:D:) { nqp::elems($!storage) } multi method Int(Map:D: --> Int:D) { self.elems } multi method Numeric(Map:D: --> Int:D) { self.elems } multi method Str(Map:D: --> Str:D) { self.sort.join("\n") } method IterationBuffer(--> IterationBuffer:D) { my \buffer := nqp::create(IterationBuffer); nqp::if( nqp::elems($!storage), nqp::stmts( (my \iterator := nqp::iterator($!storage)), nqp::setelems(buffer,nqp::elems($!storage)), (my int $i = -1), nqp::while( iterator, nqp::bindpos(buffer,++$i, Pair.new( nqp::iterkey_s(nqp::shift(iterator)), nqp::iterval(iterator) ) ) ) ) ); buffer } method List(--> List:D) { self.IterationBuffer.List } multi method head(Map:D:) { nqp::elems($!storage) ?? Pair.new( nqp::iterkey_s( nqp::shift(my \iterator := nqp::iterator($!storage)) ), nqp::iterval(iterator) ) !! Nil } # Produce a native str array with all the keys method !keys-as-str() { my $keys := nqp::list_s; nqp::if( ($!storage && my \iter := nqp::iterator($!storage)), nqp::while( iter, nqp::push_s($keys,nqp::iterkey_s(nqp::shift(iter))) ) ); $keys } # Iterator over a native string array holding the keys and producing # Pairs. my class Iterate-keys does Iterator { has $!map is built(:bind); has $!keys is built(:bind); has int $!i = -1; method pull-one() { nqp::if( nqp::islt_i(++$!i,nqp::elems($!keys)), nqp::stmts( (my \key := nqp::atpos_s($!keys,$!i)), Pair.new(key,nqp::atkey($!map,key)) ), IterationEnd ) } method push-all($target --> IterationEnd) { my \map := $!map; my \keys := $!keys; my int $i = $!i; nqp::while( nqp::islt_i(++$i,nqp::elems(keys)), nqp::stmts( (my \key := nqp::atpos_s(keys,$i)), $target.push(Pair.new(key,nqp::atkey(map,key))) ) ); $!i = $i; } } multi method sort(Map:D: --> Seq:D) { Seq.new( Iterate-keys.new( map => self, keys => Rakudo::Sorting.MERGESORT-str(self!keys-as-str) ) ) } multi method ACCEPTS(Map:D: Any $topic --> Bool:D) { self.EXISTS-KEY($topic.any).Bool } multi method ACCEPTS(Map:D: Cool:D $topic --> Bool:D) { self.EXISTS-KEY($topic); } multi method ACCEPTS(Map:D: Positional $topic --> Bool:D) { self.EXISTS-KEY($topic.any).Bool } multi method ACCEPTS(Map:D: Regex $topic --> Bool:D) { self.keys.any.match($topic).Bool; } multi method ACCEPTS(Map:D: Map:D \m --> Bool:D) { try {self eqv m} // False; } multi method EXISTS-KEY(Map:D: Str:D $key --> Bool:D) { nqp::hllbool(nqp::existskey($!storage,$key)) } multi method EXISTS-KEY(Map:D: \key --> Bool:D) { nqp::hllbool(nqp::existskey($!storage,key.Str)) } multi method gist(Map:D: --> Str:D) { self.^name ~ '.new((' ~ self.sort.head(100).map(*.gist).join(', ') ~ (', ...' if self.elems > 100) ~ '))' } multi method raku(Map:D \SELF: --> Str:D) { my $p := nqp::elems($!storage) ?? self.^name ~ '.new((' ~ self.sort.map(*.raku).join(',') ~ '))' !! self.^name ~ '.new'; nqp::iscont(SELF) ?? '$(' ~ $p ~ ')' !! $p } my class Iterate does Rakudo::Iterator::Mappy { method pull-one() { nqp::if( $!iter, nqp::stmts( nqp::shift($!iter), Pair.new(nqp::iterkey_s($!iter), nqp::iterval($!iter)) ), IterationEnd ) } method push-all(\target --> IterationEnd) { nqp::while( $!iter, nqp::stmts( # doesn't sink nqp::shift($!iter), target.push( Pair.new(nqp::iterkey_s($!iter), nqp::iterval($!iter))) ) ) } } multi method iterator(Map:D: --> Iterator:D) { Iterate.new(self) } multi method list(Map:D: --> List:D) { self.List } multi method pairs(Map:D: --> Seq:D) { Seq.new(self.iterator) } multi method keys(Map:D: --> Seq:D) { Seq.new(Rakudo::Iterator.Mappy-keys(self)) } multi method values(Map:D: --> Seq:D) { Seq.new(Rakudo::Iterator.Mappy-values(self)) } my class KV does Rakudo::Iterator::Mappy-kv-from-pairs { method pull-one() is raw { nqp::if( $!on, nqp::stmts( ($!on= 0), nqp::iterval($!iter) ), nqp::if( $!iter, nqp::stmts( ($!on= 1), nqp::iterkey_s(nqp::shift($!iter)) ), IterationEnd ) ) } method push-all(\target --> IterationEnd) { nqp::while( # doesn't sink $!iter, nqp::stmts( target.push(nqp::iterkey_s(nqp::shift($!iter))), target.push(nqp::iterval($!iter)) ) ) } } multi method kv(Map:D: --> Seq:D) { Seq.new(KV.new(self)) } my class AntiPairs does Rakudo::Iterator::Mappy { method pull-one() { nqp::if( $!iter, nqp::stmts( nqp::shift($!iter), Pair.new( nqp::iterval($!iter), nqp::iterkey_s($!iter) ) ), IterationEnd ); } method push-all(\target --> IterationEnd) { nqp::while( $!iter, nqp::stmts( # doesn't sink nqp::shift($!iter), target.push( Pair.new( nqp::iterval($!iter), nqp::iterkey_s($!iter) )) ) ) } } multi method antipairs(Map:D: --> Seq:D) { Seq.new(AntiPairs.new(self)) } multi method invert(Map:D: --> Seq:D) { Seq.new(Rakudo::Iterator.Invert(self.iterator)) } multi method AT-KEY(Map:D: Str:D $key) is raw { nqp::ifnull(nqp::atkey($!storage,$key),Nil) } multi method AT-KEY(Map:D: \key) is raw { nqp::ifnull(nqp::atkey($!storage,nqp::unbox_s(key.Str)),Nil) } multi method ASSIGN-KEY(Map:D: \key, Mu \new) { nqp::isnull(my \old := nqp::atkey($!storage,key.Str)) ?? die("Cannot add key '{key}' to an immutable {self.^name}") !! nqp::isrwcont(old) ?? (old = new) !! die("Cannot change key '{key}' in an immutable {self.^name}") } # Directly copy from the other Map's internals. method !STORE_MAP_FROM_MAP_DECONT(\map --> Map:D) { nqp::if( nqp::elems(my \other := nqp::getattr(map,Map,'$!storage')), nqp::stmts( (my \iter := nqp::iterator(other)), nqp::while( iter, nqp::bindkey( $!storage, nqp::iterkey_s(nqp::shift(iter)), nqp::decont(nqp::iterval(iter)) ) ) ) ); self } method !STORE_MAP_FROM_MAP(\map --> Map:D) { nqp::if( nqp::elems(my \other := nqp::getattr(map,Map,'$!storage')), nqp::stmts( (my \iter := nqp::iterator(other)), nqp::while( iter, nqp::bindkey( $!storage, nqp::iterkey_s(nqp::shift(iter)), nqp::iterval(iter) ) ) ) ); self } # Directly copy from the Object Hash's internals, but pay respect to the # fact that we're only interested in the values (which contain a Pair with # the object key and a value that we need to decontainerize. method !STORE_MAP_FROM_OBJECT_HASH_DECONT(\map --> Map:D) { nqp::if( nqp::elems(my \other := nqp::getattr(map,Map,'$!storage')), nqp::stmts( (my \iter := nqp::iterator(other)), nqp::while( iter, nqp::bindkey( $!storage, nqp::getattr( (my Mu \pair := nqp::iterval(nqp::shift(iter))), Pair, '$!key' ).Str, nqp::decont(nqp::getattr(pair,Pair,'$!value')) ) ) ) ); self } method !STORE_MAP_FROM_OBJECT_HASH(\map --> Map:D) { nqp::if( nqp::elems(my \other := nqp::getattr(map,Map,'$!storage')), nqp::stmts( (my \iter := nqp::iterator(other)), nqp::while( iter, nqp::bindkey( $!storage, nqp::getattr( (my Mu \pair := nqp::iterval(nqp::shift(iter))), Pair, '$!key' ).Str, nqp::getattr(pair,Pair,'$!value') ) ) ) ); self } # Copy the contents of a Mappy thing that's not in a container. method !STORE_MAP_DECONT(\map --> Map:D) { nqp::istype(map,Hash::Object) ?? self!STORE_MAP_FROM_OBJECT_HASH_DECONT(map) !! self!STORE_MAP_FROM_MAP_DECONT(map) } method PUSH_FROM_MAP(Hash:D \target --> Nil) is implementation-detail { my $iter := nqp::iterator(nqp::getattr(self,Map,'$!storage')); nqp::while( $iter, target.STORE_AT_KEY( nqp::iterkey_s(nqp::shift($iter)),nqp::iterval($iter) ) ); } method !STORE_MAP(\map --> Map:D) { nqp::istype(map,Hash::Object) ?? self!STORE_MAP_FROM_OBJECT_HASH(map) !! self!STORE_MAP_FROM_MAP(map) } method store-odd-number($x) is implementation-detail { my int $elems = self.elems; nqp::istype($x,Failure) ?? $x.throw !! $elems || nqp::not_i(nqp::istype($x,Callable)) ?? X::Hash::Store::OddNumber.new( found => 2 * $elems + 1, last => $x ).throw !! die qq:to/ERROR/.chomp; Cannot use a Callable as the only argument to store in a {self.^name}. If the intent was to store the contents of a Hash, one should probably use the %( ) hash constructor instead of \{ }. Causes of \{ } misinterpretation: - using ';' instead of ',' to separate values, as these imply statements - using '\$_' or any placeholder variable, as they imply a block scope ERROR } # Store the contents of an iterator into the Map method !STORE_MAP_FROM_ITERATOR_DECONT($iterator --> Map:D) is raw { nqp::until( nqp::eqaddr((my Mu $x := $iterator.pull-one),IterationEnd), nqp::if( nqp::istype($x,Pair), nqp::bindkey( $!storage, nqp::getattr(nqp::decont($x),Pair,'$!key').Str, nqp::decont(nqp::getattr(nqp::decont($x),Pair,'$!value')) ), nqp::if( (nqp::istype($x,Map) && nqp::not_i(nqp::iscont($x))), self!STORE_MAP_DECONT($x), nqp::if( nqp::eqaddr((my Mu $y := $iterator.pull-one),IterationEnd), self.store-odd-number($x), nqp::bindkey($!storage,$x.Str,nqp::decont($y)) ) ) ) ); self } method !STORE_MAP_FROM_ITERATOR($iterator --> Map:D) is raw { nqp::until( nqp::eqaddr((my Mu $x := $iterator.pull-one),IterationEnd), nqp::if( nqp::istype($x,Pair), nqp::bindkey( $!storage, nqp::getattr(nqp::decont($x),Pair,'$!key').Str, nqp::getattr(nqp::decont($x),Pair,'$!value') ), nqp::if( (nqp::istype($x,Map) && nqp::not_i(nqp::iscont($x))), self!STORE_MAP($x), nqp::if( nqp::eqaddr((my Mu $y := $iterator.pull-one),IterationEnd), self.store-odd-number($x), nqp::bindkey($!storage,$x.Str,$y) ) ) ) ); self } proto method STORE(Map:D: |) {*} multi method STORE(Map:D: Map:D \map, :INITIALIZE($)!, :DECONT($)! --> Map:D) { nqp::if( nqp::istype(map,Hash::Object), self!STORE_MAP_FROM_OBJECT_HASH_DECONT(map), nqp::if( nqp::elems(my \other := nqp::getattr(map,Map,'$!storage')), nqp::if( nqp::eqaddr(map.WHAT,Map), nqp::p6bindattrinvres(self,Map,'$!storage',other), self.STORE(map.iterator, :INITIALIZE, :DECONT) ), self # nothing to do ) ) } multi method STORE(Map:D: Map:D \map, :INITIALIZE($)! --> Map:D) { nqp::if( nqp::istype(map,Hash::Object), self!STORE_MAP_FROM_OBJECT_HASH(map), nqp::if( nqp::elems(my \other := nqp::getattr(map,Map,'$!storage')), nqp::if( nqp::eqaddr(map.WHAT,Map), nqp::p6bindattrinvres(self,Map,'$!storage',other), nqp::p6bindattrinvres(self,Map,'$!storage',nqp::clone(other)) ), self # nothing to do ) ) } multi method STORE(Map:D: Iterator:D $iterator, :INITIALIZE($)!, :DECONT($)! --> Map:D) { self!STORE_MAP_FROM_ITERATOR_DECONT($iterator) } multi method STORE(Map:D: Iterator:D $iterator, :INITIALIZE($)! --> Map:D) { self!STORE_MAP_FROM_ITERATOR($iterator) } multi method STORE(Map:D: \to_store, :INITIALIZE($)!, :DECONT($)! --> Map:D) { self!STORE_MAP_FROM_ITERATOR_DECONT(to_store.iterator) } multi method STORE(Map:D: \to_store, :INITIALIZE($)! --> Map:D) { self!STORE_MAP_FROM_ITERATOR(to_store.iterator) } multi method STORE(Map:D: \keys, \values, :INITIALIZE($)! --> Map:D) { my \iterkeys := keys.iterator; my \itervalues := values.iterator; my \storage := $!storage := nqp::hash; nqp::until( nqp::eqaddr((my \key := iterkeys.pull-one),IterationEnd), nqp::bindkey( storage, nqp::if(nqp::istype(key,Str),key,key.Str), itervalues.pull-one ) ); self } multi method STORE(Map:D: |) { X::Assignment::RO.new(value => self).throw } method Capture(Map:D:) { nqp::p6bindattrinvres(nqp::create(Capture),Capture,'%!hash',$!storage) } method FLATTENABLE_LIST() is implementation-detail { nqp::list() } method FLATTENABLE_HASH() is implementation-detail { $!storage } multi method fmt(Map:D: Str:D $format = "%s\t\%s", $sep = "\n" --> Str:D) { nqp::iseq_i(nqp::sprintfdirectives( nqp::unbox_s($format.Stringy)),1) ?? self.keys.fmt($format, $sep) !! self.pairs.fmt($format, $sep) } method hash() { self } method clone(Map:D:) { self } multi method roll(Map:D:) { nqp::if( $!storage && nqp::elems($!storage), nqp::stmts( (my int $i = nqp::add_i(nqp::floor_n(nqp::rand_n(nqp::elems($!storage))),1)), (my \iter := nqp::iterator($!storage)), nqp::while( nqp::shift(iter) && --$i, nqp::null ), Pair.new(nqp::iterkey_s(iter),nqp::iterval(iter)) ), Nil ) } multi method roll(Map:D: Callable:D $calculate) { self.roll( $calculate(self.elems) ) } multi method roll(Map:D: Whatever $) { self.roll(Inf) } my class RollN does Iterator { has $!storage; has $!keys; has $!pairs; has $!count; method !SET-SELF(\hash, $count) { $!storage := nqp::getattr(hash,Map,'$!storage'); $!count = $count; my int $i = nqp::elems($!storage); my \iter := nqp::iterator($!storage); $!keys := nqp::setelems(nqp::list_s,$i); $!pairs := nqp::setelems(nqp::list,$i); nqp::while( nqp::isge_i(--$i,0), nqp::bindpos_s($!keys,$i, nqp::iterkey_s(nqp::shift(iter))) ); self } method new(\hash, $count) { nqp::create(self)!SET-SELF(hash, $count) } method pull-one() { nqp::if( $!count, nqp::stmts( --$!count, # must be HLL to handle Inf nqp::ifnull( nqp::atpos( $!pairs, (my int $i = nqp::floor_n(nqp::rand_n(nqp::elems($!keys)))) ), nqp::bindpos($!pairs,$i, Pair.new( nqp::atpos_s($!keys,$i), nqp::atkey($!storage,nqp::atpos_s($!keys,$i)) ) ) ) ), IterationEnd ) } method is-lazy() { $!count == Inf } method is-deterministic(--> False) { } } multi method roll(Map:D: $count) { Seq.new( $!storage && nqp::elems($!storage) && $count > 0 ?? RollN.new(self,$count) !! Rakudo::Iterator.Empty ) } multi method pick(Map:D:) { self.roll } multi method Set(Map:D: --> Set:D) { nqp::create(Set).SET-SELF(Rakudo::QuantHash.COERCE-MAP-TO-SET(self)) } multi method SetHash(Map:D: --> SetHash:D) { nqp::create(SetHash).SET-SELF(Rakudo::QuantHash.COERCE-MAP-TO-SET(self)) } multi method Bag(Map:D: --> Bag:D) { nqp::create(Bag).SET-SELF(Rakudo::QuantHash.COERCE-MAP-TO-BAG(self)) } multi method BagHash(Map:D: --> BagHash:D) { nqp::create(BagHash).SET-SELF(Rakudo::QuantHash.COERCE-MAP-TO-BAG(self)) } multi method Mix(Map:D: --> Mix:D) { nqp::create(Mix).SET-SELF(Rakudo::QuantHash.COERCE-MAP-TO-MIX(self)) } multi method MixHash(Map:D: --> MixHash:D) { nqp::create(MixHash).SET-SELF(Rakudo::QuantHash.COERCE-MAP-TO-MIX(self)) } } multi sub infix:(Map:D \a, Map:D \b --> Bool:D) { class NotEQV { } nqp::hllbool( nqp::unless( nqp::eqaddr(nqp::decont(a),nqp::decont(b)), nqp::if( # not comparing with self nqp::eqaddr(a.WHAT,b.WHAT), nqp::if( # same types (my \amap := nqp::getattr(nqp::decont(a),Map,'$!storage')) && (my int $elems = nqp::elems(amap)), nqp::if( # elems on left (my \bmap := nqp::getattr(nqp::decont(b),Map,'$!storage')) && nqp::iseq_i($elems,nqp::elems(bmap)), nqp::stmts( # same elems on right (my \iter := nqp::iterator(amap)), nqp::while( iter && infix:( nqp::iterval(nqp::shift(iter)), nqp::ifnull(nqp::atkey(bmap,nqp::iterkey_s(iter)),NotEQV) ), --$elems ), nqp::not_i($elems) # ok if none left ), 0 ), nqp::isfalse( # nothing on left (my \map := nqp::getattr(nqp::decont(b),Map,'$!storage')) && nqp::elems(map) # something on right: fail ) ) ) ) ) } #line 1 SETTING::src/core.c/Hash/Typed.rakumod my role Hash::Typed[::TValue] does Associative[TValue] { # make sure we get the right descriptor multi method new(::?CLASS:) { nqp::p6bindattrinvres( nqp::create(self),Hash,'$!descriptor', ContainerDescriptor.new(:of(TValue), :default(TValue)) ) } method ASSIGN-KEY(::?CLASS:D: Mu \key, Mu \assignval) is raw { my \storage := nqp::getattr(self, Map, '$!storage'); my \which := key.Str; my \existing := nqp::atkey(storage,which); nqp::if( nqp::isnull(existing), nqp::stmts( ((my \scalar := nqp::p6scalarfromdesc( # assign before nqp::getattr(self,Hash,'$!descriptor') # binding to get )) = assignval), # type check nqp::bindkey(storage,which,scalar) ), (existing = assignval) ) } method BIND-KEY(Mu \key, TValue \value) is raw { nqp::bindkey( nqp::getattr(self,Map,'$!storage'), key.Str, value ) } multi method raku(::?CLASS:D \SELF:) { SELF.rakuseen('Hash', { '$' x nqp::iscont(SELF) # self is always deconted ~ (self.elems ?? "(my {TValue.raku} % = { self.sort.map({.raku}).join(', ') })" !! "(my {TValue.raku} %)" ) }) } } #line 1 SETTING::src/core.c/Hash/Object.rakumod my role Hash::Object[::TValue, ::TKey] does Associative[TValue] { # make sure we get the right descriptor multi method new(::?CLASS:) { nqp::p6bindattrinvres( nqp::create(self),Hash,'$!descriptor', ContainerDescriptor.new(:of(TValue), :default(TValue)) ) } method keyof () { TKey } method AT-KEY(::?CLASS:D: TKey \key) is raw { my \storage := nqp::getattr(self, Map, '$!storage'); my str $which = nqp::unbox_s(key.WHICH); nqp::existskey(storage,$which) ?? nqp::getattr(nqp::atkey(storage,$which),Pair,'$!value') !! nqp::p6scalarfromdesc( ContainerDescriptor::BindObjHashKey.new( nqp::getattr(self,Hash,'$!descriptor'), self, key, $which, Pair ) ) } method STORE_AT_KEY(::?CLASS:D: TKey \key, Mu \value --> Nil) { nqp::bindkey( nqp::getattr(self,Map,'$!storage'), nqp::unbox_s(key.WHICH), Pair.new( key, nqp::p6scalarfromdesc(nqp::getattr(self,Hash,'$!descriptor')) = value ) ) } method PUSH_FROM_MAP(\target --> Nil) is implementation-detail { my $iter := nqp::iterator(nqp::getattr(self,Map,'$!storage')); nqp::while( $iter, nqp::stmts( (my $pair := nqp::iterval(nqp::shift($iter))), target.STORE_AT_KEY( nqp::getattr($pair,Pair,'$!key'), nqp::getattr($pair,Pair,'$!value'), ) ) ); } method ASSIGN-KEY(::?CLASS:D: TKey \key, Mu \assignval) is raw { my \storage := nqp::getattr(self, Map, '$!storage'); my \WHICH := key.WHICH; my \existing := nqp::atkey(storage,WHICH); nqp::if( nqp::isnull(existing), nqp::stmts( ((my \scalar := nqp::p6scalarfromdesc( # assign before nqp::getattr(self,Hash,'$!descriptor') # binding to get )) = assignval), # type check nqp::bindkey(storage,WHICH,Pair.new(key,scalar)), scalar ), (nqp::getattr(existing,Pair,'$!value') = assignval) ) } method BIND-KEY(TKey \key, TValue \value) is raw { nqp::getattr( nqp::bindkey( nqp::getattr(self,Map,'$!storage'), key.WHICH, Pair.new(key,value) ), Pair, '$!value' ) } method EXISTS-KEY(TKey \key) { nqp::hllbool( nqp::existskey(nqp::getattr(self,Map,'$!storage'),key.WHICH) ) } method DELETE-KEY(TKey \key) { nqp::if( nqp::isnull(my \value := nqp::atkey( nqp::getattr(self,Map,'$!storage'), (my str $WHICH = key.WHICH) )), TValue, nqp::stmts( nqp::deletekey(nqp::getattr(self,Map,'$!storage'),$WHICH), nqp::getattr(value,Pair,'$!value') ) ) } method FLATTENABLE_HASH() { my $flattened := nqp::hash; nqp::if( (my $iter := nqp::iterator(nqp::getattr(self,Map,'$!storage'))), nqp::while( $iter, nqp::bindkey( $flattened, nqp::if( nqp::istype( (my $key := nqp::getattr( nqp::iterval(nqp::shift($iter)), Pair, '$!key' )), Str, ), $key, $key.Str ), nqp::getattr(nqp::iterval($iter),Pair,'$!value') ) ) ); $flattened } method IterationBuffer() { my \storage := nqp::getattr(self, Map, '$!storage'); my \buffer := nqp::create(IterationBuffer); nqp::if( nqp::elems(storage), nqp::stmts( (my \iterator := nqp::iterator(storage)), nqp::setelems(buffer,nqp::elems(storage)), (my int $i = -1), nqp::while( iterator, nqp::bindpos(buffer,++$i,nqp::iterval(nqp::shift(iterator))) ) ) ); buffer } multi method head(::?CLASS:D:) { my \storage := nqp::getattr(self, Map, '$!storage'); nqp::elems(storage) ?? nqp::iterval(nqp::shift(nqp::iterator(storage))) !! Nil } multi method sort(::?CLASS:D: Bool :$safe --> Seq:D) { # With :safe we don't sort directly over the keys but stringify them appropriately first. This is necessary # whenever a key could happen to be a Junction, in which case MERGESORT-REIFIED-LIST-AS would throw due to # autothreading. Seq.new( Rakudo::Iterator.ReifiedList( Rakudo::Sorting.MERGESORT-REIFIED-LIST-AS( self.IterationBuffer.List, ($safe ?? { ($_ ~~ Junction ?? .gist !! .Stringy) with .key } !! *.key)))) } my class Keys does Rakudo::Iterator::Mappy { method pull-one() { $!iter ?? nqp::getattr(nqp::iterval(nqp::shift($!iter)),Pair,'$!key') !! IterationEnd } } method keys() { Seq.new(Keys.new(self)) } my class Values does Rakudo::Iterator::Mappy { method pull-one() is raw { $!iter ?? nqp::getattr(nqp::iterval(nqp::shift($!iter)),Pair,'$!value') !! IterationEnd } } method values() { Seq.new(Values.new(self)) } method kv() { Seq.new(Rakudo::Iterator.Mappy-kv-from-pairs(self)) } method iterator() { Rakudo::Iterator.Mappy-values(self) } my class AntiPairs does Rakudo::Iterator::Mappy { method pull-one() { $!iter ?? nqp::iterval(nqp::shift($!iter)).antipair !! IterationEnd } } method antipairs() { Seq.new(AntiPairs.new(self)) } multi method roll(::?CLASS:D:) { my \storage := nqp::getattr(self, Map, '$!storage'); nqp::if( nqp::elems(storage), nqp::stmts( (my int $i = nqp::add_i(nqp::floor_n(nqp::rand_n(nqp::elems(storage))),1)), (my \iter := nqp::iterator(storage)), nqp::while( nqp::shift(iter) && --$i, nqp::null ), nqp::iterval(iter) ), Nil ) } multi method roll(::?CLASS:D: Callable:D $calculate) { self.roll( $calculate(self.elems) ) } multi method roll(::?CLASS:D: Whatever $) { self.roll(Inf) } my class RollN does Iterator { has $!storage; has $!keys; has $!count; method !SET-SELF(\hash, $count) { $!storage := nqp::getattr(hash,Map,'$!storage'); $!count = $count; my $iter := nqp::iterator($!storage); $!keys := nqp::list_s; nqp::while( $iter, nqp::push_s($!keys,nqp::iterkey_s(nqp::shift($iter))) ); self } method new(\hash, $count) { nqp::create(self)!SET-SELF(hash, $count) } method pull-one() { nqp::if( $!count, nqp::stmts( --$!count, # must be HLL to handle Inf nqp::atkey( $!storage, nqp::atpos_s( $!keys, nqp::floor_n(nqp::rand_n(nqp::elems($!keys))) ) ) ), IterationEnd ) } method is-lazy() { $!count == Inf } method is-deterministic(--> False) { } } multi method roll(::?CLASS:D: $count) { Seq.new( $count > 0 && nqp::elems(nqp::getattr(self,Map,'$!storage')) ?? RollN.new(self, $count) !! Rakudo::Iterator.Empty ) } multi method raku(::?CLASS:D \SELF:) { SELF.rakuseen('Hash', { my $TKey-perl := TKey.raku; my $TValue-perl := TValue.raku; $TKey-perl eq 'Any' && $TValue-perl eq 'Mu' ?? ( '$(' x nqp::iscont(SELF) ~ ':{' ~ SELF.sort.map({.raku}).join(', ') ~ '}' ~ ')' x nqp::iscont(SELF) ) !! '$' x nqp::iscont(SELF) ~ (self.elems ?? "(my $TValue-perl %\{$TKey-perl\} = { self.sort.map({.raku}).join(', ') })" !! "(my $TValue-perl %\{$TKey-perl\})" ) }) } multi method gist(::?CLASS:D:) { self.gistseen: self.^name, { '{' ~ self.sort(:safe).head(100).map(*.gist).join(', ') ~ (', ...' if self.elems > 100) ~ '}' } } # gotta force capture keys to strings or binder fails method Capture() { nqp::elems(nqp::getattr(self,Map,'$!storage')) ?? do { my $cap := nqp::create(Capture); my $h := nqp::hash(); for self.kv -> \k, \v { nqp::bindkey($h, nqp::unbox_s(nqp::istype(k,Str) ?? k !! k.Str), v) } nqp::bindattr($cap,Capture,'%!hash',$h); $cap } !! nqp::create(Capture) } method Map() { self.pairs.Map } method TEMP-LET-LOCALIZE() is raw is implementation-detail { my \handle = self.TEMP-LET-GET-HANDLE; my \iter = nqp::iterator(nqp::getattr(self, Map, '$!storage')); nqp::bindattr(self, Map, '$!storage', my \new-storage = nqp::hash); nqp::while( iter, nqp::stmts( nqp::shift(iter), # What we do here is very much stripped down versions of ASSIGN-KEY and BIND-KEY. (my \p = nqp::iterval(iter)), nqp::bindkey( new-storage, nqp::iterkey_s(iter), Pair.new( p.key, nqp::if( nqp::isrwcont(my \v = p.value), nqp::p6assign(nqp::p6scalarfromdesc(nqp::getattr(self, Hash, '$!descriptor')), v), v ))))); handle } } #line 1 SETTING::src/core.c/Hash.rakumod my class X::Invalid::ComputedValue { ... }; my class Hash { # declared in BOOTSTRAP # my class Hash is Map # has Mu $!descriptor; multi method WHICH(Hash:D: --> ObjAt:D) { self.Mu::WHICH } multi method Hash(Hash:) { self } multi method Map(Hash:U:) { Map } multi method Map(Hash:D: :$view) { # :view is implementation-detail $view # Agreeing that the Hash won't be changed after the .Map ?? nqp::p6bindattrinvres( nqp::create(Map), Map, '$!storage', nqp::getattr(self,Map,'$!storage') ) !! nqp::create(Map).STORE(self, :INITIALIZE, :DECONT) } method clone(Hash:D:) is raw { nqp::p6bindattrinvres( nqp::p6bindattrinvres( nqp::create(self),Map,'$!storage', nqp::clone(nqp::getattr(self,Map,'$!storage'))), Hash, '$!descriptor', nqp::clone($!descriptor)) } method !AT_KEY_CONTAINER(Str:D $key) is raw { nqp::p6scalarfromcertaindesc( ContainerDescriptor::BindHashKey.new($!descriptor,self,$key) ) } multi method AT-KEY(Hash:D: Str:D $key) is raw { nqp::ifnull( nqp::atkey(nqp::getattr(self,Map,'$!storage'),$key), self!AT_KEY_CONTAINER($key) ) } multi method AT-KEY(Hash:D: \key) is raw { nqp::ifnull( nqp::atkey(nqp::getattr(self,Map,'$!storage'),key.Str), self!AT_KEY_CONTAINER(key.Str) ) } proto method STORE_AT_KEY(|) is implementation-detail {*} multi method STORE_AT_KEY(Str:D $key, Mu \value --> Nil) { nqp::bindkey( nqp::getattr(self,Map,'$!storage'), $key, nqp::p6scalarwithvalue($!descriptor,value), ) } multi method STORE_AT_KEY(\key, Mu \value --> Nil) { nqp::bindkey( nqp::getattr(self,Map,'$!storage'), nqp::unbox_s(key.Str), nqp::p6scalarwithvalue($!descriptor,value), ) } proto method STORE(|) {*} multi method STORE(Hash:D: \to_store) { $!descriptor := $!descriptor.next if nqp::eqaddr($!descriptor.WHAT, ContainerDescriptor::UninitializedAttribute); my $temp := nqp::p6bindattrinvres( nqp::clone(self), # make sure we get a possible descriptor as well Map, '$!storage', my $storage := nqp::hash ); my $iter := to_store.iterator; my Mu $x; my Mu $y; nqp::until( nqp::eqaddr(($x := $iter.pull-one),IterationEnd), nqp::if( nqp::istype($x,Pair), $temp.STORE_AT_KEY( nqp::getattr(nqp::decont($x),Pair,'$!key'), nqp::getattr(nqp::decont($x),Pair,'$!value') ), nqp::if( (nqp::istype($x,Map) && nqp::not_i(nqp::iscont($x))), $x.PUSH_FROM_MAP($temp), nqp::if( nqp::eqaddr(($y := $iter.pull-one),IterationEnd), $temp.store-odd-number($x), $temp.STORE_AT_KEY($x,$y) ) ) ) ); nqp::p6bindattrinvres(self,Map,'$!storage',$storage) } multi method STORE(Hash:D: \keys, \values) { $!descriptor := $!descriptor.next if nqp::eqaddr($!descriptor.WHAT, ContainerDescriptor::UninitializedAttribute); my \iterkeys := keys.iterator; my \itervalues := values.iterator; nqp::bindattr(self,Map,'$!storage',nqp::hash); nqp::until( nqp::eqaddr((my \key := iterkeys.pull-one),IterationEnd), self.STORE_AT_KEY(key,itervalues.pull-one) ); self } multi method ASSIGN-KEY(Hash:D: Str:D $key, Mu \assignval) is raw { my \storage := nqp::getattr(self,Map,'$!storage'); nqp::p6assign( nqp::ifnull( nqp::atkey(storage, $key), nqp::bindkey(storage, $key, nqp::p6bindattrinvres(nqp::create(Scalar), Scalar, '$!descriptor', $!descriptor))), assignval) } multi method ASSIGN-KEY(Hash:D: \key, Mu \assignval) is raw { my str $key = key.Str; my \storage := nqp::getattr(self, Map, '$!storage'); nqp::p6assign( nqp::ifnull( nqp::atkey(storage, $key), nqp::bindkey(storage, $key, nqp::p6bindattrinvres(nqp::create(Scalar), Scalar, '$!descriptor', $!descriptor))), assignval) } proto method BIND-KEY(|) {*} multi method BIND-KEY(Hash:D: Str:D $key, Mu \bindval) is raw { nqp::bindkey(nqp::getattr(self,Map,'$!storage'),$key,bindval) } multi method BIND-KEY(Hash:D: \key, Mu \bindval) is raw { nqp::bindkey(nqp::getattr(self,Map,'$!storage'),key.Str,bindval) } multi method DELETE-KEY(Hash:U: --> Nil) { } multi method DELETE-KEY(Hash:D: Str:D $key) { nqp::if( nqp::isnull(my \value := nqp::atkey( nqp::getattr(self,Map,'$!storage'), $key )), $!descriptor.default, nqp::stmts( nqp::deletekey(nqp::getattr(self,Map,'$!storage'),$key), value ) ) } multi method DELETE-KEY(Hash:D: \key) { self.DELETE-KEY(key.Str) } multi method raku(Hash:D \SELF:) { SELF.rakuseen(self.^name, { '$' x nqp::iscont(SELF) # self is always deconted ~ '{' ~ self.sort.map({.raku}).join(', ') ~ '}' }) } multi method gist(Hash:D:) { self.gistseen: self.^name, { '{' ~ self.sort.head(100).map(*.gist).join(', ') ~ (', ...' if self.elems > 100) ~ '}' } } multi method DUMP( Hash:D: :$indent-step = 4, :%ctx ) is implementation-detail { %ctx ?? self.DUMP-OBJECT-ATTRS( nqp::list( '$!descriptor',$!descriptor,'$!storage', nqp::getattr(self,Map,'$!storage') ), :$indent-step, :%ctx ) !! DUMP(self, :$indent-step) } # introspection method keyof() { Str(Any) } # overridden by Hash::Object proto method of() {*} multi method of(Hash:U:) { Mu } multi method of(Hash:D:) { $!descriptor.of } method name(Hash:D:) { $!descriptor.name } method default(Hash:D:) { $!descriptor.default } method dynamic(Hash:D:) { nqp::hllbool($!descriptor.dynamic) } method push(+values) { return self.fail-iterator-cannot-be-lazy('.push') if values.is-lazy; my $previous; my int $has_previous = 0; nqp::if( $has_previous, nqp::stmts( self!_push_construct($previous,$_), ($has_previous = 0) ), nqp::if( nqp::istype($_,Pair), self!_push_construct(.key,.value), nqp::stmts( ($previous := $_), ($has_previous = 1) ) ) ) for values; warn "Trailing item in {self.^name}.push" if $has_previous; self } method append(+values) { return self.fail-iterator-cannot-be-lazy('.append') if values.is-lazy; my $previous; my int $has_previous = 0; nqp::if( $has_previous, nqp::stmts( self!_append_construct($previous,$_), ($has_previous = 0) ), nqp::if( nqp::istype($_,Pair), self!_append_construct(.key,.value), nqp::stmts( ($previous := $_), ($has_previous = 1) ) ) ) for values; warn "Trailing item in {self.^name}.append" if $has_previous; self } proto method classify-list(|) {*} multi method classify-list( &test, \list, :&as ) { return self.fail-iterator-cannot-be-lazy('classify') if list.is-lazy; my \iter = (nqp::istype(list, Iterable) ?? list !! list.list).iterator; my $value := iter.pull-one; unless $value =:= IterationEnd { my Mu $tested := test($value); # multi-level classify if nqp::istype($tested, Iterable) { my $els = $tested.elems; loop { my @keys = @$tested; @keys == $els or X::Invalid::ComputedValue.new( :name, :method, :value('an item with different number of elements ' ~ 'in it than previous items'), :reason('all values need to have the same number ' ~ 'of elements. Mixed-level classification is ' ~ 'not supported.'), ).throw; my $last := @keys.pop; my $hash = self; $hash = $hash{$_} //= self.new for @keys; $hash{$last}.push(&as ?? as($value) !! $value); last if ($value := iter.pull-one) =:= IterationEnd; $tested := test($value); }; } # just a simple classify else { loop { self{$tested}.push(&as ?? as($value) !! $value); last if ($value := iter.pull-one) =:= IterationEnd; nqp::istype(($tested := test($value)), Iterable) and X::Invalid::ComputedValue.new( :name, :method, :value('an item with different number of elements ' ~ 'in it than previous items'), :reason('all values need to have the same number ' ~ 'of elements. Mixed-level classification is ' ~ 'not supported.'), ).throw; }; } } self; } multi method classify-list( %test, |c ) { self.classify-list( { %test{$^a} }, |c ); } multi method classify-list( @test, |c ) { self.classify-list( { @test[$^a] }, |c ); } multi method classify-list(&test, **@list, |c) { self.classify-list(&test, @list, |c); } proto method categorize-list(|) {*} multi method categorize-list( &test, \list, :&as ) { return self.fail-iterator-cannot-be-lazy('.categorize') if list.is-lazy; my \iter = (nqp::istype(list, Iterable) ?? list !! list.list).iterator; my $value := iter.pull-one; unless $value =:= IterationEnd { my Mu $tested := test($value); # multi-level categorize if nqp::istype($tested[0],Iterable) { my $els = $tested[0].elems; loop { for $tested.cache -> $cat { my @keys = @$cat or next; my $last := @keys.pop; my $hash = self; $hash = $hash{$_} //= self.new for @keys; $hash{$last}.push(&as ?? as($value) !! $value); } last if ($value := iter.pull-one) =:= IterationEnd; $tested := test($value); nqp::istype($tested[0],Iterable) and $els == $tested[0] or X::Invalid::ComputedValue.new( :name, :method, :value('an item with different number of elements ' ~ 'in it than previous items'), :reason('all values need to have the same number ' ~ 'of elements. Mixed-level classification is ' ~ 'not supported.'), ).throw; } } # simple categorize else { loop { my $tested-iter := $tested.iterator; until ($_ := $tested-iter.pull-one) =:= IterationEnd { self{$_}.push(&as ?? as($value) !! $value) } last if ($value := iter.pull-one) =:= IterationEnd; nqp::istype(($tested := test($value))[0], Iterable) and X::Invalid::ComputedValue.new( :name, :method, :value('an item with different number of elements ' ~ 'in it than previous items'), :reason('all values need to have the same number ' ~ 'of elements. Mixed-level classification is ' ~ 'not supported.'), ).throw; }; } } self; } multi method categorize-list( %test, |c ) { self.categorize-list( { %test{$^a} }, |c ); } multi method categorize-list( @test, |c ) { self.categorize-list( { @test[$^a] }, |c ); } multi method categorize-list( &test, **@list, |c ) { self.categorize-list( &test, @list, |c ); } # push a value onto a hash slot, constructing an array if necessary method !_push_construct(Mu $key, Mu \value --> Nil) { nqp::istype((my \current := self.AT-KEY($key)),Array) ?? current.push(value) !! (current = self.EXISTS-KEY($key) ?? [current,value] !! value) } # append values into a hash slot, constructing an array if necessary method !_append_construct(Mu $key, Mu \value --> Nil) { nqp::istype((my \current := self.AT-KEY($key)),Array) ?? current.append(|value) !! (current = self.EXISTS-KEY($key) ?? [|current,|value] !! value) } my class LTHandle { has Mu $!storage; has Mu $!descriptor; } method TEMP-LET-GET-HANDLE() is raw is implementation-detail { my \handle = nqp::create(LTHandle); nqp::bindattr(handle, LTHandle, '$!storage', nqp::getattr(self, Map, '$!storage')); nqp::bindattr(handle, LTHandle, '$!descriptor', nqp::getattr(self, Hash, '$!descriptor')); handle } method TEMP-LET-LOCALIZE() is raw is implementation-detail { my \handle = self.TEMP-LET-GET-HANDLE; # Re-initialize self from the original state by taking into account conterization status of keys. my \iter = nqp::iterator(nqp::getattr(self, Map, '$!storage')); nqp::bindattr(self, Map, '$!storage', my \new-storage = nqp::hash); nqp::while( iter, nqp::stmts( nqp::shift(iter), (my \v = nqp::iterval(iter)), nqp::bindkey( new-storage, nqp::iterkey_s(iter), nqp::if( nqp::isrwcont(v), nqp::p6assign(nqp::p6scalarfromdesc(nqp::getattr(self, Hash, '$!descriptor')), v), v )))); handle } method TEMP-LET-RESTORE(\handle --> Nil) is implementation-detail { nqp::bindattr(self, Hash, '$!descriptor', nqp::getattr(handle, LTHandle, '$!descriptor')); nqp::bindattr(self, Map, '$!storage', nqp::getattr(handle, LTHandle, '$!storage')); } method ^parameterize(Mu:U \hash, Mu \of, Mu \keyof = Str(Any)) { # fast path if nqp::eqaddr(of,Mu) && nqp::eqaddr(keyof,Str(Any)) { hash } # error checking elsif nqp::isconcrete(of) { "Can not parameterize {hash.^name} with {of.raku}" } # only constraint on type elsif nqp::eqaddr(keyof,Str(Any)) { my $what := hash.^mixin(Hash::Typed[of]); # needs to be done in COMPOSE phaser when that works $what.^set_name: hash.^name ~ '[' ~ of.^name ~ ']'; $what } # error checking elsif nqp::isconcrete(keyof) { "Can not parameterize {hash.^name} with {keyof.raku}" } # no support for native types yet elsif nqp::objprimspec(keyof) { 'Parameterization of hashes with native ' ~ keyof.raku ~ ' not yet implemented. Sorry.' } # a true object hash else { my $what := hash.^mixin(Hash::Object[of, keyof]); # needs to be done in COMPOSE phaser when that works $what.^set_name: hash.^name ~ '[' ~ of.^name ~ ',' ~ keyof.^name ~ ']'; $what } } } proto sub circumfix:<{ }>(|) {*} multi sub circumfix:<{ }>(*@elems) { my % = @elems } # XXX parse dies with 'don't change grammar in the setting, please!' # with ordinary sub declaration #sub circumfix:<:{ }>(*@elems) { Hash.^parameterize(Mu,Any).new(@elems) } BEGIN my &circumfix:<:{ }> = sub (*@e) { Hash.^parameterize(Mu,Any).new(@e) } proto sub hash(|) {*} multi sub hash(*%h) { %h } multi sub hash(*@a, *%h) { my % = flat @a, %h } #line 1 SETTING::src/core.c/Stash.rakumod my class Stash { # declared in BOOTSTRAP # class Stash is Hash # has str $!longname; # has $!lock; multi method new(Stash: --> Stash:D) { nqp::p6bindattrinvres(nqp::create(self), Stash, '$!lock', Lock.new) } method clone(Stash:D:) is raw { my $cloned := callsame(); nqp::bindattr_s( $cloned, Stash, '$!longname', nqp::getattr_s(self, Stash, '$!longname')); nqp::bindattr($cloned, Stash, '$!lock', Lock.new); $cloned } multi method AT-KEY(Stash:D: Str:D $key) is raw { my \storage := nqp::getattr(self,Map,'$!storage'); nqp::existskey(storage,$key) ?? nqp::atkey(storage,$key) !! nqp::p6scalarfromdesc( ContainerDescriptor::BindHashKey.new(Mu, self, $key) ) } multi method AT-KEY(Stash:D: Str() $key, :$global_fallback!) is raw { my \storage := nqp::getattr(self,Map,'$!storage'); nqp::if( nqp::existskey(storage,$key), nqp::atkey(storage,$key), nqp::if( $global_fallback, nqp::if( nqp::existskey(GLOBAL.WHO,$key), nqp::atkey(GLOBAL.WHO,$key), "Could not find symbol '$key' in '{self}'".Failure ), nqp::p6scalarfromdesc( ContainerDescriptor::BindHashKey.new(Mu, self, $key) ) ) ) } method VIVIFY-KEY(Stash:D: $key) is raw is implementation-detail { self.BIND-KEY($key, (my str $sigil = nqp::substr($key,0,1)) eq '$' ?? (my $) !! $sigil eq '&' ?? (my &) !! $sigil eq '@' ?? [] !! {} # assume % ) unless self.EXISTS-KEY($key); self.AT-KEY($key) } # New proto is introduced here in order to cut off Hash candidates completely. There are few reasons to do so: # 1. Hash is not thread-safe whereas Stash claims to be # 2. Stash candidates are fully overriding their counterparts from Hash # 3. Minor: could result in some minor improvement in multi-dispatch lookups due to lesser number of candidates proto method ASSIGN-KEY(Stash:D: $, $) {*} multi method ASSIGN-KEY(Stash:D: Str:D $key, Mu \assignval) is raw { my $storage := nqp::getattr(self,Map,'$!storage'); my \existing-key := nqp::atkey($storage, $key); if nqp::isnull(existing-key) { $!lock.protect: { my \scalar := nqp::bindkey( ($storage := nqp::clone($storage)), $key, nqp::p6assign( nqp::p6bindattrinvres( nqp::create(Scalar), Scalar, '$!descriptor', nqp::getattr(self, Hash, '$!descriptor')), assignval) ); nqp::atomicbindattr(self, Map, '$!storage', $storage); scalar }; } else { nqp::p6assign(existing-key, assignval); } } multi method ASSIGN-KEY(Stash:D: \key, Mu \assignval) is raw { nextwith(key.Str, assignval) } # See the comment for ASSIGN-KEY on proto. proto method BIND-KEY(|) {*} multi method BIND-KEY(Stash:D: Str:D $key, Mu \bindval) is raw { $!lock.protect: { my $storage := nqp::clone(nqp::getattr(self,Map,'$!storage')); nqp::bindkey($storage, $key, bindval); nqp::atomicbindattr(self, Map, '$!storage', $storage); } bindval } multi method BIND-KEY(Stash:D: \key, Mu \bindval) is raw { nextwith(key.Str, bindval) } method package_at_key(Stash:D: str $key) { my $storage := nqp::getattr(self,Map,'$!storage'); nqp::ifnull( nqp::atkey($storage,$key), $!lock.protect({ my $pkg := Metamodel::PackageHOW.new_type(:name("{$!longname}::$key")); $pkg.^compose; $storage := nqp::clone($storage); nqp::bindkey($storage,$key,$pkg); nqp::atomicbindattr(self, Map, '$!storage', $storage); $pkg }) ) } multi method gist(Stash:D:) { self.Str } multi method Str(Stash:D:) { nqp::isnull_s($!longname) ?? '' !! $!longname } method merge-symbols(Stash:D: Mu \globalish) { # NQP gives a Hash, not a Stash if nqp::defined(globalish) { $!lock.protect: { # For thread safety, ModuleLoader's merge_globals is calling this method when its target is a Stash. # Therefore we call it on cloned symbol hash. This prevents recursion and works slightly faster. nqp::gethllsym('Raku','ModuleLoader').merge_globals( (my $storage := nqp::clone(my $old-storage := nqp::getattr(self,Map,'$!storage'))), globalish ); nqp::atomicbindattr(self, Map, '$!storage', $storage); } } } } #line 1 SETTING::src/core.c/Label.rakumod my class Label { has Str $.name; has Str $.file; has Int $.line; has Str $!prematch; has Str $!postmatch; method new(:$name, :$line, :$prematch, :$postmatch) { # XXX Register in &?BLOCK.labels when we have &?BLOCK. my $obj := nqp::create(self); nqp::bindattr($obj, Label, '$!name', $name); nqp::bindattr($obj, Label, '$!file', nqp::p6box_s(nqp::ifnull(nqp::getlexdyn('$?FILES'), ''))); nqp::bindattr($obj, Label, '$!line', $line); nqp::bindattr($obj, Label, '$!prematch', nqp::p6box_s($prematch)); nqp::bindattr($obj, Label, '$!postmatch', nqp::p6box_s($postmatch)); $obj } method goto(*@) { NYI("{self.^name}.goto()").throw; } method leave(*@) { NYI("{self.^name}.leave()").throw; } multi method Str(Label:D:) { "$!name $!file:$!line" } multi method gist(Label:D:) { my ($red,$clear,$green,$yellow,$eject) = Rakudo::Internals.error-rcgye; "Label<$!name>(at $!file:$!line, '$green$!prematch$yellow$eject$red$!name$green$!postmatch$clear')" } method Int() { nqp::where(self) } method next() { my Mu $ex := nqp::newexception(); nqp::setpayload($ex, self); nqp::setextype($ex, nqp::const::CONTROL_NEXT + nqp::const::CONTROL_LABELED); nqp::throw($ex); } method redo() { my Mu $ex := nqp::newexception(); nqp::setpayload($ex, self); nqp::setextype($ex, nqp::const::CONTROL_REDO + nqp::const::CONTROL_LABELED); nqp::throw($ex); } method last() { my Mu $ex := nqp::newexception(); nqp::setpayload($ex, self); nqp::setextype($ex, nqp::const::CONTROL_LAST + nqp::const::CONTROL_LABELED); nqp::throw($ex); } } #line 1 SETTING::src/core.c/PseudoStash.rakumod my class X::Bind { ... } my class X::Caller::NotDynamic { ... } my class PseudoStash is Map { has Mu $!ctx; has int $!mode; # Lookup modes. my int constant PICK_CHAIN_BY_NAME = 0; my int constant STATIC_CHAIN = 1; my int constant DYNAMIC_CHAIN = 2; my int constant PRECISE_SCOPE = 4; my int constant REQUIRE_DYNAMIC = 8; method new() { my $obj := nqp::create(self); my $ctx := nqp::ctxcaller(nqp::ctx()); nqp::bindattr($obj, PseudoStash, '$!ctx', $ctx); nqp::bindattr($obj, Map, '$!storage', nqp::ctxlexpad($ctx)); $obj } sub ok-to-include(Mu \value) { nqp::not_i(nqp::istype(value,Code) && value.is-implementation-detail) } method keys(:$implementation-detail --> Seq:D) { $implementation-detail ?? (nextsame) !! Seq.new(self.iterator).map: { .key if ok-to-include(.value) } } method values(:$implementation-detail --> Seq:D) { $implementation-detail ?? (nextsame) !! callsame.grep: &ok-to-include } method kv(:$implementation-detail --> Seq:D) { $implementation-detail ?? (nextsame) !! Seq.new(self.iterator).map: { (.key,.value).Slip if ok-to-include(.value) } } method pairs(:$implementation-detail --> Seq:D) { $implementation-detail ?? (nextsame) !! Seq.new(self.iterator).map: { $_ if ok-to-include(.value) } } method sort(:$implementation-detail --> Seq:D) { $implementation-detail ?? (nextsame) !! self.pairs.sort } method elems(:$implementation-detail) { $implementation-detail ?? (nextsame) !! self.values.elems } multi method WHICH(PseudoStash:D: --> ObjAt:D) { self.Mu::WHICH } my $pseudoers := nqp::hash( 'MY', -> $cur { my $stash := nqp::clone($cur); nqp::bindattr_i($stash, PseudoStash, '$!mode', PRECISE_SCOPE); nqp::setwho( Metamodel::ModuleHOW.new_type(:name('MY')), $stash); }, 'CORE', -> $cur { # In 6.c and 6.d implementations of rakudo CORE was always pointing at the outermost setting. # XXX If EVAL get :unit option we'd need to check for intermidiate CORE.setting. But for now this code # should be ok. my Mu $ctx := nqp::getattr(nqp::decont($cur), PseudoStash, '$!ctx'); my $found-ctx := nqp::null(); until nqp::isnull($ctx) { my $pad := nqp::ctxlexpad($ctx); if nqp::existskey($pad, 'CORE-SETTING-REV') { $found-ctx := $ctx; } $ctx := nqp::ctxouterskipthunks($ctx); } nqp::if( nqp::isnull($found-ctx), Nil, nqp::stmts( (my $stash := nqp::create(PseudoStash)), nqp::bindattr($stash, Map, '$!storage', nqp::ctxlexpad($found-ctx)), nqp::bindattr($stash, PseudoStash, '$!ctx', $found-ctx), nqp::bindattr_i($stash, PseudoStash, '$!mode', PRECISE_SCOPE), nqp::setwho( Metamodel::ModuleHOW.new_type(:name('CORE')), $stash))) }, 'CALLER', -> $cur { nqp::if( nqp::isnull( my Mu $ctx := nqp::ctxcallerskipthunks( nqp::getattr(nqp::decont($cur), PseudoStash, '$!ctx'))), Nil, nqp::stmts( (my $stash := nqp::create(PseudoStash)), nqp::bindattr($stash, Map, '$!storage', nqp::ctxlexpad($ctx)), nqp::bindattr($stash, PseudoStash, '$!ctx', $ctx), nqp::bindattr_i($stash, PseudoStash, '$!mode', PRECISE_SCOPE +| REQUIRE_DYNAMIC), nqp::setwho( Metamodel::ModuleHOW.new_type(:name('CALLER')), $stash))) }, 'OUTER', -> $cur { my Mu $ctx := nqp::ctxouterskipthunks( nqp::getattr(nqp::decont($cur),PseudoStash,'$!ctx')); if nqp::isnull($ctx) { Nil } else { my $stash := nqp::create(PseudoStash); nqp::bindattr($stash, Map, '$!storage', nqp::ctxlexpad($ctx)); nqp::bindattr($stash, PseudoStash, '$!ctx', $ctx); nqp::bindattr_i($stash, PseudoStash, '$!mode', PRECISE_SCOPE); nqp::setwho( Metamodel::ModuleHOW.new_type(:name('OUTER')), $stash) } }, 'LEXICAL', -> $cur { my $stash := nqp::clone($cur); nqp::bindattr_i($stash, PseudoStash, '$!mode', STATIC_CHAIN); nqp::setwho( Metamodel::ModuleHOW.new_type(:name('LEXICAL')), $stash); }, 'OUTERS', -> $cur { my Mu $ctx := nqp::ctxouterskipthunks( nqp::getattr(nqp::decont($cur), PseudoStash, '$!ctx')); if nqp::isnull($ctx) { Nil } else { my $stash := nqp::create(PseudoStash); nqp::bindattr($stash, Map, '$!storage', nqp::ctxlexpad($ctx)); nqp::bindattr($stash, PseudoStash, '$!ctx', $ctx); nqp::bindattr_i($stash, PseudoStash, '$!mode', STATIC_CHAIN); nqp::setwho( Metamodel::ModuleHOW.new_type(:name('OUTERS')), $stash) } }, 'DYNAMIC', -> $cur { my $stash := nqp::clone($cur); nqp::bindattr_i($stash, PseudoStash, '$!mode', DYNAMIC_CHAIN); nqp::setwho( Metamodel::ModuleHOW.new_type(:name('DYNAMIC')), $stash); }, 'CALLERS', -> $cur { nqp::if( nqp::isnull( my Mu $ctx := nqp::ctxcallerskipthunks( nqp::getattr(nqp::decont($cur), PseudoStash, '$!ctx'))), Nil, nqp::stmts( (my $stash := nqp::create(PseudoStash)), nqp::bindattr($stash, Map, '$!storage', nqp::ctxlexpad($ctx)), nqp::bindattr($stash, PseudoStash, '$!ctx', $ctx), nqp::bindattr_i($stash, PseudoStash, '$!mode', DYNAMIC_CHAIN +| REQUIRE_DYNAMIC), nqp::setwho( Metamodel::ModuleHOW.new_type(:name('CALLERS')), $stash))) }, 'UNIT', -> $cur { my Mu $ctx := nqp::getattr(nqp::decont($cur), PseudoStash, '$!ctx'); until nqp::isnull($ctx) || nqp::existskey(nqp::ctxlexpad($ctx), '!UNIT_MARKER') { $ctx := nqp::ctxouterskipthunks($ctx); } nqp::if( nqp::isnull($ctx), Nil, nqp::stmts( (my $stash := nqp::create(PseudoStash)), nqp::bindattr($stash, Map, '$!storage',nqp::ctxlexpad($ctx)), nqp::bindattr($stash, PseudoStash, '$!ctx', $ctx), nqp::bindattr_i($stash, PseudoStash, '$!mode', PRECISE_SCOPE), nqp::setwho( Metamodel::ModuleHOW.new_type(:name('UNIT')), $stash))) }, 'SETTING', -> $cur { # Same as UNIT, but go a little further out (two steps, for # internals reasons). my Mu $ctx := nqp::getattr(nqp::decont($cur), PseudoStash, '$!ctx'); until nqp::isnull($ctx) || nqp::existskey(nqp::ctxlexpad($ctx), '!UNIT_MARKER') { $ctx := nqp::ctxouterskipthunks($ctx); } my $is-rakuast := nqp::isconcrete($ctx) && nqp::existskey(nqp::ctxlexpad($ctx), '!RAKUAST_MARKER'); nqp::if( nqp::isnull($ctx) || nqp::isnull($ctx := nqp::ctxouter($ctx)) || nqp::isnull(nqp::if($is-rakuast, $ctx, ($ctx := nqp::ctxouter($ctx)))), Nil, nqp::stmts( (my $stash := nqp::create(PseudoStash)), nqp::bindattr($stash, Map, '$!storage', nqp::ctxlexpad($ctx)), nqp::bindattr($stash, PseudoStash, '$!ctx', $ctx), nqp::bindattr_i($stash, PseudoStash, '$!mode', STATIC_CHAIN), nqp::setwho( Metamodel::ModuleHOW.new_type(:name('SETTING')), $stash))) }, 'CLIENT', -> $cur { my $pkg := nqp::getlexrel( nqp::getattr(nqp::decont($cur), PseudoStash, '$!ctx'), '$?PACKAGE'); my Mu $ctx := nqp::ctxcallerskipthunks( nqp::getattr(nqp::decont($cur), PseudoStash, '$!ctx')); while nqp::eqaddr(nqp::getlexrel($ctx, '$?PACKAGE'), $pkg) { $ctx := nqp::ctxcallerskipthunks($ctx); die "No client package found" unless $ctx; } my $stash := nqp::create(PseudoStash); nqp::bindattr($stash, Map, '$!storage', nqp::ctxlexpad($ctx)); nqp::bindattr($stash, PseudoStash, '$!ctx', $ctx); nqp::bindattr_i($stash, PseudoStash, '$!mode', PRECISE_SCOPE +| REQUIRE_DYNAMIC); nqp::setwho( Metamodel::ModuleHOW.new_type(:name('CLIENT')), $stash); }, 'OUR', -> $cur { nqp::getlexrel( nqp::getattr(nqp::decont($cur), PseudoStash, '$!ctx'), '$?PACKAGE') } ); multi method AT-KEY(PseudoStash:D: Str() $key) is raw { nqp::if( nqp::existskey($pseudoers,$key), nqp::atkey($pseudoers,$key)(self), nqp::stmts( (my $is-star := nqp::iseq_i(nqp::ord(nqp::unbox_s($key),1),42)), # has * twigil nqp::if( nqp::bitand_i($!mode,PRECISE_SCOPE), nqp::stmts( (my Mu $res := nqp::if( nqp::existskey( nqp::getattr(self,Map,'$!storage'),nqp::unbox_s($key)), nqp::atkey( nqp::getattr(self,Map,'$!storage'),nqp::unbox_s($key)), Nil )), nqp::if( (nqp::not_i(nqp::eqaddr($res,Nil)) && nqp::bitand_i($!mode,REQUIRE_DYNAMIC)), nqp::unless( ($is-star || try $res.VAR.dynamic), X::Caller::NotDynamic.new(symbol => $key).throw)), $res ), nqp::if( nqp::bitand_i($!mode,nqp::bitor_i(DYNAMIC_CHAIN,PICK_CHAIN_BY_NAME)) && $is-star, nqp::ifnull( nqp::getlexreldyn( nqp::getattr(self,PseudoStash,'$!ctx'),nqp::unbox_s($key)), Nil ), nqp::ifnull( # STATIC_CHAIN nqp::getlexrel( nqp::getattr(self,PseudoStash,'$!ctx'),nqp::unbox_s($key)), Nil ))))) } multi method ASSIGN-KEY(PseudoStash:D: Str() $key, Mu \value) is raw { self.AT-KEY($key) = value } method BIND-KEY(Str() $key, \value) is raw { nqp::if( nqp::existskey($pseudoers,$key), X::Bind.new(target => "pseudo-package $key").throw, nqp::if( nqp::bitand_i($!mode,PRECISE_SCOPE), nqp::bindkey( nqp::getattr(self,Map,'$!storage'),nqp::unbox_s($key),value), nqp::if( (nqp::bitand_i($!mode,nqp::bitor_i(DYNAMIC_CHAIN,PICK_CHAIN_BY_NAME)) && nqp::iseq_i(nqp::ord(nqp::unbox_s($key),1),42)), # "*" (die "Binding to dynamic variables not yet implemented"), (die "This case of binding is not yet implemented")))) } # for some reason we get an ambiguous dispatch error by making this a multi method EXISTS-KEY(PseudoStash:D: Str() $key) { nqp::unless( nqp::existskey($pseudoers,$key), nqp::hllbool( nqp::if( nqp::bitand_i($!mode,PRECISE_SCOPE), nqp::existskey( nqp::getattr(self,Map,'$!storage'),nqp::unbox_s($key)), nqp::if( nqp::bitand_i( $!mode,nqp::bitor_i(DYNAMIC_CHAIN,PICK_CHAIN_BY_NAME) ) && nqp::iseq_i(nqp::ord(nqp::unbox_s($key),1),42), # "*" nqp::not_i( nqp::isnull( nqp::getlexreldyn( nqp::getattr(self, PseudoStash, '$!ctx'), nqp::unbox_s($key)))), nqp::not_i( # STATIC_CHAIN nqp::isnull( nqp::getlexrel( nqp::getattr(self, PseudoStash, '$!ctx'), nqp::unbox_s($key)))))))) } } #line 1 SETTING::src/core.c/Parameter.rakumod my class Parameter { # declared in BOOTSTRAP # class Parameter is Any # has str $!variable_name # has @!named_names # has @!type_captures # has int $!flags # has @!post_constraints # has Signature $!sub_signature # has Code $!default_value # has Mu $!container_descriptor; # has Mu $!attr_package; # has Mu $!why; my constant $SIG_ELEM_BIND_CAPTURE = 1 +< 0; my constant $SIG_ELEM_BIND_PRIVATE_ATTR = 1 +< 1; my constant $SIG_ELEM_BIND_PUBLIC_ATTR = 1 +< 2; my constant $SIG_ELEM_SLURPY_POS = 1 +< 3; my constant $SIG_ELEM_SLURPY_NAMED = 1 +< 4; my constant $SIG_ELEM_SLURPY_LOL = 1 +< 5; my constant $SIG_ELEM_INVOCANT = 1 +< 6; my constant $SIG_ELEM_MULTI_INVOCANT = 1 +< 7; my constant $SIG_ELEM_IS_RW = 1 +< 8; my constant $SIG_ELEM_IS_COPY = 1 +< 9; my constant $SIG_ELEM_IS_RAW = 1 +< 10; my constant $SIG_ELEM_IS_OPTIONAL = 1 +< 11; my constant $SIG_ELEM_ARRAY_SIGIL = 1 +< 12; my constant $SIG_ELEM_HASH_SIGIL = 1 +< 13; my constant $SIG_ELEM_DEFAULT_FROM_OUTER = 1 +< 14; my constant $SIG_ELEM_IS_CAPTURE = 1 +< 15; my constant $SIG_ELEM_UNDEFINED_ONLY = 1 +< 16; my constant $SIG_ELEM_DEFINED_ONLY = 1 +< 17; my constant $SIG_ELEM_DEFAULT_IS_LITERAL = 1 +< 20; my constant $SIG_ELEM_SLURPY_ONEARG = 1 +< 24; my constant $SIG_ELEM_CODE_SIGIL = 1 +< 25; my constant $SIG_ELEM_IS_COERCIVE = 1 +< 26; my constant $SIG_ELEM_IS_NOT_POSITIONAL = $SIG_ELEM_SLURPY_POS +| $SIG_ELEM_SLURPY_NAMED +| $SIG_ELEM_SLURPY_LOL +| $SIG_ELEM_SLURPY_ONEARG +| $SIG_ELEM_IS_CAPTURE; my constant $SIG_ELEM_IS_SLURPY = $SIG_ELEM_SLURPY_POS +| $SIG_ELEM_SLURPY_NAMED +| $SIG_ELEM_SLURPY_LOL +| $SIG_ELEM_SLURPY_ONEARG; my constant $SIG_ELEM_IS_NOT_READONLY = $SIG_ELEM_IS_RW +| $SIG_ELEM_IS_COPY +| $SIG_ELEM_IS_RAW; my constant $sigils2bit = nqp::hash( Q/@/, $SIG_ELEM_ARRAY_SIGIL, Q/%/, $SIG_ELEM_HASH_SIGIL, Q/&/, $SIG_ELEM_CODE_SIGIL, Q/\/, $SIG_ELEM_IS_RAW, Q/|/, $SIG_ELEM_IS_CAPTURE +| $SIG_ELEM_IS_RAW, ); sub set-sigil-bits(str $sigil, \flags --> Nil) { if nqp::atkey($sigils2bit,$sigil) -> $bit { flags +|= $bit } } sub definitize-type(Str:D $type, Bool:D $definite --> Mu) { Metamodel::DefiniteHOW.new_type(:base_type(::($type)), :$definite) } sub str-to-type(Str:D $type, Int:D $flags is rw --> Mu) { if $type.ends-with(Q/:D/) { $flags +|= $SIG_ELEM_DEFINED_ONLY; definitize-type($type.chop(2), True) } elsif $type.ends-with(Q/:U/) { $flags +|= $SIG_ELEM_UNDEFINED_ONLY; definitize-type($type.chop(2), False) } elsif $type.ends-with(Q/:_/) { ::($type.chop(2)) } else { ::($type) } } submethod BUILD( Parameter:D: Str:D :$name is copy = "", Int:D :$flags is copy = 0, Bool:D :$named is copy = False, Bool:D :$optional is copy = False, Bool:D :$mandatory is copy = False, Bool:D :$is-copy = False, Bool:D :$is-raw = False, Bool:D :$is-rw = False, Bool:D :$multi-invocant = True, *%args # type / default / where / sub_signature captured through %_ --> Nil ) { if $name { # specified a name? if $name.ends-with(Q/!/) { $name = $name.substr(0,*-1); $mandatory = True; } elsif $name.ends-with(Q/?/) { $name = $name.substr(0,*-1); $optional = True; } my $sigil = $name.substr(0,1); if $sigil eq Q/:/ { $name = $name.substr(1); $sigil = $name.substr(0,1); $named = True; } elsif $sigil eq Q/+/ { $name = $name.substr(1); $sigil = $name.substr(-1,1); $flags +|= $SIG_ELEM_IS_RAW +| $SIG_ELEM_SLURPY_ONEARG; } if $name.ends-with(Q/)/) { if $named { my $start = $name.index(Q/(/); # XXX handle multiple @!named_names := nqp::list_s($name.substr(0,$start)); $name := $name.substr($start + 1, *-1); } else { die "Can only specify alternative names on named parameters: $name"; } } if $sigil eq Q/*/ { # is it a slurpy? $name = $name.substr(1); $sigil = $name.substr(0,1); if %args.EXISTS-KEY('type') { die "Slurpy named parameters with type constraints are not supported|" } if $sigil eq Q/*/ { # is it a double slurpy? $name = $name.substr(1); $sigil = $name.substr(0,1); $flags +|= $SIG_ELEM_SLURPY_LOL; } elsif $sigil eq Q/@/ { # a slurpy array? $flags +|= $SIG_ELEM_SLURPY_POS; } elsif $sigil eq Q/%/ { # a slurpy hash? $flags +|= $SIG_ELEM_SLURPY_NAMED; } } if $name.substr(1,1) -> $twigil { if $twigil eq Q/!/ { $flags +|= $SIG_ELEM_BIND_PRIVATE_ATTR; } elsif $twigil eq Q/./ { $flags +|= $SIG_ELEM_BIND_PUBLIC_ATTR; } } set-sigil-bits($sigil, $flags); $name = $name.substr(1) if $sigil eq Q/\/ || $sigil eq Q/|/; } if %args.EXISTS-KEY('type') { my $type := %args.AT-KEY('type'); if $type.DEFINITE { if nqp::istype($type,Str) { if $type.ends-with(Q/)/) { my $start = $type.index(Q/(/); my $constraint-type := str-to-type($type.substr($start + 1, *-1), my $); my $target-type := str-to-type($type.substr(0, $start), $flags); $!type := Metamodel::CoercionHOW.new_type($target-type, $constraint-type); } else { $!type := str-to-type($type, $flags) } } else { $!type := $type.WHAT; } } else { $!type := $type; } } else { $!type := Any; } if %args.EXISTS-KEY('default') { my $default := %args.AT-KEY('default'); if nqp::istype($default,Code) { $!default_value := $default; } else { nqp::bind($!default_value,$default); $flags +|= $SIG_ELEM_DEFAULT_IS_LITERAL; } $flags +|= $SIG_ELEM_IS_OPTIONAL; } if %args.EXISTS-KEY('where') { nqp::bind(@!post_constraints,nqp::list(%args.AT-KEY('where'))); } if %args.EXISTS-KEY('sub-signature') { $!sub_signature := %args.AT-KEY('sub-signature'); } if $named { $flags +|= $SIG_ELEM_IS_OPTIONAL unless $mandatory; @!named_names := nqp::list_s($name.substr(1)) unless @!named_names; } else { $flags +|= $SIG_ELEM_IS_OPTIONAL if $optional; } $flags +|= $SIG_ELEM_MULTI_INVOCANT if $multi-invocant; $flags +|= $SIG_ELEM_IS_COPY if $is-copy; $flags +|= $SIG_ELEM_IS_RAW if $is-raw; $flags +|= $SIG_ELEM_IS_RW if $is-rw; $flags +|= $SIG_ELEM_IS_COERCIVE if $!type.^archetypes.coercive; $!variable_name = $name if $name; $!flags = $flags; } method name(Parameter:D: --> Str:D) { nqp::isnull_s($!variable_name) ?? '' !! $!variable_name } method usage-name(Parameter:D: --> Str:D) { nqp::isnull_s($!variable_name) ?? '' !! nqp::iseq_i(nqp::index('@$%&',nqp::substr($!variable_name,0,1)),-1) ?? $!variable_name !! nqp::iseq_i(nqp::index('*!.',nqp::substr($!variable_name,1,1)),-1) ?? nqp::substr($!variable_name,1) !! nqp::substr($!variable_name,2) } method sigil(Parameter:D: --> Str:D) { nqp::bitand_i($!flags,$SIG_ELEM_IS_CAPTURE) ?? '|' !! nqp::isnull_s($!variable_name) ?? nqp::bitand_i($!flags,$SIG_ELEM_ARRAY_SIGIL) ?? '@' !! nqp::bitand_i($!flags,$SIG_ELEM_HASH_SIGIL) ?? '%' !! nqp::bitand_i($!flags,$SIG_ELEM_CODE_SIGIL) ?? '&' !! nqp::bitand_i($!flags,$SIG_ELEM_IS_RAW) && $.name && nqp::isnull($!default_value) ?? '\\' !! '$' !! nqp::bitand_i($!flags,$SIG_ELEM_IS_RAW) && nqp::iseq_i( nqp::index('@$%&',nqp::substr($!variable_name,0,1)),-1) ?? '\\' !! nqp::substr($!variable_name,0,1) } method twigil(Parameter:D: --> Str:D) { nqp::bitand_i($!flags,$SIG_ELEM_BIND_PUBLIC_ATTR) ?? '.' !! nqp::bitand_i($!flags,$SIG_ELEM_BIND_PRIVATE_ATTR) ?? '!' !! nqp::isnull_s($!variable_name) ?? '' !! nqp::eqat($!variable_name,"*",1) ?? '*' !! '' } method prefix(Parameter:D: --> Str:D) { nqp::bitand_i($!flags, nqp::bitor_i($SIG_ELEM_SLURPY_POS, $SIG_ELEM_SLURPY_NAMED)) ?? '*' !! nqp::bitand_i($!flags, $SIG_ELEM_SLURPY_LOL) ?? '**' !! nqp::bitand_i($!flags, $SIG_ELEM_SLURPY_ONEARG) ?? '+' !! '' } method suffix(Parameter:D: --> Str:D) { nqp::isnull(@!named_names) ?? nqp::bitand_i($!flags, $SIG_ELEM_IS_OPTIONAL) && nqp::isnull($!default_value) ?? '?' !! '' !! nqp::bitand_i($!flags, $SIG_ELEM_IS_OPTIONAL) ?? '' !! '!' } method modifier(Parameter:D: --> Str:D) { nqp::bitand_i($!flags,$SIG_ELEM_DEFINED_ONLY) ?? ':D' !! nqp::bitand_i($!flags,$SIG_ELEM_UNDEFINED_ONLY) ?? ':U' !! '' } method constraint_list(Parameter:D: --> List:D) { nqp::isnull(@!post_constraints) ?? () !! nqp::hllize(@!post_constraints) } method constraints(Parameter:D: --> Junction:D) { all(nqp::isnull(@!post_constraints) ?? () !! nqp::hllize(@!post_constraints)) } method type(Parameter:D: --> Mu) { $!type } # XXX Must be marked as DEPRECATED method coerce_type(Parameter:D: --> Mu) { $!type.^archetypes.coercive ?? $!type.^target_type !! Mu } method nominal_type(Parameter:D: --> Mu) { $!type.^archetypes.nominalizable ?? $!type.^nominalize !! $!type } method named_names(Parameter:D: --> List:D) { nqp::if( @!named_names && (my int $elems = nqp::elems(@!named_names)), nqp::stmts( (my $buf := nqp::setelems(nqp::create(IterationBuffer),$elems)), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos($buf,$i,nqp::atpos_s(@!named_names,$i)) ), $buf.List ), nqp::create(List) ) } method named(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::not_i(nqp::isnull(@!named_names)) || nqp::bitand_i($!flags,$SIG_ELEM_SLURPY_NAMED)) } method positional(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::isnull(@!named_names) && nqp::iseq_i(nqp::bitand_i($!flags,$SIG_ELEM_IS_NOT_POSITIONAL),0)) } method slurpy(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::bitand_i($!flags,$SIG_ELEM_IS_SLURPY)) } method optional(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::bitand_i($!flags,$SIG_ELEM_IS_OPTIONAL)) } method raw(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::bitand_i($!flags,$SIG_ELEM_IS_RAW)) } method capture(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::bitand_i($!flags,$SIG_ELEM_IS_CAPTURE)) } method rw(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::bitand_i($!flags,$SIG_ELEM_IS_RW)) } method onearg(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::bitand_i($!flags,$SIG_ELEM_SLURPY_ONEARG)) } method copy(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::bitand_i($!flags,$SIG_ELEM_IS_COPY)) } method readonly(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::iseq_i(nqp::bitand_i($!flags,$SIG_ELEM_IS_NOT_READONLY),0)) } method invocant(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::bitand_i($!flags,$SIG_ELEM_INVOCANT)) } method multi-invocant(Parameter:D: --> Bool:D) { nqp::hllbool(nqp::bitand_i($!flags,$SIG_ELEM_MULTI_INVOCANT)) } method default(Parameter:D: --> Code:_) { nqp::isnull($!default_value) ?? Code !! nqp::bitand_i($!flags,$SIG_ELEM_DEFAULT_IS_LITERAL) ?? { $!default_value } !! $!default_value } method type_captures(Parameter:D: --> List:D) { nqp::if( @!type_captures && (my int $elems = nqp::elems(@!type_captures)), nqp::stmts( (my $buf := nqp::setelems(nqp::create(IterationBuffer),$elems)), (my int $i = -1), nqp::while( nqp::islt_i(++$i,$elems), nqp::bindpos($buf,$i,nqp::atpos_s(@!type_captures,$i)) ), $buf.List ), nqp::create(List) ) } multi method ACCEPTS(Parameter:D: Parameter:D \other --> Bool:D) { # we're us my \o := nqp::decont(other); return True if nqp::eqaddr(self,o); # nominal type is acceptable if $!type.ACCEPTS(nqp::getattr(o,Parameter,'$!type')) { my \oflags := nqp::getattr(o,Parameter,'$!flags'); # flags are not same, so we need to look more in depth if nqp::isne_i($!flags,oflags) { # here not defined only, or both defined only return False unless nqp::isle_i( nqp::bitand_i($!flags,$SIG_ELEM_DEFINED_ONLY), nqp::bitand_i( oflags,$SIG_ELEM_DEFINED_ONLY)) # here not undefined only, or both undefined only && nqp::isle_i( nqp::bitand_i($!flags,$SIG_ELEM_UNDEFINED_ONLY), nqp::bitand_i( oflags,$SIG_ELEM_UNDEFINED_ONLY)) # here is rw, or both is rw && nqp::isle_i( nqp::bitand_i($!flags,$SIG_ELEM_IS_RW), nqp::bitand_i( oflags,$SIG_ELEM_IS_RW)) # other is optional, or both are optional && nqp::isle_i( nqp::bitand_i( oflags,$SIG_ELEM_IS_OPTIONAL), nqp::bitand_i($!flags,$SIG_ELEM_IS_OPTIONAL)) # other is slurpy positional, or both are slurpy positional && nqp::isle_i( nqp::bitand_i( oflags,$SIG_ELEM_SLURPY_POS), nqp::bitand_i($!flags,$SIG_ELEM_SLURPY_POS)) # other is slurpy named, or both are slurpy named && nqp::isle_i( nqp::bitand_i( oflags,$SIG_ELEM_SLURPY_NAMED), nqp::bitand_i($!flags,$SIG_ELEM_SLURPY_NAMED)) # other is slurpy one arg, or both are slurpy one arg && nqp::isle_i( nqp::bitand_i( oflags,$SIG_ELEM_SLURPY_ONEARG), nqp::bitand_i($!flags,$SIG_ELEM_SLURPY_ONEARG)) # here is part of MMD, or both are part of MMD && nqp::isle_i( nqp::bitand_i($!flags,$SIG_ELEM_MULTI_INVOCANT), nqp::bitand_i( oflags,$SIG_ELEM_MULTI_INVOCANT)); } } # nominal type not same else { return False; } # have nameds here my \onamed_names := nqp::getattr(o,Parameter,'@!named_names'); if @!named_names { # nameds there if onamed_names { # too many nameds there, can never be subset my int $elems = nqp::elems(@!named_names); return False if nqp::isgt_i(nqp::elems(onamed_names),$elems); # set up lookup hash my \lookup := nqp::hash; my int $i = -1; nqp::bindkey(lookup,nqp::atpos_s(@!named_names,$i),1) while nqp::islt_i(++$i,$elems); # make sure the other nameds are all here $elems = nqp::elems(onamed_names); $i = -1; return False unless nqp::existskey(lookup,nqp::atpos_s(onamed_names,$i)) while nqp::islt_i(++$i,$elems); } } # no nameds here, but we do there (implies not a subset) elsif onamed_names { return False; } # we have sub sig and not the same if nqp::isconcrete($!sub_signature) { my \osub_signature := nqp::getattr(o,Parameter,'$!sub_signature'); return False unless nqp::isconcrete(osub_signature) && $!sub_signature.ACCEPTS(osub_signature); } if nqp::isconcrete($!signature_constraint) { my \osignature_constraint := nqp::getattr(o, Parameter, '$!signature_constraint'); return False unless nqp::isconcrete(osignature_constraint) && $!signature_constraint.ACCEPTS(osignature_constraint); } # we have a post constraint if nqp::isconcrete(@!post_constraints) { # callable means runtime check, so no match return False if nqp::istype(nqp::atpos(@!post_constraints,0),Callable); # other doesn't have a post constraint my \opc := nqp::getattr(o,Parameter,'@!post_constraints'); return False unless nqp::islist(opc); # other post constraint is a Callable, so runtime check, so no match return False if nqp::istype(nqp::atpos(opc,0),Callable); # not same literal value return False unless nqp::atpos(@!post_constraints,0).ACCEPTS( nqp::atpos(opc,0)); } # it's a match! True; } multi method raku(Parameter:D: Mu:U :$elide-type = Any --> Str:D) { my $raku = ''; $raku ~= "::$_ " for @.type_captures; my $modifier = $.modifier; my $type = $!type.^name; if $!flags +& $SIG_ELEM_ARRAY_SIGIL or $!flags +& $SIG_ELEM_HASH_SIGIL or $!flags +& $SIG_ELEM_CODE_SIGIL { $type ~~ / .*? \[ <( .* )> \] $$/; $raku ~= $/ ~ $modifier if $/; } elsif $modifier or !nqp::eqaddr($!type, nqp::decont($elide-type)) { $raku ~= $type ~ $modifier; } my $prefix = $.prefix; my $sigil = $.sigil; my $twigil = $.twigil; my $usage-name = $.usage-name // ''; my $name = ''; if $prefix eq '+' && $sigil eq '\\' { # We don't want \ to end up in the name of slurpy parameters, but # we still need to know whether or not they have this sigil later. $name ~= $usage-name; } else { $name ~= $sigil ~ $twigil ~ $usage-name; } if nqp::isconcrete($!signature_constraint) { $name ~= $!signature_constraint.raku; } if nqp::isconcrete(@!named_names) { my $var-is-named = False; my @outer-names = gather for @.named_names { if !$var-is-named && $_ eq $usage-name { $var-is-named = True; } else { .take; } }; $name = ":$name" if $var-is-named; $name = ":$_\($name)" for @outer-names; } my $rest = ''; if $!flags +& $SIG_ELEM_IS_RW { $rest ~= ' is rw'; } elsif $!flags +& $SIG_ELEM_IS_COPY { $rest ~= ' is copy'; } if $!flags +& $SIG_ELEM_IS_RAW && $sigil ne '\\' | '|' { # Do not emit cases of anonymous '\' which we cannot reparse # This is all due to unspace. $rest ~= ' is raw'; } unless nqp::isnull($!sub_signature) { $rest ~= ' ' ~ $!sub_signature.raku.substr: 1; } unless nqp::isnull(@!post_constraints) { # it's a Cool constant if !$rest && $name eq '$' && nqp::elems(@!post_constraints) == 1 && nqp::istype( (my \value := nqp::atpos(@!post_constraints,0)), Cool ) { return value.raku; } $rest ~= ' where { ... }'; } if $.default { $rest ~= " = $!default_value.raku()"; } elsif $!flags +& $SIG_ELEM_DEFAULT_FROM_OUTER { $rest ~= " = OUTER::<$name>"; } $name = "$prefix$name$.suffix"; $raku ~= ($raku ?? ' ' !! '') ~ $name if $name; $raku ~= $rest if $rest; $raku } method sub_signature(Parameter:D: --> Signature:_) { nqp::isnull($!sub_signature) ?? Signature !! $!sub_signature } method signature_constraint(Parameter:D: --> Signature:_) { nqp::isnull($!signature_constraint) ?? Signature !! $!signature_constraint } method untyped(Parameter:D: --> Bool:D) { nqp::hllbool( nqp::eqaddr($!type, Mu) && nqp::isnull(@!post_constraints) && nqp::isnull($!sub_signature) && nqp::isnull($!signature_constraint)) } method set_why(Parameter:D: $why --> Nil) { $!why := $why; } method set_default(Parameter:D: Code:D $default --> Nil) { $!default_value := $default; } } multi sub infix:(Parameter:D $a, Parameter:D $b) { # we're us return True if nqp::eqaddr($a,$b); # different container type return False unless $a.WHAT =:= $b.WHAT; # different nominal or coerce type my \atype = nqp::getattr($a,Parameter,'$!type'); my \btype = nqp::getattr($b,Parameter,'$!type'); # (atype is btype) && (btype is atype) ensures type equivalence. Works for different curryings of a parametric role # which are parameterized with the same argument. nqp::eqaddr is not applicable here because if coming from # different compunits the curryings would be different typeobject instances. return False unless (atype.^archetypes.generic && btype.^archetypes.generic) || (nqp::istype(atype, btype) && nqp::istype(btype, atype)); # different flags return False if nqp::isne_i( nqp::getattr($a,Parameter,'$!flags'), nqp::getattr($b,Parameter,'$!flags') ); # only pass if both subsignatures are defined and equivalent my \asub_signature := nqp::getattr($a,Parameter,'$!sub_signature'); my \bsub_signature := nqp::getattr($b,Parameter,'$!sub_signature'); if asub_signature { return False unless bsub_signature && (asub_signature eqv bsub_signature); } elsif bsub_signature { return False; } # first is named if $a.named { # other is not named return False unless $b.named; # not both actually have a name (e.g. *%_ doesn't) my $anames := nqp::getattr($a.named_names,List,'$!reified'); my $bnames := nqp::getattr($b.named_names,List,'$!reified'); my int $adefined = nqp::defined($anames); return False if nqp::isne_i($adefined,nqp::defined($bnames)); # not same basic name return False if $adefined && nqp::isne_s(nqp::atpos($anames,0),nqp::atpos($bnames,0)); } # unnamed vs named elsif $b.named { return False; } # first has a post constraint my Mu $pca := nqp::getattr($a,Parameter,'@!post_constraints'); if nqp::islist($pca) { # callable means runtime check, so no match return False if nqp::istype(nqp::atpos($pca,0),Callable); # second doesn't have a post constraint my Mu $pcb := nqp::getattr($b,Parameter,'@!post_constraints'); return False unless nqp::islist($pcb); # second is a Callable, so runtime check, so no match return False if nqp::istype(nqp::atpos($pcb,0),Callable); # not same literal value return False unless nqp::atpos($pca,0) eqv nqp::atpos($pcb,0); } # first doesn't, second *does* have a post constraint elsif nqp::islist(nqp::getattr($b,Parameter,'@!post_constraints')) { return False; } # it's a match True } #line 1 SETTING::src/core.c/Signature.rakumod my class X::Cannot::Capture { ... } my class Signature { # declared in BOOTSTRAP # class Signature is Any # has @!params; # VM's array of parameters # has Mu $!returns; # return type # has int $!arity; # arity # has Num $!count; # count # has Code $!code; # has int $!readonly; # bit mask indicating read-only positionals multi method new(Signature:U: :@params, Mu :$returns, Int:D :$arity = @params.elems, Num:D :$count = $arity.Num ) { nqp::create(self)!SET-SELF(@params, $returns, $arity, $count) } method !SET-SELF(@params, Mu $returns, $arity, $count) { nqp::bind(@!params,nqp::getattr(@params,List,'$!reified')); $!returns := $returns; $!arity = $arity; $!count := $count; self } multi method ACCEPTS(Signature:D: Mu \topic) { nqp::hllbool(nqp::istrue(try self.ACCEPTS: topic.Capture)) } multi method ACCEPTS(Signature:D: Capture $topic) { nqp::hllbool(nqp::p6isbindable(self, nqp::decont($topic))); } multi method ACCEPTS(Signature:D: Signature:D $topic) { my @r-params := self.params; my @l-params := $topic.params; my $l-params := @l-params.elems; my $todo := $l-params; my @r-pos-queue; my %r-named-queue; my $r-pos-sink := False; my $r-named-sink := False; for @r-params -> $r-param is raw { if $r-param.positional { if $r-param.slurpy { $r-pos-sink := True; } elsif $todo { # When a required or optional positional parameter exists # in a signature, it will be prepended. Typechecks can be # predicted when such parameters exist in the topic too. my $l-param := @l-params[$l-params - $todo]; if $l-param.positional and not $l-param.slurpy { return False unless $l-param ~~ $r-param; $todo := $todo - 1; } else { @r-pos-queue.push: $r-param; } } else { @r-pos-queue.push: $r-param; } } elsif $r-param.named { if $r-param.slurpy { $r-named-sink := True; } else { %r-named-queue{$_} := $r-param for $r-param.named_names; } } else { $r-pos-sink := $r-named-sink := True; } } for @l-params.tail: $todo -> $l-param is raw { state %r-to-l-named{Mu}; if $l-param.positional { if $l-param.slurpy { return False unless $r-pos-sink; } elsif @r-pos-queue { return False unless $l-param ~~ @r-pos-queue.shift; } else { return False unless $r-pos-sink; } } elsif $l-param.named { if $l-param.slurpy { return False unless $r-named-sink; } elsif %r-named-queue { my $found := False; for $l-param.named_names -> $name is raw { if %r-named-queue{$name}:exists { my $r-param := %r-named-queue{$name}:delete; return False unless $l-param ~~ $r-param; return False if %r-to-l-named{$r-param}:exists and not %r-to-l-named{$r-param} =:= $l-param; %r-to-l-named{$r-param} := $l-param; $found := True; } } return False unless $found or $l-param.optional && $l-param.untyped; } else { return False unless $r-named-sink; } } else { return False unless $r-pos-sink && $r-named-sink; } } return False unless .optional for @r-pos-queue; return False unless .optional && .untyped for %r-named-queue.values; self.returns =:= $topic.returns } method Capture() { X::Cannot::Capture.new( :what(self) ).throw } method arity() { $!arity } method count() { $!count } method params() { nqp::p6bindattrinvres(nqp::create(List), List, '$!reified', nqp::clone(@!params)); } method !gistraku(Signature:D: $raku, Mu:U :$elide-type = Mu) { # Opening. my $text = $raku ?? ':(' !! '('; # Parameters. if self.params.Array -> @params { if @params[0].invocant { my $invocant = @params.shift.raku(:$elide-type); $invocant .= chop(2) if $invocant.ends-with(' $'); $text ~= "$invocant: "; } $text ~= ';; ' if @params && !@params[0].multi-invocant; my $sep = ''; for @params.kv -> $i, $param { $text ~= $sep ~ $_ with $param.raku(:$elide-type); # Remove sigils from anon typed scalars, leaving type only $text .= subst(/» ' $'$/,'') unless $raku; $sep = $param.multi-invocant && !@params[$i+1].?multi-invocant ?? ';; ' !! ', ' } } if !nqp::isnull($!returns) && !($!returns =:= Mu) { $text = $text ~ ' --> ' ~ (nqp::can($!returns, 'raku') ?? $!returns.raku !! $!returns.^name) } # Closer. $text ~ ')' } method !deftype(Signature:D:) { !nqp::isnull($!code) && $!code ~~ Routine ?? Any !! Mu } multi method raku(Signature:D:) { self!gistraku(True, :elide-type(self!deftype)) } multi method gist(Signature:D:) { self!gistraku(False, :elide-type(self!deftype)) } } multi sub infix:(Signature:D $a, Signature:D $b) { # we're us return True if nqp::eqaddr($a,$b); # different container type return False unless nqp::eqaddr($a.WHAT,$b.WHAT); # different return return False unless nqp::eqaddr($a.returns,$b.returns); # arity or count mismatch return False if $a.arity != $b.arity || $a.count != $b.count; # different number of parameters or no parameters my $ap := nqp::getattr($a.params,List,'$!reified'); my $bp := nqp::getattr($b.params,List,'$!reified'); my int $elems = nqp::elems($ap); return False if nqp::isne_i($elems,nqp::elems($bp)); return True unless $elems; # compare all positionals my int $i = -1; Nil while nqp::islt_i(++$i,$elems) && nqp::atpos($ap,$i) eqv nqp::atpos($bp,$i); # not all matching positionals if nqp::islt_i($i,$elems) { # not all same and different number of positionals return False if (!nqp::atpos($ap,$i).named || !nqp::atpos($bp,$i).named); # create lookup table my int $j = $i = $i - 1; my $lookup := nqp::hash; while nqp::islt_i(++$j,$elems) { my $p := nqp::atpos($ap,$j); my $nn := nqp::getattr($p,Parameter,'@!named_names'); my str $key = nqp::isnull($nn) ?? '' !! nqp::elems($nn) ?? nqp::atpos_s($nn,0) !! ''; die "Found named parameter '{ nqp::chars($key) ?? $key !! '(unnamed)' }' twice in signature {$a.raku}: {$p.raku} vs {nqp::atkey($lookup,$key).raku}" if nqp::existskey($lookup,$key); nqp::bindkey($lookup,$key,$p); } # named variable mismatch while nqp::islt_i(++$i,$elems) { my $p := nqp::atpos($bp,$i); my $nn := nqp::getattr($p,Parameter,'@!named_names'); my str $key = nqp::defined($nn) && nqp::elems($nn) ?? nqp::atpos_s($nn,0) !! ''; # named param doesn't exist in other or is not equivalent return False unless nqp::existskey($lookup,$key) && $p eqv nqp::atkey($lookup,$key); } } # it's a match True } Perl6::Metamodel::Configuration.set_multi_sig_comparator( -> \a, \b { a.signature eqv b.signature } ); #line 1 SETTING::src/core.c/Rational.rakumod # stub of this role is also present in Numeric.rakumod; be sure to update # definition there as well, if changing this one my role Rational[::NuT = Int, ::DeT = ::("NuT")] does Real { has NuT $.numerator; has DeT $.denominator; multi method WHICH(Rational:D: --> ValueObjAt:D) { nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Rational), 'Rational|', nqp::concat(nqp::unbox_s(self.^name), '|') ), nqp::concat( nqp::tostr_I($!numerator), nqp::concat('/', nqp::tostr_I($!denominator)) ) ), ValueObjAt ) } method new(NuT:D \nu = 0, DeT:D \de = 1) { my \object := nqp::create(self); nqp::if( de, nqp::stmts( # normal rational (my \gcd := nqp::gcd_I(nqp::decont(nu), nqp::decont(de), Int)), (my \numerator := nqp::div_I(nqp::decont(nu), gcd, NuT)), (my \denominator := nqp::div_I(nqp::decont(de), gcd, DeT)), nqp::if( nqp::islt_I(denominator,0), # need to switch sign? nqp::stmts( # yup, so switch nqp::bindattr( object,::?CLASS,'$!numerator',nqp::neg_I(numerator,Int) ), nqp::p6bindattrinvres( object,::?CLASS,'$!denominator',nqp::neg_I(denominator,Int) ) ), nqp::stmts( # no, so just store nqp::bindattr( object,::?CLASS,'$!numerator',numerator ), nqp::p6bindattrinvres( object,::?CLASS,'$!denominator',denominator ) ) ) ), nqp::stmts( # Inf / NaN nqp::bindattr(object,::?CLASS,'$!numerator', nqp::box_i( nqp::isgt_I(nqp::decont(nu),0) || nqp::neg_i(nqp::istrue(nu)), nu.WHAT ) ), nqp::p6bindattrinvres(object,::?CLASS,'$!denominator', nqp::decont(de) ) ) ) } multi method raku(Rational:D: --> Str:D) { "Rational.new($!numerator, $!denominator)" } method nude() { $!numerator, $!denominator } method Num(Rational:D: --> Num:D) { $!denominator || $!numerator >= 0 ?? nqp::p6box_n(nqp::div_In($!numerator,$!denominator)) !! -Inf } method !divide-by-zero(Str:D $what) { X::Numeric::DivideByZero.new( :details("when calling .$what on Rational") ).Failure } method floor(Rational:D: --> Int:D) { $!denominator ?? $!denominator == 1 ?? $!numerator !! $!numerator div $!denominator !! self!divide-by-zero('floor') } method ceiling(Rational:D: --> Int:D) { $!denominator ?? $!denominator == 1 ?? $!numerator !! ($!numerator div $!denominator + 1) !! self!divide-by-zero('ceiling') } method Int(Rational:D: --> Int:D) { $!denominator ?? self.truncate !! self!divide-by-zero('Int') } multi method Bool(::?CLASS:D:) { nqp::hllbool(nqp::istrue($!numerator)) } method Range(::?CLASS:U:) { Range.new(-Inf, Inf) } method isNaN (--> Bool:D) { nqp::hllbool(nqp::isfalse($!denominator) && nqp::isfalse($!numerator)) } method is-prime(--> Bool:D) { $!denominator == 1 && $!numerator.is-prime } multi method Str(::?CLASS:D: --> Str:D) { if $!denominator { my \abs := self.abs; # N / D my \whole := abs.floor; (my \fract := abs - whole) # fight floating point noise issues https://github.com/Raku/old-issue-tracker/issues/4524 ?? nqp::eqaddr(self.WHAT,Rat) && fract.Num == 1e0 # 42.666? ?? self!UNITS(nqp::add_I(whole,1,Int)) # next Int !! self!STRINGIFY(self!UNITS(whole), fract, # 42.666 nqp::eqaddr(self.WHAT,Rat) # Stringify Rats to at least 6 significant digits. There does not # appear to be any written spec for this but there are tests in # roast that specifically test for 6 digits. ?? nqp::islt_I($!denominator,100_000) ?? 6 !! (nqp::chars(nqp::tostr_I($!denominator)) + 1) # TODO v6.d FatRats are tested in roast to have a minimum # precision pf 6 decimal places - mostly due to there being no # formal spec and the desire to test SOMETHING. With this # speed increase, 16 digits would work fine; but it isn't spec. # !! $!denominator < 1_000_000_000_000_000 # ?? 16 !! $!denominator < 100_000 ?? 6 !! (nqp::chars(nqp::tostr_I($!denominator)) + nqp::chars(nqp::tostr_I(whole)) + 5 ) ) !! self!UNITS(whole) # no fract val } else { # N / 0 DIVIDE_BY_ZERO } } method !UNITS(Int:D $whole --> Str:D) { nqp::islt_I($!numerator,0) # next Int ?? nqp::concat("-",nqp::tostr_I($whole)) # < 0 !! nqp::tostr_I($whole) # >= 0 } method !STRINGIFY(str $units, $fract, int $digits) { my str $s = nqp::tostr_I( ($fract * nqp::pow_I(10,$digits,Num,Int)).round ); $s = nqp::concat(nqp::x('0',$digits - nqp::chars($s)),$s) if nqp::chars($s) < $digits; my int $i = nqp::chars($s); nqp::while( nqp::eqat($s,'0',$i - 1) && --$i > 0, nqp::null ); $i ?? nqp::concat($units,nqp::concat('.',nqp::substr($s,0,$i))) !! $units } sub DIVIDE_BY_ZERO() { X::Numeric::DivideByZero.new( :details('when coercing Rational to Str') ).throw } sub BASE_OUT_OF_RANGE(int $got) { X::OutOfRange.new( :what('base argument to base'),:$got,:range<2..36> ).Failure } sub DIGITS_OUT_OF_RANGE(int $got) { X::OutOfRange.new( :what('digits argument to base'),:$got,:range<2..36> ).Failure } proto method base(|) {*} # Convert to given base with sensible number of digits depending on value multi method base(Rational:D: Int:D $base --> Str:D) { if 2 <= $base <= 36 { # Limit log calculation to 10**307 or less. # log coerces to Num. When larger than 10**307, it overflows and # returns Inf. my constant $lim = 10**307; if $!denominator < $lim { self!base( $base, $!denominator < $base**6 ?? 6 !! $!denominator.log($base).ceiling + 1, 0 ) } else { # If the internal log method is modified to handle larger # numbers, this branch can be modified/removed. my $d = $!denominator; my $exp = 0; ++$exp while ($d div= $base) > $lim; self!base($base, $exp + $d.log($base).ceiling + 2, 0) } } else { BASE_OUT_OF_RANGE($base) } } # Convert to given base until no fraction left: **CAUTION** this will # loop indefinitely for simple values such as 1/3 multi method base(Rational:D: Int:D $base, Whatever --> Str:D) { 2 <= $base <= 36 ?? self!base($base, 0, 0) !! BASE_OUT_OF_RANGE($base) } # Convert to given base for given number of digits. This will display # trailing 0's if number of digits exceeds accuracy of value, unless # inhibited with the :no-trailing-zeroes named argument. multi method base(Rational:D: Int:D $base, Int() $digits, Bool:D :$no-trailing-zeroes = False --> Str:D) { 2 <= $base <= 36 ?? $digits > 0 ?? self!base($base, $digits, nqp::not_i($no-trailing-zeroes)) !! $digits == 0 ?? self.round.base($base) !! DIGITS_OUT_OF_RANGE($digits) !! BASE_OUT_OF_RANGE($base) } # Lookup table for converting from numerical value to string digit my str $num2digit = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; # Actual .base conversion workhorse. Takes base, precision (0 for # conversion until no fractional part left) and a flag indicating # whether the preserve trailing zeroes method !base( Int:D $base, # radix to output value in int $digits, # number of digits to generate, 0 = indefinite int $trailing-zeroes, # do not remove trailing zeroes --> Str:D) { my $result := nqp::list_s; # set up initial values my $abs; if nqp::islt_I($!numerator,0) { nqp::push_s($result,'-'); $abs := -self; } else { $abs := self; } my $whole := $abs.floor; my $fract := $abs - $whole; # fight floating point noise issues https://github.com/Raku/old-issue-tracker/issues/4524 if $fract.Num == 1e0 { $whole := $whole + 1; $fract := 0; } # have something after the decimal point if $fract { # we have a specific precision in mind if $digits { my str $s = ($fract * $base**$digits).round.base($base); my int $force-decimal; if nqp::chars($s) > $digits { $whole := $whole + 1; $s = nqp::substr($s,1); $force-decimal = 1; } elsif nqp::chars($s) < $digits { $s = nqp::concat(nqp::x('0',$digits - nqp::chars($s)),$s); } my int $i = nqp::chars($s); if $trailing-zeroes { # we want trailing zeroes nqp::while( nqp::eqat($s,'0',--$i) && $i >= $digits, nqp::null ); ++$i; # correct for premature decrement } else { # no trailing zeroes nqp::while( nqp::eqat($s,'0',$i - 1) && --$i > 0, nqp::null ); } nqp::push_s($result,$whole.base($base)); if $i || $force-decimal { nqp::push_s($result,'.'); nqp::push_s($result,nqp::substr($s,0,$i)); } } # no precision, go on until nothing left, possibly forever else { nqp::push_s($result,$whole.base($base)); nqp::push_s($result,'.'); while $fract { $fract := $fract * $base; my $digit := $fract.floor; nqp::push_s($result,nqp::substr($num2digit,$digit,1)); $fract := $fract - $digit; } } } # nothing after decimal point else { nqp::push_s($result,$whole.base($base)); if $digits && $trailing-zeroes { nqp::push_s($result,'.'); nqp::push_s($result,nqp::x('0',$digits)) } } nqp::join('',$result) } method base-repeating($base = 10) { return ~self, '' if self.narrow ~~ Int; my @quotients; my @remainders; my %remainders; push @quotients, [div] my ($nu, $de) = abs(self).nude; loop { push @remainders, $nu %= $de; last if %remainders{$nu}++ or $nu == 0; $nu *= $base; push @quotients, $nu div $de; } @quotients .= map(*.base($base)); my @cycle = $nu ?? splice @quotients, @remainders.first($nu,:k) + 1 !! (); splice @quotients, 1, 0, '.'; '-' x (self < 0) ~ @quotients.join, @cycle.join; } method succ { self.new($!numerator + $!denominator, $!denominator); } method pred { self.new($!numerator - $!denominator, $!denominator); } method norm() { self } method narrow(::?CLASS:D:) { $!denominator == 1 ?? $!numerator !! self; } multi method round(::?CLASS:D: --> Int:D) { $!denominator ?? nqp::div_I( nqp::add_I(nqp::mul_I($!numerator, 2, Int), $!denominator, Int), nqp::mul_I($!denominator, 2, Int), Int ) !! self!divide-by-zero('round') } } #line 1 SETTING::src/core.c/Rat.rakumod # XXX: should be Rational[Int, uint] my class Rat is Cool does Rational[Int, Int] { method Rat(Rat:D: Real $? --> Rat:D) { self } method FatRat(Rat:D: Real $? --> FatRat:D) { nqp::p6bindattrinvres( nqp::p6bindattrinvres( nqp::create(FatRat),FatRat,'$!numerator',$!numerator ), FatRat,'$!denominator',$!denominator ) } multi method raku(Rat:D: --> Str:D) { if $!denominator == 1 { $!numerator ~ '.0' } else { my $d = $!denominator; unless $d == 0 { $d = $d div 5 while $d %% 5; $d = $d div 2 while $d %% 2; } if $d == 1 and (my $b := self.base(10,*)).Numeric === self { $b; } else { '<' ~ $!numerator ~ '/' ~ $!denominator ~ '>' } } } } my constant UINT64_UPPER = nqp::pow_I(2, 64, Num, Int); my class FatRat is Cool does Rational[Int, Int] { method FatRat(FatRat:D:) { self } method Rat(FatRat:D:) { $!denominator < UINT64_UPPER ?? nqp::p6bindattrinvres( nqp::p6bindattrinvres( nqp::create(Rat),Rat,'$!numerator',$!numerator ), Rat,'$!denominator',$!denominator ) !! "Cannot convert from FatRat to Rat because denominator is too big".Failure } multi method raku(FatRat:D: --> Str:D) { (nqp::eqaddr(self.WHAT,FatRat) ?? 'FatRat' !! self.^name) ~ ".new($!numerator, $!denominator)" } method UPGRADE-RAT(\nu, \de) is raw { nqp::p6bindattrinvres( nqp::p6bindattrinvres(nqp::create(FatRat),FatRat,'$!numerator',nu), FatRat,'$!denominator',de ) } } # NORMALIZE two integer values and create a Rat/FatRat/float from them. # Provide two types: if either of them is a FatRat, then a FatRat will be # returned. If a Rat is to be created, then a check for denominator overflow # is done: if that is the case, then a float will be returned. sub DIVIDE_NUMBERS( Int:D $nu, Int:D $de, \t1, \t2 ) is raw is implementation-detail { nqp::if( $de, nqp::stmts( (my \gcd := nqp::gcd_I($nu,$de,Int)), (my \numerator := nqp::div_I($nu,gcd,Int)), (my \denominator := nqp::div_I($de,gcd,Int)), nqp::if( nqp::islt_I(denominator,0), CREATE_RATIONAL_FROM_INTS( nqp::neg_I(numerator,Int), nqp::neg_I(denominator,Int), t1, t2 ), CREATE_RATIONAL_FROM_INTS( numerator, denominator, t1, t2 ) ) ), CREATE_RATIONAL_FROM_INTS( nqp::box_i(nqp::isgt_I($nu,0) || nqp::neg_i(nqp::istrue($nu)),Int), 0, t1, t2 ) ) } # Initialize the $*RAT-OVERFLOW dynamic var so that it can be used PROCESS::<$RAT-OVERFLOW> = Num; # ALL RATIONALS MUST BE NORMALIZED, however in some operations we cannot # ever get a non-normalized Rational, if we start with a normalized Rational. # For such cases, we can use this routine, to bypass normalization step, # which would be useless. Also used when normalization *was* needed. proto sub CREATE_RATIONAL_FROM_INTS(|) is implementation-detail {*} multi sub CREATE_RATIONAL_FROM_INTS(Int:D $nu, Int:D $de, Any, Any) is raw { nqp::islt_I($de,UINT64_UPPER) # do we need to downgrade to float? ?? nqp::p6bindattrinvres( # no, we need to keep a Rat nqp::p6bindattrinvres(nqp::create(Rat),Rat,'$!numerator',$nu), Rat,'$!denominator',$de ) !! $*RAT-OVERFLOW.UPGRADE-RAT($nu, $de) } # already a FatRat, so keep that multi sub CREATE_RATIONAL_FROM_INTS( Int:D $nu, Int:D $de, FatRat, Any --> FatRat:D) is raw { nqp::p6bindattrinvres( nqp::p6bindattrinvres(nqp::create(FatRat),FatRat,'$!numerator',$nu), FatRat,'$!denominator',$de ) } multi sub CREATE_RATIONAL_FROM_INTS( Int:D $nu, Int:D $de, Any, FatRat --> FatRat:D) is raw { nqp::p6bindattrinvres( nqp::p6bindattrinvres(nqp::create(FatRat),FatRat,'$!numerator',$nu), FatRat,'$!denominator',$de ) } multi sub CREATE_RATIONAL_FROM_INTS( Int:D $nu, Int:D $de, FatRat, FatRat --> FatRat:D) is raw { nqp::p6bindattrinvres( nqp::p6bindattrinvres(nqp::create(FatRat),FatRat,'$!numerator',$nu), FatRat,'$!denominator',$de ) } multi sub prefix:<->(Rat:D $a --> Rat:D) { # Rat.new(-a.numerator, a.denominator); nqp::p6bindattrinvres( nqp::clone($a), Rat,'$!numerator',nqp::neg_I($a.numerator,Int) ) } multi sub prefix:<->(FatRat:D $a --> FatRat:D) { # FatRat.new(-a.numerator, a.denominator); nqp::p6bindattrinvres( nqp::clone($a), FatRat,'$!numerator',nqp::neg_I($a.numerator,Int) ) } multi sub infix:<+>(Rational:D $a, Rational:D $b) { my \adenom := $a.denominator; my \bdenom := $b.denominator; DIVIDE_NUMBERS( $a.numerator * bdenom + $b.numerator * adenom, adenom * bdenom, $a, $b ) } multi sub infix:<+>(Rational:D $a, Int:D $b) { my \adenom := $a.denominator; DIVIDE_NUMBERS( $a.numerator + $b * adenom, adenom, $a, $b ) } multi sub infix:<+>(Int:D $a, Rational:D $b) { my \bdenom := $b.denominator; DIVIDE_NUMBERS( $a * bdenom + $b.numerator, bdenom, $a, $b ) } multi sub infix:<->(Rational:D $a, Rational:D $b) { my \adenom := $a.denominator; my \bdenom := $b.denominator; DIVIDE_NUMBERS( $a.numerator * bdenom - $b.numerator * adenom, adenom * bdenom, $a, $b ) } multi sub infix:<->(Rational:D $a, Int:D $b) { my \adenom := $a.denominator; DIVIDE_NUMBERS( $a.numerator - $b * adenom, adenom, $a, $b ) } multi sub infix:<->(Int:D $a, Rational:D $b) { my \bdenom := $b.denominator; DIVIDE_NUMBERS( $a * bdenom - $b.numerator, bdenom, $a, $b ) } multi sub infix:<*>(Rational:D $a, Rational:D $b) { DIVIDE_NUMBERS( $a.numerator * $b.numerator, $a.denominator * $b.denominator, $a, $b ) } multi sub infix:<*>(Rational:D $a, Int:D $b) { DIVIDE_NUMBERS( $a.numerator * $b, $a.denominator, $a, $b ) } multi sub infix:<*>(Int:D $a, Rational:D $b) { DIVIDE_NUMBERS( $a * $b.numerator, $b.denominator, $a, $b ) } multi sub infix:(Rational:D $a, Rational:D $b) { DIVIDE_NUMBERS( $a.numerator * $b.denominator, $a.denominator * $b.numerator, $a, $b ) } multi sub infix:(Rational:D $a, Int:D $b) { DIVIDE_NUMBERS( $a.numerator, $a.denominator * $b, $a, $b ) } multi sub infix:(Int:D $a, Rational:D $b) { DIVIDE_NUMBERS( $b.denominator * $a, $b.numerator, $a, $b ) } multi sub infix:(Int:D $a, Int:D $b) { DIVIDE_NUMBERS($a, $b, $a, $b) } multi sub infix:<%>(Rational:D $a, Int:D $b) { $a - floor($a / $b) * $b } multi sub infix:<%>(Int:D $a, Rational:D $b) { $a - floor($a / $b) * $b } multi sub infix:<%>(Rational:D $a, Rational:D $b) { $a - floor($a / $b) * $b } multi sub infix:<**>(Rational:D $a, Int:D $b) { my $nu; my $de; nqp::if( nqp::isge_I($b,0), nqp::if( # if we got Inf nqp::istype( ($nu := nqp::pow_I($a.numerator,$b,Num,Int)), Num ), X::Numeric::Overflow.new.Failure, nqp::if( # if we got Inf nqp::istype( ($de := nqp::pow_I($a.denominator,$b,Num,Int)), Num ), X::Numeric::Overflow.new.Failure, CREATE_RATIONAL_FROM_INTS($nu, $de, $a, $b) ) ), nqp::if( # if we got Inf nqp::istype( ($nu := nqp::pow_I($a.numerator,nqp::neg_I($b,Int),Num,Int)), Num ), X::Numeric::Underflow.new.Failure, nqp::if( # if we got Inf nqp::istype( ($de := nqp::pow_I($a.denominator,nqp::neg_I($b,Int),Num,Int)), Num ), X::Numeric::Underflow.new.Failure, CREATE_RATIONAL_FROM_INTS($de, $nu, $a, $b) ) ) ) } multi sub infix:<==>(Rational:D $a, Rational:D $b --> Bool:D) { nqp::hllbool( nqp::iseq_I( (my \anum := $a.numerator), $b.numerator ) && nqp::iseq_I( (my \adenom := $a.denominator), $b.denominator ) && ( # num/denom both same nqp::istrue(anum) # 1/X, Inf == Inf also true || nqp::istrue(adenom) # 0/1, NaN == NaN becomes false ) ) } multi sub infix:<==>(Rational:D $a, Int:D $b --> Bool:D) { nqp::hllbool( nqp::iseq_I($a.denominator,1) && nqp::iseq_I($a.numerator,$b) ) } multi sub infix:<==>(Int:D $a, Rational:D $b --> Bool:D) { nqp::hllbool( nqp::iseq_I($b.denominator,1) && nqp::iseq_I($a,$b.numerator) ) } multi sub infix:<===>(Rational:D $a, Rational:D $b --> Bool:D) { nqp::hllbool( nqp::eqaddr($a.WHAT, $b.WHAT) && nqp::iseq_I($a.numerator, $b.numerator) && nqp::iseq_I($a.denominator,$b.denominator) ) } multi sub infix:«<»(Rational:D $a, Rational:D $b --> Bool:D) { # a.numerator * b.denominator < b.numerator * a.denominator nqp::hllbool( nqp::islt_I( nqp::mul_I($a.numerator,$b.denominator,Int), nqp::mul_I($b.numerator,$a.denominator,Int) ) ) } multi sub infix:«<»(Rational:D $a, Int:D $b --> Bool:D) { # a.numerator < b * a.denominator nqp::hllbool( nqp::islt_I($a.numerator,nqp::mul_I($b,$a.denominator,Int)) ) } multi sub infix:«<»(Int:D $a, Rational:D $b --> Bool:D) { # a * b.denominator < b.numerator nqp::hllbool( nqp::islt_I(nqp::mul_I($a,$b.denominator,Int),$b.numerator) ) } multi sub infix:«<=»(Rational:D $a, Rational:D $b --> Bool:D) { # a.numerator * b.denominator <= b.numerator * a.denominator nqp::hllbool( nqp::isle_I( nqp::mul_I($a.numerator,$b.denominator,Int), nqp::mul_I($b.numerator,$a.denominator,Int) ) ) } multi sub infix:«<=»(Rational:D $a, Int:D $b --> Bool:D) { # a.numerator <= b * a.denominator nqp::hllbool( nqp::isle_I($a.numerator,nqp::mul_I($b,$a.denominator,Int)) ) } multi sub infix:«<=»(Int:D $a, Rational:D $b --> Bool:D) { # a * b.denominator <= b.numerator nqp::hllbool( nqp::isle_I(nqp::mul_I($a,$b.denominator,Int),$b.numerator) ) } multi sub infix:«>»(Rational:D $a, Rational:D $b --> Bool:D) { # a.numerator * b.denominator > b.numerator * a.denominator nqp::hllbool( nqp::isgt_I( nqp::mul_I($a.numerator,$b.denominator,Int), nqp::mul_I($b.numerator,$a.denominator,Int) ) ) } multi sub infix:«>»(Rational:D $a, Int:D $b --> Bool:D) { # a.numerator > b * a.denominator nqp::hllbool( nqp::isgt_I($a.numerator,nqp::mul_I($b,$a.denominator,Int)) ) } multi sub infix:«>»(Int:D $a, Rational:D $b --> Bool:D) { # a * b.denominator > b.numerator nqp::hllbool( nqp::isgt_I(nqp::mul_I($a,$b.denominator,Int),$b.numerator) ) } multi sub infix:«>=»(Rational:D $a, Rational:D $b --> Bool:D) { # a.numerator * b.denominator >= b.numerator * a.denominator nqp::hllbool( nqp::isge_I( nqp::mul_I($a.numerator,$b.denominator,Int), nqp::mul_I($b.numerator,$a.denominator,Int) ) ) } multi sub infix:«>=»(Rational:D $a, Int:D $b --> Bool:D) { # a.numerator >= b * a.denominator nqp::hllbool( nqp::isge_I($a.numerator,nqp::mul_I($b,$a.denominator,Int)) ) } multi sub infix:«>=»(Int:D $a, Rational:D $b --> Bool:D) { # a * b.denominator >= b.numerator nqp::hllbool( nqp::isge_I(nqp::mul_I($a,$b.denominator,Int),$b.numerator) ) } multi sub infix:«<=>»(Rational:D $a, Rational:D $b) { # a.numerator * b.denominator <=> b.numerator * a.denominator ORDER( nqp::cmp_I( nqp::mul_I($a.numerator,$b.denominator,Int), nqp::mul_I($b.numerator,$a.denominator,Int) ) ) } multi sub infix:«<=>»(Rational:D $a, Int:D $b) { # a.numerator <=> b * a.denominator ORDER( nqp::cmp_I($a.numerator,nqp::mul_I($b,$a.denominator,Int)) ) } multi sub infix:«<=>»(Int:D $a, Rational:D $b) { # a * b.denominator <=> b.numerator ORDER( nqp::cmp_I(nqp::mul_I($a,$b.denominator,Int),$b.numerator) ) } #line 1 SETTING::src/core.c/Complex.rakumod my class X::Numeric::Real { ... }; my class Complex is Cool does Numeric { has num $.re; has num $.im; method !SET-SELF($!re, $!im) { self } proto method new(|) {*} multi method new() { nqp::create(self)!SET-SELF(0e0, 0e0) } multi method new(Num $re, Num $im) { nqp::create(self)!SET-SELF($re, $im) } multi method new(Real:D $re, Real:D $im) { nqp::create(self)!SET-SELF($re.Num, $im.Num) } multi method WHICH(Complex:D: --> ValueObjAt:D) { nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Complex), 'Complex|', nqp::concat(nqp::unbox_s(self.^name), '|') ), nqp::concat($!re, nqp::concat('|', $!im)) ), ValueObjAt ) } method reals(Complex:D:) { (self.re, self.im); } method isNaN(Complex:D:) { self.re.isNaN || self.im.isNaN; } method !coerce-to-real-failed($target) { X::Numeric::Real.new( :$target, reason => "imaginary part not zero", source => self ).Failure } method !coerce-to-real($target) { $!im ≅ 0e0 ?? $!re !! self!coerce-to-real-failed($target) } multi method Real(Complex:D:) { self!coerce-to-real(Real) } # should probably be eventually supplied by role Numeric method Num(Complex:D:) { self!coerce-to-real(Num).Num } method Int(Complex:D:) { self!coerce-to-real(Int).Int } proto method Rat(|) {*} multi method Rat(Complex:D:) { self!coerce-to-real(Rat).Rat } multi method Rat(Complex:D: $epsilon) { self!coerce-to-real(Rat).Rat: $epsilon } proto method FatRat(|) {*} multi method FatRat(Complex:D:) { self!coerce-to-real(FatRat).FatRat } multi method FatRat(Complex:D: $epsilon?) { self!coerce-to-real(FatRat).FatRat: $epsilon } multi method Bool(Complex:D:) { $!re != 0e0 || $!im != 0e0; } method Complex() { self } multi method Str(Complex:D:) { nqp::concat( $!re, nqp::concat( nqp::if(nqp::iseq_i( # we could have negative zero, so stringify nqp::ord(my $im := nqp::p6box_s($!im)),45),'','+'), nqp::concat( $im, nqp::if(nqp::isnanorinf($!im),'\\i','i') ) ) ) } multi method raku(Complex:D:) { '<' ~ self.Str ~ '>'; } method conj(Complex:D:) { Complex.new($.re, -$.im); } method abs(Complex $x:) { nqp::p6box_n(nqp::sqrt_n( nqp::add_n( nqp::mul_n($!re, $!re), nqp::mul_n($!im, $!im), ) )) } method polar() { $.abs, $!im.atan2($!re); } multi method log(Complex:D:) { my Num ($mag, $angle) = self.polar; Complex.new($mag.log, $angle); } method cis(Complex:D:) { self.cos + self.sin*Complex.new(0,1) } multi method sqrt(Complex:D:) { my Num $abs = self.abs; my Num $re = (($abs + self.re)/2).sqrt; my Num $im = (($abs - self.re)/2).sqrt; Complex.new($re, self.im < 0 ?? -$im !! $im); } multi method exp(Complex:D:) { my Num $mag = $!re.exp; Complex.new($mag * $!im.cos, $mag * $!im.sin); } method roots(Complex:D: Int() $n) { return NaN if $n < 1; return self if $n == 1; for $!re, $!im { return NaN if $_ eq 'Inf' || $_ eq '-Inf' || $_ eq 'NaN'; } my ($mag, $angle) = self.polar; $mag **= 1e0 / $n; (^$n).map: { $mag.unpolar( ($angle + $_ * 2e0 * pi) / $n) }; } method sin(Complex:D:) { $!re.sin * $!im.cosh + ($!re.cos * $!im.sinh)i; } method asin(Complex:D:) { (Complex.new(0e0, -1e0) * log((self)i + sqrt(1e0 - self * self))); } method cos(Complex:D:) { $!re.cos * $!im.cosh - ($!re.sin * $!im.sinh)i; } method acos(Complex:D:) { (pi / 2e0) - self.asin; } method tan(Complex:D:) { self.sin / self.cos; } method atan(Complex:D:) { ((log(1e0 - (self)i) - log(1e0 + (self)i))i / 2e0); } method sec(Complex:D:) { 1e0 / self.cos; } method asec(Complex:D:) { (1e0 / self).acos; } method cosec(Complex:D:) { 1e0 / self.sin; } method acosec(Complex:D:) { (1e0 / self).asin; } method cotan(Complex:D:) { self.cos / self.sin; } method acotan(Complex:D:) { (1e0 / self).atan; } method sinh(Complex:D:) { -((Complex.new(0e0, 1e0) * self).sin)i; } method asinh(Complex:D:) { (self + sqrt(1e0 + self * self)).log; } method cosh(Complex:D:) { (Complex.new(0e0, 1e0) * self).cos; } method acosh(Complex:D:) { (self + sqrt(self * self - 1e0)).log; } method tanh(Complex:D:) { -((Complex.new(0e0, 1e0) * self).tan)i; } method atanh(Complex:D:) { (((1e0 + self) / (1e0 - self)).log / 2e0); } method sech(Complex:D:) { 1e0 / self.cosh; } method asech(Complex:D:) { (1e0 / self).acosh; } method cosech(Complex:D:) { 1e0 / self.sinh; } method acosech(Complex:D:) { (1e0 / self).asinh; } method cotanh(Complex:D:) { 1e0 / self.tanh; } method acotanh(Complex:D:) { (1e0 / self).atanh; } method floor(Complex:D:) { Complex.new( self.re.floor, self.im.floor ); } method ceiling(Complex:D:) { Complex.new( self.re.ceiling, self.im.ceiling ); } proto method round(|) {*} multi method round(Complex:D:) { Complex.new( self.re.round, self.im.round ); } multi method round(Complex:D: Real() $scale) { Complex.new( self.re.round($scale), self.im.round($scale) ); } method truncate(Complex:D:) { Complex.new( self.re.truncate, self.im.truncate ); } method narrow(Complex:D:) { self == 0e0 ?? 0 !! $!re == 0e0 ?? self !! $!im / $!re ≅ 0e0 ?? $!re.narrow !! self; } } multi sub prefix:<->(Complex:D $a --> Complex:D) { my $new := nqp::create(Complex); nqp::bindattr_n($new,Complex,'$!re', nqp::neg_n(nqp::getattr_n($a,Complex,'$!re')) ); nqp::bindattr_n($new,Complex,'$!im', nqp::neg_n(nqp::getattr_n($a,Complex,'$!im')) ); $new } multi sub abs(Complex:D $a --> Num:D) { my num $re = nqp::getattr_n($a,Complex,'$!re'); my num $im = nqp::getattr_n($a,Complex,'$!im'); nqp::p6box_n( nqp::sqrt_n( nqp::add_n(nqp::mul_n($re,$re),nqp::mul_n($im,$im)) ) ) } multi sub infix:<+>(Complex:D $a, Complex:D $b --> Complex:D) { my $new := nqp::create(Complex); nqp::bindattr_n($new,Complex,'$!re', nqp::add_n( nqp::getattr_n($a,Complex,'$!re'), nqp::getattr_n($b,Complex,'$!re'), ) ); nqp::bindattr_n($new,Complex,'$!im', nqp::add_n( nqp::getattr_n($a,Complex,'$!im'), nqp::getattr_n($b,Complex,'$!im'), ) ); $new } multi sub infix:<+>(Complex:D $a, Num(Real) $b --> Complex:D) { my $new := nqp::create(Complex); nqp::bindattr_n($new,Complex,'$!re', nqp::add_n( nqp::getattr_n($a,Complex,'$!re'), nqp::unbox_n($b) ) ); nqp::bindattr_n($new,Complex,'$!im', nqp::getattr_n($a,Complex,'$!im'), ); $new } multi sub infix:<+>(Num(Real) $a, Complex:D $b --> Complex:D) { my $new := nqp::create(Complex); nqp::bindattr_n($new,Complex,'$!re', nqp::add_n( nqp::unbox_n($a), nqp::getattr_n($b,Complex,'$!re'), ) ); nqp::bindattr_n($new,Complex,'$!im', nqp::getattr_n($b,Complex,'$!im'), ); $new } multi sub infix:<->(Complex:D $a, Complex:D $b --> Complex:D) { my $new := nqp::create(Complex); nqp::bindattr_n($new,Complex,'$!re', nqp::sub_n( nqp::getattr_n($a,Complex,'$!re'), nqp::getattr_n($b,Complex,'$!re'), ) ); nqp::bindattr_n($new,Complex,'$!im', nqp::sub_n( nqp::getattr_n($a,Complex,'$!im'), nqp::getattr_n($b,Complex,'$!im'), ) ); $new } multi sub infix:<->(Complex:D $a, Num(Real) $b --> Complex:D) { my $new := nqp::create(Complex); nqp::bindattr_n($new,Complex,'$!re', nqp::sub_n(nqp::getattr_n($a,Complex,'$!re'),$b) ); nqp::bindattr_n($new,Complex,'$!im', nqp::getattr_n($a,Complex,'$!im') ); $new } multi sub infix:<->(Num(Real) $a, Complex:D $b --> Complex:D) { my $new := nqp::create(Complex); nqp::bindattr_n($new,Complex,'$!re', nqp::sub_n($a,nqp::getattr_n($b,Complex,'$!re')) ); nqp::bindattr_n($new,Complex,'$!im', nqp::neg_n(nqp::getattr_n($b,Complex,'$!im')) ); $new } multi sub infix:<*>(Complex:D $a, Complex:D $b --> Complex:D) { my num $a_re = nqp::getattr_n($a,Complex,'$!re'); my num $a_im = nqp::getattr_n($a,Complex,'$!im'); my num $b_re = nqp::getattr_n($b,Complex,'$!re'); my num $b_im = nqp::getattr_n($b,Complex,'$!im'); my $new := nqp::create(Complex); nqp::bindattr_n($new,Complex,'$!re', nqp::sub_n(nqp::mul_n($a_re,$b_re),nqp::mul_n($a_im,$b_im)) ); nqp::bindattr_n($new, Complex, '$!im', nqp::add_n(nqp::mul_n($a_re, $b_im), nqp::mul_n($a_im, $b_re)) ); $new; } multi sub infix:<*>(Complex:D $a, Num(Real) $b --> Complex:D) { my $new := nqp::create(Complex); my num $b_num = $b; nqp::bindattr_n($new,Complex,'$!re', nqp::mul_n(nqp::getattr_n($a,Complex,'$!re'),$b_num) ); nqp::bindattr_n($new,Complex,'$!im', nqp::mul_n(nqp::getattr_n($a,Complex,'$!im'),$b_num) ); $new } multi sub infix:<*>(Num(Real) $a, Complex:D $b --> Complex:D) { my $new := nqp::create(Complex); my num $a_num = $a; nqp::bindattr_n($new,Complex,'$!re', nqp::mul_n($a_num,nqp::getattr_n($b,Complex,'$!re')) ); nqp::bindattr_n($new,Complex,'$!im', nqp::mul_n($a_num,nqp::getattr_n($b,Complex,'$!im')) ); $new } multi sub infix:(Complex:D $a, Complex:D $b --> Complex:D) { my num $a_re = nqp::getattr_n($a,Complex,'$!re'); my num $a_im = nqp::getattr_n($a,Complex,'$!im'); my num $b_re = nqp::getattr_n($b,Complex,'$!re'); my num $b_im = nqp::getattr_n($b,Complex,'$!im'); my num $d = nqp::add_n(nqp::mul_n($b_re,$b_re),nqp::mul_n($b_im,$b_im)); my $new := nqp::create(Complex); nqp::bindattr_n($new,Complex,'$!re', nqp::div_n( nqp::add_n(nqp::mul_n($a_re,$b_re),nqp::mul_n($a_im, $b_im)), $d ) ); nqp::bindattr_n($new,Complex,'$!im', nqp::div_n( nqp::sub_n(nqp::mul_n($a_im,$b_re),nqp::mul_n($a_re,$b_im)), $d ) ); $new } multi sub infix:(Complex:D $a, Real:D $b --> Complex:D) { Complex.new($a.re / $b, $a.im / $b) } multi sub infix:(Real:D $a, Complex:D $b --> Complex:D) { Complex.new($a, 0e0) / $b; } multi sub infix:<**>(Complex:D $a, Complex:D $b --> Complex:D) { $a.re == 0e0 && $a.im == 0e0 ?? $b.re == 0e0 && $b.im == 0e0 ?? Complex.new(1e0, 0e0) !! Complex.new(0e0, 0e0) !! ($b * $a.log).exp } multi sub infix:<**>(Num(Real) $a, Complex:D $b --> Complex:D) { $a == 0e0 ?? $b.re == 0e0 && $b.im == 0e0 ?? Complex.new(1e0, 0e0) !! Complex.new(0e0, 0e0) !! ($b * $a.log).exp } multi sub infix:<**>(Complex:D \a, Num(Real) \b --> Complex:D) { a.isNaN || b.isNaN ?? Complex.new(NaN, NaN) !! b == Inf || b == -Inf ?? b == Inf && a.abs < 1e0 || b == -Inf && a.abs > 1e0 ?? Complex.new(0e0, 0e0) !! Complex.new(NaN, NaN) !! (my $ib := b.Int) == b ?? a ** $ib !! (my $fb2 := b - $ib * 2) == 1e0 ?? a ** $ib * a.sqrt !! $fb2 == -1e0 ?? a ** $ib / a.sqrt !! (b * a.log).exp } multi sub infix:<**>(Complex:D $a, Int:D $b --> Complex:D) { my $r := Complex.new(1e0, 0e0); nqp::if( $b == 0, $r, nqp::if( $a == $r || $b == 1, $a, nqp::stmts( (my $u := $b.abs), (my $t := $a), nqp::while( $u > 0, nqp::stmts( nqp::if( $u +& 1 == 1, $r := $r * $t ), ($u := $u +> 1), ($t := $t * $t) ) ), nqp::if( $b < 0, 1e0 / $r, $r ) ) ) ) } multi sub infix:<==>(Complex:D $a, Complex:D $b --> Bool:D) { $a.re == $b.re && $a.im == $b.im } multi sub infix:<==>(Complex:D $a, Num(Real) $b --> Bool:D) { $a.re == $b && $a.im == 0e0 } multi sub infix:<==>(Num(Real) $a, Complex:D $b --> Bool:D) { $a == $b.re && 0e0 == $b.im } multi sub infix:<===>(Complex:D $a, Complex:D $b --> Bool:D) { $a.WHAT =:= $b.WHAT && $a.re === $b.re && $a.im === $b.im } multi sub infix:<≅>(Complex:D $a, Complex:D $b --> Bool:D) { $a.re ≅ $b.re && $a.im ≅ $b.im || $a <=> $b =:= Same } multi sub infix:<≅>(Complex:D $a, Num(Real) $b --> Bool:D) { $a ≅ $b.Complex } multi sub infix:<≅>(Num(Real) $a, Complex:D $b --> Bool:D) { $a.Complex ≅ $b } # Meaningful only for sorting purposes, of course. # We delegate to Real::cmp rather than <=> because parts might be NaN. multi sub infix:(Complex:D $a, Complex:D $b) { nqp::eqaddr((my $cmp := $a.re cmp $b.re),Order::Same) ?? $a.im cmp $b.im !! $cmp } multi sub infix:(Num(Real) $a, Complex:D $b) { nqp::eqaddr((my $cmp := $a cmp $b.re),Order::Same) ?? 0 cmp $b.im !! $cmp } multi sub infix:(Complex:D $a, Num(Real) $b) { nqp::eqaddr((my $cmp := $a.re cmp $b),Order::Same) ?? $a.im cmp 0 !! $cmp } multi sub infix:«<=>»(Complex:D $a, Complex:D $b) { my $tolerance := $a && $b ?? ($a.re.abs + $b.re.abs) / 2 * $*TOLERANCE # Scale slop to average real parts. !! $*TOLERANCE; # Don't want tolerance 0 if either arg is 0. # Fail unless imaginary parts are relatively negligible, compared to real parts. infix:<≅>($a.im, 0e0, :$tolerance) && infix:<≅>($b.im, 0e0, :$tolerance) ?? $a.re <=> $b.re !! X::Numeric::Real.new( target => Real, reason => "Complex is not numerically orderable", source => "Complex" ).Failure } multi sub infix:«<=>»(Num(Real) $a, Complex:D $b) { $a.Complex <=> $b } multi sub infix:«<=>»(Complex:D $a, Num(Real) $b) { $a <=> $b.Complex } constant i = Complex.new(0e0, 1e0); proto sub postfix:($, *% --> Complex:D) is pure {*} multi sub postfix:(Real:D $a --> Complex:D) { Complex.new(0e0, $a) } multi sub postfix:(Complex:D $a --> Complex:D) { Complex.new(-$a.im, $a.re) } multi sub postfix:(Numeric $a --> Complex:D) { $a * i } multi sub postfix:(Cool:D $a --> Complex:D) { $a.Numeric * i } #line 1 SETTING::src/core.c/Backtrace.rakumod my class Exception { ... } my class Backtrace { ... } my class CompUnit::RepositoryRegistry is repr('Uninstantiable') { ... } my class Backtrace::Frame { has Str $.file; has Int $.line; has Mu $.code; has Str $.subname; method !SET-SELF($!file,$!line,\code,$!subname) { $!code := code; self } multi method new(Backtrace::Frame: $file, $line, $code, $subname) { nqp::create(self)!SET-SELF($file, $line, $code, $subname) } multi method new(Backtrace::Frame: |c) { self.bless(|c) } method subtype(Backtrace::Frame:D:) { my $s = $!code.^name.lc.split('+', 2).cache[0]; $s eq 'mu' ?? '' !! $s; } method package(Backtrace::Frame:D:) { $.code.package; } multi method Str(Backtrace::Frame:D:) { my $s = self.subtype; $s ~= ' ' if $s.chars; my $text = " in {$s}$.subname at {$.file} line $.line\n"; if Backtrace.RAKUDO_VERBOSE_STACKFRAME -> $extra { my $io = $!file.IO; if $io.e { my @lines = $io.lines; my $from = max $!line - $extra, 1; my $to = min $!line + $extra, +@lines; for $from..$to -> $line { my $star = $line == $!line ?? '*' !! ' '; $text ~= "$line.fmt('%5d')$star @lines[$line - 1]\n"; } $text ~= "\n"; } } $text; } method is-hidden(Backtrace::Frame:D:) { nqp::can($!code,"is-hidden-from-backtrace") ?? $!code.is-hidden-from-backtrace !! False } method is-routine(Backtrace::Frame:D:) { nqp::hllbool(nqp::istype($!code,Routine)) } method is-setting(Backtrace::Frame:D:) { $!file.starts-with("SETTING::") || $!file.starts-with("NQP::") || $!file ~~ / [ "CORE." \w+ ".setting" | "NQPCORE.setting" | "BOOTSTRAP/v6" \w ] ".{ Rakudo::Internals.PRECOMP-EXT }" $ / || $!file.ends-with(".nqp") } } my class Backtrace { has Mu $!bt; has Mu $!frames; has Int $!bt-next; # next bt index to vivify my $RAKUDO_VERBOSE_STACKFRAME := nqp::null; method RAKUDO_VERBOSE_STACKFRAME() is implementation-detail { nqp::ifnull( $RAKUDO_VERBOSE_STACKFRAME, $RAKUDO_VERBOSE_STACKFRAME := (%*ENV // 0).Int ) } method !SET-SELF($!bt,$!bt-next) { $!frames := nqp::list; self } multi method new() { nqp::create(self)!SET-SELF( nqp::backtrace(nqp::null), 0) } multi method new(Int:D $offset) { nqp::create(self)!SET-SELF( nqp::backtrace(nqp::null), $offset) } multi method new(Exception:D $ex) { nqp::create(self)!SET-SELF( nqp::backtrace(nqp::getattr($ex,Exception,'$!ex')), 0 ) } multi method new(Mu \ex) { # assume BOOTException nqp::create(self)!SET-SELF(nqp::backtrace(nqp::decont(ex)),0) } multi method new(Exception:D $ex, Int:D $offset) { nqp::create(self)!SET-SELF( nqp::backtrace(nqp::getattr($ex,Exception,'$!ex')), $offset) } multi method new(Mu \ex, Int:D $offset) { # assume BOOTException nqp::create(self)!SET-SELF(nqp::backtrace(nqp::decont(ex)),$offset) } # note that backtraces are nqp::list()s, marshalled to us as a List multi method new(List:D $bt) { nqp::create(self)!SET-SELF($bt,0) } multi method new(List:D $bt, Int:D $offset) { nqp::create(self)!SET-SELF($bt,$offset) } method AT-POS($pos) { return nqp::atpos($!frames,$pos) if nqp::existspos($!frames,$pos); my int $elems = $!bt.elems; return Nil if $!bt-next >= $elems; # bt-next can init > elems my int $todo = $pos - nqp::elems($!frames) + 1; return Nil if $todo < 1; # in case absurd $pos passed while $!bt-next < $elems { my $frame := $!bt.AT-POS($!bt-next++); my $sub := $frame; next unless defined $sub; my Mu $do := nqp::getattr(nqp::decont($sub), ForeignCode, '$!do'); next if nqp::isnull($do); my $annotations := $frame; next unless $annotations; my $file := $annotations; next unless $file; if CompUnit::RepositoryRegistry.file-for-spec($file) -> $path { $file := $path.absolute; } next if $file.ends-with('BOOTSTRAP.nqp') || $file.ends-with('QRegex.nqp') || $file.ends-with('Perl6/Ops.nqp'); my $name := nqp::p6box_s(nqp::getcodename($do)); if ($file.starts-with('NQP::') && $file.ends-with('Compiler.nqp') && $name eq 'eval') || $file.ends-with('NQPHLL.moarvm') { # This could mean we're at the end of the interesting backtrace, # or it could mean that we're in something like sprintf (which # uses an NQP grammar to parse the format string). while $!bt-next < $elems { my $frame := $!bt.AT-POS($!bt-next++); my $annotations := $frame; next unless $annotations; my $file := $annotations; next unless $file; if $file.starts-with('SETTING::') && !$file.ends-with('.nqp') { $!bt-next--; # re-visit this frame last; } } next; } my $line := $annotations; next unless $line; if $name eq 'handle-begin-time-exceptions' { $!bt-next = $elems; last; } elsif $name ne '' { my $code; try { $code := nqp::getcodeobj($do); $code := Any unless nqp::istype($code, Mu); }; nqp::push($!frames, Backtrace::Frame.new( $file, $line.Int, $code, $name.starts-with("_block") ?? '' !! $name )); } last unless $todo = $todo - 1; } # found something if nqp::existspos($!frames,$pos) { nqp::atpos($!frames,$pos); } } method next-interesting-index(Backtrace:D: Int $idx is copy = 0, :$named, :$noproto, :$setting, :$reveal) { ++$idx; while self.AT-POS($idx++) -> $cand { next if !$reveal # keep hidden && $cand.is-hidden; # if hidden from backtrace next if $noproto # no proto's please && nqp::can($cand,"is_dispatcher") && $cand.code.is_dispatcher; # if a dispatcher next if !$setting # no settings please && $cand.is-setting; # and in setting my $n := $cand.subname; next if $named && !$n; # only want named ones and no name next if $n eq ''; # outer calling context return $idx - 1; } Nil; } method outer-caller-idx(Backtrace:D: Int $startidx) { if self.AT-POS($startidx).code -> $start { my %outers; my $current = $start.outer; while $current.DEFINITE { %outers{$current.static_id} = $start; $current = $current.outer; } my @outers; my $i = $startidx; while self.AT-POS($i++) -> $cand { my $code = $cand.code; next unless $code.DEFINITE && %outers{$code.static_id}.DEFINITE; @outers.push: $i - 1; last if $cand.is-routine; } @outers; } else { $startidx.list; } } method nice(Backtrace:D: :$oneline) { my $setting = %*ENV; try { CATCH { default { return ""; } } my @frames; my Int $i = self.next-interesting-index(-1); while $i.defined { $i = self.next-interesting-index($i, :$setting) if $oneline; last unless $i.defined; my $prev = self.AT-POS($i); if $prev.is-routine { @frames.push: $prev; } else { my @outer_callers := self.outer-caller-idx($i); my $target_idx = @outer_callers.keys.grep({self.AT-POS($i).code.^isa(Routine)})[0]; $target_idx ||= @outer_callers[0] || $i; my $current = self.AT-POS($target_idx); @frames.append: $current.clone(line => $prev.line); $i = $target_idx; } last if $oneline; $i = self.next-interesting-index($i, :$setting); } @frames.join; } } multi method gist(Backtrace:D:) { my $els := +self.list; 'Backtrace(' ~ $els ~ ' frame' ~ 's' x ($els != 1) ~ ')' } multi method Str(Backtrace:D:) { self.nice } multi method flat(Backtrace:D:) { self.list } multi method map(Backtrace:D: &block) { my $pos = 0; gather while self.AT-POS($pos++) -> $cand { take block($cand); } } multi method first(Backtrace:D: Mu $test) { my $pos = 0; while self.AT-POS($pos++) -> $cand { return-rw $cand if $cand ~~ $test; } Nil; } multi method list(Backtrace:D:) { self.AT-POS(1_000_000); # will stop when no more frames to be found nqp::p6bindattrinvres(nqp::create(List), List, '$!reified', $!frames) } method first-none-setting-line(Backtrace:D:) { (self.first({ !.is-hidden && !.is-setting }) // "\n").Str; } method concise(Backtrace:D:) { (self.grep({ !.is-hidden && .is-routine && !.is-setting }) // "\n").join; } method full(Backtrace:D:) { self.list.join } method summary(Backtrace:D:) { (self.grep({ !.is-hidden && (.is-routine || !.is-setting)}) // "\n").join; } method is-runtime (Backtrace:D:) { my $bt = $!bt; for $bt.keys { my $p6sub := $bt[$_]; if nqp::istype($p6sub, ForeignCode) { try { my Mu $sub := nqp::getattr(nqp::decont($p6sub), ForeignCode, '$!do'); my str $name = nqp::getcodename($sub); return True if nqp::iseq_s($name, 'THREAD-ENTRY'); return True if nqp::iseq_s($name, 'eval'); return True if nqp::iseq_s($name, 'print_control'); return False if nqp::iseq_s($name, 'compile'); } } } False; } } #line 1 SETTING::src/core.c/StrDistance.rakumod my class StrDistance is Cool { has Str $.before is built(:bind); has Str $.after is built(:bind); has Int $!distance; method Bool() { $.before ne $.after } multi method ACCEPTS(StrDistance:D: Mu \a) { self } method Numeric() { self.Int } method Str { $.after } multi method Int(StrDistance:D:) { $!distance //= do { my @s = *, |$.before.comb; my @t = *, |$.after.comb; my @d; @d[$_][ 0] = $_ for ^@s.end; @d[ 0][$_] = $_ for ^@t.end; my int $s_elems = @s.elems; my int $t_elems = @t.elems; loop (my int $i = 1; $i < $s_elems; $i = $i + 1) { loop (my int $j = 1; $j < $t_elems; $j = $j + 1) { @d[$i][$j] = @s[$i] eq @t[$j] ?? @d[$i-1][$j-1] # No operation required when eq !! ( @d[$i-1][$j ], # Deletion @d[$i ][$j-1], # Insertion @d[$i-1][$j-1], # Substitution ).min + 1; } } @d.tail.tail; } } } #line 1 SETTING::src/core.c/Exception.rakumod my class Allomorph { ... } my role X::Comp { ... } my class X::ControlFlow { ... } my role X::Control { ... } my class Exception { has $!ex; has $!bt; method backtrace(Exception:D:) { $!bt ?? $!bt !! nqp::isconcrete($!ex) ?? ($!bt := Backtrace.new($!ex)) !! Nil } method Failure(Exception:D:) is hidden-from-backtrace { Failure.new: self } # Only valid if .backtrace has not been called yet method vault-backtrace(Exception:D:) { nqp::isconcrete($!ex) && $!bt ?? Backtrace.new($!ex) !! Nil } method reset-backtrace(Exception:D: --> Nil) { $!ex := Nil } multi method Str(Exception:D:) { my $str; if nqp::isconcrete($!ex) { my str $message = nqp::getmessage($!ex); $str = nqp::isnull_s($message) ?? '' !! nqp::p6box_s($message); } $str ||= (try self.message); $str = ~$str if defined $str; $str // "Something went wrong in {self.WHAT.gist}"; } multi method gist(Exception:D:) { my $str; if nqp::isconcrete($!ex) { my str $message = nqp::getmessage($!ex); $str = nqp::isnull_s($message) ?? (try self.message) // "Died with {self.^name}" !! nqp::p6box_s($message); $str ~= "\n"; try $str ~= self.backtrace || Backtrace.new() || ' (no backtrace available)'; } else { $str = (try self.message) // "Unthrown {self.^name} with no message"; } $str; } method throw(Exception:D: $bt?) { unless nqp::isconcrete($!ex) and $bt { my $orig-ex := $!ex; $!ex := nqp::newexception(); self!maybe-set-control() unless nqp::isconcrete($orig-ex); } $!bt := $bt; # Even if !$bt nqp::setpayload($!ex, self); nqp::throw($!ex) } method rethrow(Exception:D:) { unless nqp::isconcrete($!ex) { $!ex := nqp::newexception(); try nqp::setmessage($!ex, self.message); self!maybe-set-control(); } nqp::setpayload($!ex, self); nqp::rethrow($!ex) } method !maybe-set-control(--> Nil) { if nqp::istype(self, X::Control) { nqp::setextype($!ex, nqp::const::CONTROL_ANY); } } method resume(Exception:D: --> True) { nqp::resume($!ex); } method die(Exception:D:) { self.throw } method fail(Exception:D:) { try self.throw; my $fail := $!.Failure; CATCH { $fail.exception.throw } nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, $fail); } method is-compile-time(--> False) { } method message() { ... } method UPGRADE-RAT(Int $nu, Int $de) { die "Upgrading of Rat $nu / $de not allowed" } } my role X::Wrapper { has Mu $!exception is required is built(:bind); has Mu $!ex-payload; has $!is-raku-exception; method exception is raw { my $ex := nqp::decont($!exception); nqp::isconcrete(nqp::decont($!ex-payload)) ?? $!ex-payload !! ($!ex-payload := nqp::istype($ex, Exception) ?? $ex !! (nqp::ifnull(nqp::getpayload($ex), $ex))) } method !is-raku-exception { $!is-raku-exception //= nqp::istype(self.exception, Exception) } method !wrappee-message(:$concise, :$details) { my $ex-msg := self!is-raku-exception ?? $!ex-payload.message !! nqp::getmessage($!ex-payload); my $message := $concise ?? $ex-msg !! ($!is-raku-exception ?? $!ex-payload.gist !! $ex-msg ~ "\n" ~ Backtrace.new(nqp::backtrace($!exception))); $details ?? "; exception details:\n\n" ~ $message.indent(4) !! $message } method !exception-name-message { self!is-raku-exception ?? " with " ~ $!ex-payload.^name !! "" } } my class X::SecurityPolicy is Exception {} my class X::SecurityPolicy::Eval is X::SecurityPolicy { has $.payload = "EVAL is a very dangerous function!!!"; my role SlurpySentry { } method message() { (($.payload ~~ SlurpySentry ?? $.payload.list.join # Remove spaces die(*@msg)/fail(*@msg) forms !! $.payload.Str ) ~ " (use the MONKEY-SEE-NO-EVAL pragma to override this error but only if you're VERY sure your data contains no injection attacks)." ).naive-word-wrapper } method Numeric() { $.payload.Numeric } method from-slurpy (|cap) { self.new(:payload(cap does SlurpySentry)) } } my class X::AdHoc is Exception { has $.payload is default(Nil) = "Unexplained error"; my role SlurpySentry { } method message() { # Remove spaces for die(*@msg)/fail(*@msg) forms given $.payload { when SlurpySentry { $_.list.join; } default { .Str; } } } method Numeric() { $.payload.Numeric } method from-slurpy (|cap) { self.new(:payload(cap does SlurpySentry)) } } my class X::NQP::NotFound is Exception { has $.op; method message() { "Could not find nqp::$.op, did you forget 'use nqp;' ?" } } my class X::NotFoundInRepository is Exception { has $.file; has @.repos; method message() { "Could not find $.file in:\n" ~ (@.repos || $*REPO.repo-chain).join("\n").indent(4) } } my class X::Dynamic::NotFound is Exception { has $.name; method message() { "Dynamic variable $.name not found"; } } my class X::Method::NotFound is Exception { has Mu $.invocant; has $.method; has $.typename; has Bool $.private; has $.addendum; has @!suggestions; has @!tips; has $!message; # These attributes are an implementation detail. Not to be documented. has $.in-class-call; has $.containerized; method of-type() { nqp::eqaddr(nqp::decont($!invocant),IterationEnd) ?? "IterationEnd" !! "of type '$.typename'" } method message() { (try $!message // self!create-message) // "Method " ~ $.invocant.^name ~ "." ~ $.method ~ " not found"; } method suggestions() { self!create-message unless $!message; @!suggestions } method tips() { self!create-message unless $!message; @!tips } method !create-message() { my @message = $.private ?? "No such private method '!$.method' for invocant $.of-type" !! nqp::istype($.invocant,Str) ?? "No such method '$.method' for string '$.invocant'" !! "No such method '$.method' for invocant $.of-type"; @!tips.push: "You actually called '$.method' on a container, was that what you intended?" if $.containerized; @message.push: $.addendum if $.addendum; my $indirect-method = $.method.starts-with("!") ?? $.method.substr(1) !! ""; my %suggestions; my int $max_length = do given $.method.chars { when 0..3 { 1 } when 4..8 { 2 } when 9..* { 3 } } if $.method eq 'length' { given $!invocant { when List { %suggestions{$_} = 0 for } when Cool { %suggestions{$_} = 0 for ; } default { %suggestions{$_} = 0 for ; } } } elsif $.method eq 'bytes' { %suggestions = 0; } elsif $.method eq 'ceil' { %suggestions = 0; } elsif $.method eq 'last' { %suggestions = 0; } my sub code-name(Mu $meth) { # KnowHOW `methods` method returns a hash. Respectively, iteration over .^methods gives us Pairs. return $meth.key if $meth ~~ Pair; my $code-obj := nqp::decont($meth); (try nqp::can($code-obj,'name') ?? $code-obj.name !! nqp::getcodename($code-obj)) // '?' } my $public_suggested = 0; sub find_public_suggestion($before, $after --> Nil) { my $dist := StrDistance.new( before => $before.fc, after => $after.fc ); if $dist <= $max_length { $public_suggested = 1; %suggestions{$after} = $dist.Int; } } if nqp::can($!invocant.HOW, 'methods') { # $!invocant can be a KnowHOW which method `methods` returns a hash, not a list. my $invocant_methods := Set.new: $!invocant.^methods(:local).map: { code-name($_) }; my \type = ::($.typename); my $found-types := SetHash.new; for $!invocant.^methods(:all) -> $method_candidate { my $method_name := code-name($method_candidate); # GH#1758 do not suggest a submethod from a parent next if $method_candidate.^name eq 'Submethod' # a submethod && !$invocant_methods{$method_name}; # unknown method if $.method eq $method_name { $found-types.set($method_candidate.package.^name()); } elsif nqp::istype(type, Failure) { type.Bool; # defuse } elsif nqp::can(type, $method_name) { find_public_suggestion($.method, $method_name); } } if $found-types.keys -> @types { @!tips.push: "Found '$.method' on type{@types.elems > 1 ?? "s: " !! ""} '@types.join(q|', '|)'"; } # handle special unintrospectable cases for -> $method_name { find_public_suggestion($.method, $method_name); } } my $private_suggested = 0; if $.in-class-call && nqp::can($!invocant.HOW, 'private_method_table') { for $!invocant.^private_method_table.keys -> $method_name { my $dist = StrDistance.new( before => $.method.fc, after => $method_name.fc ); if $dist <= $max_length { $private_suggested = 1; %suggestions{"!$method_name"} = $dist.Int unless $indirect-method eq $method_name; } } } if $indirect-method && !$.private && $private_suggested { @!tips.push: "Method name starts with '!', did you mean 'self!\"$indirect-method\"()'?"; } if %suggestions.sort(-> $a, $b { $a.value cmp $b.value || $a.key cmp $b.key }) -> @!suggestions { my $boundary := @!suggestions[@!suggestions.end min 3].value; @!suggestions = @!suggestions.grep(*.value <= $boundary).map(*.key); if @!suggestions == 1 { @!tips.push: "Did you mean '@!suggestions[0]'?"; } elsif @!suggestions { @!tips.push: "Did you mean any of these: { @!suggestions.map( { "'$_'" } ).join(", ") }?"; } } if !$indirect-method && ($private_suggested ^^ $public_suggested) && ($private_suggested ^^ $.private) { @!tips.push: "Perhaps a " ~ ($private_suggested ?? "private" !! "public") ~ " method call must be used." } if @!tips > 1 { @!tips = @!tips.map: "\n" ~ ("- " ~ *).naive-word-wrapper(:indent(" ")); @message.push: ($.addendum ?? "Other possible" !! "Possible") ~ " causes are:"; } elsif @!tips { @message.push: @!tips.shift; } @message[0] ~= "." if @message > 1; $!message = @message.join(" ").naive-word-wrapper ~ @!tips.join } } my class X::Method::Duplicate is Exception { has $.method-type; has $.method; has $.typename; method message() { "Package '" ~ $.typename ~ "' already has a " ~ $.method-type ~ " '" ~ $.method ~ "' (did you mean to declare a multi method?)" } } my class X::Method::InvalidQualifier is Exception { has $.method; has $.invocant; has $.qualifier-type; method message() { "Cannot dispatch to method $.method on {$.qualifier-type.^name} " ~ "because it is not inherited or done by {$.invocant.^name}"; } } my class X::Role::Parametric::NoSuchCandidate is Exception { has Mu $.role; has $.hint; method message { "No appropriate parametric role variant available for '" ~ $.role.^name ~ "'" ~ ($.hint ?? ":\n" ~ (~$.hint).indent(4) !! "") } } my class X::Pragma::NoArgs is Exception { has $.name; method message { "The '$.name' pragma does not take any arguments." } } my class X::Pragma::CannotPrecomp is Exception { has $.what = 'This compilation unit'; method message { "$.what cannot be pre-compiled and thus cannot be used in a module" } } my class X::Pragma::CannotWhat is Exception { has $.what; has $.name; method message { "'$.what $.name' is not an option." } } my class X::Pragma::MustOneOf is Exception { has $.name; has $.alternatives; method message { "'$.name' pragma expects one parameter out of $.alternatives." } } my class X::Pragma::UnknownArg is Exception { has $.name; has $.arg; method message { "Unknown argument '{$.arg.raku}' specified with the '$.name' pragma." } } my class X::Pragma::OnlyOne is Exception { has $.name; method message { "The '$.name' pragma only takes one argument." } } my class X::Pragma::Unknown is Exception { has $.name; method message { "The '$.name' pragma is unknown." } } my class X::Ism::Unknown is Exception { has $.name; method message { "The '$.name' ism is unknown." } } my role X::Control is Exception { } my class CX::Next does X::Control { method message() { "" } } my class CX::Redo does X::Control { method message() { "" } } my class CX::Last does X::Control { method message() { "" } } my class CX::Take does X::Control { method message() { "" } } my class CX::Warn does X::Control { has $.message; method UPGRADE-RAT(Int $nu, Int $de) is raw { warn "Downgrading Rat $nu / $de to Num"; nqp::p6box_n(nqp::div_In($nu,$de)) } } my class CX::Succeed does X::Control { method message() { "" } } my class CX::Proceed does X::Control { method message() { "" } } my class CX::Return does X::Control { method message() { "" } } my class CX::Emit does X::Control { method message() { "" } } my class CX::Done does X::Control { method message() { "" } } sub EXCEPTION(|) is implementation-detail { my Mu $vm_ex := nqp::shift(nqp::p6argvmarray()); my Mu $payload := nqp::getpayload($vm_ex); if nqp::istype($payload, Exception) { nqp::bindattr($payload, Exception, '$!ex', $vm_ex); $payload; } else { my int $type = nqp::getextype($vm_ex); my $ex; if $type +& nqp::const::CONTROL_NEXT { $ex := CX::Next.new(); } elsif $type +& nqp::const::CONTROL_REDO { $ex := CX::Redo.new(); } elsif $type +& nqp::const::CONTROL_LAST { $ex := CX::Last.new(); } elsif $type == nqp::const::CONTROL_TAKE { $ex := CX::Take.new(); } elsif $type == nqp::const::CONTROL_WARN { my str $message = nqp::getmessage($vm_ex); $message = 'Warning' if nqp::isnull_s($message) || $message eq ''; $ex := CX::Warn.new(:$message); } elsif $type == nqp::const::CONTROL_SUCCEED { $ex := CX::Succeed.new(); } elsif $type == nqp::const::CONTROL_PROCEED { $ex := CX::Proceed.new(); } elsif $type == nqp::const::CONTROL_RETURN { $ex := CX::Return.new(); } elsif $type == nqp::const::CONTROL_EMIT { $ex := CX::Emit.new(); } elsif $type == nqp::const::CONTROL_DONE { $ex := CX::Done.new(); } else { $ex := nqp::create(X::AdHoc); nqp::bindattr($ex, X::AdHoc, '$!payload', nqp::p6box_s(nqp::getmessage($vm_ex) // 'unknown exception')); } nqp::bindattr($ex, Exception, '$!ex', $vm_ex); $ex; } } my class X::Comp::AdHoc { ... } sub COMP_EXCEPTION(|) is implementation-detail { my Mu $vm_ex := nqp::shift(nqp::p6argvmarray()); my Mu $payload := nqp::getpayload($vm_ex); if nqp::istype($payload, Exception) { nqp::bindattr($payload, Exception, '$!ex', $vm_ex); $payload; } else { my $ex := nqp::create(X::Comp::AdHoc); nqp::bindattr($ex, Exception, '$!ex', $vm_ex); nqp::bindattr($ex, X::AdHoc, '$!payload', nqp::p6box_s(nqp::getmessage($vm_ex))); $ex; } } do { sub print_exception(|) { my Mu $ex := nqp::atpos(nqp::p6argvmarray(), 0); my $e := EXCEPTION($ex); $*EXIT = 1; $*EXCEPTION = $e; if %*ENV -> $handler { my $class := ::("Exceptions::$handler"); unless nqp::istype($class,Failure) { temp %*ENV = ""; # prevent looping unless $class.process($e) { nqp::getcurhllsym('&THE_END')(); return } } } my Mu $err := $*ERR; try { my $v := $e.vault-backtrace; $e.backtrace; # This is where most backtraces actually happen if $e.is-compile-time || $e.backtrace && $e.backtrace.is-runtime { $err.say($e.gist); if $v and !$e.gist.ends-with($v.Str) { $err.say("Actually thrown at:"); $err.say($v.Str); } } elsif Rakudo::Internals.VERBATIM-EXCEPTION(0) { $err.print($e.Str); } else { $err.say("===SORRY!==="); $err.say($e.Str); } nqp::getcurhllsym('&THE_END')(); CONTROL { when CX::Warn { .resume } } } if $! -> $secondary-ex { $err.say: "===SORRY!=== Error while reporting exception " ~ $e.^name ~ (try { ": secondary " ~ $secondary-ex.^name ~ " has been thrown" } || "") ~ (try { "\n The original message was: " ~ $e.message } || "") ~ (try { "\n The secondary message is: " ~ $secondary-ex.message } || "") ~ (try { "\n The original backtrace:\n" ~ $e.backtrace.Str.indent(4) } || ""); nqp::rethrow(nqp::getattr(nqp::decont($!), Exception, '$!ex')) } } sub print_control(|) { my Mu $ex := nqp::atpos(nqp::p6argvmarray(),0); my int $type = nqp::getextype($ex); my $backtrace = Backtrace.new(nqp::backtrace($ex)); nqp::if( nqp::iseq_i($type,nqp::const::CONTROL_WARN), nqp::stmts( (my Mu $err := $*ERR), (my str $msg = nqp::getmessage($ex)), $err.say(nqp::if(nqp::chars($msg),$msg,"Warning")), $err.print($backtrace.first-none-setting-line), nqp::resume($ex) ) ); my $label = $type +& nqp::const::CONTROL_LABELED ?? "labeled " !! ""; if $type +& nqp::const::CONTROL_LAST { X::ControlFlow.new(illegal => "{$label}last", enclosing => 'loop construct', :$backtrace).throw; } elsif $type +& nqp::const::CONTROL_NEXT { X::ControlFlow.new(illegal => "{$label}next", enclosing => 'loop construct', :$backtrace).throw; } elsif $type +& nqp::const::CONTROL_REDO { X::ControlFlow.new(illegal => "{$label}redo", enclosing => 'loop construct', :$backtrace).throw; } elsif $type +& nqp::const::CONTROL_PROCEED { X::ControlFlow.new(illegal => 'proceed', enclosing => 'when clause', :$backtrace).throw; } elsif $type +& nqp::const::CONTROL_SUCCEED { # XXX: should work like leave() ? X::ControlFlow.new(illegal => 'succeed', enclosing => 'when clause', :$backtrace).throw; } elsif $type +& nqp::const::CONTROL_TAKE { X::ControlFlow.new(illegal => 'take', enclosing => 'gather', :$backtrace).throw; } elsif $type +& nqp::const::CONTROL_EMIT { X::ControlFlow.new(illegal => 'emit', enclosing => 'supply or react', :$backtrace).throw; } elsif $type +& nqp::const::CONTROL_DONE { X::ControlFlow.new(illegal => 'done', enclosing => 'supply or react', :$backtrace).throw; } else { X::ControlFlow.new(illegal => 'control exception', enclosing => 'handler', :$backtrace).throw; } } my Mu $comp := nqp::getcomp('Raku'); $comp.^add_method('handle-exception', method (|) { my Mu $ex := nqp::atpos(nqp::p6argvmarray(), 1); print_exception($ex); nqp::exit(1); 0; } ); $comp.^add_method('handle-control', method (|) { my Mu $ex := nqp::atpos(nqp::p6argvmarray(), 1); print_control($ex); nqp::rethrow($ex); } ); } my role X::OS is Exception { has $.os-error; method message() { $.os-error } } my role X::IO does X::OS { }; my class X::IO::Unknown does X::IO { has $.trying; method message { "Unknown IO error trying '$.trying'" } } my class X::IO::Rename does X::IO { has $.from; has $.to; method message() { "Failed to rename '$.from' to '$.to': $.os-error" } } my class X::IO::Copy does X::IO { has $.from; has $.to; method message() { "Failed to copy '$.from' to '$.to': $.os-error" } } my class X::IO::Lock does X::IO { has $.lock-type; method message() { "Could not obtain $.lock-type lock: $.os-error" } } my class X::IO::Move does X::IO { has $.from; has $.to; method message() { "Failed to move '$.from' to '$.to': $.os-error" } } my class X::IO::DoesNotExist does X::IO { has $.path; has $.trying; method message() { "Failed to find '$.path' while trying to do '.$.trying'" } } my class X::IO::NotAFile does X::IO { has $.path; has $.trying; method message() { "'$.path' is not a regular file while trying to do '.$.trying'" } } my class X::IO::Null does X::IO { method message() { "Cannot use null character (U+0000) as part of the path" } } my class X::IO::Directory does X::IO { has $.path; has $.trying; has $.use; method message () { my $x = "'$.path' is a directory, cannot do '.$.trying' on a directory"; if $.use { $x ~= ", try '{$.use}()' instead" } $x; } } my class X::IO::Symlink does X::IO { has $.target; has $.name; method message() { "Failed to create symlink called '$.name' on target '$.target': $.os-error" } } my class X::IO::Link does X::IO { has $.target; has $.name; method message() { "Failed to create link called '$.name' on target '$.target': $.os-error" } } my class X::IO::Mkdir does X::IO { has $.path; has $.mode; method message() { "Failed to create directory '$.path' with mode '0o{$.mode.fmt("%03o")}': $.os-error" } } my class X::IO::Chdir does X::IO { has $.path; method message() { "Failed to change the working directory to '$.path': $.os-error" } } my class X::IO::Dir does X::IO { has $.path; method message() { "Failed to get the directory contents of '$.path': $.os-error" } } my class X::IO::Cwd does X::IO { method message() { "Failed to get the working directory: $.os-error" } } my class X::IO::Flush does X::IO { method message() { "Cannot flush handle: $.os-error" } } my class X::IO::NotAChild does X::IO { has $.path; has $.child; method message() { "Path {$.child.raku} is not a child of path {$.path.raku}" } } my class X::IO::Resolve does X::IO { has $.path; method message() { "Failed to completely resolve {$.path.raku}" } } my class X::IO::Rmdir does X::IO { has $.path; method message() { "Failed to remove the directory '$.path': $.os-error" } } my class X::IO::Unlink does X::IO { has $.path; method message() { "Failed to remove the file '$.path': $.os-error" } } my class X::IO::Chmod does X::IO { has $.path; has $.mode; method message() { "Failed to set the mode of '$.path' to '0o{$.mode.fmt("%03o")}': $.os-error" } } my class X::IO::Chown does X::IO { has $.path; has $.uid; has $.gid; method message() { "Failed to change owner of '$.path' to $.uid/$.gid: $.os-error" } } my class X::IO::BinaryAndEncoding does X::IO { method message { "Cannot open a handle in binary mode (:bin) and also specify an encoding" } } my class X::IO::BinaryMode does X::IO { has $.trying; method message { "Cannot do '$.trying' on a handle in binary mode" } } my class X::IO::Closed does X::IO { has $.trying; method message { "Cannot do '$.trying' on a closed handle" } } my role X::Comp is Exception { has $.pos; has $.filename; has $.line; has $.directive-filename; # set with #line directive and if differs from $.filename has $.column; has @.modules; has $.is-compile-time = False; has $.pre; has $.post; has @.highexpect; multi method gist(::?CLASS:D: :$sorry = True, :$expect = True) { if $.is-compile-time { my ($red,$clear,$green,$yellow,$eject) = Rakudo::Internals.error-rcgye; my $r = $sorry ?? self.sorry_heading() !! ""; $r ~= $_ ?? "$.message\n$_" !! $.message with self.at-line($.line); $r ~= "\n------> $green$.pre$yellow$eject$red$.post$clear" if defined $.pre; if $expect && @.highexpect { $r ~= "\n expecting any of:"; for flat @.highexpect».list { $r ~= "\n $_"; } } for @.modules.reverse.skip { my $line = nqp::p6box_i($_); $r ~= $_.defined ?? "\n from module $_ ($_ line $line)" !! "\n from $_ line $line"; } $r; } else { self.Exception::gist; } } method at-line(*@lines, :$filename is copy) { my $fn = $.directive-filename // $.filename; # If $filename is specified and is different from $fn then the message is about a "long" location crossing a # #line directive. Most typical it would be an unclosed brace or a quote starting before the directive. with $filename { $fn = $_ eq $fn ?? "" !! $_; } !$fn || ($fn eq '') ?? !$filename.defined && @lines == 1 && @lines.head == 1 ?? "" !! "at line" ~ (@lines == 1 ?? ' ' !! 's ') ~ @lines.join(", ") !! "at $fn:" ~ @lines.join(","); } method sorry_heading() { my ($red, $clear) = Rakudo::Internals.error-rcgye; "$red==={$clear}SORRY!$red===$clear Error while compiling{ $.filename eq '' ?? ':' !! " $.filename" }\n" } method SET_FILE_LINE($file, $line) is implementation-detail { $!filename = $file; $!line = $line; $!is-compile-time = True; } } class X::Comp::Group is Exception { has $.panic; has @.sorrows; has @.worries; method TWEAK(Mu :$sorries --> Nil) { @!sorrows := $sorries<> if $sorries; } method is-compile-time(--> True) { } multi method gist(::?CLASS:D:) { my $r = ""; if $.panic || @.sorrows { my ($red, $clear) = Rakudo::Internals.error-rcgye; $r ~= "$red==={$clear}SORRY!$red===$clear\n"; for @.sorrows { $r ~= .gist(:!sorry, :!expect) ~ "\n"; } if $.panic { $r ~= $.panic.gist(:!sorry) ~ "\n"; } } if @.worries { $r ~= $.panic || @.sorrows ?? "Other potential difficulties:\n" !! "Potential difficulties:\n"; for @.worries { $r ~= .gist(:!sorry, :!expect).indent(4) ~ "\n"; } } $r } method message() { my @m; for @.sorrows { @m.append(.message); } if $.panic { @m.append($.panic.message); } for @.worries { @m.append(.message); } @m.join("\n") } } my role X::MOP is Exception { } my class X::Comp::BeginTime does X::Comp { has str $.use-case; has $.exception; method message() { $!exception ~~ X::MOP ?? $!exception.message !! "An exception {$!exception.^name} occurred while $!use-case" ~ (try { ": " ~ $!exception.message } // "") } multi method gist(::?CLASS:D: :$sorry = True) { my $r = $sorry ?? self.sorry_heading() !! ""; $r ~= "$.message\nat $.filename():$.line"; for @.modules.reverse.skip { my $line = nqp::p6box_i($_); $r ~= $_.defined ?? "\n from module $_ ($_ line $line)" !! "\n from $_ line $line"; } unless $!exception ~~ X::MOP { $r ~= "\nException details:\n" ~ $!exception.gist.indent(2); } $r; } } my class X::Coerce is Exception { has Mu $.target-type is built(:bind); has Mu $.from-type is built(:bind); method message() { "from '" ~ $!from-type.^name ~ "' into '" ~ $!target-type.^name ~ "'" } } my class X::Coerce::Impossible is X::Coerce { has $.hint is required; method message() { "Impossible coercion " ~ callsame() ~ ": " ~ $!hint } } my class X::Coerce::Role is X::Coerce does X::Wrapper { method message() { "Coercion " ~ callsame() ~ " died" ~ self!exception-name-message ~ " while trying to pun the target role" ~ self!wrappee-message(:details) } } # XXX a hack for getting line numbers from exceptions from the metamodel my class X::Comp::AdHoc is X::AdHoc does X::Comp { method is-compile-time(--> True) { } } my class X::Comp::FailGoal does X::Comp { has $.dba; has $.goal; has $.line-real; has $.filename-real; method is-compile-time(--> True) { } method message { "Unable to parse expression in $.dba; couldn't find final $.goal" ~ " (corresponding starter was " ~ self.at-line($.line-real, :filename($.filename-real)) ~ ")" } } my role X::Syntax does X::Comp { } my role X::Pod { } my class X::NYI is Exception { has $.feature; has $.did-you-mean; has $.workaround; method message() { my $msg = ($.feature ?? $.feature ~ " not" !! "Not") ~ " yet implemented. Sorry."; $msg ~= "\nDid you mean: {$.did-you-mean.gist}?" if $.did-you-mean; $msg ~= "\nWorkaround: $.workaround" if $.workaround; $msg } } sub NYI(str $feature) { X::NYI.new(:$feature).Failure } my class X::Comp::NYI is X::NYI does X::Comp { }; my class X::NYI::Available is X::NYI { has @.available = die("Must give :available for installation. "); method available-str { my @a = @.available; my $a = @a.pop; @a ?? (@a.join(', ') || (), $a).join(" or ") !! $a; } method message() { "Please install { self.available-str } for $.feature support. " } } my class X::NYI::BigInt is Exception { has $.op; has $.big; has $.side = 'right'; method message() { "Big integer $!big not yet supported on {$!side}hand side of '$!op' operator" } } my class X::Experimental does X::Comp { has $.feature; has $.use = $!feature; method message() { "Use of $.feature is experimental; please 'use experimental :$.use'" } } my class X::Worry is Exception { } my class X::Worry::P5 is X::Worry { } my class X::Worry::P5::Reference is X::Worry::P5 { method message { q/To pass an array, hash or sub to a function in Raku, just pass it as is. For other uses of Perl's ref operator consider binding with ::= instead. Parenthesize as \\(...) if you intended a capture of a single variable./ } } my class X::Worry::P5::BackReference is X::Worry::P5 { method message { q/To refer to a positional match capture, just use $0 (numbering starts at 0). Parenthesize as \\(...) if you intended a capture of a single numeric value./ } } my class X::Worry::P5::LeadingZero is X::Worry::P5 { has $.value; method message { ('Leading 0 has no meaning. If you meant to create an octal number' ~ ", use '0o' prefix" ~ ( $.value.comb.first(*.unival > 7) ?? ", but note that $.value is not a valid octal number" !! "; like, '0o$.value'" ) ~ '. If you meant to create a string, please add quotation marks.' ).naive-word-wrapper } } my class X::Worry::Precedence::Range is X::Worry { has $.action; method message { "To $!action a range, parenthesize the whole range. (Or parenthesize the whole endpoint expression, if you meant that.)" } } my role X::Trait is Exception { has $.type; # is, will, of etc. has $.subtype; # wrong subtype being tried has $.declaring; # variable, sub, parameter, etc. (optional) } my class X::Trait::Invalid does X::Trait { has $.name; # target of trait, e.g., '$foo' in `$foo is rw` has $.reason; # reason the trait was invalid (optional) method message () { "Cannot use '$.type $.subtype' on $.declaring '$.name'" ~($!reason ?? " because:\n$!reason.indent(4)" !! '.'); } } my class X::Comp::Trait::Invalid is X::Trait::Invalid does X::Comp { }; my class X::Trait::Unknown does X::Trait { method message () { "Can't use unknown trait '{ try { $.type } // "unknown type" }' -> '{ try { $.subtype } // "unknown subtype" }' in $.declaring declaration." } } my class X::Comp::Trait::Unknown is X::Trait::Unknown does X::Comp { }; my class X::Trait::NotOnNative does X::Trait { has $.native; # type of native (optional) method message () { "Can't use trait '$.type $.subtype' on a native" ~ ( $.native ?? " $.native." !! "." ); } } my class X::Comp::Trait::NotOnNative is X::Trait::NotOnNative does X::Comp { }; my class X::Trait::Scope does X::Trait { has $.scope; # not supported (but used) scope has $.supported; # hint about what is allowed instead method message () { "Can't apply trait '$.type $.subtype' on a $.scope scoped $.declaring." ~ ( $.supported ?? " Only {$.supported.join(' and ')} scoped {$.declaring}s are supported." !! '' ); } } my class X::Comp::Trait::Scope is X::Trait::Scope does X::Comp { }; my class X::Exhausted is Exception { has $.what; has $.reason; method message { $.reason ?? "Could not create another $.what because of: $.reason" !! "Could not create another $.what" } } my class X::OutOfRange is Exception { has $.what = 'Argument'; has $.got = ''; has $.range = ''; has $.comment; method message() { my $result = $.comment.defined ?? "$.what out of range. Is: $.got.gist(), should be in $.range.gist(); $.comment" !! "$.what out of range. Is: $.got.gist(), should be in $.range.gist()"; $result; } } my class X::Buf::AsStr is Exception { has $.object; has $.method; method message() { my $message = $.method.starts-with('Str') ?? "Stringification of a {$.object.^name} is not done with '$.method'" !! "A {$.object.^name} is not a Str, so using '$.method' will not work"; ($message ~ ". The 'decode' method should be used to convert a {$.object.^name} to a Str." ).naive-word-wrapper } } my class X::Buf::Pack is Exception { has $.directive; method message() { "Unrecognized directive '$.directive'"; } } my class X::Buf::Pack::NonASCII is Exception { has $.char; method message() { "non-ASCII character '$.char' while processing an 'A' template in pack"; } } my class X::Signature::Placeholder does X::Comp { has $.placeholder; method message() { "Placeholder variable '$.placeholder' cannot override existing signature"; } } my class X::Placeholder::Block does X::Comp { has $.placeholder; method message() { "Placeholder variable '$.placeholder' may not be used here because the surrounding block does not take a signature.".naive-word-wrapper; } } my class X::Placeholder::NonPlaceholder does X::Comp { has $.variable_name; has $.placeholder; has $.decl; method message() { my $decl = $!decl ?? $!decl !! 'block'; "'$!variable_name' has already been used as a non-placeholder in the surrounding $decl, so you will confuse the reader if you suddenly declare $!placeholder here.".naive-word-wrapper } } my class X::Placeholder::Mainline is X::Placeholder::Block { method message() { "Cannot use placeholder parameter $.placeholder outside of a sub or block" } } my class X::Placeholder::Attribute is X::Placeholder::Block { method message() { "Cannot use placeholder parameter $.placeholder in an attribute initializer" } } my class X::Undeclared does X::Comp { has $.what = 'Variable'; has $.symbol; has @.suggestions; method message() { my $message := "$.what '$.symbol' is not declared"; if +@.suggestions == 1 { $message := "$message. Did you mean '@.suggestions[0]'?"; } elsif +@.suggestions > 1 { $message := "$message. Did you mean any of these: { @.suggestions.map( { "'$_'" } ).join(", ") }?"; } elsif $.what eq 'Variable' { $message := "$message. Perhaps you forgot a 'sub' if this was intended to be part of a signature?"; } $message.naive-word-wrapper } } my class X::Attribute::Undeclared is X::Undeclared { has $.package-kind; has $.package-name; method message() { "Attribute $.symbol not declared in $.package-kind $.package-name"; } } my class X::Attribute::Regex is X::Undeclared { method message() { "Attribute '$.symbol' not available inside of a regex, since regexes are methods on the Cursor class. Consider storing the attribute in a lexical, and using that in the regex.".naive-word-wrapper } } my class X::Undeclared::Symbols does X::Comp { has %.post_types; has %.unk_types; has %.unk_routines; has %.routine_suggestion; has %.type_suggestion; multi method gist(X::Undeclared::Symbols:D: :$sorry = True) { ($sorry ?? self.sorry_heading() !! "") ~ self.message } method message(X::Undeclared::Symbols:D:) { sub l(@l) { my @lu = @l.map({ nqp::hllize($_) }).unique.sort; # Linenumbers here are disrespecting #line directives. Therefore let's be explicit about the filename. # Perhaps this message can be improved for multi-filename case of many #line directives per source by # nicely formatting all of them. 'used ' ~ self.at-line(@l, :$.filename); } sub s(@s) { "Did you mean '{ @s.join("', '") }'?"; } my $r = ""; if %.post_types { $r ~= "Illegally post-declared type" ~ (%.post_types.elems == 1 ?? "" !! "s") ~ ":\n"; for %.post_types.sort(*.key) { $r ~= " $_.key() &l($_.value)\n"; } } if %.unk_types { $r ~= "Undeclared name" ~ (%.unk_types.elems == 1 ?? "" !! "s") ~ ":\n"; for %.unk_types.sort(*.key) { $r ~= " $_.key() &l($_.value)"; if +%.type_suggestion{$_.key()} { $r ~= ". " ~ s(%.type_suggestion{$_.key()}); } $r ~= "\n"; } } if %.unk_routines { my $obs = { y => "tr", qr => "rx", local => "temp (or dynamic var)", new => "method call syntax", foreach => "for", use => '"v" prefix for pragma (e.g., "use v6;", "use v6.c;")', need => '"v" prefix and "use" for pragma (e.g., "use v6;", "use v6.c;")', } $r ~= "Undeclared routine" ~ (%.unk_routines.elems == 1 ?? "" !! "s") ~ ":\n"; for %.unk_routines.sort(*.key) { $r ~= " $_.key() &l($_.value)"; $r ~= " (in Raku please use " ~ $obs{$_.key()} ~ " instead)" if $obs{$_.key()}; if +%.routine_suggestion{$_.key()}.list { $r ~= ". " ~ s(%.routine_suggestion{$_.key()}.list); } $r ~= "\n"; } } $r } } my class X::Redeclaration does X::Comp { has $.symbol; has $.postfix = ''; has $.what = 'symbol'; method message() { ("Redeclaration of $.what '$.symbol'" ~ (" $.postfix" if $.postfix) ~ ($.what eq 'routine' ?? ". Did you mean to declare a multi-sub?" !! ".") ).naive-word-wrapper } } my class X::Redeclaration::Outer does X::Comp { has $.symbol; method message() { "Lexical symbol '$.symbol' is already bound to an outer symbol. The implicit outer binding must be rewritten as 'OUTER::<$.symbol>' before you can unambiguously declare a new '$.symbol' in this scope.".naive-word-wrapper } } my class X::Dynamic::Postdeclaration does X::Comp { has $.symbol; method message() { "Illegal post-declaration of dynamic variable '$.symbol'. Earlier access must be written as 'CALLERS::<$.symbol>' if that's what you meant.".naive-word-wrapper } } my class X::QuoteWords::Missing::Closer does X::Comp { has $.opener; has $.closer; method message() { "Unable to parse quote-words subscript; couldn't find '$.closer' (corresponding '$.opener' was at line $.line)".naive-word-wrapper; } } my class X::Dynamic::Package does X::Comp { has $.symbol; method message() { "Dynamic variables cannot have package-like names (with '::'), so '$!symbol' is not allowed.".naive-word-wrapper } } my class X::Import::Redeclaration does X::Comp { has @.symbols; has $.source-package-name; method message() { (@.symbols == 1 ?? "Cannot import symbol '@.symbols[0]' from '$.source-package-name', because it already exists in this lexical scope." !! "Cannot import the following symbols from '$.source-package-name', because they already exist in this lexical scope: { @.symbols.map( { "'$_'" } ).join(', ')}." ).naive-word-wrapper } } my class X::Import::OnlystarProto does X::Comp { has @.symbols; has $.source-package-name; method message() { (@.symbols == 1 ?? "Cannot import symbol '@.symbols[0]' from '$.source-package-name', because only onlystar-protos ('proto foo(|) {*}') can be merged." !! "Cannot import the following symbols from '$.source-package-name', only onlystar-protos ('proto foo(|) {*}') can be merged: { @.symbols.map( { "'$_'" } ).join(', ')}." ).naive-word-wrapper } } my class X::PoisonedAlias does X::Comp { has str $.alias; has str $.package-type = 'package'; has str $.package-name; method message() { ("Cannot directly use poisoned alias '$.alias' because it was declared by several {$.package-type}s." ~ ($.package-name ?? " Please access it via explicit package name like: '{$.package-name}::{$!alias}'" !! '') ).naive-word-wrapper } } my class X::Phaser::Multiple does X::Comp { has $.block; method message() { "Only one $.block block is allowed" } } my class X::Obsolete does X::Comp { has $.old; has $.replacement; # can't call it $.new, collides with constructor has $.when = 'in Raku'; method message() { "Unsupported use of $.old. $.when.tc() please use: $.replacement.".naive-word-wrapper } } my class X::Parameter::Default does X::Comp { has $.how; has $.parameter; method message() { $.parameter ?? "Cannot put default on $.how parameter $.parameter" !! "Cannot put default on anonymous $.how parameter"; } } my class X::Parameter::Default::TypeCheck does X::Comp { has $.what = 'parameter'; has $.got is default(Nil); has $.expected is default(Nil); method message() { "Default value '{Rakudo::Internals.MAYBE-STRING: $!got}' will never bind to a parameter of type {$!expected.^name}" } } my class X::Parameter::AfterDefault does X::Syntax { has $.type; has $.modifier; has $.default; method message() { "The $.type '$.modifier' came after the default value. Did you mean: ...$.modifier $.default?".naive-word-wrapper } } my class X::Parameter::Placeholder does X::Comp { has $.type; has $.parameter; has $.right; method message() { "$.type.tc() placeholder variables like '$.parameter' are not allowed in signatures. Did you mean: '$.right' ?".naive-word-wrapper } } my class X::Parameter::Twigil does X::Comp { has $.parameter; has $.twigil; method message() { "Parameters with a '$.twigil' twigil, like '$.parameter', are not allowed in signatures.".naive-word-wrapper } } my class X::Parameter::MultipleTypeConstraints does X::Comp { has $.parameter; method message() { ($.parameter ?? "Parameter $.parameter" !! 'A parameter') ~ " may only have one prefix type constraint"; } } my role X::BadType { has Mu $.type; method action() {...} method message() { my $what = ~$!type.HOW.WHAT.^name.match(/ .* '::' <(.*)> HOW/) // 'Namespace'; "$what '$!type.^name()' is insufficiently type-like to {self.action()}. Did you mean 'class'?".naive-word-wrapper } } my class X::Parameter::BadType does X::Comp does X::BadType { method action { 'qualify a parameter' } } my class X::Parameter::WrongOrder does X::Comp { has $.misplaced; has $.parameter; has $.after; method message() { "Cannot put $.misplaced parameter $.parameter after $.after parameters"; } } my class X::Parameter::InvalidConcreteness is Exception { has $.expected; has $.got; has $.routine; has $.param; has Bool $.should-be-concrete; has Bool $.param-is-invocant; method message() { $!routine = '' if not $!routine.defined or $!routine eq ''; $!param = '' if not $!param.defined or $!param eq ''; my $beginning = $!param-is-invocant ?? 'Invocant of method' !! "Parameter '$!param' of routine"; my $must-be = $!should-be-concrete ?? 'an object instance' !! 'a type object'; my $not-a = $!should-be-concrete ?? 'a type object' !! 'an object instance'; my $suggestion = $!should-be-concrete ?? '.new' !! 'multi'; "$beginning '$!routine' must be $must-be of type '$!expected', not $not-a of type '$!got'. Did you forget a '$suggestion'?".naive-word-wrapper } } my class X::Parameter::InvalidType does X::Comp { has $.typename; has @.suggestions; method message() { my $msg := "Invalid typename '$.typename' in parameter declaration."; if +@.suggestions > 0 { $msg := $msg ~ " Did you mean '" ~ @.suggestions.join("', '") ~ "'?"; } $msg.naive-word-wrapper } } my class X::Parameter::RW is Exception { has $.got; has $.symbol; method message() { "Parameter '$.symbol' expects a writable container (variable) as an argument, but got '{Rakudo::Internals.MAYBE-STRING: $.got, method => 'gist'}' ($.got.^name()) as a value without a container.".naive-word-wrapper } } my class X::Parameter::TypedSlurpy does X::Comp { has $.kind; method message() { "Slurpy $.kind parameters with type constraints are not supported" } } my class X::Signature::NameClash does X::Comp { has $.name; method message() { "Name $.name used for more than one named parameter"; } } my class X::Method::Private::Permission does X::Comp { has $.method; has $.source-package; has $.calling-package; method message() { "Cannot call private method '$.method' on package '$.source-package' because it does not trust the '$.calling-package' package.".naive-word-wrapper } } my class X::Method::Private::Unqualified does X::Comp { has $.method; method message() { "Calling private method '$.method' must be fully qualified with the package containing that private method.".naive-word-wrapper } } my class X::Adverb is Exception { has $.what is rw; has $.source is rw; has @.unexpected; has @.nogo; method message { my $text = ''; if @!unexpected.elems -> $elems { $text = $elems > 1 ?? "$elems unexpected adverbs ('@.unexpected.join("', '")')" !! "Unexpected adverb '@!unexpected[0]'" } if @!nogo { $text ~= $text ?? " and u" !! "U"; $text ~= "nsupported combination of adverbs ('@.nogo.join("', '")')"; } ($text ~ " passed to $!what on '$!source'.").naive-word-wrapper } method unexpected { @!unexpected.sort } method nogo { @!nogo.sort } } my class X::Delete is Exception { has $.target; method message() { $.target.defined ?? "Cannot delete from $.target" !! 'Cannot delete from this left-hand side' } } my class X::Bind is Exception { has $.target; method message() { $.target.defined ?? "Cannot bind to $.target" !! 'Cannot use bind operator with this left-hand side' } } my class X::Bind::Rebind is X::Bind { has $.is-type; method message() { ("Cannot bind to '$.target' because " ~ do given $.target.comb[0] { when <$ @ %>.any { "it was bound in a signature and variables bound in signatures cannot be rebound unless they were declared with the 'is rw' or 'is copy' traits" } when '&' { "Code items cannot be rebound" } when ?$.is-type { "Types cannot be rebound" } default { "it is a term and terms cannot be rebound" } }).naive-word-wrapper } } my class X::Bind::NativeType does X::Comp { has $.name; method message() { "Cannot bind to natively typed variable '$.name'; use assignment instead" } } my class X::Bind::Slice is Exception { has $.type; method message() { "Cannot bind to {$.type.^name} slice"; } } my class X::Bind::ZenSlice is X::Bind::Slice { method message() { "Cannot bind to {$.type.^name} zen slice"; } } my class X::Subscript::Negative is Exception { has $.index; has $.type; method message() { "Calculated index ({$.index}) is negative, but {$.type.^name} allows only 0-based indexing"; } } my class X::Invalid::Value is Exception { has $.method; has $.name; has $.value; method message { "Invalid value '$.value' for :$.name on method $.method" } } my class X::Invalid::ComputedValue is Exception { has $.method; has $.name; has $.value; has $.reason; method message { "$.name {"on $.method " if $.method}computed to $.value," ~ " which cannot be used" ~ (" because $.reason" if $.reason); } } my class X::Value::Dynamic does X::Comp { has $.what; method message() { "$.what value must be known at compile time" } } my class X::Syntax::Name::Null does X::Syntax { method message() { 'Name component may not be null'; } } my class X::Syntax::UnlessElse does X::Syntax { has $.keyword; method message() { qq|"unless" does not take "$!keyword", please rewrite using "if"| } } my class X::Syntax::WithoutElse does X::Syntax { has $.keyword; method message() { qq|"without" does not take "$!keyword", please rewrite using "with"| } } my class X::Syntax::KeywordAsFunction does X::Syntax { has $.word; has $.needparens; method message { ("The word '$.word' is interpreted as a '{$.word}()' function call. Please use whitespace " ~ ($.needparens ?? 'around the' !! 'instead of') ~ " parentheses." ).naive-word-wrapper } } my class X::Syntax::ParentAsHash does X::Syntax { has $.type; has $.parent; has $.what; method message() { "Parent class specification is probably missing some whitespace. Found '$.type is $.parent\{ ...', which tries to specify a parent with a '$.what'. You probably meant '$.type is $.parent \{ ...'.".naive-word-wrapper } } my class X::Syntax::Malformed::Elsif does X::Syntax { has $.what = 'else if'; method message() { qq{In Raku, please use "elsif' instead of "$.what"} } } my class X::Syntax::Reserved does X::Syntax { has $.reserved; has $.instead = ''; method message() { "The $.reserved is reserved$.instead" } } my class X::Syntax::P5 does X::Syntax { method message() { 'This appears to be Perl code' } } my class X::Syntax::NegatedPair does X::Syntax { has $.key; method message() { "Argument not allowed on negated pair with key '$.key'" } } my class X::Syntax::Variable::Numeric does X::Syntax { has $.what = 'variable'; method message() { "Cannot declare a numeric $.what" } } my class X::Syntax::Variable::Match does X::Syntax { method message() { 'Cannot declare a match variable' } } my class X::Syntax::Variable::Initializer does X::Syntax { has $.name = ''; method message() { "Cannot use variable $!name in declaration to initialize itself" } } my class X::Syntax::Variable::SignatureAssignment does X::Syntax { method message() { "Cannot use assignment when declaring a variable via signature binding.\n" ~" Did you mean to use binding? If so, use `:=` instead of `=`.\n" ~" Or did you mean to use list assignment? If so, don't use `:(...)`\n" ~" (the signature literal syntax) on the left-hand side." } } my class X::Syntax::Variable::SignatureWithoutInitializer does X::Syntax { method message { "Variable declaration using a signature literal requires an initializer.\n" ~" Did you mean to declare a list of variables with `(...)` instead of\n" ~" a signature literal with `:(...)`?" } } my class X::Syntax::Variable::Twigil does X::Syntax { has $.what = 'variable'; has $.name; has $.twigil; has $.scope; has $.additional = ''; method message() { "Cannot use a '$.twigil' twigil on a '$.scope $.name' $.what$.additional.".naive-word-wrapper } } my class X::Syntax::Variable::IndirectDeclaration does X::Syntax { method message() { 'Cannot declare a variable by indirect name (use a hash instead?)' } } my class X::Syntax::Variable::BadType does X::Comp does X::BadType { method action { 'qualify a variable' } } my class X::Syntax::Variable::ConflictingTypes does X::Comp { has Mu $.outer; has Mu $.inner; method message() { "$!inner.^name() not allowed here; variable list already declared with type $!outer.^name()" } } my class X::Syntax::Augment::WithoutMonkeyTyping does X::Syntax { method message() { "augment not allowed without 'use MONKEY-TYPING'" }; } my class X::Syntax::Augment::Illegal does X::Syntax { has $.package; method message() { "Cannot augment $.package because it is closed" }; } my class X::Syntax::Augment::Adverb does X::Syntax { method message() { "Cannot put adverbs on a typename when augmenting" } } my class X::Syntax::Type::Adverb does X::Syntax { has $.adverb; method message() { "Cannot use adverb $.adverb on a type name (only 'ver', 'auth' and 'api' are understood)" } } my class X::Syntax::Argument::MOPMacro does X::Syntax { has $.macro; method message() { "Cannot give arguments to $.macro" }; } my class X::Role::Instantiation is Exception does X::Wrapper { has $.role; method message() { "Could not instantiate role '" ~ $!role.^name ~ "'" ~ (self!is-raku-exception ?? " because it is died" ~ self!exception-name-message !! "") ~ self!wrappee-message(:details) } } my class X::Role::Initialization is Exception { method message() { "Can only supply an initialization value for a role if it has a single public attribute, but this is not the case for '{$.role.^name}'" } } my class X::Role::Group::Documenting is Exception { has $.role-name is required; method message() { "Parametric role group cannot be documented, use one of the candidates instead for '{$.role-name}'" } } my role X::RoleApplier is Exception { has Mu $.target is required; } my role X::RoleApplier::Method { # This attribute could either be of MultiToIncorporate or Collisions classes, depending on particular exception # kind. Both a MOP classes without `item` method. Thefore the attribute must be accessed either as $!method or as # self.method, not $.method. has Mu $.method is required; } my class X::Role::Unimplemented::Multi does X::RoleApplier does X::RoleApplier::Method { method message() { "Multi method '" ~ self.method.name ~ "' with signature " ~ self.method.code.signature.raku ~ " must be implemented by " ~ self.target.^name ~ " because it is required by a role" } } my class X::Role::Unresolved does X::RoleApplier does X::RoleApplier::Method { method must-be-resolved { ~ "' must be resolved by class " ~ $!target.^name ~ " because it exists in multiple roles (" ~ nqp::hllizefor($!method.roles, 'Raku').join(", ") ~ ")" } } my class X::Role::Unresolved::Private is X::Role::Unresolved { method message { "Private method '" ~ self.method.name ~ self.must-be-resolved } } my class X::Role::Unresolved::Multi is X::Role::Unresolved { method message { "Multi method '" ~ self.method.name ~ "' with signature " ~ self.method.multi.signature.raku ~ self.must-be-resolved } } my class X::Role::Unresolved::Method is X::Role::Unresolved { method message { "Method '" ~ self.method.name ~ self.must-be-resolved } } my role X::Role::Attribute does X::RoleApplier { has Attribute:D $.attribute is required; } my class X::Role::Attribute::Exists does X::Role::Attribute { method message { "Attribute '" ~ $!attribute.name ~ "' already exists in the class '" ~ self.target.^name ~ "', but a role also wishes to compose it" } } my class X::Role::Attribute::Conflicts does X::Role::Attribute { # Conflicting roles has Mu $.from1 is required; has Mu $.from2 is required; method message { "Attribute '" ~ $!attribute.name ~ "' conflicts in role '" ~ self.target.^name ~ "' composition: declared in both '" ~ $!from1.^name ~ "' and '" ~ $!from2.^name ~ "'" } } my class X::Syntax::Comment::Embedded does X::Syntax { method message() { "Opening bracket required for #` comment" } } my class X::Syntax::Pod::DeclaratorLeading does X::Syntax { method message() { "Opening bracket required for #| declarator block" } } my class X::Syntax::Pod::DeclaratorTrailing does X::Syntax { method message() { "Opening bracket required for #= declarator block" } } my class X::Syntax::Doc::Declarator::MissingDeclarand does X::Syntax { has $.position = 'trailing'; method message() { "Missing declarand for $.position declarator doc" } } my class X::Syntax::Pod::BeginWithoutIdentifier does X::Syntax does X::Pod { method message() { '=begin must be followed by an identifier; (did you mean "=begin pod"?)' } } my class X::Syntax::Pod::BeginWithoutEnd does X::Syntax does X::Pod { has $.type; has $.spaces; has $.instead; method message() { if $.instead { qq{Expected "=end $.type" to terminate "=begin $.type"; found "=end $.instead" instead.} } else { "'=begin' not terminated by matching '$.spaces=end $.type'" } } } my class X::Syntax::Pod::BeginWithDirective does X::Syntax does X::Pod { has $.directive; has $.for = 'begin'; method message() { "=$.for may not be followed by directive '$.directive'" } } my class X::Syntax::Confused does X::Syntax { has $.reason = 'Confused'; method message() { $.reason } } my class X::Syntax::Malformed does X::Syntax { has $.what; method message() { "Malformed $.what" } } my class X::Syntax::Missing does X::Syntax { has $.what; method message() { "Missing $.what" } } my class X::Syntax::BlockGobbled does X::Syntax { has $.what; method message() { my $looks_like_type = $.what ~~ /'::' | <[A..Z]><[a..z]>+/; $.what ~~ /^'is '/ ?? "Trait '$.what' needs whitespace before block" !! "{ $.what ?? "Function '$.what'" !! 'Expression' } needs parens to avoid gobbling block" ~ ($looks_like_type ?? " (or perhaps it's a class that's not declared or available in this scope?)" !! ""); }; } my class X::Syntax::ConditionalOperator::PrecedenceTooLoose does X::Syntax { has $.operator; method message() { "Precedence of $.operator is too loose to use inside ?? !!; please parenthesize" } } my class X::Syntax::ConditionalOperator::SecondPartGobbled does X::Syntax { method message() { "Your !! was gobbled by the expression in the middle; please parenthesize" } } my class X::Syntax::ConditionalOperator::SecondPartInvalid does X::Syntax { has $.second-part; method message() { "Please use !! rather than $.second-part" } } my class X::Syntax::Perl5Var does X::Syntax { has $.name; has $.identifier-name; my constant $m = nqp::hash( '$"', '.join() method', '$$', '$*PID', '$;', 'real multidimensional hashes', '$&', '$<>', '$`', '$/.prematch', '$\'', '$/.postmatch', '$,', '.join() method', '$.', "the .kv method on e.g. .lines", '$/', "the filehandle's .nl-in attribute", '$\\', "the filehandle's .nl-out attribute", '$|', "the filehandle's .out-buffer attribute", '$?', '$! for handling child errors also', '$@', '$!', '$]', '$*RAKU.version or $*RAKU.compiler.version', '$^C', 'COMPILING namespace', '$^H', '$?FOO variables', '$^N', '$/[*-1]', '$^O', 'VM.osname', '$^R', 'an explicit result variable', '$^S', 'context function', '$^T', '$*INIT-INSTANT', '$^V', '$*RAKU.version or $*RAKU.compiler.version', '$^X', '$*EXECUTABLE-NAME', '@-', '.from method', '@+', '.to method', '%-', '.from method', '%+', '.to method', '%^H', '$?FOO variables', ); method message() { my $name = $!name; my $v = $name ~~ m/ <[ $ @ % & ]> [ \^ <[ A..Z ]> | \W ] /; my $sugg = nqp::atkey($m,~$v); if $name eq '$#' { # Currently only `$#` var has this identifier business handling. # Should generalize the logic if we get more of stuff like this. $name ~= $!identifier-name; $sugg = '@' ~ $!identifier-name ~ '.end'; } $v ?? $sugg ?? "Unsupported use of $name variable; in Raku please use $sugg" !! "Unsupported use of $name variable" !! 'Weird unrecognized variable name: ' ~ $name; } } my class X::Syntax::Self::WithoutObject does X::Syntax { method message() { "'self' used where no object is available" } } my class X::Syntax::VirtualCall does X::Syntax { has $.call; method message() { "Virtual method call $.call may not be used on partially constructed object (maybe you mean {$.call.subst('.','!')} for direct attribute access here?)" } } my class X::Syntax::NoSelf does X::Syntax { has $.variable; method message() { "Variable $.variable used where no 'self' is available" } } my class X::Syntax::Number::RadixOutOfRange does X::Syntax { has $.radix; method message() { "Radix $.radix out of range (allowed: 2..36)" } } my class X::Syntax::Number::IllegalDecimal does X::Syntax { method message() { "Decimal point must be followed by digit" } } my class X::Syntax::Number::LiteralType does X::Syntax { has $.varname; has $.vartype; has $.value; has $.valuetype = $!value.^name; has $.suggestiontype = ($!vartype,$!valuetype).are.^name; has $.native = nqp::objprimspec($!valuetype); method message() { my $vartype := $!vartype.WHAT.^name; my $conversionmethod := $vartype.tc; $vartype := $vartype.lc if $.native; my $vt := $!value.^name; my $value := nqp::istype($.value,Allomorph) ?? $!value.Str !! $!value.raku; my $val = "Cannot assign a literal of type $.valuetype ($value) to a { "native" if $.native } variable of type $vartype. You can declare the variable to be of type $.suggestiontype, or try to coerce the value with $value.$conversionmethod or $conversionmethod\($value\)"; try $val ~= ", or just write the value as " ~ $!value."$vartype"().raku; "$val.".naive-word-wrapper } } my class X::Syntax::NonAssociative does X::Syntax { has $.left; has $.right; method message() { "Operators '$.left' and '$.right' are non-associative and require parentheses"; } } my class X::Syntax::NonListAssociative is X::Syntax::NonAssociative { method message() { "Only identical operators may be list associative; since '$.left' and '$.right' differ, they are non-associative and you need to clarify with parentheses"; } } my class X::Syntax::CannotMeta does X::Syntax { has $.meta; has $.operator; has $.reason; has $.dba; method message() { "Cannot $.meta $.operator because $.dba operators are $.reason"; } } my class X::Syntax::Adverb does X::Syntax { has $.what; method message() { "You can't adverb " ~ $.what } } my class X::Syntax::Regex::Adverb does X::Syntax { has $.adverb; has $.construct; method message() { "Adverb $.adverb not allowed on $.construct" } } my class X::Syntax::Regex::UnrecognizedMetachar does X::Syntax { has $.metachar; method message() { "Unrecognized regex metacharacter $.metachar (must be quoted to match literally)" } } my class X::Syntax::Regex::UnrecognizedModifier does X::Syntax { has $.modifier; method message() { "Unrecognized regex modifier :$.modifier" } } my class X::Syntax::Regex::NullRegex does X::Syntax { method message() { "Null regex not allowed. Please use .comb if you wanted to produce a sequence of characters from a string.".naive-word-wrapper } } my class X::Syntax::Regex::MalformedRange does X::Syntax { method message() { 'Malformed Range. If attempting to use variables for end points, ' ~ 'wrap the entire range in curly braces.' } } my class X::Syntax::Regex::Unspace does X::Syntax { has $.char; method message { "No unspace allowed in regex; if you meant to match the literal character, please enclose in single quotes ('$.char') or use a backslashed form like \\x&sprintf('%02x', $.char.ord).".naive-word-wrapper } } my class X::Syntax::Regex::InsignificantWhitespace does X::Syntax { method message { "Space is not significant here; please use quotes or :s (:sigspace) modifier (or, to suppress this warning, omit the space, or otherwise change the spacing).".naive-word-wrapper } } my class X::Syntax::Regex::Unterminated does X::Syntax { method message { 'Regex not terminated.' } } my class X::Syntax::Regex::SpacesInBareRange does X::Syntax { method message { 'Spaces not allowed in bare range.' } } my class X::Syntax::Regex::QuantifierValue does X::Syntax { has $.inf; has $.non-numeric; has $.non-numeric-range; has $.empty-range; method message { $!inf && 'Minimum quantity to match for quantifier cannot be Inf.' ~ ' Did you mean to use + or * quantifiers instead of **?' || $!non-numeric-range && 'Cannot use Range with non-Numeric or NaN end points as quantifier' || $!non-numeric && 'Cannot use non-Numeric or NaN value as quantifier' || $!empty-range && 'Cannot use empty Range as quantifier' || 'Invalid quantifier value' } } my class X::Syntax::Regex::SolitaryQuantifier does X::Syntax { method message { 'Quantifier quantifies nothing' } } my class X::Syntax::Regex::NonQuantifiable does X::Syntax { method message { 'Can only quantify a construct that produces a match' } } my class X::Syntax::Regex::SolitaryBacktrackControl does X::Syntax { method message { "Backtrack control ':' does not seem to have a preceding atom to control" } } my class X::Syntax::Regex::Alias::LongName does X::Syntax { method message() { "Can only alias to a short name (without '::')"; } } my class X::Syntax::Term::MissingInitializer does X::Syntax { method message { 'Term definition requires an initializer' } } my class X::Syntax::Variable::MissingInitializer does X::Syntax { has $.what; has $.type; has $.implicit; has $.maybe; method message { my $modality = $.maybe ?? "may need" !! "needs"; my $type = $.implicit ?? "$.type (implicit $.implicit)" !! "$.type"; my $requirement = $.what eq 'attribute' ?? 'to be marked as required or given an initializer' !! 'to be given an initializer'; "$.what.tc() definition of type $type $modality $requirement" } } my class X::Syntax::AddCategorical::TooFewParts does X::Syntax { has $.category; has $.needs; method message() { "Not enough symbols provided for categorical of type $.category; needs $.needs" } } my class X::Syntax::AddCategorical::TooManyParts does X::Syntax { has $.category; has $.needs; method message() { "Too many symbols provided for categorical of type $.category; needs only $.needs" } } my class X::Syntax::Signature::InvocantMarker does X::Syntax { method message() { "Can only use : as invocant marker in a signature after the first parameter" } } my class X::Syntax::Signature::InvocantNotAllowed does X::Syntax { method message() { "Can only use the : invocant marker in the signature for a method" } } my class X::Syntax::Extension::Category does X::Syntax { has $.category; method message() { "Cannot add tokens of category '$.category'"; } } my class X::Syntax::Extension::Null does X::Syntax { method message() { "Null operator is not allowed"; } } my class X::Syntax::Extension::TooComplex does X::Syntax { has $.name; method message() { "Colon pair value '$.name' too complex to use in name"; } } my class X::Syntax::Coercer::TooComplex does X::Syntax { method message() { 'Coercer is too complex. Only type objects, with optional type' ~ " smileys, or empty parentheses, implying 'Any', are supported." } } my class X::Syntax::Extension::SpecialForm does X::Syntax { has $.category; has $.opname; has $.hint; method message() { "Cannot override $.category operator '$.opname', as it is a special form " ~ "handled directly by the compiler" ~ ($!hint ?? "\n$!hint" !! "") } } my class X::Syntax::InfixInTermPosition does X::Syntax { has $.infix; method message() { my $infix := $!infix.trim; "Preceding context expects a term, but found infix $infix instead." ~ ( $.post && $.post.starts-with('end ') ?? "\nDid you forget '=begin $.post.substr(4)' Pod marker?" !! "\nDid you make a mistake in Pod syntax?" if $infix eq '=' ) } } my class X::Syntax::DuplicatedPrefix does X::Syntax { has $.prefixes; method message() { my $prefix = substr($.prefixes,0,1); "Expected a term, but found either infix $.prefixes or redundant prefix $prefix\n" ~ " (to suppress this message, please use a space like $prefix $prefix)"; } } my class X::Attribute::Package does X::Comp { has $.package-kind; has $.name; method message() { "A $.package-kind cannot have attributes, but you tried to declare '$.name'" } } my class X::Attribute::NoPackage does X::Comp { has $.name; method message() { "You cannot declare attribute '$.name' here; maybe you'd like a class or a role?" } } my class X::Attribute::Required does X::MOP { has $.name; has $.why; method message() { $.why && nqp::istype($.why,Str) ?? "The attribute '$.name' is required because $.why,\nbut you did not provide a value for it." !! "The attribute '$.name' is required, but you did not provide a value for it." } } my class X::Attribute::Scope::Package does X::Comp { has $.scope; has $.allowed; has $.disallowed; method message() { "Cannot use {$.scope}-scoped attribute in $.disallowed" ~ ($.allowed ?? ", only $.allowed." !! ".") } } my class X::Declaration::Scope does X::Comp { has $.scope; has $.declaration; method message() { "Cannot use '$.scope' with $.declaration declaration" } } my class X::Declaration::Scope::Multi is X::Declaration::Scope { method message() { "Cannot use '$.scope' with individual multi candidates. Please declare an {$.scope}-scoped proto instead"; } } my class X::Declaration::OurScopeInRole does X::Comp { has $.declaration; method message() { "Cannot declare our-scoped $.declaration inside of a role\n" ~ "(the scope inside of a role is generic, so there is no unambiguous\n" ~ "package to install the symbol in)" } } my class X::Anon::Multi does X::Comp { has $.multiness; has $.routine-type = 'routine'; method message() { "An anonymous $.routine-type may not take a $.multiness declarator" } } my class X::Anon::Augment does X::Comp { has $.package-kind; method message() { "Cannot augment anonymous $.package-kind" } } my class X::Augment::NoSuchType does X::Comp { has $.package-kind; has $.package; method message() { "You tried to augment $.package-kind $.package, but it does not exist" } } my class X::Routine::Unwrap is Exception { method message() { "Cannot unwrap routine: invalid wrap handle" } } my class X::Constructor::Positional is Exception { has $.type; method message() { "Default constructor for '" ~ $.type.^name ~ "' only takes named arguments" } } my class X::Constructor::BadType is Exception does X::BadType { method action { 'be instantiated' } } my class X::Hash::Store::OddNumber is Exception { has $.found; has $.last; method message() { my $msg = "Odd number of elements found where hash initializer expected"; if $.found == 1 { $msg ~= $.last ?? ":\nOnly saw: $.last.raku()" !! ":\nOnly saw 1 element" } else { $msg ~= ":\nFound $.found (implicit) elements"; $msg ~= ":\nLast element seen: $.last.raku()" if $.last; } } } my class X::Pairup::OddNumber is Exception { method message() { "Odd number of elements found for .pairup()" } } my class X::Match::Bool is Exception { has $.type; method message() { "Cannot use Bool as Matcher with '" ~ $.type ~ "'. Did you mean to use \$_ inside a block?" } } my class X::LibEmpty does X::Comp { method message { q/Repository specification can not be an empty string. Did you mean 'use lib "."' ?/ } } my class X::LibNone does X::Comp { method message { q/Must specify at least one repository. Did you mean 'use lib "lib"' ?/ } } my class X::Package::UseLib does X::Comp { has $.what; method message { "Cannot 'use lib' inside a $.what" } } my class X::Package::Stubbed does X::Comp { has @.packages; method message() { "The following packages were stubbed but not defined:\n " ~ @.packages.join("\n "); } # The unnamed named param is here so this candidate, rather than # the one from X::Comp is used. (is it a bug that this is needed? # No idea: https://colabti.org/irclogger/irclogger_log/perl6-dev?date=2017-09-14#l405 multi method gist(::?CLASS:D: :$) { $.message; } } my class X::Phaser::PrePost is Exception { has $.phaser = 'PRE'; has $.condition; method message { my $what = $.phaser eq 'PRE' ?? 'Precondition' !! 'Postcondition'; $.condition.defined ?? "$what '$.condition.trim()' failed" !! "$what failed"; } } my class X::Str::InvalidCharName is Exception { has $.name; method message() { $!name.chars ?? "Unrecognized character name [{$!name}]" !! "Cannot use empty name as character name" } } my class X::Str::Numeric is Exception { has $.source; has $.pos; has $.reason; method source-indicator { my ($red,$clear,$green,$,$eject) = Rakudo::Internals.error-rcgye; my sub escape($str) { $str.raku.substr(1).chop } join '', "in '", $green, escape(substr($.source,0, $.pos)), $eject, $red, escape(substr($.source,$.pos)), $clear, "' (indicated by ", $eject, $clear, ")", ; } method message() { "Cannot convert string to number: $.reason $.source-indicator"; } } my class X::Str::Match::x is Exception { has $.got is default(Nil); method message() { "in Str.match, got invalid value of type {$.got.^name} for :x, must be Int or Range" } } my class X::Str::Subst::Adverb is Exception { has $.name; has $.got; method message() { "Cannot use :$.name adverb in Str.subst, got $.got" } } my class X::Str::Trans::IllegalKey is Exception { has $.key; method message { "in Str.trans, got illegal substitution key of type {$.key.^name} (should be a Regex or Str)" } } my class X::Str::Trans::InvalidArg is Exception { has $.got is default(Nil); method message() { "Only Pair objects are allowed as arguments to Str.trans, got {$.got.^name}"; } } my class X::Str::Sprintf::Directives::Count is Exception { has int $.args-used; # number of directives actually detected in the format string has int $.args-have; # number of args supplied has str $.format; method message() { my $msg = "Your printf-style directives specify "; if $.args-used == 1 { $msg ~= "1 argument, but "; } else { $msg ~= "$.args-used arguments, but "; } if $.args-have < 1 { $msg ~= "no argument was"; } else { if $.args-have == 1 { $msg ~= "1 argument was"; } else { # too many args $msg ~= "$.args-have arguments were"; } } $msg ~= " supplied to format '$.format'."; if $.args-used > $.args-have { $msg ~= " Are you using an interpolated '\$'?"; } $msg.naive-word-wrapper } } my class X::Str::Sprintf::Directives::Unsupported is Exception { has str $.directive; has str $.sequence; method message() { "Directive $.directive is not valid in sprintf format '$.sequence'".naive-word-wrapper } } my class X::Str::Sprintf::Directives::BadType is Exception { has str $.type; has str $.directive; has str $.expected; has str $.format; has Mu $.value; method value() { nqp::istype($!value,List) ?? (' (' ~ Rakudo::Internals.SHORT-STRING($!value[0]) ~ ')') !! nqp::isconcrete($!value) ?? (' (' ~ Rakudo::Internals.SHORT-STRING($!value) ~ ')') !! ""; } method message() { (($.expected ?? "Directive %$.directive expected a $.expected value, not a $.type$.value" !! "Directive %$.directive not applicable for value of type $.type$.value" ) ~ " in format '$.format'").naive-word-wrapper } } my role X::Encoding is Exception { } my class X::Encoding::Unknown does X::Encoding { has $.name; method message() { "Unknown string encoding '$.name'" } } my class X::Encoding::AlreadyRegistered does X::Encoding { has $.name; method message() { "An encoding with name '$.name' has already been registered" } } my class X::Range::InvalidArg is Exception { has $.got is default(Nil); method message() { "{$.got.^name} objects are not valid endpoints for Ranges"; } } my class X::Range::Incomparable is Exception { has $.topic; has $.endpoint; has $.what-endpoint; method message() { "Value of type '" ~ $.topic.^name ~ "' cannot be compared with range $.what-endpoint of type '" ~ $.endpoint.^name ~ "'" } } my class X::Range::CannotIterate is Exception { has $.min; method message() { "Range cannot be iterated over because its starting point '$.min.raku()' does not have a '.succ' method".naive-word-wrapper } } my class X::Sequence::Deduction is Exception { has $.from; method message() { $!from ?? "Unable to deduce arithmetic or geometric sequence from: $!from\nDid you really mean '..'?" !! 'Unable to deduce sequence for some unfathomable reason' } } my class X::Sequence::Endpoint is Exception { has $.from; has $.endpoint; method message() { "Incompatible endpoint for sequence: " ~ $!from.raku ~ " ... " ~ $!endpoint.raku } } my class X::Cannot::Map is Exception { has $.what = "()"; has $.using = "()"; has $.suggestion; method message() { my $message = "Cannot map a $.what using $.using"; $.suggestion ?? "$message\n$.suggestion" !! $message } } my class X::Cannot::Lazy is Exception { has $.action; has $.what; method message() { $.what ?? "Cannot $.action a lazy list onto a $.what" !! "Cannot $.action a lazy list"; } } my class X::Cannot::Empty is Exception { has $.action; has $.what; method message() { "Cannot $.action from an empty $.what"; } } my class X::Cannot::New is Exception { has $.class; method message() { "Cannot make a {$.class.^name} object using .new"; } } my class X::Cannot::Capture is Exception { has $.what; method message() { "Cannot unpack or Capture `$!what.gist()`.\n" ~ "To create a Capture, add parentheses: \\(...)\n" ~ 'If unpacking in a signature, perhaps you needlessly used' ~ ' parentheses? -> ($x) {} vs. -> $x {}' ~ "\nor missed `:` in signature unpacking? -> \&c:(Int) \{}"; } } my class X::Backslash::UnrecognizedSequence does X::Syntax { has $.sequence; has $.suggestion; method message() { "Unrecognized backslash sequence: '\\$.sequence'" ~ (nqp::defined($!suggestion) ?? ". Did you mean $!suggestion?" !! '') } } my class X::Backslash::NonVariableDollar does X::Syntax { method message() { "Non-variable \$ must be backslashed" } } my class X::ControlFlow is Exception { has $.illegal; # something like 'next' has $.enclosing; # .... outside a loop has $.backtrace; # where the bogus control flow op was method backtrace() { $!backtrace || nextsame(); } method message() { "$.illegal without $.enclosing" } } my class X::ControlFlow::Return is X::ControlFlow { has Bool $.out-of-dynamic-scope; submethod BUILD(Bool() :$!out-of-dynamic-scope) {} method illegal() { 'return' } method enclosing() { 'Routine' } method message() { 'Attempt to return outside of ' ~ ( $!out-of-dynamic-scope ?? 'immediately-enclosing Routine (i.e. `return` execution is' ~ ' outside the dynamic scope of the Routine where `return` was used)' !! 'any Routine' ) } } my class X::NoZeroArgMeaning is Exception { has $.name; method message() { "No zero-argument meaning for: $.name" } } my class X::Composition::NotComposable does X::Comp { has $.target-name; has $.composer; method message() { $!composer.^name ~ " is not composable, so $!target-name cannot compose it"; } } my class X::ParametricConstant is Exception { method message { 'Parameterization of constants is forbidden' } } my class X::TypeCheck is Exception { has $.operation; has $!got is built(:bind) is default(Nil); has $!expected is built(:bind) is default(Nil); method got() { $!got } method expected() { $!expected } method gotn() { my Str:D $raku := Rakudo::Internals.SHORT-STRING: $!got, :method; nqp::eqaddr($!got.WHAT, $!expected.WHAT) ?? $raku !! nqp::can($!got.HOW, 'name') ?? "$!got.^name() ($raku)" !! $raku } method expectedn() { nqp::eqaddr($!got.WHAT, $!expected.WHAT) ?? Rakudo::Internals.MAYBE-STRING($!expected, :method) !! nqp::can($!expected.HOW, 'name') ?? $!expected.^name !! '?' } method priors() { nqp::isconcrete($!got) && nqp::istype($!got, Failure) ?? "Earlier failure:\n " ~ $!got.mess ~ "\nFinal error:\n " !! '' } method complainee-message(Mu $complainee = $!expected.HOW.complainee) { $complainee ~~ Callable || $complainee.^can('CALL-ME') ?? $complainee($!got) !! $complainee.Str } method explain { nqp::istype($!expected.HOW, Metamodel::Explaining) ?? self.complainee-message !! "expected $.expectedn but got $.gotn" } method message() { self.priors() ~ "Type check failed in $.operation; " ~ $.explain } } my class X::Comp::TypeCheck is X::TypeCheck does X::Comp { } my class X::TypeCheck::Binding is X::TypeCheck { has $.symbol; method operation { 'binding' } method message() { my $to = $!symbol.defined && $!symbol ne '$' ?? " to '$!symbol'" !! ""; my $expected = nqp::eqaddr(self.expected, self.got) ?? "expected type $.expectedn cannot be itself" !! self.explain; self.priors() ~ "Type check failed in $.operation$to; $expected"; } } my class X::TypeCheck::Binding::Parameter is X::TypeCheck::Binding { has Parameter $.parameter; has Bool $.constraint; has Str $.what; method expectedn() { $.constraint && nqp::istype(self.expected, Code) ?? 'anonymous constraint to be met' !! (nqp::istype($.expected, Signature) ?? $.expected.raku !! callsame()) } method gotn() { nqp::istype($.expected, Signature) && nqp::eqaddr($.got, Nil) ?? "none" !! callsame() } method explain { nqp::istype(nqp::decont($!parameter), Metamodel::Explaining) && nqp::defined($!parameter.complainee) ?? self.complainee-message($!parameter.complainee) !! callsame() } method message() { my $to = $.symbol.defined && $.symbol ne '$' ?? " to parameter '$.symbol'" !! " to anonymous parameter"; my $expected = nqp::eqaddr(self.expected, self.got) ?? "expected type $.expectedn cannot be itself" !! self.explain; my $what-check = $.what // ($.constraint ?? 'Constraint type' !! 'Type'); self.priors() ~ "$what-check check failed in $.operation$to; $expected"; } } my class X::TypeCheck::Return is X::TypeCheck { method operation { 'returning' } method message() { my $expected = nqp::eqaddr(self.expected, self.got) ?? "expected return type $.expectedn cannot be itself " ~ "(perhaps $.operation a :D type object?)" !! self.explain; self.priors() ~ "Type check failed for return value; $expected"; } } my class X::TypeCheck::Assignment is X::TypeCheck { has Mu $.desc; has $.symbol; method operation { 'assignment' } method message { my $symbol := $!symbol // $!desc.name; my $location = !$symbol.defined || $symbol eq '$' ?? "in assignment" !! $symbol.starts-with("@" | "%") ?? "for an element of $symbol" !! "in assignment to $symbol"; my $is-itself := nqp::eqaddr(self.expected, self.got); my $expected = $is-itself ?? "expected type $.expectedn cannot be itself" !! (nqp::defined($!desc) && nqp::istype($!desc, Metamodel::Explaining) && nqp::defined($!desc.complainee) ?? self.complainee-message($!desc.complainee) !! self.explain); my $maybe-Nil := $is-itself || nqp::istype(self.expected.HOW, Metamodel::DefiniteHOW) && nqp::eqaddr(self.expected.^base_type, self.got) ?? ' (perhaps Nil was assigned to a :D which had no default?)' !! ''; self.priors() ~ "Type check failed $location; $expected$maybe-Nil" } } my class X::TypeCheck::Argument is X::TypeCheck { has $.protoguilt; has @.arguments; has $.objname; has $.signature; method message { my $multi = $!signature ~~ /\n/ // ''; "Calling {$!objname}({ join(', ', @!arguments) }) will never work with " ~ ( $!protoguilt ?? 'signature of the proto ' !! $multi ?? 'any of these multi signatures:' !! 'declared signature ' ) ~ $!signature; } } my class X::TypeCheck::Attribute::Default is X::TypeCheck does X::Comp { has str $.name; has $.operation; method message { self.priors() ~ (nqp::istype($.expected.HOW, Metamodel::Explaining) ?? "Can never $.operation default value to attribute '$.name': $.explain" !! "Can never $.operation default value $.gotn to attribute '$.name', it expects: $.expectedn") } } my class X::TypeCheck::Splice is X::TypeCheck does X::Comp { has $.action; method message { self.priors() ~ "Type check failed in {$.action}; $.explain" } } my class X::Assignment::RO is Exception { has Mu $.value is built(:bind) is required; method message { my $what = $!value === Nil ?? 'Nil value' !! nqp::isconcrete($!value) ?? "{$!value.^name} ({ Rakudo::Internals.SHORT-STRING: $!value })" !! "'{$!value.^name}' type object"; "Cannot modify an immutable " ~ $what } method typename { $.value.^name } } my class X::Assignment::RO::Comp does X::Comp { has $.variable; method message { "Cannot assign to readonly variable {$.variable}" } } my class X::Immutable is Exception { has $.typename; has $.method; method message { "Cannot call '$.method' on an immutable '$.typename'"; } } my class X::NoDispatcher is Exception { has $.redispatcher; method message() { "$.redispatcher is not in the dynamic scope of a dispatcher"; } } my class X::Localizer::NoContainer is Exception { has $.localizer; method message() { "Can only use '$.localizer' on a container"; } } my class X::Mixin::NotComposable is Exception { has $.target; has $.rolish; method message() { "Cannot mix in non-composable type {$.rolish.^name} into object of type {$.target.^name}"; } } my class X::Inheritance::Unsupported does X::Comp { # note that this exception is thrown before the child type object # has been composed, so it's useless to carry it around. Use the # name instead. has $.child-typename; has $.parent; method message { $!parent.^name ~ ' does not support inheritance, so ' ~ $!child-typename ~ ' cannot inherit from it'; } } my class X::Inheritance::UnknownParent is Exception { has $.child; has $.parent; has @.suggestions is rw; method message { my $message := "'" ~ $.child ~ "' cannot inherit from '" ~ $.parent ~ "' because it is unknown."; if +@.suggestions > 1 { $message := $message ~ "\nDid you mean one of these?\n '" ~ @.suggestions.join("'\n '") ~ "'\n"; } elsif +@.suggestions == 1 { $message := $message ~ "\nDid you mean '" ~ @.suggestions[0] ~ "'?\n"; } $message; } } my class X::Inheritance::SelfInherit is Exception { has $.name; method message { "'$.name' cannot inherit from itself." } } my class X::Export::NameClash does X::Comp { has $.symbol; method message() { "A symbol '$.symbol' has already been exported"; } } my class X::HyperOp::NonDWIM is Exception { has &.operator; has $.left-elems; has $.right-elems; has $.recursing; method message() { "Lists on either side of non-dwimmy hyperop of &.operator.name() are not of the same length" ~ " while recursing" x +$.recursing ~ "\nleft: $.left-elems elements, right: $.right-elems elements"; } } my class X::HyperOp::Infinite is Exception { has &.operator; has $.side; method message() { $.side eq "both" ?? "Lists on both sides of hyperop of &.operator.name() are known to be infinite" !! "List on $.side side of hyperop of &.operator.name() is known to be infinite" } } my class X::Set::Coerce is Exception { has $.thing; method message { "Cannot coerce object of type {$.thing.^name} to Set. To create a one-element set, pass it to the 'set' function"; } } my role X::Temporal is Exception { } my class X::Temporal::InvalidFormat does X::Temporal { has $.invalid-str; has $.target = 'Date'; has $.format; method message() { "Invalid $.target string '$.invalid-str'; use $.format instead"; } } my class X::DateTime::TimezoneClash does X::Temporal { method message() { 'DateTime.new(Str): :timezone argument not allowed with a timestamp offset'; } } my class X::DateTime::InvalidDeltaUnit does X::Temporal { has $.unit; method message() { "Cannot use unit $.unit with Date.delta"; } } my class X::Temporal::OutOfRange is X::OutOfRange does X::Temporal { } my class X::Eval::NoSuchLang is Exception { has $.lang; method message() { "No compiler available for language '$.lang'"; } } my class X::Import::MissingSymbols is Exception { has $.from; has @.missing; method message() { "Trying to import from '$.from', but the following symbols are missing: " ~ @.missing.join(', '); } } my class X::Import::NoSuchTag is Exception { has $.source-package; has $.tag; method message() { "Error while importing from '$.source-package': no such tag '$.tag'" } } my class X::Import::Positional is Exception { has $.source-package; method message() { "Error while importing from '$.source-package':\n" ~ "no EXPORT sub, but you provided positional argument in the 'use' statement" } } my class X::Numeric::CannotConvert is Exception { has $.target; has $.reason; has $.source; method message() { $!reason ?? "Cannot convert {$!source // $!source.raku} to {$!target // $!target.raku}: $!reason" !! "Cannot convert {$!source // $!source.raku} to {$!target // $!target.raku}"; } } my class X::Numeric::Real is X::Numeric::CannotConvert {} my class X::Numeric::DivideByZero is Exception { has $.using; has $.details; has $.numerator; method message() { "Attempt to divide{$.numerator ?? " $.numerator" !! ''} by zero" ~ ( $.using ?? " using $.using" !! '' ) ~ ( " $_" with $.details ); } } my class X::Numeric::Overflow is Exception { method message() { "Numeric overflow" } } my class X::Numeric::Underflow is Exception { method message() { "Numeric underflow" } } my class X::Numeric::Uninitialized is Exception { has Numeric $.type; method message() { "Use of uninitialized value of type " ~ $!type.^name ~ " in numeric context" } } my class X::Numeric::Confused is Exception { has $.num; has $.base; method message() { "This call only converts base-$.base strings to numbers; value " ~ "{$.num.raku} is of type {$.num.WHAT.^name}, so cannot be converted!" ~ ( "\n(If you really wanted to convert {$.num.raku} to a base-$.base" ~ " string, use {$.num.raku}.base($.base) instead.)" if $.num.^can('base') ); } } my class X::Enum::NoValue is Exception { has Mu $.type is required; has $.value is required; method message { "No value '" ~ $!value ~ "' found in enum " ~ $!type.^name } } my class X::PseudoPackage::InDeclaration does X::Comp { has $.pseudo-package; has $.action; method message() { "Cannot use pseudo package $.pseudo-package in $.action"; } } my class X::NoSuchSymbol is Exception { has $.symbol; method message { "No such symbol '$.symbol'" } } my class X::NoCoreRevision is Exception { has $.lang-rev; method message { "No CORE for language revision $!lang-rev" } } my class X::Item is Exception { has $.aggregate; has $.index; method message { "Cannot index {$.aggregate.^name} with $.index" } } my class X::Make::MatchRequired is Exception { has $!got is built(:bind) is default(Nil); method got() { $!got } method message() { "The make function expects \$/ to contain a Match, but it contains $!got.^name()" } } my class X::Multi::Ambiguous is Exception { has $.dispatcher; has @.ambiguous; has $.capture; method message { my @bits; my @priors; if $.capture { for $.capture.list { try @bits.push(.WHAT.raku); @bits.push($_.^name) if $!; when Failure { @priors.push(" " ~ .mess); } } for $.capture.hash { if .value ~~ Failure { @priors.push(" " ~ .value.mess); } if .value ~~ Bool { @bits.push(':' ~ ('!' x !.value) ~ .key); } else { try @bits.push(":$(.key)\($(.value.WHAT.raku))"); @bits.push(':' ~ .value.^name) if $!; } } } else { @bits.push('...'); } if @.ambiguous[0].signature.gist.contains: ': ' { my $invocant = @bits.shift; my $first = @bits ?? @bits.shift !! ''; @bits.unshift($invocant ~ ': ' ~ $first); } my $cap = '(' ~ @bits.join(", ") ~ ')'; @priors = flat "Earlier failures:\n", @priors, "\nFinal error:\n " if @priors; @priors.join ~ join "\n", "Ambiguous call to '$.dispatcher.name()$cap'; these signatures all match:", @.ambiguous.map: { my $sig := .signature.raku.substr(1).subst(/ \s* "-->" <-[)]>+ /); .?default ?? " $sig is default" !! " $sig" } } } my class X::Multi::NoMatch is Exception { has $.dispatcher; has $.capture; method message { my @cand = $.dispatcher.dispatchees.map(*.signature.gist); my @un-rw-cand; if first / 'is rw' /, @cand { my $rw-capture = Capture.new( :list( $!capture.list.map({ my $ = $_ }) ), :hash( $!capture.hash.map({ .key => my $ = .value }).hash ), ); @un-rw-cand = $.dispatcher.dispatchees».signature.grep({ $rw-capture ~~ $^cand })».gist; } my $where = so first / where /, @cand; my @bits; my @priors; if $.capture { for $.capture.list { try @bits.push( $where ?? Rakudo::Internals.SHORT-STRING($_) !! .WHAT.raku ~ ':' ~ (.defined ?? "D" !! "U") ); @bits.push($_.^name) if $!; if nqp::istype($_,Failure) { @priors.push(" " ~ .mess); } } for $.capture.hash { if .value ~~ Failure { @priors.push(" " ~ .value.mess); } if .value ~~ Bool { @bits.push(':' ~ ('!' x !.value) ~ .key); } else { try @bits.push(":$(.key)\($($where ?? Rakudo::Internals.SHORT-STRING(.value) !! .value.WHAT.raku ))"); @bits.push(':' ~ .value.^name) if $!; } } } else { @bits.push('...'); } if @cand && @cand[0] ~~ /': '/ { my $invocant = @bits.shift; my $first = @bits ?? @bits.shift !! ''; @bits.unshift($invocant ~ ': ' ~ $first); } my $cap = '(' ~ @bits.join(", ") ~ ')'; @priors = flat "Earlier failures:\n", @priors, "\nFinal error:\n " if @priors; @priors.join ~ "Cannot resolve caller $.dispatcher.name()$cap; " ~ ( @un-rw-cand ?? "the following candidates\nmatch the type but require " ~ 'mutable arguments:' ~ join("\n ", '', @un-rw-cand) ~ ( "\n\nThe following do not match for other reasons:" ~ join("\n ", '', sort keys @cand ∖ @un-rw-cand) unless @cand == @un-rw-cand ) !! ( @cand ?? join "\n ", 'none of these signatures matches:', @cand !! "Routine does not have any candidates. Is only the proto defined?" ) ); } } my class X::Symbol::Kind is Exception { has $.symbol is required; has $.package is required; has $.kind is required; method message { "Cannot access '$.symbol' through $.package, because it is not declared as $.kind"; } } my class X::Symbol::NotDynamic is X::Symbol::Kind { method new(*%initattrs) { nextwith(|%initattrs, kind => 'dynamic') } } my class X::Symbol::NotLexical is X::Symbol::Kind { method new(*%initattrs) { nextwith(|%initattrs, kind => 'lexical') } } my class X::Caller::NotDynamic is X::Symbol::Kind { method new(*%initattrs) { nextwith(|%initattrs, package => 'CALLER', kind => 'dynamic') } } my class X::NotSingleGrapheme is Exception { has $.characters; method message() { ('"\c[' ~ $.characters.ords.map(*.uniname).join(", ") ~ ']" did not resolve to a single grapheme' ).naive-word-wrapper } } my class X::Inheritance::NotComposed does X::MOP { # normally, we try very hard to capture the types # and not just their names. But in this case, both types # involved aren't composed yet, so they basically aren't # usable at all. has $.child-name; has $.parent-name; method message() { "'$.child-name' cannot inherit from '$.parent-name' because '$.parent-name' isn't composed yet" ~ ' (maybe it is stubbed)'; } } my class X::PhaserExceptions is Exception { has @.exceptions; method new(:@exceptions) { # This exception is raised by BOOTSTRAP which passes in # BOOTException type objects and we want HLLized versions. nextwith(exceptions => @exceptions.map: -> Mu \ex { EXCEPTION(ex) }); } method message() { "Multiple exceptions were thrown by LEAVE/POST phasers" } multi method gist(X::PhaserExceptions:D:) { join "\n", flat "Multiple exceptions were thrown by LEAVE/POST phasers\n", @!exceptions>>.gist>>.indent(4) } } my class X::Language::IncompatRevisions is Exception { has Mu $.type-a is built(:bind) is required; has Mu $.type-b is built(:bind) is required; method message() { "Type object " ~ $!type-a.^name ~ " of " ~ $!type-a.^language-version ~ " is not compatible with " ~ $!type-b.^name ~ " of " ~ $!type-b.^language-version } } my role X::Nominalizable is Exception { has Mu $.nominalizable is built(:bind) is required; } my class X::Nominalizable::NoWrappee does X::Nominalizable { has %.kinds is required where *.elems; # This would be the named parameters passed to Metamodel::Nominalizable::wrappee method method message() { my $kinds = %!kinds.keys.join(", or "); "Can't find requested wrappee of " ~ $kinds ~ " on " ~ $.nominalizable.^name } } my class X::Nominalizable::NoKind does X::Nominalizable { method message { $.nominalizable.HOW.^name ~ " does not declare 'nominalizable_kind' method" } } nqp::bindcurhllsym('P6EX', BEGIN nqp::hash( 'X::NoDispatcher', -> $redispatcher is raw { X::NoDispatcher.new(:$redispatcher).throw; }, )); my class X::HyperWhatever::Multiple is Exception { method message() { "Multiple HyperWhatevers and Whatevers may not be used together" } } my class X::EXPORTHOW::InvalidDirective does X::Comp { has $.directive; method message() { "Unknown EXPORTHOW directive '$.directive' encountered during import" } } my class X::EXPORTHOW::NothingToSupersede does X::Comp { has $.declarator; method message() { "There is no package declarator '$.declarator' to supersede" } } my class X::EXPORTHOW::Conflict does X::Comp { has $.declarator; has $.directive; method message() { "'EXPORTHOW::{$.directive}::{$.declarator}' conflicts with an existing meta-object imported into this lexical scope" } } my class X::UnitScope::Invalid does X::Syntax { has $.what; has $.where; has Str:D $.suggestion = 'Please use the block form.'; method message() { "A unit-scoped $.what definition is not allowed $.where;\n$!suggestion" } } my class X::UnitScope::TooLate does X::Syntax { has $.what; method message() { "Too late for unit-scoped $.what definition;\n" ~ "Please use the block form." } } my class X::StubCode is Exception { has $.message = 'Stub code executed'; } my class X::TooLateForREPR is X::Comp { has $.type; method message() { "Cannot change REPR of $!type.^name() now (must be set at initial declaration)"; } } my class X::MustBeParametric is Exception { has $.type; method message() { "$!type.^name() *must* be parameterized"; } } my class X::NotParametric is Exception { has $.type; method message() { "$!type.^name() cannot be parameterized"; } } my class X::InvalidType does X::Comp { has $.typename; has @.suggestions; method message() { my $msg := "Invalid typename '$.typename'"; if +@.suggestions > 0 { $msg := $msg ~ ". Did you mean '" ~ @.suggestions.join("', '") ~ "'?"; } $msg; } } my class X::InvalidTypeSmiley does X::Comp { has $.name; method message() { "Invalid type smiley ':$.name' used, only ':D', ':U' and ':_' are allowed"; } } my class X::MultipleTypeSmiley does X::Comp { method message() { "Multiple type smileys cannot be used, did you forget a ':' somewhere?"; } } my class X::Seq::Consumed is Exception { has $.kind = Seq; method message() { my $kind_name = $!kind.^name; ("The iterator of this $kind_name is already in use/consumed by another $kind_name" ~ " (you might solve this by adding .cache on usages of the $kind_name, or " ~ "by assigning the $kind_name into an array)").naive-word-wrapper } } my class X::Seq::NotIndexable is Exception { method message() { "Cannot index a Seq; coerce it to a list or assign it to an array first" } } my class X::WheneverOutOfScope is Exception { method message() { "Cannot have a 'whenever' block outside the scope of a 'supply' or 'react' block" } } my class X::Comp::WheneverOutOfScope does X::Comp { method message() { "Cannot have a 'whenever' block outside the scope of a 'supply' or 'react' block" } } my class X::IllegalOnFixedDimensionArray is Exception { has $.operation; method message() { "Cannot $.operation a fixed-dimension array" } } my class X::NotEnoughDimensions is Exception { has $.operation; has $.got-dimensions; has $.needed-dimensions; method message() { "Cannot $.operation a $.needed-dimensions dimension array with only $.got-dimensions dimensions" } } my class X::TooManyDimensions is Exception { has $.operation; has $.got-dimensions; has $.needed-dimensions; method message() { "Cannot $.operation a $.needed-dimensions dimension array with $.got-dimensions dimensions" } } my class X::IllegalDimensionInShape is Exception { has $.dim; method message() { "Illegal dimension in shape: $.dim. All dimensions must be integers bigger than 0" } } my class X::ArrayShapeMismatch is Exception { has $.action = "assign"; has $.target-shape; has $.source-shape; method message() { "Cannot assign an array of shape $.source-shape to an array of shape $.target-shape" } } my class X::Assignment::ArrayShapeMismatch is X::ArrayShapeMismatch { } my class X::Assignment::ToShaped is Exception { has $.shape; method message() { "Assignment to array with shape $.shape must provide structured data" } } my class X::Language::Unsupported is Exception { has $.version; method message() { "No compiler available for Raku $.version" } } my class X::Language::TooLate is Exception { method message() { "Too late to switch language version. Must be used as the very first statement." } } my class X::Language::ModRequired is Exception { has $.version; has $.modifier; method message() { "Raku $.version requires $.modifier modifier" } } my class X::Proc::Unsuccessful is Exception { has $.proc; method message() { "The spawned command '{$.proc.command[0]}' exited unsuccessfully (exit code: $.proc.exitcode(), signal: $.proc.signal())" } } class CompUnit::DependencySpecification { ... } class CompUnit::Repository::FileSystem { ... } my class X::CompUnit::UnsatisfiedDependency is Exception { has CompUnit::DependencySpecification $.specification; my sub is-core($name) { my @parts = $name.split("::"); my $last := @parts.pop; my $ns := ::CORE.WHO; for @parts { return False unless $ns{$_}:exists; $ns := $ns{$_}.WHO; }; $ns{$last}:exists and not nqp::istype(nqp::how($ns{$last}), Metamodel::PackageHOW) } method !is-missing-from-meta-file() { $*REPO.isa(CompUnit::Repository::FileSystem) and $*REPO.prefix.add("META6.json").e } method message() { my $name = $.specification.short-name; is-core($name) ?? "{$name} is a builtin type, not an external module" !! "Could not find $.specification in:\n" ~ $*REPO.repo-chain.join("\n").indent(4) ~ ($.specification ~~ / $=.+ '::from' $ / ?? "\n\nIf you meant to use the :from adverb, use" ~ " a single colon for it: $:from<...>\n" !! self!is-missing-from-meta-file ?? "\n\nPlease note that a 'META6.json' file was found in '$*REPO.prefix.relative()'," ~ " of which the 'provides' section was used to determine if a dependency is available" ~ " or not. Perhaps you need to add '$!specification' in the section of" ~ " that file? Or need to specify a directory that does *not* have a 'META6.json' file?" !! '' ) } } my class X::InvalidCodepoint is Exception { has $.code; method message() { $.code > 0x10ffff ?? "Invalid codepoint $.code; must not exceed 0x10ffff (1114111)" !! "Invalid surrogate codepoint $.code" } } my class Exceptions::JSON { method process($ex) { $*ERR.print: Rakudo::Internals::JSON.to-json($ex); False # done processing } } # Provide Metamodel::Configuration with symbol lookup routine. We do it here because throw_or_die method learn about # availability of all exception classes based on this registration. OTOH, it is better to provide them as soon as # possible as this might improve diagnostics of CORE.setting compilation failures. Metamodel::Configuration.set_sym_lookup_routine( -> $sym is raw { ::($sym) } ); #line 1 SETTING::src/core.c/Failure.rakumod my class Failure is Nil { has $.exception; has $.backtrace; has int $!handled; method !SET-SELF(\exception) { $!exception := exception; $!backtrace := exception.backtrace || Backtrace.new( 4 ); exception.reset-backtrace; self } multi method new(Failure:D:) { self!throw } multi method new(Failure:U:) { my $stash := CALLER::LEXICAL::; my $payload := nqp::existskey($stash,'$!') ?? nqp::atkey($stash,'$!') !! "Failed"; nqp::create(self)!SET-SELF( nqp::isconcrete($payload) ?? nqp::istype($payload,Exception) ?? $payload !! X::AdHoc.new(:$payload) !! X::AdHoc.new(:payload) ) } multi method new(Failure:U: Exception:D \exception) { nqp::create(self)!SET-SELF(exception) } multi method new(Failure:U: $payload) { nqp::create(self)!SET-SELF(X::AdHoc.new(:$payload)) } multi method new(Failure:U: |cap (*@msg)) { nqp::create(self)!SET-SELF(X::AdHoc.from-slurpy(|cap)) } method Failure(Failure:D:) is hidden-from-backtrace { self } submethod DESTROY () { note "WARNING: unhandled Failure detected in DESTROY. If you meant " ~ "to ignore it, you can mark it as handled by calling .Bool, " ~ ".so, .not, or .defined methods. The Failure was:\n" ~ self.mess unless $!handled; } # allow Failures to throw when they replace an Iterable multi method iterator(Failure:D:) { self!throw } multi method list(Failure:D:) { self!throw } # Marks the Failure has handled (since we're now fatalizing it) and throws. method !throw(Failure:D:) { $!handled = 1; $!exception.throw($!backtrace); } # Turns out multidimensional lookups are one way to leak unhandled failures, so # we'll just propagate the initial failure much as we propagate Nil on methods. method AT-POS(|) { self } method AT-KEY(|) { self } multi method defined(Failure:D: --> False) { $!handled = 1 } multi method Bool(Failure:D: --> False) { $!handled = 1 } method handled() is rw { Proxy.new( FETCH => { nqp::hllbool($!handled) }, STORE => -> $, $value { $!handled = $value.Bool.Numeric } ) } method Capture() { self.DEFINITE.not || $!handled ?? X::Cannot::Capture.new(what => self).throw !! self!throw } method Int(Failure:D:) { $!handled ?? Int !! self!throw(); } method Num(Failure:D:) { $!handled ?? NaN !! self!throw(); } method Numeric(Failure:D:) { $!handled ?? NaN !! self!throw(); } method Set(Failure:D:) { $!handled ?? Set.new(self) !! self!throw } method SetHash(Failure:D:) { $!handled ?? SetHash.new(self) !! self!throw } method Bag(Failure:D:) { $!handled ?? Bag.new(self) !! self!throw } method BagHash(Failure:D:) { $!handled ?? BagHash.new(self) !! self!throw } method Mix(Failure:D:) { $!handled ?? Mix.new(self) !! self!throw } method MixHash(Failure:D:) { $!handled ?? MixHash.new(self) !! self!throw } multi method Str(Failure:D:) { $!handled ?? $.mess !! self!throw(); } multi method gist(Failure:D:) { $!handled ?? $.mess !! self!throw(); } multi method gist(Failure:U:) { '(' ~ self.^name ~ ')' } multi method raku(Failure:D:) { $!handled ?? '&CORE::infix:(' ~ self.Mu::raku ~ ', *.self)' !! self.Mu::raku } multi method raku(Failure:U:) { self.^name } method mess (Failure:D:) { my $message = (try self.exception.message) // self.exception.^name ~ ' with no message'; "(HANDLED) " x $!handled ~ "$message\n" ~ self.backtrace; } method sink(Failure:D:) { self!throw() unless $!handled } method self(Failure:D:) { self!throw() unless $!handled; self } method CALL-ME(Failure:D: |) { self!throw() } method FALLBACK(Failure:D: *@) { self!throw() } method STORE(Failure:D: *@) { self!throw() } method UPGRADE-RAT(Int $nu, Int $de) { "Upgrading of Rat $nu / $de not allowed".Failure } } proto sub fail(|) {*}; multi sub fail(--> Nil) { my $stash := CALLER::LEXICAL::; my $payload = ($stash<$!>:exists && $stash<$!>.DEFINITE) ?? $stash<$!> !! "Failed"; my $fail := Failure.new( $payload ~~ Exception ?? $payload !! X::AdHoc.new(:$payload)); CATCH { $fail.exception.throw } nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, $fail); } multi sub fail(Exception:U $e --> Nil) { my $fail := Failure.new( X::AdHoc.new(:payload("Failed with undefined " ~ $e.^name)) ); CATCH { $fail.exception.throw } nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, $fail); } multi sub fail($payload --> Nil) { my $fail := Failure.new( $payload ~~ Exception ?? $payload !! X::AdHoc.new(:$payload) ); CATCH { $fail.exception.throw } nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, $fail); } multi sub fail(|cap (*@msg) --> Nil) { my $fail := Failure.new(X::AdHoc.from-slurpy(|cap)); CATCH { $fail.exception.throw } nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, $fail); } multi sub fail(Failure:U $f --> Nil) { my $fail := Failure.new( X::AdHoc.new(:payload("Failed with undefined " ~ $f.^name)) ); CATCH { $fail.exception.throw } nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, $fail); } multi sub fail(Failure:D $fail --> Nil) { $fail.handled = 0; CATCH { $fail.exception.throw } nqp::throwpayloadlexcaller(nqp::const::CONTROL_RETURN, $fail); } multi sub die(Failure:D $f --> Nil) { $f.exception.throw } multi sub die(Failure:U $f --> Nil) { X::AdHoc.new(:payload("Died with undefined " ~ $f.^name)).throw; } #line 1 SETTING::src/core.c/Match.rakumod my class Match is Capture is Cool does NQPMatchRole { # from NQPMatchRole # has int $!from; # start position of match # has int $!pos; # current cursor position # has int $!to; # (if negative, use $!pos) # has $!shared; # shared parse attributes, see ParseShared # has $!braid; # current braid # has $!bstack; # backtracking stack # has $!cstack; # captures stack # has $!regexsub; # actual sub for running the regex # has $!restart; # sub for restarting a search # has $!made; # value set by "make" # has $!match; # flag indicating Match object set up (NQPdidMATCH) # has str $!name; # name if named capture my constant $EMPTY_LIST = nqp::list(); my constant $EMPTY_HASH = nqp::hash(); # When nothing's `made`, we get an NQPMu that we'd like to replace # with Nil; all Rakudo objects typecheck as Mu, while NQPMu doesn't method ast() { nqp::istype($!made, Mu) ?? $!made !! Nil } method made() { nqp::istype($!made, Mu) ?? $!made !! Nil } method Int(--> Int:D) { self.Str.Int } method Str() is raw { self.NQPMatchRole::Str } method STR() is implementation-detail { nqp::eqaddr(nqp::getattr(self,Match,'$!match'),NQPdidMATCH) ?? self.Str !! self.MATCH.Str } method MATCH() is implementation-detail { nqp::unless( nqp::eqaddr(nqp::getattr(self,Match,'$!match'),NQPdidMATCH), nqp::if( # must still set up nqp::islt_i( nqp::getattr_i(self,Match,'$!pos'), nqp::getattr_i(self,Match,'$!from') ) || nqp::isnull(my $rxsub := nqp::getattr(self,Match,'$!regexsub')) || nqp::isnull(my $CAPS := nqp::tryfindmethod($rxsub,'CAPS')) || nqp::isnull(my $captures := $CAPS($rxsub)) || nqp::not_i($captures.has-captures), nqp::stmts( # no captures nqp::bindattr(self,Capture,'@!list',$EMPTY_LIST), nqp::bindattr(self,Capture,'%!hash',$EMPTY_HASH), nqp::bindattr(self,Match,'$!match',NQPdidMATCH) # mark as set up ), self!MATCH-CAPTURES($captures) # go reify all the captures ) ); self } method !MATCH-CAPTURES(Mu $captures --> Nil) { # Initialize capture lists. my $list := nqp::findmethod($captures,'prepare-raku-list')($captures); my $hash := nqp::findmethod($captures,'prepare-raku-hash')($captures); # walk the capture stack and populate the Match. if nqp::istrue(my $cs := nqp::getattr(self,Match,'$!cstack')) { # only one destination, avoid repeated hash lookups if $captures.onlyname -> str $onlyname { # numeric: <= ord("9") so positional capture my Mu $dest := nqp::atpos( nqp::islt_i(nqp::ord($onlyname),58) ?? $list !! $hash, $onlyname ); # simpLy reify all the cursors my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems($cs)), nqp::stmts( (my $cursor := nqp::atpos($cs,$i)), nqp::unless( nqp::isnull_s(nqp::getattr_s($cursor,$?CLASS,'$!name')), nqp::push($dest,$cursor.MATCH) # recurse ) ) ); } # more than one destination else { my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems($cs)), nqp::stmts( # handle this cursor (my $cursor := nqp::atpos($cs,$i)), (my str $name = nqp::getattr_s($cursor,$?CLASS,'$!name')), nqp::if( nqp::not_i(nqp::isnull_s($name)) && nqp::isge_i(nqp::chars($name),1), nqp::stmts( # has a name (my $match := $cursor.MATCH), # recurse nqp::if( nqp::iseq_s($name,'$!from') || nqp::iseq_s($name,'$!to'), nqp::bindattr_i(self,Match,$name, # it's from|to nqp::getattr_i($match,Match,'$!from')), nqp::stmts( # other name(s) (my $names := nqp::split('=',$name)), nqp::while( nqp::elems($names), nqp::if( nqp::iscclass( nqp::const::CCLASS_NUMERIC, ($name = nqp::shift($names)), 0 ), nqp::if( # positional capture nqp::istype(nqp::atpos($list,$name),Array), nqp::atpos($list,$name).push($match), nqp::bindpos($list,$name,$match) # XXX ), nqp::if( # named capture nqp::istype(nqp::atkey($hash,$name),Array), nqp::atkey($hash,$name).push($match), nqp::bindkey($hash,$name,$match) # XXX ) ) ) ) ) ) ) ) ) } } # save in object nqp::bindattr(self,Capture,'@!list', nqp::isconcrete($list) ?? $list !! $EMPTY_LIST); nqp::bindattr(self,Capture,'%!hash',$hash); # We've produced the captures. If we know we're finished and will # never be backtracked into, we can release cstack and regexsub. nqp::unless( nqp::defined(nqp::getattr(self,Match,'$!bstack')), nqp::bindattr(self,Match,'$!cstack', nqp::bindattr(self,Match,'$!regexsub',nqp::null) ) ); # mark as set up nqp::bindattr(self,Match,'$!match',NQPdidMATCH); } # from !cursor_next in nqp method CURSOR_NEXT() is raw is implementation-detail { nqp::if( nqp::defined($!restart), $!restart(self), nqp::stmts( (my $cur := self."!cursor_start_cur"()), $cur."!cursor_fail"(), $cur ) ) } # adapted from !cursor_more in nqp method CURSOR_OVERLAP() is raw is implementation-detail { my $new := nqp::create(self); nqp::bindattr( $new,$?CLASS,'$!shared',$!shared); nqp::bindattr( $new,$?CLASS,'$!braid',$!braid); nqp::bindattr_i($new,$?CLASS,'$!from', nqp::bindattr_i($new,$?CLASS,'$!to',-1)); nqp::bindattr_i($new,$?CLASS,'$!pos',nqp::add_i($!from,1)); $!regexsub($new) } # adapted from !cursor_more in nqp method CURSOR_MORE() is raw is implementation-detail { my $new := nqp::create(self); nqp::bindattr( $new,$?CLASS,'$!shared',$!shared); nqp::bindattr( $new,$?CLASS,'$!braid',$!braid); nqp::bindattr_i($new,$?CLASS,'$!from', nqp::bindattr_i($new,$?CLASS,'$!to',-1)); nqp::bindattr_i($new,$?CLASS,'$!pos',nqp::isge_i($!from,$!pos) ?? nqp::add_i($!from,1) !! $!pos); $!regexsub($new) } submethod BUILD( :$orig = '', :$from = 0, :to(:$pos), :ast(:$made), :$shared, :$braid, :$list, :$hash) { # :build tells !cursor_init that it's too late to do a CREATE self.'!cursor_init'($orig, :build, :p($pos), :$shared, :$braid); nqp::bindattr_i(self, Match, '$!from', $from); nqp::bindattr( self, Match, '$!made', nqp::decont($made)) if $made.defined; } method clone() is raw { nqp::clone(self) } multi method WHICH(Match:D: --> ObjAt:D) { self.Mu::WHICH # skip Capture's as Match is not a value type } proto method Bool(|) {*} multi method Bool(Match:U: --> False) { } multi method Bool(Match:D:) { nqp::hllbool($!pos >= $!from) } proto method not(|) {*} multi method not(Match:U: --> True) { } multi method not(Match:D:) { nqp::hllbool($!pos < $!from) } multi method Numeric(Match:D:) { self.Str.Numeric } multi method ACCEPTS(Match:D: Mu) { self } method prematch(Match:D:) { nqp::substr(self.target,0,$!from) } method postmatch(Match:D:) { nqp::substr(self.target,self.to) } method !sort-on-from-pos() { nqp::add_i( nqp::bitshiftl_i(nqp::getattr_i(self,Match,'$!from'),32), nqp::getattr_i(self,Match,'$!pos') ) } method caps(Match:D:) { my $caps := nqp::list; for self.pairs { my \key := .key; my \value := .value; if nqp::istype(value,List) { nqp::push($caps,Pair.new(key, $_)) for value.list; } elsif nqp::isconcrete(value) { nqp::push($caps,$_); } } Rakudo::Sorting.MERGESORT-REIFIED-LIST-AS( $caps, *.value!sort-on-from-pos ) } method chunks(Match:D:) { my $prev = $!from; my $target := self.target; gather { for self.caps { if .value.from > $prev { take '~' => substr($target,$prev, .value.from - $prev) } take $_; $prev = .value.pos; } take '~' => substr($target,$prev, $!pos - $prev) if $prev < $!pos; } } multi method raku(Match:D: --> Str:D) { my $attrs := nqp::list_s; nqp::push_s($attrs,(orig => self.orig // '').raku); nqp::push_s($attrs,(from => self.from // 0).raku); nqp::push_s($attrs,(pos => self.pos // 0).raku); if self.Capture::list -> @list { nqp::push_s($attrs,:@list.raku) } if self.Capture::hash -> %hash { nqp::push_s($attrs,:%hash.raku) } nqp::push_s($attrs,(made => $_).raku) with self.made; nqp::concat('Match.new(',nqp::concat(nqp::join(', ',$attrs),')')) } multi method gist (Match:D: $d = 0) { return "#" unless self; my $s = ' ' x ($d + 1); my $r = ("=> " if $d) ~ "\x[FF62]{self}\x[FF63]\n"; for @.caps { $r ~= $s ~ (.key // '?') ~ ' ' ~ .value.gist($d + 1) } $d == 0 ?? $r.chomp !! $r; } method replace-with(Match:D: Str() $replacement --> Str:D) { self.prematch ~ $replacement ~ self.postmatch } } multi sub infix:(Match:D $a, Match:D $b) { $a =:= $b || [&&] ( $a.pos eqv $b.pos, $a.from eqv $b.from, $a.orig eqv $b.orig, ($a.made // Any) eqv ($b.made // Any), ($a.Capture::list // nqp::list ) eqv ($b.Capture::list // nqp::list ), ($a.Capture::hash // nqp::hash ) eqv ($b.Capture::hash // nqp::hash ) ); } sub make(Mu \made) { my $slash := nqp::decont(nqp::getlexcaller('$/')); nqp::istype($slash, NQPMatchRole) ?? nqp::bindattr($slash,Match,'$!made',made) !! X::Make::MatchRequired.new(:got($slash)).throw } #line 1 SETTING::src/core.c/INTERPOLATE.rakumod augment class Match { # INTERPOLATE will iterate over the string $tgt beginning at position 0. # If it can't match against pattern var (or any element of var if it is an array) # it will increment $pos and try again. Therefore it is important to only match # against the current position. # $i is case insensitive flag # $m is ignore accent marks flag # $s is for sequential matching instead of junctive # $a is true if we are in an assertion # INTERPOLATE's parameters are non-optional since the ops for optional params # aren't currently JITted on MoarVM proto method INTERPOLATE(|) is implementation-detail {*} multi method INTERPOLATE(Callable:D \var, $, $, $, $, $) { # Call it if it is a routine. This will capture if requested. (var)(self) } multi method INTERPOLATE(Iterable:D \var, int \im, int \monkey, int \s, $, \context) { my $maxmatch; my \cur := self.'!cursor_start_cur'(); my str $tgt = cur.target; my int $eos = nqp::chars($tgt); my int $maxlen = -1; my int $pos = nqp::getattr_i(cur, $?CLASS, '$!from'); my int $start = 1; my int $nomod = im == 0; my Mu $order; X::Syntax::Reserved.new( reserved => "use of hashes in regexes", ).throw if nqp::istype(var,Hash); # Looks something we need to loop over if !nqp::iscont(var) { my \varlist := var.list; my int $elems = varlist.elems; # reifies my \list := nqp::getattr(varlist,List,'$!reified'); # Order matters for sequential matching, so no NFA involved. if s { $order := list; } # prepare to run the NFA if var is array-ish. else { my Mu \nfa := QRegex::NFA.new; my Mu \alts := nqp::setelems(nqp::list,$elems); my int $fate = 0; my int $j = -1; while nqp::islt_i(++$j,$elems) { my Mu $topic := nqp::atpos(list,$j); nqp::bindpos(alts,$j,$topic); # A Regex already. if nqp::istype($topic,Regex) { nfa.mergesubstates($start,0,nqp::decont($fate), nqp::findmethod($topic,'NFA')($topic), Mu); } # The pattern is a string. else { my Mu \lit := QAST::Regex.new( :rxtype, $topic, :subtype( $nomod ?? '' !! im == 2 ?? im == 1 ?? 'ignorecase+ignoremark' !! 'ignoremark' !! 'ignorecase') ); my Mu \nfa2 := QRegex::NFA.new; my Mu \node := nqp::findmethod(nfa2,'addnode')(nfa2,lit); nfa.mergesubstates($start,0,nqp::decont($fate), nqp::findmethod(node,'save')(node,:non_empty(1)), Mu); } ++$fate; } # Now run the NFA my Mu \fates := nqp::findmethod(nfa,'run')(nfa,$tgt,$pos); my int $count = nqp::elems(fates); nqp::setelems(($order := nqp::list),$count); $j = -1; nqp::bindpos($order,$j, nqp::atpos(alts,nqp::atpos_i(fates,$j))) while nqp::islt_i(++$j,$count); } } # Use the var as it is if it's not array-ish. else { $order := nqp::list(var); } my str $topic_str; my int $omax = nqp::elems($order); my int $o = -1; while nqp::islt_i(++$o,$omax) { my Mu $topic := nqp::atpos($order,$o); my $match; my int $len; # A Regex already. if nqp::istype($topic,Regex) { $match := self.$topic; $len = $match.pos - $match.from; } # The pattern is a string. $len and and $topic_str are used # later on if this condition does not hold. elsif nqp::iseq_i(($len = nqp::chars($topic_str = $topic.Str)),0) { $match = 1; } # no modifier, match literally elsif $nomod { $match = nqp::eqat($tgt, $topic_str, $pos); } # ignoremark+ignorecase elsif im == 3 { $match = nqp::eqaticim($tgt, $topic_str, $pos); } # ignoremark elsif im == 2 { $match = nqp::eqatim($tgt, $topic_str, $pos); } # ignorecase elsif im == 1 { $match = nqp::eqatic($tgt, $topic_str, $pos); } if $match && nqp::isgt_i($len,$maxlen) && nqp::isle_i(nqp::add_i($pos,$len),$eos) { $maxlen = $len; $maxmatch := $match; last if s; # stop here for sequential alternation } } nqp::istype($maxmatch, Match) ?? $maxmatch !! nqp::isge_i($maxlen,0) ?? cur.'!cursor_pass'(nqp::add_i($pos,$maxlen), '') !! cur } multi method INTERPOLATE(Associative:D \var, int \im, $, $, $, \context) { my \cur := self.'!cursor_start_cur'(); my $maxmatch; my str $tgt = cur.target; my int $maxlen = -1; my int $pos = nqp::getattr_i(cur, $?CLASS, '$!from'); my str $topic_str; my $match; my int $len; # The pattern is a string. $len and and $topic_str are used # later on if this condition does not hold. if nqp::iseq_i(($len = nqp::chars($topic_str = var.Str)),0) { $match = 1; } # no modifier, match literally elsif im == 0 { $match = nqp::eqat($tgt, $topic_str, $pos); } # ignoremark+ignorecase elsif im == 3 { $match = nqp::eqaticim($tgt, $topic_str, $pos); } # ignoremark elsif im == 2 { $match = nqp::eqatim($tgt, $topic_str, $pos); } # ignorecase elsif im == 1 { $match = nqp::eqatic($tgt, $topic_str, $pos); } if $match && nqp::isgt_i($len,$maxlen) && nqp::isle_i(nqp::add_i($pos,$len),nqp::chars($tgt)) { $maxlen = $len; $maxmatch := $match; } nqp::istype($maxmatch, Match) ?? $maxmatch !! nqp::isge_i($maxlen,0) ?? cur.'!cursor_pass'(nqp::add_i($pos,$maxlen), '') !! cur } multi method INTERPOLATE(Regex:D \var, int \im, int \monkey, $, $, $) { my $maxmatch; my \cur := self.'!cursor_start_cur'(); my int $maxlen = -1; my int $pos = nqp::getattr_i(cur, $?CLASS, '$!from'); my Mu $topic := var; my $match := self.$topic; if $match { my int $len = $match.pos - $match.from; if nqp::isgt_i($len,$maxlen) && nqp::isle_i(nqp::add_i($pos,$len),nqp::chars(cur.target)) { $maxlen = $len; $maxmatch := $match; } } nqp::istype($maxmatch, Match) ?? $maxmatch !! nqp::isge_i($maxlen,0) ?? cur.'!cursor_pass'(nqp::add_i($pos,$maxlen), '') !! cur } multi method INTERPOLATE(Mu:D \var, int \im, int \monkey, $, $, \context) { my \cur = self.'!cursor_start_cur'(); my str $tgt = cur.target; my int $maxlen = -1; my int $pos = nqp::getattr_i(cur, $?CLASS, '$!from'); my str $topic_str; my $match; my int $len; # The pattern is a zero length string. $len and and $topic_str # are used later on if this condition does not hold. if nqp::iseq_i(($len = nqp::chars($topic_str = var.Str)),0) { $match = 1; } # no modifier, match literally elsif im == 0 { $match = nqp::eqat($tgt, $topic_str, $pos); } # ignoremark+ignorecase elsif im == 3 { $match = nqp::eqaticim($tgt, $topic_str, $pos); } # ignoremark elsif im == 2 { $match = nqp::eqatim($tgt, $topic_str, $pos); } # ignorecase elsif im == 1 { $match = nqp::eqatic($tgt, $topic_str, $pos); } if $match && nqp::isgt_i($len,$maxlen) && nqp::isle_i(nqp::add_i($pos,$len),nqp::chars($tgt)) { $maxlen = $len; } nqp::isge_i($maxlen,0) ?? cur.'!cursor_pass'(nqp::add_i($pos,$maxlen), '') !! cur } multi method INTERPOLATE(Mu:U \var, $, $, $, $, $) { self."!cursor_start_cur"() } proto method INTERPOLATE_ASSERTION(|) is implementation-detail {*} multi method INTERPOLATE_ASSERTION(Associative:D $, $, $, $, $, $) { return self.'!cursor_start_cur'().'!cursor_start_cur'() } multi method INTERPOLATE_ASSERTION(Iterable:D \var, int \im, int \monkey, int \s, $, \context) { my $maxmatch; my \cur := self.'!cursor_start_cur'(); my str $tgt = cur.target; my int $eos = nqp::chars($tgt); my int $maxlen = -1; my int $pos = nqp::getattr_i(cur, $?CLASS, '$!from'); my int $start = 1; my int $nomod = im == 0; my Mu $order := nqp::list(); # Looks something we need to loop over if !nqp::iscont(var) { my \varlist := var.list; my int $elems = varlist.elems; # reifies my \list := nqp::getattr(varlist,List,'$!reified'); # Order matters for sequential matching, so no NFA involved. if s { $order := list; } # prepare to run the NFA if var is array-ish. else { my Mu \nfa := QRegex::NFA.new; my Mu \alts := nqp::setelems(nqp::list,$elems); my int $fate = 0; my int $j = -1; while nqp::islt_i(++$j,$elems) { my Mu $topic := nqp::atpos(list,$j); nqp::bindpos(alts,$j,$topic); # We are in a regex assertion, the strings we get will # be treated as regex rules. return cur.'!cursor_start_cur'() if nqp::istype($topic,Associative); my $rx := MAKE_REGEX($topic,im == 1 || im == 3,im == 2 || im == 3,monkey,context); nfa.mergesubstates($start,0,nqp::decont($fate),nqp::findmethod($rx,'NFA')($rx),Mu); ++$fate; } # Now run the NFA my Mu \fates := nqp::findmethod(nfa,'run')(nfa,$tgt,$pos); my int $count = nqp::elems(fates); nqp::setelems($order,$count); $j = -1; nqp::bindpos($order,$j,nqp::atpos(alts,nqp::atpos_i(fates,$j))) while nqp::islt_i(++$j,$count); } } # Use the var as it is if it's not array-ish. else { nqp::push($order, var); } my str $topic_str; my int $omax = nqp::elems($order); my int $o = -1; while nqp::islt_i(++$o,$omax) { my Mu $topic := nqp::atpos($order,$o); my $match; my int $len; # We are in a regex assertion, the strings we get will be # treated as regex rules. return cur.'!cursor_start_cur'() if nqp::istype($topic,Associative); my $rx := MAKE_REGEX($topic,im == 1 || im == 3,im == 2 || im == 3,monkey,context); $match := self.$rx; $len = $match.pos - $match.from; if $match && nqp::isgt_i($len,$maxlen) && nqp::isle_i(nqp::add_i($pos,$len),$eos) { $maxlen = $len; $maxmatch := $match; last if s; # stop here for sequential alternation } } nqp::istype($maxmatch, Match) ?? $maxmatch !! nqp::isge_i($maxlen,0) ?? cur.'!cursor_pass'(nqp::add_i($pos,$maxlen), '') !! cur } multi method INTERPOLATE_ASSERTION(Mu:D \var, int \im, int \monkey, $, $, \context) { # We are in a regex assertion, the strings we get will be # treated as regex rules. my $rx := MAKE_REGEX(var,im == 1 || im == 3,im == 2 || im == 3,monkey,context); my Match \match := self.$rx; my int $len = match.pos - match.from; match.Bool && nqp::isgt_i($len,-1) && nqp::isle_i(nqp::add_i(nqp::getattr_i(self, $?CLASS, '$!pos'),$len),nqp::chars(self.target)) ?? match !! self.'!cursor_start_fail'() } method CALL_SUBRULE($rule, |c) is implementation-detail { $rule(self, |c) } method DYNQUANT_LIMITS($mm) is implementation-detail { # Treat non-Range values as range with that value on both end points # Throw for non-Numeric or NaN Ranges, or if minimum limit is +Inf # Convert endpoints that are less than 0 to 0, then, # throw if Range is empty. nqp::if( nqp::istype($mm,Range), nqp::if( nqp::isfalse(nqp::istype((my $min := $mm.min),Numeric)) || nqp::isfalse(nqp::istype((my $max := $mm.max),Numeric)) || $min.isNaN || $max.isNaN, X::Syntax::Regex::QuantifierValue.new(:non-numeric-range).throw, nqp::if( $min == Inf, X::Syntax::Regex::QuantifierValue.new(:inf).throw, nqp::stmts( nqp::if( nqp::islt_i( ($min := nqp::add_i($min == -Inf ?? -1 !! $min.Int, $mm.excludes-min)), 0), $min := 0), nqp::if( $max == Inf, nqp::list_i($min,-1), nqp::stmts( nqp::if( $max == -Inf || nqp::islt_i( ($max := nqp::sub_i($max.Int,$mm.excludes-max)),0), $max := 0), nqp::if( nqp::islt_i($max, $min), X::Syntax::Regex::QuantifierValue.new(:empty-range).throw, nqp::list_i($min,$max))))))), nqp::if( nqp::istype((my $v := $mm.Int), Failure), nqp::stmts( ($v.so), # handle Failure nqp::if( nqp::istype($mm,Numeric) && nqp::isfalse($mm.isNaN), nqp::if( $mm == Inf, X::Syntax::Regex::QuantifierValue.new(:inf).throw, nqp::list_i(0,0)), # if we got here, $mm is -Inf, treat as zero X::Syntax::Regex::QuantifierValue.new(:non-numeric).throw)), nqp::if( nqp::islt_i($v,0), nqp::list_i(0,0), nqp::list_i($v,$v)))) } method OTHERGRAMMAR($grammar, $name, |) is implementation-detail { my $lang_cursor := $grammar.'!cursor_init'(self.target(), :p(self.pos())); $lang_cursor.clone_braid_from(self); $lang_cursor."$name"(); } method INDMETHOD($name, |c) is implementation-detail { self."$name"(|c); } method INDRULE($rule, |c) is implementation-detail { $rule(self, |c) } method RECURSE() is implementation-detail { nqp::getlexdyn('$?REGEX')(self) } my role CachedCompiledRegex { has $.regex; } multi sub MAKE_REGEX(Regex \arg, $, $, int \monkey, $) { arg } multi sub MAKE_REGEX(CachedCompiledRegex \arg, $, $, int \monkey, $) { arg.regex } multi sub MAKE_REGEX(\arg, \i, \m, int \monkey, \context) { my $*RESTRICTED = "Prohibited regex interpolation" unless monkey; # Comes from when regex was originally compiled. my \rx = EVAL('anon regex { ' ~ nqp::if(i, nqp::if(m, ':i :m ', ':i '), nqp::if(m, ':m ', ' ')) ~ arg ~ '}', :context(context)); arg does CachedCompiledRegex(rx); rx } } #line 1 SETTING::src/core.c/Cursor.rakumod my constant Cursor = Match; #line 1 SETTING::src/core.c/Grammar.rakumod my class Grammar is Match { method parse( $orig is raw, :$rule = "TOP", :$args, Mu :$actions ) is raw { my $*LINEPOSCACHE; my $grammar := self.new(:$orig, |%_).set_actions($actions); nqp::decont(nqp::getlexcaller('$/') = nqp::if( (my $cursor := nqp::if( $args, $grammar."$rule"(|$args.Capture), $grammar."$rule"() )), nqp::stmts( (my int $chars = $orig.chars), # must be HLL, $orig can be Cool nqp::while( $cursor && nqp::isne_i(nqp::getattr_i($cursor,Match,'$!pos'),$chars), $cursor := $cursor.'!cursor_next'() ), nqp::if( $cursor, $cursor.MATCH, Nil ) ), Nil )) } method subparse($orig is raw, :$rule = "TOP", :$args, :$actions) is raw { my $grammar := self.new(:$orig, |%_).set_actions($actions); nqp::decont( nqp::getlexcaller('$/') = $args ?? $grammar."$rule"(|$args.Capture).MATCH !! $grammar."$rule"().MATCH ) } method parsefile(Str(Cool) $filename, :$enc) is raw { nqp::decont( nqp::getlexcaller('$/') = nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? self.parse($filename.IO.slurp(:$enc), :$filename, |%_) !! self.parse($filename.IO.slurp(:$enc), :$filename) ) } } #line 1 SETTING::src/core.c/Regex.rakumod my class Regex { # declared in BOOTSTRAP # class Regex is Method # has $!caps; # has Mu $!nfa; # has %!alt_nfas; # has str $!source; # has Mu $!topic; # has Mu $!slash; proto method ACCEPTS(|) {*} multi method ACCEPTS(Regex:D: Mu:U \a) { False } # use of Any on topic to force autothreading # so that all(@foo) ~~ Type works as expected multi method ACCEPTS(Regex:U: Any \topic) { nqp::hllbool(nqp::istype(topic, self)) } # Create a braid and fail cursor that we can use with all the normal, # "boring", regex matches that are on the Regex type. This saves them # being created every single time. my $cursor := Match.'!cursor_init'(''); my $braid := $cursor.braid; my $fail_cursor := $cursor.'!cursor_start_cur'(); multi method ACCEPTS(Regex:D \SELF: Any \topic) { my $slash := nqp::getlexrelcaller( nqp::ctxcallerskipthunks(nqp::ctx()), '$/' ); nqp::isrwcont($slash) ?? nqp::decont($slash = SELF!ACCEPTS-Any(topic)) !! SELF!ACCEPTS-Any(topic) } method !ACCEPTS-Any(Regex:D \SELF: Any \topic) { my \cursor := SELF.(Match.'!cursor_init'(topic, :c(0), :$braid, :$fail_cursor)); nqp::isge_i(nqp::getattr_i(cursor,Match,'$!pos'),0) ?? cursor.MATCH !! Nil } multi method ACCEPTS(Regex:D \SELF: Uni:D \uni) { $/ := nqp::getlexrelcaller(nqp::ctxcallerskipthunks(nqp::ctx()),'$/'); self.ACCEPTS(uni.Str) } multi method ACCEPTS(Regex:D \SELF: @a) { SELF!ACCEPT-ITERATOR( nqp::getlexrelcaller(nqp::ctxcallerskipthunks(nqp::ctx()),'$/'), @a.iterator ) } multi method ACCEPTS(Regex:D \SELF: %h) { SELF!ACCEPT-ITERATOR( nqp::getlexrelcaller(nqp::ctxcallerskipthunks(nqp::ctx()),'$/'), %h.keys.iterator ) } method !ACCEPT-ITERATOR(Regex:D \SELF: \slash, Iterator:D \iter) { nqp::decont(slash = nqp::stmts( nqp::until( nqp::eqaddr( # nothing to check? (my $pulled := iter.pull-one),IterationEnd) || nqp::isge_i( # valid match? nqp::getattr_i( (my \cursor := SELF.(Match.'!cursor_init'($pulled,:0c,:$braid,:$fail_cursor))), Match,'$!pos'), 0), nqp::null ), nqp::if( nqp::eqaddr($pulled,IterationEnd), Nil, # no match found cursor.MATCH # found it! ) ) ) } multi method Bool(Regex:D:) { my Mu \topic = $!topic; nqp::istype_nd(topic, Rakudo::Internals::RegexBoolification6cMarker) ?? self!Bool6c() !! nqp::isconcrete(topic) ?? ($!slash = topic.match(self)).Bool !! False } method !Bool6c() { my $ctx := nqp::ctx; nqp::until( nqp::isnull($ctx := nqp::ctxcallerskipthunks($ctx)) || nqp::isconcrete( my $underscore := nqp::getlexrelcaller($ctx,'$_') ), nqp::null ); nqp::if( nqp::isnull($ctx), False, nqp::stmts( (my $slash := nqp::getlexrelcaller($ctx,'$/')), ($slash = $underscore.match(self)).Bool ) ) } multi method gist(Regex:D:) { nqp::isnull_s($!source) ?? '' !! $!source } multi method raku(Regex:D:) { nqp::isnull_s($!source) ?? '' !! $!source } method clone(Mu :$topic is raw, Mu :$slash is raw --> Regex) { nqp::p6bindattrinvres( nqp::p6bindattrinvres(self.Method::clone, Regex, '$!topic', $topic), Regex, '$!slash', $slash) } } multi sub infix:<~~>(Mu \topic, Regex:D $matcher) { $/ := nqp::getlexrelcaller(nqp::ctxcallerskipthunks(nqp::ctx()),'$/'); $matcher.ACCEPTS(topic) } #line 1 SETTING::src/core.c/allomorphs.rakumod my class Allomorph is Str { multi method Bool(::?CLASS:D:) { self.Numeric.Bool } multi method ACCEPTS(Allomorph:D: Any:D \a) is default { nqp::istype(a, Numeric) ?? self.Numeric.ACCEPTS(a) !! nqp::istype(a, Str) ?? self.Str.ACCEPTS(a) !! self.Str.ACCEPTS(a) && self.Numeric.ACCEPTS(a) } method succ(Allomorph:D:) { self.Numeric.succ } method pred(Allomorph:D:) { self.Numeric.pred } method words(Allomorph:D: |c) { nqp::getattr_s(self,Str,'$!value').words(|c) } method comb(Allomorph:D: |c) { nqp::getattr_s(self,Str,'$!value').comb(|c) } method split(Allomorph:D: |c) { nqp::getattr_s(self,Str,'$!value').split(|c) } method subst(Allomorph:D: |c) { nqp::getattr_s(self,Str,'$!value').subst(|c) } method subst-mutate(Allomorph:D \SELF: |c) { (SELF = nqp::getattr_s(self,Str,'$!value')).subst-mutate(|c) } method samecase(Allomorph:D: |c) { nqp::getattr_s(self,Str,'$!value').samecase(|c) } method samemark(Allomorph:D: |c) { nqp::getattr_s(self,Str,'$!value').samemark(|c) } method samespace(Allomorph:D: |c) { nqp::getattr_s(self,Str,'$!value').samespace(|c) } method chop(Allomorph:D: |c) { nqp::getattr_s(self,Str,'$!value').chop(|c) } method chomp(Allomorph:D:) { nqp::getattr_s(self,Str,'$!value').chomp } method trim(Allomorph:D:) { nqp::getattr_s(self,Str,'$!value').trim } method trim-leading(Allomorph:D:) { nqp::getattr_s(self,Str,'$!value').trim-leading } method trim-trailing(Allomorph:D:) { nqp::getattr_s(self,Str,'$!value').trim-trailing } method lc(Allomorph:D:) { nqp::getattr_s(self,Str,'$!value').lc } method uc(Allomorph:D:) { nqp::getattr_s(self,Str,'$!value').uc } method tc(Allomorph:D:) { nqp::getattr_s(self,Str,'$!value').tc } method tclc(Allomorph:D:) { nqp::getattr_s(self,Str,'$!value').tclc } method fc(Allomorph:D:) { nqp::getattr_s(self,Str,'$!value').fc } method flip(Allomorph:D:) { nqp::getattr_s(self,Str,'$!value').flip } method substr(Allomorph:D: |c) { nqp::getattr_s(self,Str,'$!value').substr(|c) } method substr-rw(Allomorph:D \SELF: $start = 0, $want = Whatever ) is rw { (SELF = nqp::getattr_s(self,Str,'$!value')).substr-rw($start, $want) } method Str(Allomorph:D:) { nqp::getattr_s(self,Str,'$!value') } multi method WHICH(Allomorph:D:) { nqp::box_s( nqp::join('|',nqp::list_s( self.^name, self.Numeric.WHICH, self.Str.WHICH )), ValueObjAt ) } multi method raku(Allomorph:D:) { nqp::join("",nqp::list_s( self.^name,'.new(',self.Numeric.raku,', ',self.Str.raku,')' )) } } # the uses of add_I in this class are a trick to make bigints work right my class IntStr is Allomorph is Int { method new(Int:D $i, Str:D $s) { my \SELF = nqp::add_I($i, 0, self); nqp::bindattr_s(SELF, Str, '$!value', $s); SELF } multi method Numeric(IntStr:U:) { self.Mu::Numeric } multi method Numeric(IntStr:D:) { nqp::add_I(self,0,Int) } multi method Real(IntStr:U:) { self.Mu::Real } multi method Real(IntStr:D:) { nqp::add_I(self,0,Int) } multi method Int(IntStr:D:) { nqp::add_I(self,0,Int) } } my class NumStr is Allomorph is Num { method new(Num:D $n, Str:D $s) { my \new = nqp::create(self); nqp::bindattr_n(new,Num,'$!value',$n); nqp::bindattr_s(new,Str,'$!value',$s); new } multi method Numeric(NumStr:U: --> 0e0) { self.Mu::Numeric } multi method Numeric(NumStr:D:) { nqp::getattr_n(self,Num,'$!value') } multi method Real(NumStr:U: --> 0e0) { self.Mu::Real } multi method Real(NumStr:D:) { nqp::getattr_n(self,Num,'$!value') } multi method Int(NumStr:D:) { nqp::getattr_n(self,Num,'$!value').Int } } my class RatStr is Allomorph is Rat { method new(Rat:D $r, Str:D $s) { my \new = nqp::create(self); # no need to normalize, so don't call .new nqp::bindattr(new,Rat,'$!numerator', nqp::getattr($r,Rat,'$!numerator')); nqp::bindattr(new,Rat,'$!denominator', nqp::getattr($r,Rat,'$!denominator')); nqp::bindattr_s(new,Str,'$!value',$s); new } method Capture(RatStr:D:) { self.Mu::Capture } multi method Numeric(RatStr:U: --> 0.0) { self.Mu::Numeric } multi method Numeric(RatStr:D:) { self.Rat } multi method Real(RatStr:U: --> 0.0) { self.Mu::Real } multi method Real(RatStr:D:) { self.Rat } multi method Int(RatStr:D:) { self.Rat.Int } method Rat(RatStr:D:) { my \new := nqp::create(Rat); # no need to normalize, so don't call .new nqp::bindattr(new,Rat,'$!numerator', nqp::getattr(self,Rat,'$!numerator')); nqp::bindattr(new,Rat,'$!denominator', nqp::getattr(self,Rat,'$!denominator')); new } } my class ComplexStr is Allomorph is Complex { method new(Complex:D $c, Str $s) { my \new = nqp::create(self); nqp::bindattr_n(new,Complex,'$!re', nqp::getattr_n($c,Complex,'$!re')); nqp::bindattr_n(new,Complex,'$!im', nqp::getattr_n($c,Complex,'$!im')); nqp::bindattr_s(new,Str,'$!value',$s); new } method Capture(ComplexStr:D:) { self.Mu::Capture } multi method Numeric(ComplexStr:U:) { self.Mu::Numeric; 0i } multi method Numeric(ComplexStr:D:) { self.Complex } method !has-imaginary($target) is hidden-from-backtrace { X::Numeric::Real.new( target => $target, source => self, reason => "imaginary part not zero" ).throw } multi method Real(ComplexStr:U: --> 0e0) { self.Mu::Real } multi method Real(ComplexStr:D:) { nqp::getattr_n(self,Complex,'$!im') ?? self!has-imaginary(Real) !! nqp::getattr_n(self,Complex,'$!re') } multi method Int(ComplexStr:D:) { nqp::getattr_n(self,Complex,'$!im') ?? self!has-imaginary(Int) !! nqp::getattr_n(self,Complex,'$!re') } method Complex(ComplexStr:D:) { my \new = nqp::create(Complex); nqp::bindattr_n(new,Complex,'$!re', nqp::getattr_n(self,Complex,'$!re')); nqp::bindattr_n(new,Complex,'$!im', nqp::getattr_n(self,Complex,'$!im')); new } } multi sub infix:(Allomorph:D $a, Allomorph:D $b) is default { nqp::eqaddr((my $cmp := $a.Numeric cmp $b.Numeric),Order::Same) ?? $a.Str cmp $b.Str !! $cmp } multi sub infix:(Allomorph:D $a, Allomorph:D $b --> Bool:D) is default { nqp::eqaddr($a.WHAT,$b.WHAT) ?? $a.Numeric eqv $b.Numeric && $a.Str eqv $b.Str !! False } multi sub infix:<===>(IntStr:D $a, IntStr:D $b) { $a.Int === $b.Int && $a.Str === $b.Str } multi sub infix:<===>(RatStr:D $a, RatStr:D $b) { $a.Rat === $b.Rat && $a.Str === $b.Str } multi sub infix:<===>(NumStr:D $a, NumStr:D $b) { $a.Num === $b.Num && $a.Str === $b.Str } multi sub infix:<===>(ComplexStr:D $a, ComplexStr:D $b) { $a.Complex === $b.Complex && $a.Str === $b.Str } multi sub val(*@maybevals) { @maybevals.list.map({ val($_) }).eager; } multi sub val(Mu \mu) { warn "{ mu.raku } uselessly passed to val()"; mu } multi sub val(Slip:D \maybevals) { val(|maybevals).Slip } multi sub val(List:D \maybevals) { val(|maybevals) } multi sub val(Pair:D \ww-thing) is raw { # this is a Pair object possible in «» constructs; just pass it through. We # capture this specially from the below sub to avoid emitting a warning # whenever an affected «» construct is being processed. ww-thing } multi sub val(\one-thing) is raw { warn "Value of type { one-thing.^name } uselessly passed to val()"; one-thing } multi sub val(Str:D $MAYBEVAL, Bool :$val-or-fail, Bool :$fail-or-nil) { # TODO: # * Additional numeric styles: # + fractions in [] radix notation: :100[10,'.',53] # * Performance tuning # * Fix remaining XXXX my str $str = nqp::unbox_s($MAYBEVAL); my int $eos = nqp::chars($str); return IntStr.new(0,"") unless $eos; # handle "" # S02:3276-3277: Ignore leading and trailing whitespace my int $pos = nqp::findnotcclass(nqp::const::CCLASS_WHITESPACE, $str, 0, $eos); my int $end = nqp::sub_i($eos, 1); $end = nqp::sub_i($end, 1) while nqp::isge_i($end, $pos) && nqp::iscclass(nqp::const::CCLASS_WHITESPACE, $str, $end); # Fail all the way out when parse failures occur. Return the original # string, or a failure if we're Str.Numeric my &parse_fail := -> \msg { $val-or-fail ?? $fail-or-nil ?? return Nil !! fail X::Str::Numeric.new(:source($MAYBEVAL),:reason(msg),:$pos) !! return $MAYBEVAL } # Str.Numeric should handle blank string before val() parse_fail "Empty string not properly caught before val()" if nqp::islt_i($end, $pos); # Reset end-of-string after trimming $eos = nqp::add_i($end, 1); # return an appropriate type when we've found a number. Allomorphic unless # Str.Numeric is calling my &parse_win := -> \newval { $val-or-fail ?? return newval !! nqp::istype(newval, Num) ?? return NumStr.new(newval, $MAYBEVAL) !! nqp::istype(newval, Rat) ?? return RatStr.new(newval, $MAYBEVAL) !! nqp::istype(newval, Complex) ?? return ComplexStr.new(newval, $MAYBEVAL) !! nqp::istype(newval, Int) ?? return IntStr.new(newval, $MAYBEVAL) !! die "Unknown type {newval.^name} found in val() processing" } my sub parse-simple-number() { # Handle NaN here, to make later parsing simpler if nqp::eqat($str,'NaN',$pos) { $pos = nqp::add_i($pos, 3); return nqp::p6box_n(nqp::nan()); } # Handle any leading +/-/− sign my int $ch = nqp::ord($str, $pos); my int $neg = nqp::iseq_i($ch, 45) || nqp::iseq_i($ch, 8722); # '-', '−' if $neg || nqp::iseq_i($ch, 43) { # '-', '−', '+' ++$pos; $ch = nqp::islt_i($pos, $eos) && nqp::ord($str, $pos); } # nqp::radix_I parse results, and helper values my Mu $parse; my str $prefix; my int $radix; my int $p; my sub parse-int-frac-exp() { my $start-pos = $pos - nqp::istrue($neg); # Integer part, if any my Int $int := 0; if nqp::isne_i($ch, 46) { # '.' parse_fail "Cannot convert radix of $radix (max 36)" if nqp::isgt_i($radix, 36); $parse := nqp::radix_I($radix, $str, $pos, $neg, Int); $p = nqp::atpos($parse, 2); parse_fail "base-$radix number must begin with valid digits or '.'" if nqp::iseq_i($p, -1); $pos = $p; $int := nqp::atpos($parse, 0); nqp::isge_i($pos, $eos) ?? return $int !! ($ch = nqp::ord($str, $pos)); } # Fraction, if any my Int $frac := 0; my Int $base := 0; if nqp::iseq_i($ch, 46) { # '.' ++$pos; $parse := nqp::radix_I($radix, $str, $pos, nqp::add_i($neg, 4), Int); $p = nqp::atpos($parse, 2); parse_fail 'radix point must be followed by one or more valid digits' if nqp::iseq_i($p, -1); $pos = $p; $frac := nqp::atpos($parse, 0); $base := nqp::pow_I(nqp::box_i($radix, Int), nqp::atpos($parse, 1), Num, Int); $ch = nqp::islt_i($pos, $eos) && nqp::ord($str, $pos); } # Exponent, if 'E' or 'e' are present (forces return type Num) if nqp::iseq_i($ch, 69) || nqp::iseq_i($ch, 101) { # 'E', 'e' parse_fail "'E' or 'e' style exponent only allowed on decimal (base-10) numbers, not base-$radix" unless nqp::iseq_i($radix, 10); ++$pos; # handle the sign # XXX TODO: teach radix_I to handle '−' (U+2212) minus? my int $ch = nqp::islt_i($pos, $eos) && nqp::ord($str, $pos); my int $neg-e = nqp::if( nqp::iseq_i($ch, 43), # '+' nqp::stmts(++$pos,0), nqp::if( # '-', '−' nqp::iseq_i($ch, 45) || nqp::iseq_i($ch, 8722), nqp::stmts(++$pos,1), 0, ) ); $parse := nqp::radix_I(10, $str, $pos, $neg-e, Int); $p = nqp::atpos($parse, 2); parse_fail "'E' or 'e' must be followed by decimal (base-10) integer" if nqp::iseq_i($p, -1); $pos = $p; # now that we're satisfied the number is in valid-ish format, use nqp's numifier # to extract the actual num from the string. return nqp::numify(nqp::unbox_s(nqp::substr($str, $start-pos, $pos - $start-pos))); } # Multiplier with exponent, if single '*' is present # (but skip if current token is '**', as otherwise we # get recursive multiplier parsing stupidity) if nqp::iseq_i($ch, 42) && nqp::isne_s(substr($str, $pos, 2), '**') { # '*' ++$pos; my $mult_base := parse-simple-number(); parse_fail "'*' multiplier base must be an integer" unless nqp::istype($mult_base, Int); parse_fail "'*' multiplier base must be followed by '**' and exponent" unless nqp::eqat($str,'**',$pos); $pos = nqp::add_i($pos, 2); my $mult_exp := parse-simple-number(); parse_fail "'**' multiplier exponent must be an integer" unless nqp::istype($mult_exp, Int); my $mult := $mult_base ** $mult_exp; $int := $int * $mult; $frac := $frac * $mult; } # Return an Int if there was no radix point, otherwise, return a Rat nqp::unless($base, $int, Rat.new($int * $base + $frac, $base)); } # Look for radix specifiers if nqp::iseq_i($ch, 58) { # ':' # A string of the form :16 or :60[12,34,56] ++$pos; $parse := nqp::radix_I(10, $str, $pos, 0, Int); $p = nqp::atpos($parse, 2); parse_fail "radix (in decimal) expected after ':'" if nqp::iseq_i($p, -1); $pos = $p; $radix = nqp::atpos($parse, 0); $ch = nqp::islt_i($pos, $eos) && nqp::ord($str, $pos); if nqp::iseq_i($ch, 60) { # '<' ++$pos; my $result := parse-int-frac-exp(); parse_fail "malformed ':$radix<>' style radix number, expecting '>' after the body" unless nqp::islt_i($pos, $eos) && nqp::iseq_i(nqp::ord($str, $pos), 62); # '>' ++$pos; return $result; } elsif nqp::iseq_i($ch, 171) { # '«' ++$pos; my $result := parse-int-frac-exp(); parse_fail "malformed ':$radix«»' style radix number, expecting '»' after the body" unless nqp::islt_i($pos, $eos) && nqp::iseq_i(nqp::ord($str, $pos), 187); # '»' ++$pos; return $result; } elsif nqp::iseq_i($ch, 91) { # '[' ++$pos; my Int $result := 0; my Int $digit := 0; while nqp::islt_i($pos, $eos) && nqp::isne_i(nqp::ord($str, $pos), 93) { # ']' $parse := nqp::radix_I(10, $str, $pos, 0, Int); $p = nqp::atpos($parse, 2); parse_fail "malformed ':$radix[]' style radix number, expecting comma separated decimal values after opening '['" if nqp::iseq_i($p, -1); $pos = $p; $digit := nqp::atpos($parse, 0); parse_fail "digit is larger than {$radix - 1} in ':$radix[]' style radix number" if nqp::isge_i($digit, $radix); $result := $result * $radix + $digit; ++$pos if nqp::islt_i($pos, $eos) && nqp::iseq_i(nqp::ord($str, $pos), 44); # ',' } parse_fail "malformed ':$radix[]' style radix number, expecting ']' after the body" unless nqp::islt_i($pos, $eos) && nqp::iseq_i(nqp::ord($str, $pos), 93); # ']' ++$pos; # XXXX: Handle fractions! # XXXX: Handle exponents! return $neg ?? -$result !! $result; } else { parse_fail "malformed ':$radix' style radix number, expecting '<' or '[' after the base"; } } elsif nqp::iseq_i($ch, 48) # '0' and $radix = nqp::index(' b o d x', nqp::substr($str,nqp::add_i($pos,1),1)) and nqp::isge_i($radix, 2) { # A string starting with 0x, 0d, 0o, or 0b, # followed by one optional '_' $pos = nqp::add_i($pos, 2); ++$pos if nqp::islt_i($pos, $eos) && nqp::iseq_i(nqp::ord($str, $pos), 95); # '_' parse-int-frac-exp(); } elsif nqp::eqat($str,'Inf',$pos) { # 'Inf' $pos = nqp::add_i($pos, 3); $neg ?? -Inf !! Inf; } else { # Last chance: a simple decimal number $radix = 10; parse-int-frac-exp(); } } my sub parse-real() { # Parse a simple number or a Rat numerator my $result := parse-simple-number(); return $result if nqp::iseq_i($pos, $eos); # Check for '/' indicating Rat denominator if nqp::iseq_i(nqp::ord($str, $pos), 47) { # '/' ++$pos; parse_fail "denominator expected after '/'" unless nqp::islt_i($pos, $eos); my $denom := parse-simple-number(); $result := nqp::istype($result, Int) && nqp::istype($denom, Int) ?? Rat.new($result, $denom) !! $result / $denom; } $result; } # Parse a real number, magnitude of a pure imaginary number, # or real part of a complex number my $result := parse-real(); parse_win $result if nqp::iseq_i($pos, $eos); # Check for 'i' or '\\i' indicating first parsed number was # the magnitude of a pure imaginary number if nqp::iseq_i(nqp::ord($str, $pos), 105) { # 'i' parse_fail "Imaginary component of 'NaN' or 'Inf' must be followed by \\i" if nqp::isnanorinf($result.Num); ++$pos; $result := Complex.new(0, $result); } elsif nqp::eqat($str,'\\i',$pos) { $pos = nqp::add_i($pos, 2); $result := Complex.new(0, $result); } # Check for '+' or '-' indicating first parsed number was # the real part of a complex number elsif nqp::iseq_i(nqp::ord($str, $pos), 45) # '-' || nqp::iseq_i(nqp::ord($str, $pos), 43) # '+' || nqp::iseq_i(nqp::ord($str, $pos), 8722) { # '−' # Don't move $pos -- we want parse-real() to see the sign my $im := parse-real(); parse_fail "imaginary part of complex number must be followed by 'i' or '\\i'" unless nqp::islt_i($pos, $eos); if nqp::iseq_i(nqp::ord($str, $pos), 105) { # 'i' parse_fail "Imaginary component of 'NaN' or 'Inf' must be followed by \\i" if nqp::isnanorinf($im.Num); ++$pos; } elsif nqp::eqat($str,'\\i',$pos) { $pos = nqp::add_i($pos, 2); } else { parse_fail "imaginary part of complex number must be followed by 'i' or '\\i'" } $result := Complex.new($result, $im); } # Check for trailing garbage parse_fail "trailing characters after number" if nqp::islt_i($pos, $eos); parse_win $result; } #line 1 SETTING::src/core.c/IO.rakumod my role IO { # This role is empty and exists so that IO() coercers # that coerce to IO::Path type check the result values OK } enum SeekType ( :SeekFromBeginning(0), :SeekFromCurrent(1), :SeekFromEnd(2), ); enum ProtocolFamily ( :PF_UNSPEC(nqp::p6box_i(nqp::const::SOCKET_FAMILY_UNSPEC)), :PF_INET(nqp::p6box_i(nqp::const::SOCKET_FAMILY_INET)), :PF_INET6(nqp::p6box_i(nqp::const::SOCKET_FAMILY_INET6)), :PF_LOCAL(nqp::p6box_i(nqp::const::SOCKET_FAMILY_UNIX)), :PF_UNIX(nqp::p6box_i(nqp::const::SOCKET_FAMILY_UNIX)), :PF_MAX(nqp::p6box_i(nqp::const::SOCKET_FAMILY_UNIX + 1)), ); enum SocketType ( :SOCK_PACKET(0), :SOCK_STREAM(1), :SOCK_DGRAM(2), :SOCK_RAW(3), :SOCK_RDM(4), :SOCK_SEQPACKET(5), :SOCK_MAX(6), ); enum ProtocolType ( :PROTO_TCP(6), :PROTO_UDP(17), ); #line 1 SETTING::src/core.c/IO/Spec.rakumod my class VM { ... } my class IO::Spec { my constant $module = nqp::hash( # only list the non-Unix ones in lowercase 'mswin32', 'Win32', 'os2', 'Win32', 'dos', 'Win32', 'symbian', 'Win32', 'netware', 'Win32', 'win32', 'Win32', 'cygwin', 'Cygwin', 'qnx', 'QNX', 'nto', 'QNX', # »=>» 'Mac', # 'VMS' => 'VMS' ); proto method select(|) {*} multi method select(IO::Spec:U:) { IO::Spec::{nqp::ifnull(nqp::atkey($module,VM.osname),'Unix')}; } multi method select(IO::Spec:U: $token) { IO::Spec::{nqp::ifnull(nqp::atkey($module,$token.lc),'Unix')}; } } # temporary non-lazy initialization of $*SPEC PROCESS::<$SPEC> = IO::Spec.select; #line 1 SETTING::src/core.c/IO/Path/Parts.rakumod class IO::Path::Parts does Positional # all of these can go as soon as we don't need does Associative # the compatibility with the original "List of does Iterable # Pairs" and Map implemention anymore. { has str $.volume; has str $.dirname; has str $.basename; method !SET-SELF($!volume, $!dirname, $!basename) { self } method new($volume, $dirname, $basename) { nqp::create(self)!SET-SELF($volume, $dirname, $basename) } multi method raku(IO::Path::Parts:D:) { 'IO::Path::Parts.new(' ~ $!volume.raku ~ ',' ~ $!dirname.raku ~ ',' ~ $!basename.raku ~ ')' } #------------------------------------------------------------------------------- # all of the code below is just to provide a compatibility layer with the # original List of Pairs / Map implementation. As soon as this is no longer # needed, this can go. method of() { Str } method iterator() { (:$!volume, :$!dirname, :$!basename).iterator } method AT-POS(int $pos) { $pos == 2 ?? :$!basename !! $pos == 1 ?? :$!dirname !! $pos ?? Nil !! :$!volume } method AT-KEY(str $key) { $key eq 'basename' ?? $!basename !! $key eq 'dirname' ?? $!dirname !! $key eq 'volume' ?? $!volume !! Nil } } #line 1 SETTING::src/core.c/IO/Spec/Unix.rakumod my class IO::Spec::Unix is IO::Spec { method canonpath( $patharg, :$parent --> Str:D) { nqp::if( (my str $path = $patharg.Str), nqp::stmts( nqp::while( # // -> / nqp::isne_i(nqp::index($path,'//'),-1), $path = nqp::join('/',nqp::split('//',$path)) ), nqp::while( # /./ -> / nqp::isne_i(nqp::index($path,'/./'),-1), $path = nqp::join('/',nqp::split('/./',$path)) ), nqp::if( # /. $ -> / nqp::eqat($path,'/.',nqp::sub_i(nqp::chars($path),2)), $path = nqp::substr($path,0,nqp::sub_i(nqp::chars($path),1)) ), nqp::if( # ^ ./ -> nqp::eqat($path,'./',0) && nqp::isgt_i(nqp::chars($path),2), $path = nqp::substr($path,2) ), nqp::if( $parent, nqp::stmts( nqp::while( # ^ /.. -> / ($path ~~ s:g { [^ | ] <-[/]>+ '/..' ['/' | $ ] } = ''), nqp::null ), nqp::unless( $path, $path = '.' ) ) ), nqp::if( # ^ / nqp::eqat($path,'/',0), nqp::stmts( nqp::while( # ^ /../ -> / nqp::eqat($path,'/../',0), $path = nqp::substr($path,3) ), nqp::if( # ^ /.. $ -> / nqp::iseq_s($path,'/..'), $path = '/' ) ) ), nqp::if( # .+/ -> .+ nqp::isgt_i(nqp::chars($path),1) && nqp::eqat($path,'/',nqp::sub_i(nqp::chars($path),1)), nqp::substr($path,0,nqp::sub_i(nqp::chars($path),1)), $path ) ), '' ) } method dir-sep { '/' } # NOTE: IO::Path.resolve assumes dir sep is 1 char method curdir { '.' } method updir { '..' } method rootdir { '/' } method devnull { '/dev/null' } my $curupdir := -> str $dir { nqp::hllbool(nqp::isne_s($dir,'.') && nqp::isne_s($dir,'..')) } method curupdir { $curupdir } method basename(str \path) { my int $index = nqp::rindex(path,'/'); nqp::iseq_i($index,-1) ?? path !! nqp::substr(path,$index + 1) } method extension(str \path) { my int $index = nqp::rindex(path,'.'); nqp::iseq_i($index,-1) ?? '' !! nqp::substr(path,$index + 1) } method tmpdir { for %*ENV, '/tmp' { if .defined { my $io := IO::Path.new($_); return $io if $io.d && $io.rwx } } IO::Path.new(".") } method is-absolute( Str() \path ) { nqp::hllbool(nqp::iseq_i(nqp::ord(path), 47)) # '/' } method path { my $parts := nqp::split(':',%*ENV); my $buffer := nqp::create(IterationBuffer); nqp::while( nqp::elems($parts), nqp::push($buffer,nqp::shift($parts) || ".") ); $buffer.Seq } method splitpath( $path, :$nofile = False ) { if $nofile { ( '', $path, '' ); } else { $path ~~ m/^ ( [ .* \/ [ '.'**1..2 $ ]? ]? ) (<-[\/]>*) /; ( '', ~$0, ~$1 ); } } method split(IO::Spec::Unix: Cool:D $path) { my str $p = $path.Str; my int $chars = nqp::chars($p); nqp::while( nqp::if( ($chars = nqp::sub_i(nqp::chars($p), 1)), nqp::eqat($p, '/', $chars), ), $p = nqp::substr($p, 0, $chars), ); my str $dirname; my str $basename; my int $slash-at = nqp::rindex($p, '/'); nqp::if( $slash-at, nqp::if( nqp::iseq_i($slash-at, -1), nqp::stmts( ($dirname = ''), $basename = $p, ), nqp::stmts( ($dirname = nqp::substr($p, 0, $slash-at)), $basename = nqp::substr($p, nqp::add_i($slash-at, 1)), ), ), nqp::stmts( ($dirname = '/'), $basename = nqp::substr($p, 1), ), ); nqp::while( nqp::if( ($chars = nqp::sub_i(nqp::chars($dirname), 1)), nqp::eqat($dirname, '/', $chars), ), $dirname = nqp::substr($dirname, 0, $chars), ); nqp::if( $basename, nqp::unless($dirname, $dirname = '.'), nqp::if( nqp::iseq_s($dirname, '/'), $basename = '/', ), ); # shell dirname '' produces '.', but we don't because it's probably user error IO::Path::Parts.new('', $dirname, $basename) } method join ($, \dir, \file) { nqp::if( (nqp::iseq_s(dir, '/') && nqp::iseq_s(file, '/')) || (nqp::iseq_s(dir, '.') && file), file, nqp::concat(dir, nqp::if( dir && file && nqp::isfalse( nqp::eqat(dir, '/', nqp::sub_i(nqp::chars(dir), 1))) && nqp::isne_i(nqp::ord(file), 47), # '/' nqp::concat('/', file), file))) } method catpath( $, \dirname, \file ) { nqp::concat(dirname, nqp::if( dirname && file && nqp::isfalse( nqp::eqat(dirname, '/', nqp::sub_i(nqp::chars(dirname), 1))) && nqp::isne_i(nqp::ord(file), 47), # '/' nqp::concat('/', file), file)) } method catdir (*@parts) { self.canonpath: nqp::concat( @parts.join('/'), nqp::if(@parts, '/', ''), ) } method splitdir(Cool:D $path) { nqp::p6bindattrinvres(nqp::create(List), List, '$!reified', nqp::split('/', $path.Str)) || ('',) } method catfile( |c ) { self.catdir(|c) } method abs2rel( $path is copy, $base is copy = $*CWD ) { if self.is-absolute($path) || self.is-absolute($base) { $path = self.rel2abs( $path ); $base = self.rel2abs( $base ); } else { # save a couple of cwd()s if both paths are relative $path = self.catdir( self.rootdir, $path ); $base = self.catdir( self.rootdir, $base ); } my ($path_volume, $path_directories) = self.splitpath( $path, :nofile ); my ($base_volume, $base_directories) = self.splitpath( $base, :nofile ); # Can't relativize across volumes return $path unless $path_volume eq $base_volume; # For UNC paths, the user might give a volume like //foo/bar that # strictly speaking has no directory portion. Treat it as if it # had the root directory for that volume. if !$base_directories && self.is-absolute( $base ) { $base_directories = self.rootdir; } # Now, remove all leading components that are the same my @pathchunks = self.splitdir( $path_directories ); my @basechunks = self.splitdir( $base_directories ); if $base_directories eq self.rootdir { @pathchunks.shift; return self.canonpath( self.catpath('', self.catdir( @pathchunks ), '') ); } while @pathchunks && @basechunks && @pathchunks[0] eq @basechunks[0] { @pathchunks.shift; @basechunks.shift; } return self.curdir unless @pathchunks || @basechunks; # $base now contains the directories the resulting relative path # must ascend out of before it can descend to $path_directory. my $result_dirs = self.catdir( self.updir() xx @basechunks.elems, @pathchunks ); return self.canonpath( self.catpath('', $result_dirs, '') ); } method rel2abs(Str() \path, $base? is copy) { self.canonpath: nqp::if( nqp::iseq_i(nqp::ord(path), 47), # .starts-with: '/' path, nqp::concat( nqp::if( nqp::defined($base), nqp::if( nqp::iseq_i(nqp::ord(($base = $base.Str)), 47), # /^ '/'/ $base, nqp::if( nqp::iseq_s($base, (my $cwd := $*CWD.Str)), $base, self.rel2abs($base, $cwd))), $*CWD.Str), nqp::concat('/', path))) } } #line 1 SETTING::src/core.c/IO/Spec/Win32.rakumod my class IO::Spec::Win32 is IO::Spec::Unix { # Some regexes we use for path splitting my $slash = regex { <[\/ \\]> } my $notslash = regex { <-[\/ \\]> } my $driveletter = regex { <[A..Z a..z]> ':' } my $UNCpath = regex { [<$slash> ** 2] <$notslash>+ <$slash> [<$notslash>+ | $] } my $volume_rx = regex { <$driveletter> | <$UNCpath> } method canonpath ($patharg, :$parent) { my $path = $patharg.Str; $path eq '' ?? '' !! self!canon-cat($path, :$parent); } method catdir(*@dirs) { return "" unless @dirs; return self!canon-cat( "\\", @dirs ) if @dirs[0] eq ""; self!canon-cat(|@dirs); } # NOTE: IO::Path.resolve assumes dir sep is 1 char method dir-sep { 「\」 } method devnull { 'nul' } method rootdir { 「\」 } method splitdir(Cool:D $path) { nqp::p6bindattrinvres( nqp::create(List), List, '$!reified', nqp::split('/', nqp::join('/', nqp::split(「\」, $path.Str)))) || ('',) } method basename(str \path) { my int $indexf = nqp::rindex(path,'/'); my int $indexb = nqp::rindex(path,'\\'); nqp::iseq_i($indexf,-1) && nqp::iseq_i($indexb,-1) ?? path !! nqp::substr(path,($indexf > $indexb ?? $indexf !! $indexb) + 1) } method tmpdir { my $ENV := %*ENV; for $ENV, $ENV, $ENV, 'SYS:/temp', 'C:\system\temp', 'C:/temp', '/tmp', '/' { if .defined { my $io := IO::Path.new($_); return $io if $io.d && $io.rwx } } IO::Path.new(".") } method path { my $parts := nqp::split(";",%*ENV // %*ENV // ''); nqp::push((my $buffer := nqp::create(IterationBuffer)),"."); nqp::while( nqp::elems($parts), # unsure why old code removed all `"`, but keeping code same # https://colabti.org/irclogger/irclogger_log/perl6-dev?date=2017-05-15#l240 nqp::if( ($_ := nqp::join('',nqp::split('"',nqp::shift($parts)))), nqp::push($buffer,$_) ) ); $buffer.Seq } method is-absolute ( Str() $path) { nqp::hllbool( nqp::iseq_i(($_ := nqp::ord($path)), 92) # /^ 「\」 / || nqp::iseq_i($_, 47) # /^ 「/」 / || (nqp::eqat($path, ':', 1) # /^ <[A..Z a..z]> ':' [ 「\」 | 「/」 ] / && ( (nqp::isge_i($_, 65) && nqp::isle_i($_, 90)) # drive letter || (nqp::isge_i($_, 97) && nqp::isle_i($_, 122))) && ( nqp::iseq_i(($_ := nqp::ordat($path, 2)), 92) # slash || nqp::iseq_i($_, 47)))) } method split(IO::Spec::Win32: Cool:D $path is copy) { $path ~~ s[ <$slash>+ $] = '' #= unless $path ~~ /^ <$driveletter>? <$slash>+ $/; $path ~~ m/^ ( <$volume_rx> ? ) ( [ .* <$slash> ]? ) (.*) /; my str $volume = $0.Str; my str $dirname = $1.Str; my str $basename = $2.Str; nqp::stmts( nqp::while( # s/ <$slash>+ $// nqp::isgt_i(($_ := nqp::sub_i(nqp::chars($dirname), 1)), 0) && (nqp::eqat($dirname, 「\」, $_) || nqp::eqat($dirname, '/', $_)), $dirname = nqp::substr($dirname, 0, $_)), nqp::if( $volume && nqp::isfalse($dirname) && nqp::isfalse($basename), nqp::if( nqp::eqat($volume, ':', 1) # /^ <[A..Z a..z]> ':'/ && ( (nqp::isge_i(($_ := nqp::ord($volume)), 65) # drive letter && nqp::isle_i($_, 90)) || (nqp::isge_i($_, 97) && nqp::isle_i($_, 122))), ($dirname = '.'), ($dirname = 「\」))), nqp::if( (nqp::iseq_s($dirname, 「\」) || nqp::iseq_s($dirname, '/')) && nqp::isfalse($basename), $basename = 「\」), nqp::if( $basename && nqp::isfalse($dirname), $dirname = '.')); IO::Path::Parts.new($volume, $dirname, $basename) } method join (Str \vol, Str $dir is copy, Str $file is copy) { nqp::stmts( nqp::if( $file && nqp::iseq_s($dir, '.'), ($dir = ''), nqp::if( (nqp::iseq_s($dir, 「\」) || nqp::iseq_s($dir, 「/」)) && (nqp::iseq_s($file, 「\」) || nqp::iseq_s($file, 「/」)), nqp::stmts( ($file = ''), nqp::if( nqp::isgt_i(nqp::chars(vol), 2), # i.e. UNC path $dir = '')))), self.catpath: vol, $dir, $file) } method splitpath(Str() $path, :$nofile = False) { if $nofile { $path ~~ /^ (<$volume_rx>?) (.*) /; (~$0, ~$1, ''); } else { $path ~~ m/^ ( <$volume_rx> ? ) ( [ .* <$slash> [ '.' ** 1..2 $]? ]? ) (.*) /; (~$0, ~$1, ~$2); } } method catpath(Str $vol is copy, Str \dir, Str \file) { nqp::stmts( nqp::if( # Make sure the glue separator is present $vol && dir # unless it's a relative path like A:foo.txt && nqp::isfalse( nqp::iseq_i(nqp::ord($vol, 1), 58) # /^ <[A..Z a..z]> ':'/ && ( (nqp::isge_i(nqp::ord($vol), 65) # 'A' && nqp::isle_i(nqp::ord($vol), 90)) # 'Z' || (nqp::isge_i(nqp::ord($vol), 97) # 'a' && nqp::isle_i(nqp::ord($vol), 122)))) # 'z' && nqp::isfalse( # /<[/\\]> $/ nqp::iseq_i(92, nqp::ord( # '\' $vol, nqp::sub_i(nqp::chars($vol), 1))) || nqp::iseq_i(47, nqp::ord( # '/' $vol, nqp::sub_i(nqp::chars($vol), 1)))) && nqp::isfalse( # /^ /<[/\\]>/ nqp::iseq_i(92, nqp::ord(dir)) # '\' || nqp::iseq_i(47, nqp::ord(dir))), # '/' $vol = nqp::concat($vol, 「\」)), nqp::if( dir && file && nqp::isfalse( # /<[/\\]> $/ nqp::iseq_i(92, nqp::ord( # '\' dir, nqp::sub_i(nqp::chars(dir), 1))) || nqp::iseq_i(47, nqp::ord( # '/' dir, nqp::sub_i(nqp::chars(dir), 1)))), nqp::concat($vol, nqp::concat(dir, nqp::concat(「\」, file))), nqp::concat($vol, nqp::concat(dir, file)))) } method rel2abs (Str() $path is copy, $base? is copy, :$omit-volume) { nqp::if( (nqp::eqat($path, ':', 1) # /^ <[A..Z a..z]> ':' [ 「\」 | 「/」 ] / && ( (nqp::isge_i(($_ := nqp::ord($path)), 65) # drive letter && nqp::isle_i($_, 90)) || (nqp::isge_i($_, 97) && nqp::isle_i($_, 122))) && ( nqp::iseq_i(($_ := nqp::ordat($path, 2)), 92) # slash || nqp::iseq_i($_, 47))) || 0, #($path ~~ /^ <$UNCpath>/), self.canonpath($path), nqp::if( nqp::iseq_i(($_ := nqp::ord($path)), 92) # /^ 「\」 / || nqp::iseq_i($_, 47), # /^ 「/」 / nqp::if( $omit-volume, self.canonpath($path), nqp::stmts( (my $vol), nqp::if( nqp::defined($base), ($vol := self.splitpath($base).AT-POS(0))), nqp::unless( $vol, ($vol := self.splitpath($*CWD)[0])), self.canonpath($vol ~ $path))), nqp::stmts( nqp::unless( nqp::defined($base), ($base = $*CWD), nqp::unless( self.is-absolute($base), ($base = self.rel2abs: $base), ($base = self.canonpath: $base))), (my ($path_directories, $path_file) = self.splitpath($path)[1, 2]), (my ($base_volume, $base_directories) = self.splitpath($base, :nofile)), self.canonpath( self.catpath( $base_volume, self.catdir($base_directories, $path_directories), $path_file))))) } method !canon-cat ( $first, *@rest, :$parent --> Str:D) { $first ~~ /^ ([ <$driveletter> <$slash>? | <$UNCpath> | [<$slash> ** 2] <$notslash>+ | <$slash> ]?) (.*) /; my str $volume = ~$0; my str $path = ~$1; my int $temp; $volume = nqp::join(「\」, nqp::split('/', $volume)); $temp = nqp::ord($volume); nqp::if( nqp::eqat($volume, ':', 1) # this chunk == ~~ /^<[A..Z a..z]>':'/ && ( (nqp::isge_i($temp, 65) && nqp::isle_i($temp, 90)) || (nqp::isge_i($temp, 97) && nqp::isle_i($temp, 122))), ($volume = nqp::uc($volume)), nqp::if( ($temp = nqp::chars($volume)) && nqp::isfalse(nqp::eqat($volume, 「\」, nqp::sub_i($temp, 1))), ($volume = nqp::concat($volume, 「\」)))); $path = join 「\」, $path, @rest.flat; # /xx\\\yy\/zz --> \xx\yy\zz $path = nqp::join(「\」, nqp::split('/', $path)); nqp::while( nqp::isne_i(-1, $temp = nqp::index($path, 「\\」)), ($path = nqp::replace($path, $temp, 2, 「\」))); # xx/././yy --> xx/yy $path ~~ s:g/[ ^ | 「\」] '.' 「\.」* [ 「\」 | $ ]/\\/; nqp::if($parent, nqp::while( ($path ~~ s:g { [^ | ] <-[\\]>+ 「\..」 [ 「\」 | $ ] } = ''), nqp::null)); nqp::while( # \xx --> xx NOTE: this is *not* root nqp::iseq_i(0, nqp::index($path, 「\」)), ($path = nqp::substr($path, 1))); nqp::while( # xx\ --> xx nqp::eqat($path, 「\」, ($temp = nqp::sub_i(nqp::chars($path), 1))), ($path = nqp::substr($path, 0, $temp))); nqp::if( # \.. --> \ nqp::eqat($volume, 「\」, nqp::sub_i(nqp::chars($volume), 1)), $path ~~ s/ ^ '..' 「\..」* [ 「\」 | $ ] //); nqp::if( $path, nqp::concat($volume, $path), nqp::stmts( # \\HOST\SHARE\ --> \\HOST\SHARE nqp::iseq_i(0, nqp::index($volume, 「\\」)) && nqp::iseq_i(nqp::rindex($volume, 「\」), ($temp = nqp::sub_i(nqp::chars($volume), 1))) && ($volume = nqp::substr($volume, 0, $temp)), $volume || '.')) } } #line 1 SETTING::src/core.c/IO/Spec/Cygwin.rakumod my class IO::Spec::Cygwin is IO::Spec::Unix { method canonpath ($patharg, :$parent) { my $path = $patharg.Str; $path.=subst(:g, '\\', '/'); # Handle network path names beginning with double slash my $node = ''; if $path ~~ s/^ ('//' <-[/]>+) [ '/' | $ ] /\// { #/ $node = ~$0; } $node ~ IO::Spec::Unix.canonpath($path, :$parent); } method catdir ( *@paths ) { my $result = IO::Spec::Unix.catdir(@paths); # Don't create something that looks like a //network/path $result.subst(/ <[\\\/]> ** 2..*/, '/'); } method is-absolute ($path) { nqp::hllbool( nqp::iseq_i(($_ := nqp::ord($path)), 92) # /^ 「\」 / || nqp::iseq_i($_, 47) # /^ 「/」 / || (nqp::eqat($path, ':', 1) # /^ <[A..Z a..z]> ':' [ 「\」 | 「/」 ] / && ( (nqp::isge_i($_, 65) && nqp::isle_i($_, 90)) # drive letter || (nqp::isge_i($_, 97) && nqp::isle_i($_, 122))) && ( nqp::iseq_i(($_ := nqp::ordat($path, 2)), 92) # slash || nqp::iseq_i($_, 47)))) } method tmpdir { my %ENV := %*ENV; my $io; first( { if .defined { $io = .IO; $io.d && $io.rwx; } }, %ENV, "/tmp", %ENV, %ENV, 'C:/temp', ) ?? $io !! IO::Path.new("."); } # Paths might have a volume, so we use Win32 splitpath and catpath instead method abs2rel(|c) { IO::Spec::Win32.abs2rel(|c).subst(:global, '\\', '/'); } method rel2abs(|c) { IO::Spec::Win32.rel2abs(|c, :omit-volume).subst(:global, '\\', '/'); } method splitpath(|c) { IO::Spec::Win32.splitpath(|c)>>.subst(:global, '\\', '/'); } method catpath(|c) { IO::Spec::Win32.catpath(|c).subst(:global, '\\', '/'); } method split(IO::Spec::Cygwin: Cool:D $path) { my $parts := IO::Spec::Win32.split($path); IO::Path::Parts.new: $parts.volume.subst(:global, '\\', '/'), $parts.dirname.subst(:global, '\\', '/'), $parts.basename.subst(:global, '\\', '/') } method join(|c) { IO::Spec::Win32.join(|c).subst(:global, '\\', '/'); } } #line 1 SETTING::src/core.c/IO/Spec/QNX.rakumod my class IO::Spec::QNX is IO::Spec::Unix { method canonpath ($patharg, :$parent) { my $path = $patharg.Str; # Handle POSIX-style node names beginning with double slash (qnx, nto) # (POSIX says: "a pathname that begins with two successive slashes # may be interpreted in an implementation-defined manner, although # more than two leading slashes shall be treated as a single slash.") my $node = ''; if $path ~~ s {^ ( '//' <-[ / ]>+ ) '/'? $} = '' or $path ~~ s {^ ( '//' <-[ / ]>+ ) '/' } = '/' { $node = ~ $0; } $path = IO::Spec::Unix.canonpath($path, :$parent); $node ~ $path; } } #line 1 SETTING::src/core.c/IO/Notification.rakumod # stub what we need now my class Supplier { ... } my enum FileChangeEvent (:FileChanged(1), :FileRenamed(2)); my class IO::Notification { my class FileWatchCancellation is repr('AsyncTask') { } class Change { has $.path; has $.event; multi method gist(Change:D:) { "$.path: $.event"; } method IO { $!path.IO } multi method WHICH(Change:D: --> ValueObjAt:D) { nqp::box_s( nqp::join('|',nqp::list_s(self.^name,$!event.Str,$!path)), ValueObjAt ) } } method watch-path(Str() $path, :$scheduler = $*SCHEDULER) { my $is-dir = $path.IO.d; my $s = Supplier.new; nqp::watchfile( $scheduler.queue(:hint-affinity), -> \path, \rename, \err { if err { $s.quit(err); } else { my $event = rename ?? FileRenamed !! FileChanged; my $full-path = ( $is-dir and path ) ?? $*SPEC.catdir($path, path) !! $path; $s.emit(Change.new(:path($full-path), :$event)); } }, $path, FileWatchCancellation); $s.Supply } } #line 1 SETTING::src/core.c/IO/Special.rakumod my class Instant { ... } class IO::Special does IO { has Str $.what is built(False); method new(str $what --> IO::Special:D) { nqp::p6bindattrinvres(nqp::create(self),self,'$!what',$what) } multi method WHICH(IO::Special:D: --> ValueObjAt) { nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,IO::Special), 'IO::Special|', nqp::concat(nqp::unbox_s(self.^name), '|') ), $!what ), ValueObjAt ) } multi method Str (IO::Special:D:) { $!what } multi method raku (IO::Special:D:) { "{self.^name}.new({$!what.raku})" } method IO(IO::Special:D:) { self } method e(IO::Special:D: --> True) { } method d(IO::Special:D: --> False) { } method f(IO::Special:D: --> False) { } method s(IO::Special:D:--> 0) { } method l(IO::Special:D: --> False) { } method r(IO::Special:D: --> Bool:D) { $!what eq '' } method w(IO::Special:D: --> Bool:D) { $!what eq '' or $!what eq '' } method x(IO::Special:D: --> False) { } method modified(IO::Special:D: --> Instant) { Instant } method accessed(IO::Special:D: --> Instant) { Instant } method changed( IO::Special:D: --> Instant) { Instant } method mode(IO::Special:D: --> Nil) { } } #line 1 SETTING::src/core.c/IO/Handle.rakumod my class IO::Handle { has $.path; has $!PIO; has $.chomp is rw = Bool::True; has $.nl-in = ["\x0A", "\r\n"]; has Str:D $.nl-out is rw = "\n"; has Str $.encoding; has Encoding::Decoder $!decoder; has Encoding::Encoder $!encoder; has int $!out-buffer; submethod TWEAK (:$encoding, :$bin, IO() :$!path = Nil) { $bin ?? nqp::isconcrete($encoding) && X::IO::BinaryAndEncoding.new.throw !! ($!encoding = $encoding || 'utf8') } # Make sure we close any open files on exit my $opened := nqp::list; my $opened-locker := Lock.new; method !remember-to-close(--> Nil) { $opened-locker.protect: { nqp::setelems($opened,nqp::elems($opened) + 1024) if nqp::isge_i(nqp::filenofh($!PIO),nqp::elems($opened)); nqp::bindpos($opened,nqp::filenofh($!PIO),$!PIO); } } method !forget-about-closing(--> Nil) { $opened-locker.protect: { nqp::bindpos($opened,nqp::filenofh($!PIO),nqp::null) } } method !close-all-open-handles(--> Nil) { if nqp::elems($opened) -> int $elems { my int $i = 2; # skip STDIN, STDOUT, STDERR nqp::while( nqp::islt_i(++$i,$elems), nqp::unless( nqp::isnull(my $PIO := nqp::atpos($opened,$i)), nqp::closefh($PIO) ) ) } } method do-not-close-automatically(IO::Handle:D: --> Bool:D) { if nqp::defined($!PIO) { self!forget-about-closing; True } else { False } } method open(IO::Handle:D: :$r, :$w, :$x, :$a, :$update, :$rw, :$rx, :$ra, :$mode is copy, :$create is copy, :$append is copy, :$truncate is copy, :$exclusive is copy, :$bin, :$enc is copy, :$chomp = $!chomp, :$nl-in is copy = $!nl-in, Str:D :$nl-out is copy = $!nl-out, :$out-buffer is copy, ) { nqp::if( $bin, nqp::stmts( nqp::isconcrete($enc) && X::IO::BinaryAndEncoding.new.throw, $!encoding = Nil), nqp::unless( nqp::isconcrete($enc), $enc = $!encoding)); $mode = nqp::if( $mode, nqp::if(nqp::istype($mode, Str), $mode, $mode.Str), nqp::if($w && $r || $rw, nqp::stmts(($create = True), 'rw'), nqp::if($x && $r || $rx, nqp::stmts(($create = $exclusive = True), 'rw'), nqp::if($a && $r || $ra, nqp::stmts(($create = $append = True), 'rw'), nqp::if($r, 'ro', nqp::if($w, nqp::stmts(($create = $truncate = True), 'wo'), nqp::if($x, nqp::stmts(($create = $exclusive = True), 'wo'), nqp::if($a, nqp::stmts(($create = $append = True), 'wo'), nqp::if($update, 'rw', 'ro'))))))))); nqp::if( nqp::iseq_s($!path.Str, '-'), nqp::if( nqp::iseq_s($mode, 'ro'), nqp::if( $*IN.opened, nqp::stmts( $*IN.encoding($enc), return $*IN), nqp::stmts( nqp::if( nqp::iseq_s($*IN.path.Str, '-'), $*IN = IO::Handle.new: :path(IO::Special.new: '')), return $*IN.open: :$enc, :bin(nqp::isfalse(nqp::isconcrete($enc))))), nqp::if( nqp::iseq_s($mode, 'wo'), nqp::if( $*OUT.opened, nqp::stmts( $*OUT.encoding($enc), return $*OUT), nqp::stmts( nqp::if( nqp::iseq_s($*OUT.path.Str, '-'), $*OUT = IO::Handle.new: :path(IO::Special.new: '')), return $*OUT.open: :w, :$enc, :bin(nqp::isfalse(nqp::isconcrete($enc))))), die("Cannot open standard stream in mode '$mode'")))); if nqp::istype($!path, IO::Special) { my $what := $!path.what; if $what eq '' { $!PIO := nqp::getstdin(); } elsif $what eq '' { $!PIO := nqp::getstdout(); } elsif $what eq '' { $!PIO := nqp::getstderr(); } else { die "Don't know how to open '$_' especially"; } $!chomp = $chomp; $!nl-out = $nl-out; if nqp::isconcrete($enc) { my $encoding = Encoding::Registry.find($enc); $!decoder := $encoding.decoder(:translate-nl); $!decoder.set-line-separators(($!nl-in = $nl-in).list); $!encoder := $encoding.encoder(:translate-nl); $!encoding = $encoding.name; } self!set-out-buffer-size($out-buffer); return self; } fail X::IO::Directory.new(:$!path, :trying) if $!path.d; { CATCH { .fail } $!PIO := nqp::open( $!path.absolute, nqp::concat( nqp::if(nqp::iseq_s($mode, 'ro'), 'r', nqp::if(nqp::iseq_s($mode, 'wo'), '-', nqp::if(nqp::iseq_s($mode, 'rw'), '+', die "Unknown mode '$mode'"))), nqp::concat(nqp::if($create, 'c', ''), nqp::concat(nqp::if($append, 'a', ''), nqp::concat(nqp::if($truncate, 't', ''), nqp::if($exclusive, 'x', '')))))); self!remember-to-close; } $!chomp = $chomp; $!nl-out = $nl-out; if nqp::isconcrete($enc) { my $encoding = Encoding::Registry.find($enc); $!decoder := $encoding.decoder(:translate-nl); $!decoder.set-line-separators(($!nl-in = $nl-in).list); $!encoder := $encoding.encoder(:translate-nl); $!encoding = $encoding.name; # Add a byte order mark to the start of the file for utf16 nqp::if(nqp::iseq_s($!encoding, 'utf16'), ( if $create && !$exclusive && (!$append || $append && $!path.s == 0) { self.write: Buf[uint16].new(0xFEFF); }) ); } self!set-out-buffer-size($out-buffer); self; } method out-buffer is rw { Proxy.new: :FETCH{ $!out-buffer }, STORE => -> $, \buffer { self!set-out-buffer-size: buffer; } } method !set-out-buffer-size($buffer is copy) { $buffer //= !nqp::isttyfh($!PIO); $!out-buffer = nqp::istype($buffer, Bool) ?? ($buffer ?? 8192 !! 0) !! $buffer.Int; nqp::setbuffersizefh($!PIO, $!out-buffer); $!out-buffer } method nl-in is rw { Proxy.new( FETCH => { $!nl-in }, STORE => -> $, $nl-in { $!nl-in = $nl-in; $!decoder && $!decoder.set-line-separators($nl-in.list); $nl-in } ); } method close(IO::Handle:D: --> True) { nqp::if( nqp::defined($!PIO), nqp::stmts( nqp::if( nqp::isconcrete($!decoder), ($!decoder := Encoding::Decoder) ), self!forget-about-closing, # mark as closed nqp::closefh($!PIO), # close, ignore errors $!PIO := nqp::null # mark HLL handle now also closed ) ) } method eof(IO::Handle:D:) { nqp::hllbool($!decoder ?? $!decoder.is-empty && self.EOF !! self.EOF) } method EOF() { nqp::not_i(nqp::defined($!PIO)) || nqp::eoffh($!PIO) } method READ(Int:D $bytes) { nqp::readfh($!PIO,nqp::create(buf8.^pun),$bytes) } method !failed($trying) { ($!PIO ?? X::IO::BinaryMode !! X::IO::Closed).new(:$trying).throw } method get(IO::Handle:D:) { $!decoder ?? $!decoder.consume-line-chars(:$!chomp) // self!get-line-slow-path() !! self!failed('get') } method !get-line-slow-path() { my $line := Nil; unless self.EOF && $!decoder.is-empty { loop { my $buf := self.READ(0x100000); if $buf.elems { $!decoder.add-bytes($buf); $line := $!decoder.consume-line-chars(:$!chomp); last if nqp::isconcrete($line); } else { $line := $!decoder.consume-line-chars(:$!chomp, :eof) unless self.EOF && $!decoder.is-empty; last; } } } $line } method getc(IO::Handle:D:) { $!decoder ?? $!decoder.consume-exactly-chars(1) || (self!readchars-slow-path(1) || Nil) !! self!failed('getc') } # XXX TODO: Make these routine read handle lazily when we have Cat type method comb (IO::Handle:D: :$close, |c) { $!decoder ?? self.slurp(:$close).comb( |c ) !! self!failed('comb') } method split(IO::Handle:D: :$close, |c) { $!decoder ?? self.slurp(:$close).split( |c ) !! self!failed('split') } proto method words (|) {*} multi method words(IO::Handle:D \SELF: $limit, :$close) { $!decoder ?? nqp::istype($limit,Whatever) || $limit == Inf ?? self.words(:$close) !! $close ?? Seq.new(Rakudo::Iterator.FirstNThenSinkAll( self.words.iterator, $limit.Int, {SELF.close})) !! self.words.head($limit.Int) !! self!failed('words') } my class Words does Iterator { has $!handle is built(:bind); has $!close is built(:bind); has str $!str= ""; # https://github.com/Raku/old-issue-tracker/issues/4690; has int $!searching = 1; has int $!pos; method TWEAK() { self!next-chunk } method !next-chunk() { my int $chars = nqp::chars($!str); $!str = $!pos < $chars ?? nqp::substr($!str,$!pos) !! ""; $chars = nqp::chars($!str); while $!searching { $!str = nqp::concat($!str,$!handle.readchars); my int $new = nqp::chars($!str); $!searching = 0 if $new == $chars; # end $!pos = ($chars = $new) ?? nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE, $!str, 0, $chars) !! 0; last if $!pos < $chars; } } method pull-one() { my int $chars; my int $left; my int $nextpos; while ($chars = nqp::chars($!str)) && $!searching { while ($left = $chars - $!pos) > 0 { $nextpos = nqp::findcclass( nqp::const::CCLASS_WHITESPACE,$!str,$!pos,$left); last unless $left = $chars - $nextpos; # broken word my str $found = nqp::substr($!str, $!pos, $nextpos - $!pos); $!pos = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE,$!str,$nextpos,$left); return nqp::p6box_s($found); } self!next-chunk; } if $!pos < $chars { my str $found = nqp::substr($!str,$!pos); $!pos = $chars; nqp::p6box_s($found) } else { $!handle.close if $!close; IterationEnd } } method push-all(\target --> IterationEnd) { my int $chars; my int $left; my int $nextpos; while ($chars = nqp::chars($!str)) && $!searching { while ($left = $chars - $!pos) > 0 { $nextpos = nqp::findcclass( nqp::const::CCLASS_WHITESPACE,$!str,$!pos,$left); last unless $left = $chars - $nextpos; # broken word target.push(nqp::p6box_s( nqp::substr($!str, $!pos, $nextpos - $!pos) )); $!pos = nqp::findnotcclass( nqp::const::CCLASS_WHITESPACE,$!str,$nextpos,$left); } self!next-chunk; } target.push(nqp::p6box_s(nqp::substr($!str,$!pos))) if $!pos < $chars; $!handle.close if $!close; } } multi method words(IO::Handle:D: :$close) { $!decoder ?? Seq.new(Words.new(:handle(self), :$close)) !! self!failed('words') } my class GetLineFast does Iterator { has $!handle; has $!chomp; has $!decoder; has $!close; method new(\handle,\close) { my \res = nqp::create(self); nqp::bindattr(res, self.WHAT, '$!handle', handle); nqp::bindattr(res, self.WHAT, '$!close', close); nqp::bindattr(res, self.WHAT, '$!chomp', nqp::getattr(handle, IO::Handle, '$!chomp')); nqp::p6bindattrinvres(res, self.WHAT, '$!decoder', nqp::getattr(handle, IO::Handle, '$!decoder')) } method pull-one() { # Slow path falls back to .get on the handle, which will # replenish the buffer once we exhaust it. nqp::if( nqp::isconcrete( my \consumed := $!decoder.consume-line-chars(:$!chomp) ), consumed, nqp::if( nqp::isconcrete(my \got := $!handle.get), got, nqp::stmts( nqp::if($!close,$!handle.close), IterationEnd ) ) ) } method push-all(\target --> IterationEnd) { nqp::while( nqp::if( nqp::isconcrete( my \consumed := $!decoder.consume-line-chars(:$!chomp) ), nqp::stmts( target.push(consumed), 1 ), nqp::if( nqp::isconcrete(my \got := $!handle.get), nqp::stmts( target.push(got), 1 ) ) ), nqp::null ); $!handle.close if $!close; } method sink-all(--> IterationEnd) { $!close ?? $!handle.close # can't seek pipes, so need the `try` !! try $!handle.seek(0,SeekFromEnd) # seek to end } } my class GetLineSlow does Iterator { has $!handle; has $!close; method new(\handle,\close) { my \res = nqp::create(self); nqp::bindattr(res, self.WHAT, '$!close', close); nqp::p6bindattrinvres(res,self.WHAT,'$!handle',handle) } method pull-one() { nqp::if( nqp::isconcrete(my \line := $!handle.get), line, nqp::stmts( nqp::if($!close,$!handle.close), IterationEnd ) ) } method push-all(\target --> IterationEnd) { nqp::while( nqp::isconcrete(my \line := $!handle.get), target.push(line) ); $!handle.close if $!close; } method sink-all(--> IterationEnd) { $!close ?? $!handle.close # can't seek pipes, so need the `try` !! try $!handle.seek(0,SeekFromEnd) # seek to end } } method !LINES-ITERATOR(IO::Handle:D: $close) { $!decoder ?? nqp::eqaddr(self.WHAT,IO::Handle) ?? GetLineFast.new(self,$close) # exact type, can shortcircuit !! GetLineSlow.new(self,$close) # can *NOT* shortcircuit .get !! self!failed('lines') } proto method lines (|) {*} multi method lines(IO::Handle:D \SELF: $limit, :$close) { nqp::istype($limit,Whatever) || $limit == Inf ?? Seq.new(self!LINES-ITERATOR($close)) !! $close ?? Seq.new(Rakudo::Iterator.FirstNThenSinkAll( self!LINES-ITERATOR(0), $limit.Int, {SELF.close})) !! self.lines.head($limit.Int) } multi method lines(IO::Handle:D \SELF: :$close) { Seq.new(self!LINES-ITERATOR($close)) } method read(IO::Handle:D: Int(Cool:D) $bytes = $*DEFAULT-READ-ELEMS) { # If we have one, read bytes via. the decoder to support mixed-mode I/O. $!decoder ?? ($!decoder.consume-exactly-bytes($bytes) // self!read-slow-path($bytes)) !! nqp::eqaddr(nqp::what(self),IO::Handle) ?? nqp::readfh($!PIO,nqp::create(buf8.^pun),$bytes) !! self.READ($bytes) } method !read-slow-path($bytes) { if self.EOF && $!decoder.is-empty { nqp::create(buf8.^pun) } else { $!decoder.add-bytes(self.READ($bytes max 0x100000)); $!decoder.consume-exactly-bytes($bytes) // $!decoder.consume-exactly-bytes($!decoder.bytes-available) // nqp::create(buf8.^pun) } } method readchars(Int(Cool:D) $chars = $*DEFAULT-READ-ELEMS) { $!decoder ?? $!decoder.consume-exactly-chars($chars) // self!readchars-slow-path($chars) !! self!failed('readchars') } method !readchars-slow-path($chars) { my $result := ''; unless self.EOF && $!decoder.is-empty { loop { my $buf := self.READ(0x100000); if $buf.elems { $!decoder.add-bytes($buf); $result := $!decoder.consume-exactly-chars($chars); last if nqp::isconcrete($result); } else { $result := $!decoder.consume-exactly-chars($chars, :eof) unless self.EOF && $!decoder.is-empty; last; } } } $result } multi method Supply(IO::Handle:D: :$size = $*DEFAULT-READ-ELEMS --> Supply:D) { if $!decoder { # handle is in character mode supply { my int $chars = $size; my str $str = self.readchars($chars); nqp::while( nqp::chars($str), nqp::stmts( (emit nqp::p6box_s($str)), ($str = self.readchars($chars)) ) ); done; } } else { supply { my $buf := self.read($size); nqp::while( nqp::elems($buf), nqp::stmts( (emit $buf), ($buf := self.read($size)) ) ); done; } } } proto method seek(|) {*} multi method seek(IO::Handle:D: Int:D $offset, SeekType:D $whence = SeekFromBeginning --> True) { my int $rewind = 0; if $!decoder { # consider bytes we pre-read, when seeking from current position: $rewind = $!decoder.bytes-available if nqp::eqaddr(nqp::decont($whence), SeekFromCurrent); # Freshen decoder, so we won't have stuff left over from earlier reads # that were in the wrong place. $!decoder := Encoding::Registry.find($!encoding).decoder(:translate-nl); $!decoder.set-line-separators($!nl-in.list); } nqp::seekfh($!PIO, $offset - $rewind, +$whence); } method tell(IO::Handle:D: --> Int:D) { nqp::tellfh($!PIO) - ($!decoder ?? $!decoder.bytes-available !! 0) } method write(IO::Handle:D: Blob:D $buf --> True) { self.WRITE($buf) } method WRITE(IO::Handle:D: Blob:D $buf --> True) { nqp::writefh($!PIO, nqp::decont($buf)); } method opened(IO::Handle:D:) { nqp::hllbool(nqp::istrue($!PIO)); } method t(IO::Handle:D:) { self.opened && nqp::hllbool(nqp::isttyfh($!PIO)) } method lock(IO::Handle:D: Bool:D :$non-blocking = False, Bool:D :$shared = False --> True ) { CATCH { default { self!remember-to-close; fail X::IO::Lock.new: :os-error(.Str), :lock-type( 'non-' x $non-blocking ~ 'blocking, ' ~ ($shared ?? 'shared' !! 'exclusive') ); }} self!forget-about-closing; nqp::lockfh($!PIO, 0x10*$non-blocking + $shared); } method unlock(IO::Handle:D: --> True) { self!remember-to-close; nqp::unlockfh($!PIO); } method printf(IO::Handle:D: |c) { self.print(sprintf |c); } multi method print(IO::Handle:D: Junction:D \j) { j.THREAD: { self.print: $_ } } multi method print(IO::Handle:D: Str:D \x --> True) { $!decoder ?? self.WRITE($!encoder.encode-chars(x)) !! self!failed('print') } multi method print(IO::Handle:D: \x --> True) { $!decoder ?? self.WRITE($!encoder.encode-chars(x.Str)) !! self!failed('print') } multi method print(IO::Handle:D: | --> True) { self!failed('print') unless $!decoder; my Mu $args := nqp::p6argvmarray; nqp::shift($args); self.WRITE($!encoder.encode-chars( nqp::join("",Rakudo::Internals.StrList2list_s($args)))) } multi method put(IO::Handle:D: Junction:D \j) { j.THREAD: { self.print: nqp::concat(.Str,$!nl-out) } } multi method put(IO::Handle:D: --> True) { $!decoder ?? self.WRITE($!encoder.encode-chars($!nl-out)) !! self!failed('say') } multi method put(IO::Handle:D: \x --> True) { $!decoder ?? self.WRITE($!encoder.encode-chars(nqp::concat(x.Str, $!nl-out))) !! self!failed('put') } multi method put(IO::Handle:D: | --> True) { self!failed('put') unless $!decoder; my Mu $args := nqp::p6argvmarray; nqp::shift($args); my $parts := Rakudo::Internals.StrList2list_s($args); nqp::push_s($parts,$!nl-out); self.WRITE($!encoder.encode-chars(nqp::join("",$parts))) } multi method say(IO::Handle:D: --> True) { $!decoder ?? self.WRITE($!encoder.encode-chars($!nl-out)) !! self!failed('say') } multi method say(IO::Handle:D: \x --> True) { $!decoder ?? self.WRITE($!encoder.encode-chars(nqp::concat(x.gist,$!nl-out))) !! self!failed('say') } multi method say(IO::Handle:D: | --> True) { self!failed('say') unless $!decoder; my Mu $args := nqp::p6argvmarray; nqp::shift($args); my $parts := Rakudo::Internals.GistList2list_s($args); nqp::push_s($parts,$!nl-out); self.WRITE($!encoder.encode-chars(nqp::join("",$parts))) } # there is no special handling, since it is supposed to give a # human readable version of the Junction. Leaving the method here # so that future optimizers will not try to add it. # multi method say(Junction:D \j) { j.THREAD: { self.say: $_ } } method print-nl(IO::Handle:D: --> True) { $!decoder ?? self.WRITE($!encoder.encode-chars($!nl-out)) !! self!failed('print-nl') } proto method slurp-rest(|) {*} multi method slurp-rest(IO::Handle:D: :$bin! where *.so, :$close --> Buf:D) { # NOTE: THIS METHOD WILL BE DEPRECATED IN 6.d in favour of .slurp() # Testing of it in roast master has been removed and only kept in 6.c # If you're changing this code for whatever reason, test with 6.c-errata LEAVE self.close if $close; my $res := nqp::create(buf8.^pun); loop { my $buf := self.read(0x100000); nqp::elems($buf) ?? $res.append($buf) !! return $res } } multi method slurp-rest(IO::Handle:D: :$enc, :$bin, :$close --> Str:D) { self!failed('slurp-rest') unless $!decoder; # NOTE: THIS METHOD WILL BE DEPRECATED IN 6.d in favour of .slurp() # Testing of it in roast master has been removed and only kept in 6.c # If you're changing this code for whatever reason, test with 6.c-errata LEAVE self.close if $close; self.encoding($enc) if $enc.defined; self!slurp-all-chars() } method slurp(IO::Handle:D: :$close, :$bin) { my $res; nqp::if( $!decoder, nqp::if( $bin, nqp::stmts( ($res := nqp::create(buf8.^pun)), nqp::if( $!decoder.bytes-available, $res.append( $!decoder.consume-exactly-bytes($!decoder.bytes-available) ) ) ), ($res := self!slurp-all-chars) ), ($res := nqp::create(buf8.^pun)) ); nqp::if( nqp::isfalse($!decoder) || $bin, nqp::while( nqp::elems(my $buf := self.READ(0x100000)), $res.append($buf) ) ); # don't sink result of .close; it might be a failed Proc my $ = self.close if $close; $res } method !slurp-all-chars() { while nqp::elems(my $buf := self.READ(0x100000)) { $!decoder.add-bytes($buf); } $!decoder.consume-all-chars() } proto method spurt(|) {*} multi method spurt(IO::Handle:D: Blob $data, :$close) { LEAVE self.close if $close; self.WRITE($data); } multi method spurt(IO::Handle:D: Cool $data, :$close) { LEAVE self.close if $close; self.print($data); } method path(IO::Handle:D:) { $!path.IO } method IO(IO::Handle:D:) { $!path.IO } # use $.path, so IO::Pipe picks it up multi method Str(IO::Handle:D:) { $.path.Str } multi method gist(IO::Handle:D:) { "{self.^name}<$!path.gist()>({self.opened ?? 'opened' !! 'closed'})" } method flush(IO::Handle:D: --> True) { CATCH { default { fail X::IO::Flush.new: :os-error(.Str) } } nqp::defined($!PIO) or die 'File handle not open, so cannot flush'; nqp::flushfh($!PIO); } proto method encoding(|) {*} multi method encoding(IO::Handle:D:) { $!encoding // Nil } multi method encoding(IO::Handle:D: $new-encoding is copy, :$replacement, :$strict, Bool:D :$translate-nl = True) { with $new-encoding { if $_ eq 'bin' { $_ = Nil; } else { return $!encoding if $!decoder and $!encoding and $!encoding eq $_; } } with $!decoder { # We're switching encoding, or back to binary mode. First grab any # bytes the current decoder is holding on to but has not yet done # decoding of. my $available = $!decoder.bytes-available; with $new-encoding { my $prev-decoder := $!decoder; my $encoding = Encoding::Registry.find($new-encoding); $!decoder := $encoding.decoder(:$translate-nl, :$replacement, :$strict); $!decoder.set-line-separators($!nl-in.list); $!decoder.add-bytes($prev-decoder.consume-exactly-bytes($available)) if $available; $!encoder := $encoding.encoder(:$translate-nl, :$replacement, :$strict); $!encoding = $encoding.name; } else { nqp::seekfh($!PIO, -$available, SeekFromCurrent) if $available; $!decoder := Encoding::Decoder; $!encoder := Encoding::Encoder; $!encoding = Nil; Nil } } else { # No previous decoder; make a new one if needed, otherwise no change. with $new-encoding { my $encoding = Encoding::Registry.find($new-encoding); $!decoder := $encoding.decoder(:$translate-nl, :$replacement, :$strict); $!decoder.set-line-separators($!nl-in.list); $!encoder := $encoding.encoder(:$translate-nl, :$replacement, :$strict); $!encoding = $encoding.name; } else { Nil } } } submethod DESTROY(IO::Handle:D: --> Nil) { # Close handles with any file descriptor larger than 2. Those below # are our $*IN, $*OUT, and $*ERR, and we don't want them closed # implicitly via DESTROY, since you can't get them back again. self.close if nqp::defined($!PIO) # not closed yet && nqp::isgt_i(nqp::filenofh($!PIO),2) # not a standard handle && nqp::not_i( # marked for closing nqp::isnull(nqp::atpos($opened,nqp::filenofh($!PIO))) ) } method native-descriptor(IO::Handle:D:) { nqp::defined($!PIO) ?? nqp::filenofh($!PIO) !! X::AdHoc.new( payload => 'File handle not open, so cannot get native descriptor' ).throw } } Rakudo::Internals.REGISTER-DYNAMIC: '$*DEFAULT-READ-ELEMS', { PROCESS::<$DEFAULT-READ-ELEMS> := %*ENV // 65536; } #line 1 SETTING::src/core.c/IO/Pipe.rakumod my class IO::Pipe is IO::Handle { has $.proc; has $!on-read; has $!on-write; has $!on-close; has $!on-native-descriptor; has $!eof = False; has $!closed = False; method TWEAK(:$!on-close!, :$enc, :$bin, :$!on-read, :$!on-write, :$!on-native-descriptor --> Nil) { if $bin { X::IO::BinaryAndEncoding.new.throw if nqp::isconcrete($enc) } else { my $encoding = Encoding::Registry.find($enc || 'utf-8'); nqp::bindattr(self, IO::Handle, '$!encoding', $encoding.name); my $decoder := $encoding.decoder(:translate-nl); $decoder.set-line-separators($.nl-in.list); nqp::bindattr(self, IO::Handle, '$!decoder', $decoder); nqp::bindattr(self, IO::Handle, '$!encoder', $encoding.encoder(:translate-nl)) } } method READ($) { if $!on-read { loop { my \result = $!on-read(); if result.DEFINITE { return result if result.elems; } else { $!eof = True; return nqp::create(buf8.^pun) } } } else { X::AdHoc.new( payload => "This pipe was opened for writing, not reading" ).throw } } method EOF() { $!eof } method WRITE($data) { $!on-write ?? $!on-write($data) !! X::AdHoc.new( payload => "This pipe was opened for reading, not writing").throw } method flush(IO::Handle:D: --> True) { #`(No buffering) } method close(IO::Pipe:D:) { $!closed = True; $!on-close() } method opened(IO::Pipe:D:) { not $!closed } method t(IO::Pipe:D:) { False } method native-descriptor(IO::Pipe:D:) { $!on-native-descriptor ?? $!on-native-descriptor() !! die("This pipe does not have an associated native descriptor") } method IO { IO::Path } method path { IO::Path } } #line 1 SETTING::src/core.c/IO/Path.rakumod my class IO::Path is Cool does IO { has IO::Spec $.SPEC; # the associated IO::Spec has Str $.CWD; # the associated CWD has Str $.path; # the path as specified has $!is-absolute; # Bool:D if we know $!path is an absolute path has $!os-path; # the absolute path associated with path/SPEC/CWD has $!parts; # IO::Path::Parts object, if any sub empty-path-message() is hidden-from-backtrace { die "Must specify a non-empty string as a path" } sub null-in-path() is hidden-from-backtrace { X::IO::Null.new.throw } multi method ACCEPTS(IO::Path:D: Cool:D \other) { nqp::hllbool(nqp::iseq_s($.absolute, nqp::unbox_s(other.IO.absolute))); } method !SET-SELF(str $path, IO::Spec $SPEC, $CWD) { empty-path-message unless nqp::chars($path); null-in-path if nqp::isne_i(nqp::index($path,"\0"),-1) || nqp::isne_i(nqp::index($CWD, "\0"),-1); $!path := $path; $!SPEC := $SPEC; $!CWD := $CWD; $!is-absolute := $!os-path := $!parts := nqp::null; self } proto method new(|) {*} multi method new(IO::Path: Str:D $path, :$CWD!, IO::Spec :$SPEC = $*SPEC --> IO::Path:D) { nqp::create(self)!SET-SELF($path, $SPEC, $CWD.Str) } multi method new(IO::Path: Str:D $path, IO::Spec :$SPEC! --> IO::Path:D) { nqp::create(self)!SET-SELF($path, $SPEC, $*CWD.Str) } multi method new(IO::Path: Str:D $path --> IO::Path:D) { nqp::create(self)!SET-SELF($path, $*SPEC, $*CWD.Str) } multi method new(IO::Path: Cool:D $path, IO::Spec :$SPEC = $*SPEC, :$CWD = $*CWD --> IO::Path:D) { nqp::create(self)!SET-SELF($path.Str, $SPEC, $CWD.Str) } multi method new(IO::Path: :$basename!, :$dirname = '', :$volume = '', :$SPEC = $*SPEC, :$CWD = $*CWD, ) { nqp::create(self)!SET-SELF( $SPEC.join($volume,$dirname,$basename), $SPEC, $CWD.Str) } multi method new(IO::Path:) { empty-path-message } method is-absolute(--> Bool:D) { nqp::ifnull( $!is-absolute, $!is-absolute := nqp::hllbool($!SPEC.is-absolute: $!path) ) } method is-relative(--> Bool:D) { self.is-absolute ?? False !! True } method parts { nqp::ifnull( $!parts, $!parts := $!SPEC.split($!path) ) } method volume(IO::Path:D:) { self.parts.volume } method dirname(IO::Path:D:) { self.parts.dirname } method basename(IO::Path:D:) { self.parts.basename } my sub EXTENSION-MK-EXTENSION ( str $name, $no-ext, int $part-min, int $part-max = $part-min ) is pure { my int $offset = nqp::chars($name); my int $next-offset; my int $parts; nqp::while( nqp::if( nqp::isne_i( -1, ($next-offset = nqp::rindex($name, '.', nqp::sub_i($offset, 1)))), nqp::if($offset, nqp::islt_i($parts, $part-max)) ), nqp::stmts( ($offset = $next-offset), ++$parts ), ); nqp::if( nqp::if(nqp::isle_i($part-min, $parts), nqp::isle_i($parts, $part-max)), nqp::substr($name, nqp::add_i($offset, 1)), $no-ext, ) } my sub EXTENSION-SUBST ($ext, $base, $subst, $joiner) is pure { nqp::if( nqp::defined($ext), nqp::unless( nqp::concat( nqp::if( nqp::unless( # if extension is empty, check $base to find out if... nqp::chars($ext), #... it's a missing ext. or empty string ext. nqp::eqat($base, '.', nqp::sub_i(nqp::chars($base), 1)) ), nqp::substr($base, 0, nqp::sub_i(nqp::chars($base), nqp::add_i(nqp::chars($ext), 1)) ), $base, ), nqp::concat($joiner, $subst) ), '.' # use `.` as basename if we ended up with it being empty ), $base, ) } proto method extension(|) {*} multi method extension(IO::Path:D:) { nqp::if( nqp::iseq_i(-1, (my int $offset = nqp::rindex( (my str $basename = nqp::unbox_s(self.basename)),'.'))), '', nqp::substr($basename, nqp::add_i($offset, 1)) ) } multi method extension(IO::Path:D: Int :$parts!) { EXTENSION-MK-EXTENSION self.basename, '', nqp::if( nqp::islt_I(nqp::decont($parts), -2**63), -2**63, nqp::if( nqp::isgt_I(nqp::decont($parts), 2**63-1), 2**63-1, nqp::unbox_i($parts), ), ) } multi method extension(IO::Path:D: Range :$parts!) { my ($min, $max) := Rakudo::Internals.RANGE-AS-ints: $parts, "Can only use numeric, non-NaN Ranges as :parts"; EXTENSION-MK-EXTENSION self.basename, '', $min, $max } multi method extension(IO::Path:D: Str $subst, Int :$parts = 1, Str :$joiner = nqp::chars($subst) ?? '.' !! '' ) { self.new: :dirname(self.dirname), :volume(self.volume), :$!SPEC, :$!CWD, basename => EXTENSION-SUBST EXTENSION-MK-EXTENSION( (my str $base = nqp::unbox_s(self.basename)), Any, nqp::if( nqp::islt_I(nqp::decont($parts), -2**63), -2**63, nqp::if( nqp::isgt_I(nqp::decont($parts), 2**63-1), 2**63-1, nqp::unbox_i($parts), ), ) ), $base, $subst, $joiner; } multi method extension( Str $subst, Range :$parts, Str :$joiner = nqp::chars($subst) ?? '.' !! '' ) { my ($min, $max) := Rakudo::Internals.RANGE-AS-ints: $parts, "Can only use numeric, non-NaN Ranges as :parts"; self.new: :dirname(self.dirname), :volume(self.volume), :$!SPEC, :$!CWD, basename => EXTENSION-SUBST EXTENSION-MK-EXTENSION( (my str $base = nqp::unbox_s(self.basename)), Any, $min, $max ), $base, $subst, $joiner } method Numeric(IO::Path:D:) { self.basename.Numeric } multi method Str (IO::Path:D:) { $!path } multi method gist(IO::Path:D:) { $!is-absolute ?? qq|"$.absolute".IO| !! qq|"$.path".IO| } multi method raku(IO::Path:D:) { self.^name ~ ".new({$.path.raku}, {:$!SPEC.raku}, {:$!CWD.raku})" } method sibling(IO::Path:D: Str() \sibling) { $_ := self.parts; nqp::clone(self).cloned-with-path: $!SPEC.join(., ., sibling) } method succ(IO::Path:D:) { my int $i = nqp::index($!path,"."); $i = nqp::iseq_i($i,-1) ?? nqp::chars($!path) !! $i; nqp::clone(self).cloned-with-path(Rakudo::Internals.SUCC($!path,$i - 1)) } method pred(IO::Path:D:) { my int $i = nqp::index($!path,"."); $i = nqp::iseq_i($i,-1) ?? nqp::chars($!path) !! $i; nqp::clone(self).cloned-with-path(Rakudo::Internals.PRED($!path,$i - 1)) } multi method IO() { self } method open(IO::Path:D: |c) { IO::Handle.new(:path(self)).open(|c) } method watch(IO::Path:D:) { IO::Notification.watch-path($.absolute); } proto method absolute(|) {*} multi method absolute (IO::Path:D: --> Str:D) { nqp::ifnull( $!os-path, $!os-path := $!SPEC.rel2abs($!path,$!CWD) ) } multi method absolute (IO::Path:D: $CWD --> Str:D) { $!is-absolute ?? $!os-path !! $!SPEC.rel2abs($!path, $CWD) # do *not* set because different CWD } method relative (IO::Path:D: $CWD = $*CWD --> Str:D) { $!SPEC.abs2rel($.absolute, $CWD); } method cleanup (IO::Path:D:) { nqp::clone(self).cloned-with-path($!SPEC.canonpath($!path)) } method resolve (IO::Path:D: :$completely) { # XXXX: Not portable yet; assumes POSIX semantics my int $max-depth = 256; my str $sep = $!SPEC.dir-sep; my str $cur = $!SPEC.curdir; my str $up = $!SPEC.updir; my str $empty = ''; my Mu $res-list := nqp::list_s(); my $vdb := $!SPEC.split: self.absolute; my str $volume = $vdb.volume; my str $resolved = $volume; my $path := $!SPEC.catpath: '', $vdb.dirname, $vdb.basename; # In this bit, we work with bytes, converting $sep (and assuming it's # 1-char long) in the path to nul bytes and then splitting the path # on nul bytes. This way, even if we get some weird paths like # "/\x[308]", we still split on the /, leaving the lone combiner as # part of the path part. nqp::stmts( (my $p := nqp::encode( nqp::unbox_s($path), 'utf8-c8', nqp::create(buf8.^pun))), (my int $ord-sep = nqp::ord($sep)), (my int $els = nqp::elems($p)), (my int $i = -1), nqp::while( nqp::isne_i($els,++$i), nqp::if( nqp::iseq_i(nqp::atpos_u($p, $i), $ord-sep), nqp::atposref_u($p, $i) = 0)), my $parts := nqp::split("\0", nqp::decode($p, 'utf8-c8'))); while $parts { fail "Resolved path too deep!" if $max-depth < nqp::elems($res-list) + nqp::elems($parts); # Grab next unprocessed part, check for '', '.', '..' my str $part = nqp::shift($parts); next if nqp::iseq_s($part, $empty) || nqp::iseq_s($part, $cur); if nqp::iseq_s($part, $up) { next unless $res-list; nqp::pop_s($res-list); $resolved = $res-list ?? nqp::concat(nqp::concat($volume, $sep), nqp::join($sep, $res-list)) !! $empty; next; } # Normal part, set as next path to test my str $next = nqp::concat($resolved, nqp::concat($sep, $part)); # Path part doesn't exist... if !nqp::stat($next, nqp::const::STAT_EXISTS) { # fail() if we were asked for complete resolution and we still # have further parts to resolve. If it's the last part, # don't fail; it can be a yet-to-be-created file or dir $completely && nqp::elems($parts) && X::IO::Resolve.new(:path(self)).fail; # ...or handle rest in non-resolving mode if not $resolved = $next; while $parts { $part = nqp::shift($parts); next if nqp::iseq_s($part, $empty) || nqp::iseq_s($part, $cur); $resolved = nqp::concat($resolved, nqp::concat($sep, $part)); } } # Symlink; read it and act on absolute or relative link elsif nqp::fileislink($next) { my str $link = nqp::readlink($next); my Mu $link-parts := nqp::split($sep, $link); next unless $link-parts; # Symlink to absolute path if nqp::iseq_s($link-parts[0], $empty) { $resolved = nqp::shift($link-parts); $res-list := nqp::list_s(); } nqp::unshift($parts, nqp::pop($link-parts)) while $link-parts; } # Just a plain old path part, so append it and go on else { $resolved = $next; nqp::push_s($res-list, $part); } } $resolved = $volume ~ $sep if $resolved eq $volume; nqp::p6bindattrinvres( nqp::create(self)!SET-SELF($resolved, $!SPEC, $volume ~ $sep), IO::Path,'$!is-absolute',True ) } proto method parent(|) {*} multi method parent(IO::Path:D: Int:D $depth is copy) { if $depth > 0 { my $io := self; nqp::while( $depth--, $io := $io.parent ); $io } else { $depth ?? X::OutOfRange.new( what => 'Depth of .parent', got => $depth, range => "0..*" ).throw !! self } } multi method parent(IO::Path:D:) { my $curdir := $!SPEC.curdir; my $updir := $!SPEC.updir; nqp::clone(self).cloned-with-path: self.is-absolute ?? $!SPEC.join($.volume, $.dirname, '') !! $.dirname eq $curdir && $.basename eq $curdir ?? $!SPEC.join($.volume,$curdir,$updir) !! $.basename eq $updir && ($.dirname eq $curdir || !$!SPEC.splitdir($.dirname).first(* ne $updir)) ?? $!SPEC.join($.volume,$!SPEC.catdir($.dirname,$updir),$updir) !! $!SPEC.join($.volume, $.dirname, '') } method child (IO::Path:D: \child) { nqp::clone(self).cloned-with-path: $!SPEC.join('', $!path, child.Str) } method add (IO::Path:D: *@children) { nqp::clone(self).cloned-with-path: $!SPEC.join: '', $!path, @children.join($!SPEC.dir-sep) } proto method chdir(|) {*} multi method chdir(IO::Path:D: IO $path, |c) { self.chdir: $path.absolute, |c } multi method chdir( IO::Path:D: Str() $path is copy, :$d = True, :$r, :$w, :$x, ) { unless $!SPEC.is-absolute($path) { my ($volume,$dirs) = $!SPEC.splitpath(self.absolute, :nofile); my @dirs = $!SPEC.splitdir($dirs); @dirs.shift; # the first is always empty for absolute dirs for $!SPEC.splitdir($path) -> $dir { if $dir eq '..' { @dirs.pop if @dirs; } elsif $dir ne '.' { @dirs.push: $dir; } } @dirs.push('') if !@dirs; # need at least the rootdir $path = join($!SPEC.dir-sep, $volume, @dirs); } my $dir := nqp::p6bindattrinvres( nqp::create(self)!SET-SELF($path, $!SPEC, $!path), IO::Path,'$!is-absolute',True ); nqp::stmts( nqp::unless( nqp::unless(nqp::isfalse($d), $dir.d), fail X::IO::Chdir.new: :$path, :os-error( $dir.e ?? 'is not a directory' !! 'does not exist' ) ), nqp::unless( nqp::unless(nqp::isfalse($r), $dir.r), fail X::IO::Chdir.new: :$path, :os-error("did not pass :r test") ), nqp::unless( nqp::unless(nqp::isfalse($w), $dir.w), fail X::IO::Chdir.new: :$path, :os-error("did not pass :w test") ), nqp::unless( nqp::unless(nqp::isfalse($x), $dir.x), fail X::IO::Chdir.new: :$path, :os-error("did not pass :x test") ), $dir ) } method rename(IO::Path:D: IO() $to, :$createonly --> True) { CATCH { default { fail X::IO::Rename.new: :from($!os-path), :to($to.absolute), :os-error(.Str); }} $createonly and $to.e and fail X::IO::Rename.new: :from($.absolute), :to($to.absolute), :os-error(':createonly specified and destination exists'); nqp::rename($.absolute, nqp::unbox_s($to.absolute)); } method copy(IO::Path:D: IO() $to, :$createonly --> True) { CATCH { default { fail X::IO::Copy.new: :from($!os-path), :to($to.absolute), :os-error(.Str) }} # add fix for issue #3971 where attempt to copy a dir # to a file clobbers the file. self.d and $to.f and fail X::IO::Copy.new: :from($.absolute), :to($to.absolute), :os-error('cannot copy a directory to a file'); $createonly and $to.e and fail X::IO::Copy.new: :from($.absolute), :to($to.absolute), :os-error(':createonly specified and destination exists'); # XXX TODO: maybe move the sameness check to the nqp OP/VM nqp::if( nqp::iseq_s( (my $from-abs := $.absolute), (my $to-abs := $to.absolute)), X::IO::Copy.new(:from($from-abs), :to($to-abs), :os-error('source and target are the same')).fail, nqp::copy($from-abs, $to-abs)); } method move(IO::Path:D: |c --> True) { self.copy(|c) orelse fail X::IO::Move.new: :from(.exception.from), :to(.exception.to), :os-error(.exception.os-error); self.unlink orelse fail X::IO::Move.new: :from(.exception.from), :to(.exception.to), :os-error(.exception.os-error); } method chmod(IO::Path:D: Int() $mode --> True) { CATCH { default { fail X::IO::Chmod.new( :path($!os-path), :$mode, :os-error(.Str) ); }} nqp::chmod($.absolute, nqp::unbox_i($mode)); } method chown(IO::Path:D: :$uid is copy, :$gid is copy --> True) { CATCH { default { fail X::IO::Chown.new( :path($!os-path), :$uid, :$gid, :os-error(.Str) ); }} my str $path = self.absolute; my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s($path), 0); $uid = $uid.defined ?? $uid.UInt !! nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_UID); $gid = $gid.defined ?? $gid.UInt !! nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_GID); nqp::chown($path, nqp::unbox_u($uid), nqp::unbox_u($gid)) } method unlink(IO::Path:D: --> True) { CATCH { default { fail X::IO::Unlink.new( :path($!os-path), os-error => .Str ); }} nqp::unlink($.absolute); } method symlink(IO::Path:D: IO() $name, Bool :$absolute = True --> True) { CATCH { default { fail X::IO::Symlink.new: :target($!os-path), :name($name.absolute), :os-error(.Str); }} nqp::symlink($absolute ?? $.absolute !! ~self , nqp::unbox_s($name.absolute)); } method link(IO::Path:D: IO() $name --> True) { CATCH { default { fail X::IO::Link.new: :target($!os-path), :name($name.absolute), :os-error(.Str); }} nqp::link($.absolute, $name.absolute); } method mkdir(IO::Path:D: Int() $mode = 0o777) { CATCH { default { fail X::IO::Mkdir.new(:path($!os-path), :$mode, os-error => .Str); }} my str $abspath = $.absolute; my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s($abspath), 0); nqp::unless( nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) && nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_ISDIR), nqp::mkdir($abspath,$mode) ); self } method rmdir(IO::Path:D: --> True) { CATCH { default { fail X::IO::Rmdir.new(:path($!os-path), os-error => .Str); }} nqp::rmdir($.absolute); } # Call with cloned object, update path, keep "is-absolute" setting # and reset the rest. If the source object was an absolute path, # then the given path should also be an absolute path, and vice-versa. method cloned-with-path(Str:D $path) is implementation-detail { X::IO::Null.new.throw if nqp::isne_i(nqp::index($path, "\0"), -1); $!path := $path; $!os-path := $!parts := nqp::null; self } # create prefix to be added to each directory entry method prefix-for-dir() is implementation-detail { my str $dir-sep = $!SPEC.dir-sep; nqp::iseq_s($!path,'.') ?? '' !! $!path.ends-with($dir-sep) ?? $!path !! $!path.ends-with("$dir-sep.") ?? nqp::substr($!path,0,nqp::chars($!path) - 1) !! nqp::concat($!path,$dir-sep) } proto method dir(|) {*} # make it possible to augment with multies from modulespace multi method dir(IO::Path:D: Mu :$test!) { CATCH { default { X::IO::Dir.new(:path(self.absolute), :os-error(.Str)).throw } } Seq.new: Rakudo::Iterator.Dir(self, $test) } multi method dir(IO::Path:D:) { CATCH { default { X::IO::Dir.new(:path(self.absolute), :os-error(.Str)).throw } } # if default tester is system default, use implicit no . .. iterator Seq.new: nqp::eqaddr($!SPEC.curupdir,IO::Spec::Unix.curupdir) ?? Rakudo::Iterator.Dir(self) !! Rakudo::Iterator.Dir(self, $!SPEC.curupdir) } # slurp contents of low level handle sub slurp-PIO(Mu \PIO) is raw { constant slurp-size = 0x100000; nqp::readfh(PIO,(my $blob := nqp::create(buf8.^pun)),slurp-size); # enough to read entire buffer, assume there's more if nqp::iseq_i(nqp::elems($blob),slurp-size) { nqp::while( nqp::iseq_i( nqp::elems( nqp::readfh(PIO,(my $part := nqp::create(buf8.^pun)),slurp-size)), slurp-size ), $blob.append($part) ); $blob.append($part); # add the final, incomplete part } $blob } # slurp STDIN in binary mode sub slurp-stdin-bin() is raw { slurp-PIO(nqp::getstdin) } # slurp given path in binary mode sub slurp-path-bin(str $path) is raw { my $PIO := nqp::open($path,'r'); my $blob := slurp-PIO($PIO); nqp::closefh($PIO); $blob } # slurp STDIN with given normalized encoding sub slurp-stdin-with-encoding(str $encoding) { nqp::join("\n", nqp::split("\r\n",nqp::decode(slurp-stdin-bin,$encoding)) ) } # slurp given path with given normalized encoding sub slurp-path-with-encoding(str $path, str $encoding) { CATCH { return $_.Failure } nqp::elems(my $blob := slurp-path-bin($path)) ?? nqp::join("\n",nqp::split("\r\n",nqp::decode($blob,$encoding))) !! "" } proto method slurp() {*} multi method slurp(IO::Path:D: :$bin!) { nqp::iseq_s($!path,"-") ?? $bin ?? slurp-stdin-bin() !! slurp-stdin-with-encoding('utf8') !! $bin ?? slurp-path-bin(self.absolute) !! slurp-path-with-encoding(self.absolute,'utf8') } multi method slurp(IO::Path:D: :$enc!) { nqp::iseq_s($!path,"-") ?? slurp-stdin-with-encoding( Rakudo::Internals.NORMALIZE_ENCODING($enc)) !! slurp-path-with-encoding(self.absolute, Rakudo::Internals.NORMALIZE_ENCODING($enc)) } multi method slurp(IO::Path:D:) { nqp::iseq_s($!path,"-") ?? slurp-stdin-with-encoding('utf8') !! slurp-path-with-encoding(self.absolute,'utf8') } # spurt data to given path and file mode sub spurt-blob(str $path, str $mode, Blob:D \data) { CATCH { .fail } my $PIO := nqp::open($path,$mode); nqp::writefh($PIO,nqp::decont(data)); nqp::closefh($PIO); True } # spurt text to given path and file mode with given encoding sub spurt-string(str $path, str $mode, str $text, $encoding) { my $blob := nqp::encode( $text, (my str $enc = Rakudo::Internals.NORMALIZE_ENCODING($encoding)), nqp::create(buf8.^pun) ); # check if we need a BOM if $enc eq 'utf16' { # add a BOM if (over)writing if $mode eq 'w' { nqp::unshift_i($blob,254); nqp::unshift_i($blob,255); } # or appending to a new or existing, but zero-length, file else { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s($path), 0); if nqp::not_i(nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS)) || nqp::not_i(nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_FILESIZE)) { nqp::unshift_i($blob,254); nqp::unshift_i($blob,255); } } } spurt-blob($path, $mode, $blob) } method user(IO::Path:D:) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_UID) !! self!does-not-exist("user") } method group(IO::Path:D:) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_GID) !! self!does-not-exist("group") } proto method spurt(|) {*} multi method spurt(IO::Path:D: --> Bool:D) { self.open(:w).close } multi method spurt(IO::Path:D: Blob:D \data, :$append! --> Bool:D) { spurt-blob(self.absolute, $append ?? 'wa' !! 'w', data) } multi method spurt(IO::Path:D: Blob:D \data, :$createonly! --> Bool:D) { nqp::stat(self.absolute,nqp::const::STAT_EXISTS) # sets $!os-path ?? "Failed to open file $!os-path: File exists".Failure !! spurt-blob($!os-path, 'w', data) } multi method spurt(IO::Path:D: Blob:D \data --> Bool:D) { spurt-blob(self.absolute, 'w', data) } multi method spurt(IO::Path:D: \text, :$append!, :$enc --> Bool:D) { spurt-string(self.absolute, $append ?? 'wa' !! 'w', text.Str, $enc) } multi method spurt(IO::Path:D: \text, :$createonly!, :$enc --> Bool:D) { nqp::stat(self.absolute,nqp::const::STAT_EXISTS) # sets $!os-path ?? "Failed to open file $!os-path: File exists".Failure !! spurt-string($!os-path, 'w', text.Str, $enc) } multi method spurt(IO::Path:D: \text, :$enc --> Bool:D) { spurt-string(self.absolute, 'w', text.Str, $enc) } # XXX TODO: when we get definedness-based defaults in core, use them in # IO::Handle.open and get rid of duplication of default values here method lines(IO::Path:D: :$chomp = True, :$enc = 'utf8', :$nl-in = ["\x0A", "\r\n"], |c ) { self.open(:$chomp, :$enc, :$nl-in).lines: |c, :close } method comb(IO::Path:D: :$chomp = True, :$enc = 'utf8', :$nl-in = ["\x0A", "\r\n"], |c ) { self.open(:$chomp, :$enc, :$nl-in).comb: |c, :close } method split(IO::Path:D: :$chomp = True, :$enc = 'utf8', :$nl-in = ["\x0A", "\r\n"], |c ) { self.open(:$chomp, :$enc, :$nl-in).split: |c, :close } method words(IO::Path:D: :$chomp = True, :$enc = 'utf8', :$nl-in = ["\x0A", "\r\n"], |c ) { self.open(:$chomp, :$enc, :$nl-in).words: |c, :close } method !does-not-exist( Str:D $trying --> Failure) is hidden-from-backtrace { X::IO::DoesNotExist.new(:path($!os-path),:$trying).Failure } method e(IO::Path:D: --> Bool:D) { nqp::hllbool(Rakudo::Internals.FILETEST-E(self.absolute)) } method d(IO::Path:D: --> Bool:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::hllbool(nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_ISDIR)) !! self!does-not-exist("d") } method f(IO::Path:D: --> Bool:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::hllbool(nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_ISREG)) !! self!does-not-exist("f") } method s(IO::Path:D: --> Int:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_FILESIZE) !! self!does-not-exist("s") } method l(IO::Path:D: --> Bool:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 1); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::hllbool(nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_ISLNK)) !! self!does-not-exist("l") } method r(IO::Path:D: --> Bool:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::hllbool(nqp::dispatch("boot-syscall", "stat-is-readable", $stat)) !! self!does-not-exist("r") } method w(IO::Path:D: --> Bool:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::hllbool(nqp::dispatch("boot-syscall", "stat-is-writable", $stat)) !! self!does-not-exist("w") } method rw(IO::Path:D: --> Bool:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::hllbool(nqp::dispatch("boot-syscall", "stat-is-readable", $stat) && nqp::dispatch("boot-syscall", "stat-is-writable", $stat)) !! self!does-not-exist("rw") } method x(IO::Path:D: --> Bool:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::hllbool(nqp::dispatch("boot-syscall", "stat-is-executable", $stat)) !! self!does-not-exist("x") } method rwx(IO::Path:D: --> Bool:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::hllbool(nqp::dispatch("boot-syscall", "stat-is-readable", $stat) && nqp::dispatch("boot-syscall", "stat-is-writable", $stat) && nqp::dispatch("boot-syscall", "stat-is-executable", $stat)) !! self!does-not-exist("rwx") } method z(IO::Path:D: --> Bool:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_FILESIZE) == 0 !! self!does-not-exist("z") } method created(IO::Path:D: --> Instant:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? Instant.from-posix-nanos(nqp::dispatch("boot-syscall", "stat-time-nanos", $stat, nqp::const::STAT_CREATETIME)) !! self!does-not-exist("created") } method modified(IO::Path:D: --> Instant:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? Instant.from-posix-nanos(nqp::dispatch("boot-syscall", "stat-time-nanos", $stat, nqp::const::STAT_MODIFYTIME)) !! self!does-not-exist("modified") } method accessed(IO::Path:D: --> Instant:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? Instant.from-posix-nanos(nqp::dispatch("boot-syscall", "stat-time-nanos", $stat, nqp::const::STAT_ACCESSTIME)) !! self!does-not-exist("accessed") } method changed(IO::Path:D: --> Instant:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? Instant.from-posix-nanos(nqp::dispatch("boot-syscall", "stat-time-nanos", $stat, nqp::const::STAT_CHANGETIME)) !! self!does-not-exist("changed") } method mode(IO::Path:D: --> IntStr:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); if nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) { # sets $!os-path my Int $mode := nqp::bitand_i(nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_PLATFORM_MODE), 0o7777); my str $str = nqp::base_I($mode,8); IntStr.new( $mode, nqp::concat(nqp::x('0',4 - nqp::chars($str)),$str) ) } else { self!does-not-exist("mode") } } method inode(IO::Path:D: --> Int:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_PLATFORM_INODE) !! self!does-not-exist("inode") } method dev(IO::Path:D: --> Int:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_ISDEV) !! self!does-not-exist("dev") } method devtype(IO::Path:D: --> Int:D) { my $stat := nqp::dispatch("boot-syscall", "file-stat", nqp::decont_s(self.absolute), 0); nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_EXISTS) ?? nqp::dispatch("boot-syscall", "stat-flags", $stat, nqp::const::STAT_PLATFORM_DEV) !! self!does-not-exist("devtype") } proto method dir-with-entries(|) {*} multi method dir-with-entries(IO::Path:D: --> Bool:D) { my $handle := nqp::opendir(self.absolute); nqp::while( (my str $entry = nqp::nextfiledir($handle)) && (nqp::iseq_s($entry,'.') || nqp::iseq_s($entry,'..')), nqp::null() ); nqp::closedir($handle); nqp::hllbool(nqp::chars($entry)) } multi method dir-with-entries(IO::Path:D: :$test! --> Bool:D) { my $handle := nqp::opendir(self.absolute); nqp::while( (my str $entry = nqp::nextfiledir($handle)) && !$test.ACCEPTS($entry), nqp::null() ); nqp::closedir($handle); nqp::hllbool(nqp::chars($entry)) } method CHECKSUM(IO::Path:D: --> Str:D) is implementation-detail { my \slurped := self.slurp(:enc); nqp::istype(slurped,Failure) ?? slurped !! nqp::sha1(slurped) } } my role IO::Path::Spec[$SPEC] is IO::Path { method new(|c) { self.IO::Path::new(|c, :$SPEC) } multi method raku(::?CLASS:D:) { self.^name ~ ".new({$.path.raku}, {:$.CWD.raku})" } } my class IO::Path::Cygwin does IO::Path::Spec[IO::Spec::Cygwin] { } my class IO::Path::QNX does IO::Path::Spec[IO::Spec::QNX ] { } my class IO::Path::Unix does IO::Path::Spec[IO::Spec::Unix ] { } my class IO::Path::Win32 does IO::Path::Spec[IO::Spec::Win32 ] { } #line 1 SETTING::src/core.c/io_operators.rakumod my class IO::ArgFiles { ... } augment class Rakudo::Internals { # Set up the skeletons of the IO::Handle objects that can be setup # at compile time. Then, when running the mainline of the setting # at startup, plug in the low level handles and set up the encoder # and decoders. This shaves off about 1.5% of bare startup. my constant NL-IN = ["\x0A", "\r\n"]; my constant NL-OUT = "\n"; my constant ENCODING = "utf8"; my sub setup-handle(str $what) { my $handle := nqp::p6bindattrinvres( nqp::create(IO::Handle),IO::Handle,'$!path',nqp::p6bindattrinvres( nqp::create(IO::Special),IO::Special,'$!what',$what ) ); nqp::getattr($handle,IO::Handle,'$!chomp') = True; nqp::getattr($handle,IO::Handle,'$!nl-in') = NL-IN; nqp::getattr($handle,IO::Handle,'$!nl-out') = NL-OUT; nqp::getattr($handle,IO::Handle,'$!encoding') = ENCODING; $handle } # Set up the skeletons at compile time my constant $skeletons = nqp::hash( 'IN', setup-handle(''), 'OUT', setup-handle(''), 'ERR', setup-handle('') ); method activate-std(str $name, Mu \PIO) { my \HANDLE = nqp::atkey($skeletons,$name); nqp::setbuffersizefh(PIO,8192) unless nqp::isttyfh(PIO); my $encoding = Encoding::Registry.find(ENCODING); nqp::bindattr( HANDLE,IO::Handle,'$!decoder',$encoding.decoder(:translate-nl) ).set-line-separators(NL-IN); nqp::bindattr( HANDLE,IO::Handle,'$!encoder',$encoding.encoder(:translate-nl) ); nqp::p6bindattrinvres(HANDLE,IO::Handle,'$!PIO',PIO) } } # Activate the standard handle skeletons at runtime PROCESS::<$IN> = Rakudo::Internals.activate-std('IN', nqp::getstdin); PROCESS::<$OUT> = Rakudo::Internals.activate-std('OUT', nqp::getstdout); PROCESS::<$ERR> = Rakudo::Internals.activate-std('ERR', nqp::getstderr); proto sub printf($, |) {*} multi sub printf(Str(Cool) $format, Junction:D \j) { my $out := $*OUT; j.THREAD: { $out.print: sprintf $format, |$_ } } multi sub printf(Str(Cool) $format, |) { my $args := nqp::p6argvmarray; nqp::shift($args); $*OUT.print: sprintf $format, nqp::hllize($args) } proto sub print(|) {*} multi sub print(--> True) { } # nothing to do multi sub print(Junction:D \j) { my $out := $*OUT; j.THREAD: { $out.print: .Str } } multi sub print(Str:D \x) { $*OUT.print(x) } multi sub print(\x) { $*OUT.print(x.Str) } multi sub print(|) { $*OUT.print: nqp::join("",Rakudo::Internals.StrList2list_s(nqp::p6argvmarray)) } # To ensure that classes that mimic the $*OUT / $*ERR API (which are only # required to provide a ".print" method), all logic is done in the subs # here, and then passed on to the .print method. proto sub say(|) {*} multi sub say() { $_ := $*OUT; .print: .nl-out } multi sub say(\x) { $_ := $*OUT; .print: nqp::concat(x.gist,.nl-out) } multi sub say(|) { my $parts := Rakudo::Internals.GistList2list_s(nqp::p6argvmarray); $_ := $*OUT; nqp::push_s($parts,.nl-out); .print: nqp::join("",$parts) } proto sub put(|) {*} multi sub put() { $_ := $*OUT; .print: .nl-out } multi sub put(Junction:D \j) { my $out := $*OUT; j.THREAD: { nqp::istype($_, Junction) ?? put($_) !! $out.print: nqp::concat(.Str,$out.nl-out) } } multi sub put(\x) { $_ := $*OUT; .print: nqp::concat(x.Str,.nl-out) } multi sub put(|) { my $parts := Rakudo::Internals.StrList2list_s(nqp::p6argvmarray); $_ := $*OUT; nqp::push_s($parts,.nl-out); .print: nqp::join("",$parts) } proto sub note(|) {*} multi sub note() { $_ := $*ERR; .print: nqp::concat("Noted",.nl-out) } multi sub note(\x) { $_ := $*ERR; .print: nqp::concat(x.gist,.nl-out) } multi sub note(|) { my $parts := Rakudo::Internals.GistList2list_s(nqp::p6argvmarray); $_ := $*ERR; nqp::push_s($parts,.nl-out); .print: nqp::join("",$parts) } proto sub gist(|) {*} multi sub gist(|) { my \args := nqp::p6argvmarray(); nqp::elems(args) == 1 ?? nqp::atpos(args, 0).gist !! nqp::p6bindattrinvres(nqp::create(List), List, '$!reified', args).gist } proto sub prompt($?, *%) {*} multi sub prompt() { nqp::defined(my \res := $*IN.get) ?? val(res) !! res; } multi sub prompt($msg) { my $out := $*OUT; $out.print($msg); $out.flush(); nqp::defined(my \res := $*IN.get) ?? val(res) !! res; } proto sub dir(|) {*} multi sub dir(IO() $path, Mu :$test!) { $path.dir(:$test) } multi sub dir(IO() $path ) { $path.dir } multi sub dir(Mu :$test!) { IO::Path.new($*SPEC.curdir).dir(:$test) } multi sub dir( ) { IO::Path.new($*SPEC.curdir).dir } proto sub open($, |) {*} multi sub open(IO() $path, |c) { IO::Handle.new(:$path).open(|c) } proto sub lines($?, $?, *%) {*} multi sub lines(*%_) { nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? $*ARGFILES.lines(|%_) !! $*ARGFILES.lines } multi sub lines($what, *%_) { nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? $what.lines(|%_) !! $what.lines } multi sub lines($what, $number, *%_) { nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? $what.lines($number, |%_) !! $what.lines($number) } proto sub words($?, $?, *%) {*} multi sub words(*%_) { nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? $*ARGFILES.words(|%_) !! $*ARGFILES.words } multi sub words($what, *%_) { nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? $what.words(|%_) !! $what.words } multi sub words($what, $number, *%_) { nqp::elems(nqp::getattr(%_,Map,'$!storage')) ?? $what.words($number, |%_) !! $what.words($number) } proto sub get ($?, *%) {*} multi sub get (IO::Handle:D $fh = $*ARGFILES) { $fh.get } proto sub getc ($?, *%) {*} multi sub getc (IO::Handle:D $fh = $*ARGFILES) { $fh.getc } proto sub close($, *%) {*} multi sub close(IO::Handle:D $fh) { $fh.close } multi sub close(Channel:D $channel) { $channel.close } proto sub slurp(|) {*} multi sub slurp(*%_) { $*ARGFILES.slurp(|%_) } multi sub slurp(IO::Handle:D $fh, *%_) { $fh.slurp(|%_) } multi sub slurp(IO() $path, :$bin!) { $path.slurp(:$bin) } multi sub slurp(IO() $path, :$enc ) { $path.slurp(:$enc) } multi sub slurp(IO() $path ) { $path.slurp(:enc) } proto sub spurt($, |) {*} # Don't do anything special for the IO::Handle, as using spurt() as a sub # when you've gone through the trouble of creating an IO::Handle, is not # so likely, as you would probably just call the .spurt method on the handle. multi sub spurt(IO::Handle:D $fh, $data, *%_) is default { $fh.spurt($data, |%_) } multi sub spurt(IO() $path) { $path.spurt } multi sub spurt(IO() $path, Blob:D \data, :$append!) { $path.spurt(data, :$append) } multi sub spurt(IO() $path, Blob:D \data, :$createonly!) { $path.spurt(data, :$createonly) } multi sub spurt(IO() $path, Blob:D \data) { $path.spurt(data) } multi sub spurt(IO() $path, \text, :$append!, :$enc) { $path.spurt(text, :$append, :$enc) } multi sub spurt(IO() $path, \text, :$createonly!, :$enc) { $path.spurt(text, :$createonly, :$enc) } multi sub spurt(IO() $path, \text, :$enc!) { $path.spurt(text, :$enc) } multi sub spurt(IO() $path, \text ) { $path.spurt(text, :enc) } { sub chdir(IO() $path) { CATCH { default { return X::IO::Chdir.new(:$path, :os-error(.Str)).Failure } } nqp::chdir(nqp::unbox_s($path.absolute)); $*CWD = IO::Path.new(nqp::cwd()); } PROCESS::<&chdir> := &chdir; } proto sub chdir(|) {*} multi sub chdir(|c) { nqp::istype(($_ := $*CWD.chdir(|c)),Failure) ?? $_ !! ($*CWD = $_) } proto sub indir($, $, *%) {*} multi sub indir(IO() $path, &what, :$d = True, :$r, :$w, :$x) { { # NOTE: we need this extra block so that the IO() coercer doesn't # use our (empty at the time) $*CWD when making the IO::Path object nqp::stmts( $d && nqp::isfalse($path.d) && X::IO::Chdir.new( :$path, :os-error( $path.e ?? 'is not a directory' !! 'does not exist')).fail, $r && nqp::isfalse($path.r) && X::IO::Chdir.new( :$path, :os-error("did not pass :r test")).fail, $w && nqp::isfalse($path.w) && X::IO::Chdir.new( :$path, :os-error("did not pass :w test")).fail, $x && nqp::isfalse($path.x) && X::IO::Chdir.new( :$path, :os-error("did not pass :x test")).fail, # $*CWD gets stringified with .Str in IO::Path.new, so we need to # ensure it's set to an absolute path my $*CWD = $path.WHAT.new: $path.absolute, :SPEC($path.SPEC), :CWD($path.SPEC.rootdir)) && what } } proto sub chmod($, |) {*} multi sub chmod($mode, *@filenames) { my @ok; for @filenames -> $file { @ok.push($file) if $file.IO.chmod($mode) } @ok; } proto sub chown($, |) {*} multi sub chown(*@filenames, :$uid, :$gid) { @filenames.grep: *.IO.chown(:$uid, :$gid) } proto sub unlink(|) {*} multi sub unlink() { "unlink()".no-zero-arg } multi sub unlink(*@filenames) { my @ok; for @filenames -> $file { @ok.push($file) if $file.IO.unlink } @ok; } proto sub rmdir(|) {*} multi sub rmdir() { "rmdir()".no-zero-arg } multi sub rmdir(*@filenames) { my @ok; for @filenames -> $file { @ok.push($file) if $file.IO.rmdir } @ok; } proto sub mkdir($, $?, *%) {*} multi sub mkdir(IO() $path, Int() $mode = 0o777) { $path.mkdir($mode) } proto sub rename($, $, *%) {*} multi sub rename(IO() $from, IO() $to, :$createonly) { $from.rename($to, :$createonly) } proto sub copy($, $, *%) {*} multi sub copy(IO() $from, IO() $to, :$createonly) { $from.copy($to, :$createonly) } proto sub move($, $, *%) {*} multi sub move(IO() $from, IO() $to, :$createonly) { $from.move($to, :$createonly) } proto sub symlink($, $, *%) {*} multi sub symlink(IO() $target, IO() $name, Bool :$absolute = True) { $target.symlink($name, :$absolute) } proto sub link($, $, *%) {*} multi sub link(IO() $target, IO() $name) { $target.link($name) } #line 1 SETTING::src/core.c/IO/CatHandle.rakumod my class IO::CatHandle is IO::Handle { has $!handles; has $!active-handle is default(Nil); has $.chomp is rw; has $.nl-in; has Str $.encoding; has &.on-switch is rw; multi method raku(::?CLASS:D:) { my @handles = ($!active-handle if $!active-handle), |nqp::p6bindattrinvres(nqp::create(List),List,'$!reified',$!handles); my $parts = join ', ', (@handles.List.raku if @handles), (':!chomp' if not $!chomp), (":nl-in({$!nl-in.list.raku})" if $!nl-in !eqv ["\x0A", "\r\n"]), (nqp::isconcrete($!encoding) ?? ":encoding({$!encoding.raku})" !! ':bin'), (':&.on-switch({;})' if &!on-switch); # can't .raku Callables :( "{self.^name}.new($parts)" } method !SET-SELF ( @handles, &!on-switch, $!chomp, $!nl-in, $encoding, $bin ) { $bin ?? nqp::isconcrete($encoding) && X::IO::BinaryAndEncoding.new.throw !! ($!encoding = $encoding || 'utf8'); @handles.elems; # reify $!handles := nqp::getattr(@handles || [], List, '$!reified'); self.next-handle; self } method new ( *@handles, :&on-switch, :$chomp = True, :$nl-in = ["\x0A", "\r\n"], Str :$encoding, Bool :$bin ) { self.bless!SET-SELF: @handles, &on-switch, $chomp, $nl-in, $encoding, $bin } method next-handle { # Set $!active-handle to the next handle in line, opening it if necessary nqp::stmts( (my $old-handle is default(Nil) = $!active-handle), nqp::if( nqp::defined($!active-handle), (my $ = $!active-handle.close)), # don't sink the result, since it might # .. be an IO::Pipe that returns a Proc that might throw nqp::if( nqp::elems($!handles), nqp::if( nqp::istype(($_ := nqp::shift($!handles)), IO::Handle), nqp::if( .opened, nqp::stmts( (.encoding: $!encoding), # *Jedi wave* (.nl-in = $!nl-in), # These aren't the attribute assignment (.chomp = $!chomp), # inconsistencies you're looking for! $!active-handle = $_), nqp::if( nqp::istype( ($_ = .open: :r, :$!chomp, :$!nl-in, :enc($!encoding), :bin(nqp::hllbool(nqp::isfalse($!encoding)))), Failure), .throw, ($!active-handle = $_))), nqp::if( nqp::istype( ($_ := .IO.open: :r, :$!chomp, :$!nl-in, :enc($!encoding), :bin(nqp::hllbool(nqp::isfalse($!encoding)))), Failure), .throw, ($!active-handle = $_))), ($!active-handle = Nil)), nqp::if( &!on-switch, nqp::stmts( (my $c := &!on-switch.count), nqp::if( $c, nqp::if( nqp::istype($c, Num) || nqp::iseq_i($c, 2), # inf or 2 &!on-switch($!active-handle, $old-handle), nqp::if( nqp::iseq_i($c, 1), &!on-switch($!active-handle), die ':&on-switch must have .count 0, 1, 2, or Inf')), &!on-switch()))), $!active-handle) } my class Handles does Iterator { has $!cat is built(:bind); has $!gave-active; method pull-one { nqp::if( $!gave-active, nqp::if( nqp::defined(my $h := $!cat.next-handle), $h, IterationEnd), nqp::stmts( ($!gave-active := True), nqp::defined(my $ah := nqp::decont( nqp::getattr($!cat, IO::CatHandle, '$!active-handle'))) ?? $ah !! IterationEnd)) } } method handles(IO::Handle:D: --> Seq:D) { Seq.new(Handles.new(cat => self)) } method chomp (::?CLASS:D:) is rw { Proxy.new: :FETCH{ $!chomp }, :STORE( -> $, $chomp { $!active-handle && $!active-handle.chomp = $chomp; $!chomp = $chomp }) } # XXX TODO: Make these routine read handle lazily when we have Cat type method comb (::?CLASS:D: |c) { self.slurp.comb: |c } method split(::?CLASS:D: |c) { self.slurp.split: |c } method !WORDS { nqp::if( nqp::defined($!active-handle), (flat $!active-handle.words, gather { nqp::while( nqp::defined(self.next-handle), take $!active-handle.words)}), Seq.new(Rakudo::Iterator.Empty) ) } multi method words(::?CLASS:D \SELF: $limit, :$close) { nqp::istype($limit,Whatever) || $limit == Inf ?? self.words(:$close) !! $close ?? Seq.new(Rakudo::Iterator.FirstNThenSinkAll( self!WORDS.iterator, $limit.Int, {SELF.close})) !! self.words.head($limit.Int) } multi method words(::?CLASS:D \SELF: :$close!) { $close # use -1 as N in FirstNThenSinkAllSeq to get all items ?? Seq.new(Rakudo::Iterator.FirstNThenSinkAll( self!WORDS.iterator, -1, {SELF.close})) !! self!WORDS } multi method words(::?CLASS:D:) { self!WORDS } method !LINES { nqp::if( nqp::defined($!active-handle), (flat $!active-handle.lines, gather { nqp::while( nqp::defined(self.next-handle), take $!active-handle.lines)}), Seq.new(Rakudo::Iterator.Empty) ) } multi method lines(::?CLASS:D \SELF: $limit, :$close) { nqp::istype($limit,Whatever) || $limit == Inf ?? self.lines(:$close) !! $close ?? Seq.new(Rakudo::Iterator.FirstNThenSinkAll( self!LINES.iterator, $limit.Int, {SELF.close})) !! self.lines.head($limit.Int) } multi method lines(::?CLASS:D \SELF: :$close!) { $close # use -1 as N in FirstNThenSinkAllSeq to get all items ?? Seq.new(Rakudo::Iterator.FirstNThenSinkAll( self!LINES.iterator, -1, {SELF.close})) !! self!LINES } multi method lines(::?CLASS:D:) { self!LINES } multi method Supply (::?CLASS:D: :$size = $*DEFAULT-READ-ELEMS --> Supply:D) { nqp::if( nqp::isconcrete($!encoding), (supply nqp::stmts( (my str $str = self.readchars: $size), nqp::while( nqp::chars($str), nqp::stmts( (emit nqp::p6box_s($str)), ($str = self.readchars: $size))), done)), (supply nqp::stmts( (my $buf := self.read: $size), nqp::while( nqp::elems($buf), nqp::stmts( (emit $buf), ($buf := self.read: $size))), done))) } # Get a single result, going to the next handle on EOF method get (::?CLASS:D:) { nqp::if( nqp::defined($!active-handle), nqp::stmts( nqp::while( nqp::eqaddr(Nil, my $res := $!active-handle.get) && nqp::defined(self.next-handle), nqp::null), $res), Nil) } method getc (::?CLASS:D:) { nqp::if( nqp::defined($!active-handle), nqp::stmts( nqp::while( nqp::eqaddr(Nil, my $res := $!active-handle.getc) && nqp::defined(self.next-handle), nqp::null), $res), Nil) } method read (::?CLASS:D: Int(Cool:D) $bytes = $*DEFAULT-READ-ELEMS) { # The logic is: # read some stuff # do we have enough stuff? # -> [yes] -> return stuff # -> [no] # is current handle EOF or did we read zero stuff on last chunk? # -> [yes] -> switch handle -> repeat from start # -> [no] return stuff # The extra gymnastics are due to: # (a) possibility of TTY handles returning # fewer than requested number of bytes without being entirely # exhausted. This means when we read fewer than $bytes bytes, we # don't yet know whether we should switch the handle and thus, # if we read at least some bytes in a chunk and don't have EOF, # we gotta return whatever we managed to read # (b) XXX TODO: (this actually seems to be a bug) # possibility of .seek being used on current handle. In such a # case we can read a zero-sized chunk and EOF would still be false nqp::unless( nqp::defined($!active-handle), nqp::create(buf8.^pun), nqp::stmts( (my $ret := nqp::create(buf8.^pun)), (my int $stop = 0), nqp::until( $stop, nqp::stmts( (my $chunk := buf8.new: $!active-handle.read: nqp::sub_i($bytes,nqp::elems($ret))), $ret.append($chunk), nqp::if( nqp::isge_i(nqp::elems($ret),$bytes), ($stop = 1), nqp::if( $!active-handle.eof || nqp::isfalse(nqp::elems($chunk)), nqp::unless( nqp::defined(self.next-handle), $stop = 1), $stop = 1)))), $ret)) } method readchars (::?CLASS:D: Int(Cool:D) $chars = $*DEFAULT-READ-ELEMS) { nqp::if( nqp::defined($!active-handle), nqp::stmts( (my $ret := $!active-handle.readchars: $chars), nqp::while( nqp::islt_i(nqp::chars($ret), $chars) && nqp::defined(self.next-handle), $ret := nqp::concat($ret, $!active-handle.readchars: nqp::sub_i($chars, nqp::chars($ret)))), $ret ), '') } method slurp (::?CLASS:D: :$bin) { # we don't take a :close arg, because we close exhausted handles # and .slurp isn't lazy, so all handles will get exhausted nqp::if( nqp::defined($!active-handle), ([~] gather nqp::stmts( # the [~] takes care of both Str and Blobs (take $!active-handle.slurp(:$bin, :close)), nqp::while( nqp::defined(self.next-handle), take $!active-handle.slurp(:$bin, :close)))), Nil) } method slurp-rest (|) { # We inherit deprecated .slurp-rest from IO::Handle. Pull the # plug on it in this class, since no one is using this yet. # The old IO::ArgFiles used .slurp X::Obsolete.new( :old, :replacement, :when('with IO::CatHandle')).throw } method DESTROY { self.close } method close (::?CLASS:D: --> True) { # Note: our IO::Handles might be IO::Pipes, whose .close # method returns the Proc object, which will explode when sunk if the # process exited unsuccessfully. So here, we ensure we never sink it. nqp::stmts( nqp::if( nqp::defined($!active-handle), my $ = $!active-handle.close), (my int $i = -1), (my int $els = nqp::elems($!handles)), nqp::while( nqp::isgt_i($els,++$i), nqp::if( nqp::istype(($_ := nqp::atpos($!handles, $i)), IO::Handle), my $ = .close)), ($!handles := nqp::list), ($!active-handle = Nil)) } proto method encoding(|) {*} multi method encoding(::?CLASS:D:) { $!encoding || Nil } multi method encoding(::?CLASS:D: $enc) { $!encoding = nqp::if( nqp::defined($!active-handle), $!active-handle.encoding($enc), nqp::if( nqp::isfalse($enc.defined) || nqp::iseq_s($enc.Str, 'bin'), Nil, Encoding::Registry.find($enc.Str).name)) } method eof (::?CLASS:D: --> Bool:D) { nqp::hllbool( nqp::stmts( nqp::while( $!active-handle && $!active-handle.eof && self.next-handle, nqp::null), nqp::isfalse($!active-handle) || False)) } multi method gist (::?CLASS:D:) { "{self.^name}({self.opened ?? "opened on {$.path.gist}" !! 'closed'})" } multi method Str (::?CLASS:D:) { $!active-handle ?? $.path.Str !! '' } method IO (::?CLASS:D:) { $!active-handle ?? $!active-handle.IO !! Nil } method path (::?CLASS:D:) { $!active-handle ?? $!active-handle.path !! Nil } method opened(::?CLASS:D: --> Bool:D) { nqp::hllbool(nqp::istrue($!active-handle)) } method lock(::?CLASS:D: |c) { $!active-handle ?? $!active-handle.lock(|c) !! Nil } method nl-in (::?CLASS:D:) is rw { Proxy.new: :FETCH{ $!nl-in }, :STORE( -> $, $nl-in { $!active-handle && $!active-handle.nl-in = $nl-in; $!nl-in = $nl-in }) } method seek(::?CLASS:D: |c) { $!active-handle ?? $!active-handle.seek(|c) !! Nil } method tell(::?CLASS:D: --> Int:D) { $!active-handle ?? $!active-handle.tell !! Nil } method t (::?CLASS:D: --> Bool:D) { $!active-handle ?? $!active-handle.t !! False } method unlock(::?CLASS:D:) { $!active-handle ?? $!active-handle.unlock !! Nil } method native-descriptor (::?CLASS:D: --> Int:D) { $!active-handle ?? $!active-handle.native-descriptor !! Nil } method open (::?CLASS:D: --> ::?CLASS:D) { # The idea behind cat handle's open is to fake .open in code that # doesn't know it's dealing with a cat handle, so we accept any args # IO::Handle.open accepts and then just return self. Since that .open # takes only named args methods have `*%_` in sigs, we don't put any # args in our sig. If that ever changes, then ensure cat handle's .open # can be called with any of the IO::Handle.open's args self } # __________________________________________ # / I don't know what the write methods \ # | should do in a CatHandle, so I'll mark | # | these as NYI, for now.... Has anyone | # \ seen my cocoon? I always lose that thing! / # | ----------------------------------------- # | / # |/ # (⛣) proto method flush (|) {*} multi method flush (|) { NYI('flush').throw } proto method out-buffer (|) {*} multi method out-buffer (|) { NYI('out-buffer').throw } multi method print (|) { NYI('print').throw } proto method printf (|) {*} multi method printf (|) { NYI('printf').throw } proto method print-nl (|) {*} multi method print-nl (|) { NYI('print-nl').throw } multi method put (|) { NYI('put').throw } multi method say (|) { NYI('say').throw } proto method write (|) {*} multi method write (|) { NYI('write').throw } proto method WRITE (|) {*} multi method WRITE (|) { NYI('WRITE').throw } proto method READ (|) {*} multi method READ (|) { NYI('READ').throw } proto method EOF (|) {*} multi method EOF (|) { NYI('EOF').throw } # /|\ # Don't die on this one, as doing so breaks .Capture # proto method nl-out (|) {*} # multi method nl-out (|) { # die X::NYI.new: :feature # } } #line 1 SETTING::src/core.c/IO/ArgFiles.rakumod my class IO::ArgFiles is IO::CatHandle { # This class exists for backwards compatibility reasons. # There used to be no IO::CatHandle and IO::ArgFiles did the $*ARGFILES. # Now all the functionality has been subsumed by IO::CatHandle and # we keep $*ARGFILES as IO::ArgFiles that is just an empty subclass # of IO::CatHandle type } #line 1 SETTING::src/core.c/AST.rakumod # XXX: Would like to have this class as Perl6::AST, but ran up against # problems with the serialization context calling it that. my class AST { has $!past; has $!quasi_context; has $!Str; submethod BUILD(:$past --> Nil) { $!past := $past } method incarnate($quasi_context, @unquote_asts) { my $incarnation = self.clone(); nqp::bindattr(nqp::decont($incarnation), AST, '$!past', $incarnation.evaluate_unquotes(@unquote_asts)); nqp::bindattr(nqp::decont($incarnation), AST, '$!quasi_context', $quasi_context); $incarnation; } method evaluate_unquotes(@unquote_asts) { my $pasts := nqp::list(); for @unquote_asts { # TODO: find and report macro name X::TypeCheck::Splice.new( got => $_, expected => AST, action => 'unquote evaluation', ).throw unless nqp::istype($_,AST); nqp::push($pasts, nqp::getattr(nqp::decont($_), AST, '$!past')) } $!past.evaluate_unquotes($pasts); } method is_quasi_ast { so $!quasi_context; } method Str { $!Str; } } #line 1 SETTING::src/core.c/CallFrame.rakumod my class CallFrame { has $.annotations; has $.my; # cannot be a private method due to sub callframe method SET-SELF( \level, Mu \ctx is raw, Mu \bt is raw ) is implementation-detail { nqp::stmts( (my int $i = nqp::add_i(level,1)), (my $bt := nqp::atpos(nqp::getattr(bt,List,'$!reified'),$i)), ($!annotations := nqp::isnull($bt) ?? $bt !! nqp::atkey($bt,'annotations')), (my $ctx := ctx), nqp::while( nqp::isgt_i(--$i,0), nqp::ifnull( ($ctx := nqp::ctxcallerskipthunks($ctx)), fail "No callframe at level {level}" ) ), ($!my := nqp::p6bindattrinvres(Stash.new,Map,'$!storage',$ctx)), self ) } only method new(CallFrame: Int:D $level = 0) { # MUST BE AN only nqp::create(CallFrame).SET-SELF( # wrt to backtrace levels $level, nqp::ctxcallerskipthunks(nqp::ctx), nqp::backtrace(nqp::handle(nqp::die(''),'CATCH',nqp::exception)) ) } method line() { nqp::atkey($!annotations,'line') } method file() { nqp::atkey($!annotations,'file') } method code() { my \vm-code = nqp::ctxcode(nqp::getattr($!my,Map,'$!storage')); nqp::isnull(vm-code) ?? Nil !! nqp::getcodeobj(vm-code) } method callframe(Int:D $?) { NYI('Callframe.callframe').throw; } multi method gist(CallFrame:D:) { nqp::atkey($!annotations,'file') ~ ' at line ' ~ nqp::atkey($!annotations,'line') } method annotations() { nqp::p6bindattrinvres(nqp::create(Map),Map,'$!storage',$!annotations) } } only sub callframe(Int:D $level = 0) { # MUST BE an only wrt to backtrace levels nqp::create(CallFrame).SET-SELF( $level, nqp::ctxcallerskipthunks(nqp::ctx), nqp::backtrace(nqp::handle(nqp::die(''),'CATCH',nqp::exception)) ) } #line 1 SETTING::src/core.c/Main.rakumod # TODO: # * Command-line parsing # * Comma-separated list values # * Allow exact Raku forms, quoted away from shell # * Fix remaining XXXX my sub RUN-MAIN(&main, $mainline, :$in-as-argsfiles) { # Set up basic info my %caller-my := callframe(1).my; my $provided-a-to-c := %caller-my<&ARGS-TO-CAPTURE>; my $provided-g-u := %caller-my<&GENERATE-USAGE>; my &args-to-capture := $provided-a-to-c // &default-args-to-capture; my &generate-usage := $provided-g-u // &default-generate-usage; my %sub-main-opts := %*SUB-MAIN-OPTS // {}; # Set up proxy for default generated usage my $usage-produced; my $*USAGE := Proxy.new( FETCH => -> | { $usage-produced //= default-generate-usage(&main) }, STORE => -> | { die 'Cannot assign to $*USAGE. Please create a ' ~ '`sub GENERATE-USAGE {}` to generate custom usage message' } ); # Module loaded that depends on the old MAIN_HELPER interface and # does not provide the new interface? if !$provided-a-to-c && %caller-my<&MAIN_HELPER> -> &main_helper { # DEPRECATED message here # Make MAIN available at callframe(1) when executing main_helper # but return if there is nothing to call (old semantics) return $mainline unless my &MAIN := %caller-my<&MAIN>; # Call the MAIN_HELPER, it should do everything return &main_helper.count == 2 ?? main_helper($in-as-argsfiles,$mainline) # post 2018.06 interface !! main_helper($mainline) # original interface } # Convert raw command line args into positional and named args for MAIN sub default-args-to-capture(&main, @args is copy --> Capture:D) { my $no-named-after = nqp::isfalse(%sub-main-opts); my $bundling = nqp::istrue(%sub-main-opts); my $positional := nqp::create(IterationBuffer); my %named; my &coercer = &val; if %sub-main-opts:exists { my $type := %sub-main-opts<>; if $type =:= Numeric || $type =:= Int || $type =:= Rat || $type =:= Num || $type =:= Complex || $type =:= Str { my $method := $type.^name; &coercer = -> \value { (my \result := val(value)) ~~ Allomorph ?? result."$method"() !! result } } else { die "Unsupported allomorph coercion: { $type.raku }"; } } sub thevalue(\a) { ((my \type := ::(a)) andthen Metamodel::EnumHOW.ACCEPTS(type.HOW)) ?? type !! coercer(a) } my %options-with-req-arg = Hash.new; for &main.candidates { for .signature.params -> $param { if !$param.named { next } for $param.named_names -> str $name { my int $accepts-true = $param.type.ACCEPTS: True; for $param.constraint_list { $accepts-true++ if try .ACCEPTS: True} if !$accepts-true { %options-with-req-arg.push($name => True) } } } } while @args { my str $passed-value = @args.shift; if nqp::iseq_s($passed-value,'--') { # -- marks rest as positional nqp::push($positional, thevalue($_)) for @args; last; } if ($no-named-after && nqp::isgt_i(nqp::elems($positional),0)) { nqp::push($positional, thevalue($passed-value)); nqp::push($positional, thevalue($_)) for @args; last; } my str $optstring; my int $negated = 0; my int $short-opt = 0; # long option if nqp::eqat($passed-value, '--', 0) { if nqp::eqat($passed-value, '/', 2) { $optstring = nqp::substr($passed-value, 3); $negated = 1; } else { $optstring = nqp::substr($passed-value, 2) } } # short option elsif $passed-value ne '-' && (nqp::eqat($passed-value, '-', 0) || nqp::eqat($passed-value, ':', 0) ) { $short-opt = 1; if nqp::eqat($passed-value, '/', 1) { $optstring = nqp::substr($passed-value, 2); $negated = 1; } else { $optstring = nqp::substr($passed-value, 1) } } # positional else { nqp::push($positional, thevalue($passed-value)); next; } my $split := nqp::split("=",$optstring); $optstring = nqp::shift($split); my str $arg = nqp::join('=', $split); if $bundling && $short-opt && nqp::isgt_i(nqp::chars($optstring), 1) { die "Can't combine bundling with explicit negation" if $negated; die "Can't combine bundling with explicit arguments" if nqp::elems($split); my int $cursor = 1; my str $short-opt = nqp::substr($optstring, 0, 1); while $short-opt { %named.push: $short-opt => True; $short-opt = nqp::substr($optstring, $cursor++, 1); } } else { if nqp::existskey(%options-with-req-arg, $optstring) { if !$arg { $arg = @args.shift // '' } %named.push: $optstring => ($negated ?? thevalue($arg) but False !! thevalue($arg)); } elsif !nqp::elems($split) { %named.push: $optstring => ($negated ?? False !! True) } else { %named.push: $optstring => $negated ?? thevalue $arg but False !! thevalue $arg } } } Capture.new( list => $positional.List, hash => %named ) } # Generate $*USAGE string (default usage info for MAIN) sub default-generate-usage(&, |capture) { my $no-named-after = nqp::isfalse(%sub-main-opts); my @help-msgs; my Pair @arg-help; my sub strip_path_prefix($name) { my $SPEC := $*SPEC; my ($vol, $dir, $base) = $SPEC.splitpath($name); $dir = $SPEC.canonpath($dir); for $SPEC.path() -> $elem { my $file = $SPEC.catpath($vol, $elem, $base).IO; if $file.x && $file.f { return $base if $SPEC.canonpath($elem) eq $dir; # Shadowed command found in earlier PATH element return $name; } } # Not in PATH $name; } my $prog-name = %*ENV || $*PROGRAM-NAME; $prog-name = $prog-name eq '-e' ?? "-e '...'" !! strip_path_prefix($prog-name); # return the Cool constant if the post_constraints of a Parameter is # a single Cool constant, else Nil sub cool_constant(Parameter:D $p) { nqp::not_i( nqp::isnull( (my \post_constraints := nqp::getattr($p,Parameter,'@!post_constraints')) ) ) && nqp::elems(post_constraints) == 1 && nqp::istype((my \value := nqp::atpos(post_constraints,0)),Cool) ?? value !! Nil } # Select candidates for which to create USAGE string sub usage-candidates($capture) { my @candidates = &main.candidates.grep: { !.?is-hidden-from-USAGE } if $capture.list -> @positionals { my $first := @positionals[0]; if @candidates.grep: -> $sub { if $sub.signature.params[0] -> $param { if cool_constant($param) -> $literal { $literal.ACCEPTS($first) } } } -> @candos { return @candos; } } @candidates } for usage-candidates(capture) -> $sub { my @required-named; my @optional-named; my @positional; my $docs; for $sub.signature.params -> $param { my $argument; my int $literals-as-constraint = 0; my int $total-constraints = 0; my $constraints = ~unique $param.constraint_list.map: { ++$total-constraints; nqp::if( nqp::istype($_, Callable), 'where { ... }', nqp::stmts( (my \g = .gist), nqp::if( nqp::isconcrete($_), nqp::stmts( ++$literals-as-constraint, g), # we constrained by some literal; gist as is nqp::substr(g, 1, nqp::chars(g)-2)))) # ^ remove ( ) parens around name in the gist } $_ eq 'where { ... }' and $_ = "$param.type.^name() $_" with $constraints; if $param.named { if $param.slurpy { if $param.name { # ignore anon *% $argument = "--<$param.usage-name()>=..."; @optional-named.push("[$argument]"); } } else { my @names = $param.named_names.reverse; $argument = @names.map({ (.chars == 1 ?? '-' !! '--') ~ $_ }).join('|'); my $type := $param.type; if $type ~~ Positional { $argument ~= "=<{ $constraints || "Any" }> ..." } elsif $type !=== Bool { my int $accepts-true = $param.type.ACCEPTS: True; for $param.constraint_list { $accepts-true++ if try .ACCEPTS: True} $argument ~= ($accepts-true ?? "[={$constraints || $type.^name}]" !! "=<{$constraints || $type.^name}>"); if Metamodel::EnumHOW.ACCEPTS($type.HOW) { my $options = $type.^enum_values.keys.sort.Str; $argument ~= $options.chars > 50 ?? ' (' ~ substr($options,0,50) ~ '...' !! " ($options)" } } if $param.optional { @optional-named.push("[$argument]"); } else { @required-named.push($argument); } } } else { $argument = $param.name ?? "<$param.usage-name()>" !! $constraints ?? ($literals-as-constraint == $total-constraints) ?? $constraints !! "<{$constraints}>" !! "<$param.type.^name()>"; $argument = "[$argument ...]" if $param.slurpy; $argument = "[$argument]" if $param.optional; if $total-constraints && $literals-as-constraint == $total-constraints { $argument .= trans(["'"] => [q|'"'"'|]) # "hlfix if $argument.contains("'"); $argument = "'$argument'" if $argument.contains(' ' | '"'); } @positional.push($argument); } #@arg-help.push($argument => $param.WHY.contents) if $param.WHY and (@arg-help.grep:{ .key eq $argument}) == Empty; # Use first defined if $param.WHY and (@arg-help.grep:{ .key eq $argument}) == Empty { my $why = $param.WHY.contents; # Use first defined if $param.default -> $d { constant MAXCHARS = 20; # $middle is for long integers and other argument types # that are better split in the middle. if ( my $def = $d() ).defined { my ($middle, $q) = ($def ~~ Int, $def ~~ Str); $def .= Str; # Handle lengthy values if $def.chars > MAXCHARS { $def = do if $middle { my $half = MAXCHARS div 2; $def.substr(0, $half - 1) ~ '…' ~ $def.substr($def.chars - $half); } else { $def.substr(0, MAXCHARS - 1) ~ '…'; } } $def = "'{ $def }'" if $q; $why ~= " [default: { $def }]"; } } @arg-help.push($argument => $why); } } if $sub.WHY { $docs = '-- ' ~ $sub.WHY.contents } my $msg = $no-named-after ?? join(' ', $prog-name, @required-named, @optional-named, @positional, ($docs if $docs)) !! join(' ', $prog-name, @positional, @required-named, @optional-named, ($docs if $docs)); @help-msgs.push($msg); } if @arg-help { @help-msgs.push(''); my $offset = max(@arg-help.map: { .key.chars }) + 4; @help-msgs.append(@arg-help.map: { ' ' ~ .key ~ ' ' x ($offset - .key.chars) ~ .value }); } @help-msgs ?? "Usage:\n" ~ @help-msgs.map(' ' ~ *).join("\n") !! "No usage information could be determined" } sub has-unexpected-named-arguments($signature, %named-arguments) { return False if $signature.params.first: *.capture; my @named-params = $signature.params.grep: *.named; return False if @named-params.first: *.slurpy; my %accepts-argument is Set = @named-params.map( *.named_names.Slip ); return True unless %accepts-argument{$_} for %named-arguments.keys; False } sub find-candidates($capture) { nqp::can(&main,'cando') ?? &main # Get a list of candidates that match according to the dispatcher .cando($capture) # Sort out all that would fail due to binding .grep({ !has-unexpected-named-arguments(.signature, $capture.hash) }) !! die "MAIN must be a 'sub' to allow it to be called as a CLI handler" } # turn scalar values of nameds into 1 element arrays, return new capture sub scalars-into-arrays($capture) { my %hash = $capture.hash.map: { nqp::istype(.value,Positional) ?? $_ !! Pair.new(.key,[.value]) } Capture.new( :list($capture.list), :%hash) } # set up other new style dynamic variables my &*ARGS-TO-CAPTURE := &default-args-to-capture; my &*GENERATE-USAGE := &default-generate-usage; # Modify args if --no-foo is acceptable as an alternative to --/foo if nqp::istrue(%sub-main-opts) { $_ .= subst(/^ '--no-' /, '--/') for @*ARGS; } # Modify args if -j42 is acceptable as an alternative to --j=42 if nqp::istrue(%sub-main-opts) { for @*ARGS { $_ = "-$_.substr(0,2)=$/" if .match: /^ '-' <.alpha> <( \d+ $/; } } # Process command line arguments my $capture := args-to-capture(&main, @*ARGS); # Get a list of candidates that match according to the dispatcher my @candidates = find-candidates($capture); if !@candidates { my $alternate = scalars-into-arrays($capture); if find-candidates($alternate) -> @alternates { $capture := $alternate; @candidates = @alternates; } } # If there are still some candidates left, try to dispatch to MAIN if @candidates { if $in-as-argsfiles { my $*ARGFILES := IO::ArgFiles.new: (my $in := $*IN), :nl-in($in.nl-in), :chomp($in.chomp), :encoding($in.encoding), :bin(nqp::hllbool(nqp::isfalse($in.encoding))); main(|$capture).sink; } else { main(|$capture).sink; } } # We could not find the correct MAIN to dispatch to! # No new-style GENERATE-USAGE was provided, and no new style # ARGS-TO-CAPTURE was provided either, so try to run a user defined # USAGE sub of the old interface. elsif !$provided-g-u && !$provided-a-to-c && %caller-my<&USAGE> -> &usage { # DEPRECATED message here usage; } # Display the default USAGE message on either STDOUT/STDERR elsif $capture { $*OUT.say: generate-usage(&main,|$capture); exit 0; } else { $*ERR.say: generate-usage(&main,|$capture); exit 2; } } #line 1 SETTING::src/core.c/Instant.rakumod my class Date { ... } my class DateTime { ... } my class Duration {... } my class Instant is Cool does Real { has Int $.tai is default(0); # A linear count of nanoseconds since 1970-01-01T00:00:00Z, plus # Rakudo::Internals.initial-offset. Thus, $.tai matches TAI from 1970 # to the present. method new(*@) { X::Cannot::New.new(class => self).throw } method tai(--> Rat:D) { $!tai / 1000000000 } method from-posix-nanos(Instant:U: Int:D $nanos --> Instant:D) { nqp::p6bindattrinvres(nqp::create(Instant),Instant,'$!tai',$nanos) } method to-nanos(--> Int:D) { $!tai } proto method from-posix(|) {*} multi method from-posix($posix --> Instant:D) { nqp::p6bindattrinvres(nqp::create(Instant),Instant,'$!tai', (Rakudo::Internals.tai-from-posix($posix,0) * 1000000000).Int) } multi method from-posix($posix, Bool $prefer-leap-second --> Instant:D) { # $posix is in general not expected to be an integer. # If $prefer-leap-second is true, 915148800 is interpreted to # mean 1998-12-31T23:59:60Z rather than 1999-01-01T00:00:00Z. nqp::p6bindattrinvres(nqp::create(Instant),Instant,'$!tai', (Rakudo::Internals.tai-from-posix($posix,$prefer-leap-second) * 1000000000).Int) } method to-posix(--> List:D) { # The inverse of .from-posix, except that the second return # value is true if *and only if* this Instant is in a leap # second. Rakudo::Internals.posix-and-leap-from-tai($!tai / 1000000000) } multi method Str(Instant:D: --> Str:D) { 'Instant:' ~ self.tai } multi method raku(Instant:D: --> Str:D) { my ($posix,$flag) = self.to-posix; 'Instant.from-posix(' ~ $posix.raku ~ ($flag ?? ',True)' !! ')') } method Bridge(Instant: --> Num:D) { self.defined ?? self.tai.Bridge !! self.Real::Bridge } method Num (Instant:D: --> Num:D) { nqp::div_n(self.to-nanos.Num, 1000000000e0) } method Rat (Instant:D: --> Rat:D) { self.tai } method Int (Instant:D: --> Int:D) { self.to-nanos div 1000000000 } method narrow(Instant:D: ) { self.tai.narrow } method Date(Instant:D: --> Date:D) { Date.new(self) } method DateTime(Instant:D: --> DateTime:D) { DateTime.new(self) } method Instant() { self } # TODO: should be the new .gist, probably # method Str() { # 'Instant:' ~ default-formatter # ::DateTime.new(self), :subseconds # } } multi sub infix:«cmp»(Instant:D $a, Instant:D $b) { $a.to-nanos <=> $b.to-nanos } multi sub infix:«<=>»(Instant:D $a, Instant:D $b) { $a.to-nanos <=> $b.to-nanos } multi sub infix:«==»(Instant:D $a, Instant:D $b --> Bool:D) { $a.to-nanos == $b.to-nanos } multi sub infix:«!=»(Instant:D $a, Instant:D $b --> Bool:D) { $a.to-nanos != $b.to-nanos } multi sub infix:«<»(Instant:D $a, Instant:D $b --> Bool:D) { $a.to-nanos < $b.to-nanos } multi sub infix:«>»(Instant:D $a, Instant:D $b --> Bool:D) { $a.to-nanos > $b.to-nanos } multi sub infix:«<=»(Instant:D $a, Instant:D $b --> Bool:D) { $a.to-nanos <= $b.to-nanos } multi sub infix:«>=»(Instant:D $a, Instant:D $b --> Bool:D) { $a.to-nanos >= $b.to-nanos } multi sub infix:<+>(Instant:D $a, Instant:D $b) { die "Adding two Instant values has no meaning. Did you mean to subtract? Perhaps you need to convert to .Numeric first?" } multi sub infix:<+>(Instant:D $a, Real:D $b --> Instant:D) { Instant.from-posix-nanos($a.to-nanos + ($b * 1000000000).Int) } multi sub infix:<+>(Real:D $a, Instant:D $b --> Instant:D) { Instant.from-posix-nanos(($a * 1000000000).Int + $b.to-nanos) } multi sub infix:<+>(Instant:D $a, Duration:D $b --> Instant:D) { Instant.from-posix-nanos($a.to-nanos + $b.to-nanos) } multi sub infix:<+>(Duration:D $a, Instant:D $b --> Instant:D) { Instant.from-posix-nanos($a.to-nanos + $b.to-nanos) } multi sub infix:<->(Instant:D $a, Instant:D $b --> Duration:D) { Duration.from-posix-nanos($a.to-nanos - $b.to-nanos); } multi sub infix:<->(Instant:D $a, Real:D $b --> Instant:D) { Instant.from-posix-nanos($a.to-nanos - ($b * 1000000000).Int) } sub term:, :reason(self.^name ~ ' cannot be nested and so does not ' ~ 'support multi-level categorization'), ).throw; } # simple categorize else { loop { ++self{$_} for @$tested; last if nqp::eqaddr(($value := iter.pull-one),IterationEnd); nqp::istype(($tested := test($value))[0], Iterable) and X::Invalid::ComputedValue.new( :name, :method, :value('an item with different number of elements ' ~ 'in it than previous items'), :reason('all values need to have the same number ' ~ 'of elements. Mixed-level classification is ' ~ 'not supported.'), ).throw; }; } } self; } multi method categorize-list( %test, |c ) { self.categorize-list( { %test{$^a} }, |c ); } multi method categorize-list( @test, |c ) { self.categorize-list( { @test[$^a] }, |c ); } multi method categorize-list( &test, **@list, |c ) { self.categorize-list( &test, @list, |c ); } #--- coercion methods sub SETIFY(\raw, \type) { nqp::if( raw && nqp::elems(raw), nqp::stmts( (my \elems := nqp::clone(raw)), (my \iter := nqp::iterator(elems)), nqp::while( iter, nqp::bindkey( elems, nqp::iterkey_s(nqp::shift(iter)), nqp::getattr(nqp::iterval(iter),Pair,'$!key'), ) ), nqp::create(type).SET-SELF(elems) ), nqp::if( nqp::eqaddr(type,Set), set(), nqp::create(type) ) ) } multi method Set(Baggy:D:) { SETIFY($!elems,Set) } multi method SetHash(Baggy:D:) { SETIFY($!elems,SetHash) } sub MIXIFY(\raw, \type) { raw && nqp::elems(raw) ?? nqp::create(type).SET-SELF(Rakudo::QuantHash.BAGGY-CLONE(raw)) !! nqp::istype(type,Mix) ?? mix() !! nqp::create(MixHash) } multi method Mix(Baggy:D:) { MIXIFY($!elems, Mix) } multi method MixHash(Baggy:D:) { MIXIFY($!elems, MixHash) } method Map { nqp::if( $!elems && nqp::elems($!elems), nqp::stmts( (my \storage := nqp::hash), (my \iter := nqp::iterator($!elems)), nqp::while( iter, nqp::bindkey( storage, nqp::getattr(nqp::iterval(nqp::shift(iter)),Pair,'$!key').Str, nqp::getattr(nqp::iterval(iter),Pair,'$!value') ) ), nqp::p6bindattrinvres(nqp::create(Map),Map,'$!storage',storage) ), nqp::create(Map) ) } method RAW-HASH() is raw is implementation-detail { $!elems } } multi sub infix:(Baggy:D $a, Baggy:D $b --> Bool:D) { nqp::hllbool( nqp::eqaddr($a,$b) || (nqp::eqaddr($a.WHAT,$b.WHAT) && $a.ACCEPTS($b)) ) } #line 1 SETTING::src/core.c/Bag.rakumod my class Bag does Baggy { has ValueObjAt $!WHICH; has Int $!total; my role KeyOf[::CONSTRAINT] { method keyof() { CONSTRAINT } } method ^parameterize(Mu \base, Mu \type) { my \what := base.^mixin(KeyOf[type]); what.^set_name(base.^name ~ '[' ~ type.^name ~ ']'); what } #--- introspection methods multi method WHICH(Bag:D: --> ValueObjAt:D) { nqp::isconcrete($!WHICH) ?? $!WHICH !! self!WHICH } method !WHICH() { $!WHICH := nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Bag), 'Bag|', nqp::concat(nqp::unbox_s(self.^name), '|') ), nqp::sha1( nqp::join('\0',Rakudo::Sorting.MERGESORT-str( Rakudo::QuantHash.BAGGY-RAW-KEY-VALUES(self) )) ) ), ValueObjAt ) } method total(Bag:D: --> Int:D) { $!total // ($!total := Rakudo::QuantHash.BAG-TOTAL($!elems)) } #--- interface methods multi method STORE(Bag:D: Any:D \keys, :INITIALIZE($)! --> Bag:D) { (my \iterator := keys.iterator).is-lazy ?? self.fail-iterator-cannot-be-lazy('initialize') !! self.SET-SELF(Rakudo::QuantHash.ADD-PAIRS-TO-BAG( nqp::create(Rakudo::Internals::IterationSet),iterator,self.keyof )) } multi method STORE(Bag:D: \objects, \values, :INITIALIZE($)! --> Bag:D) { self.SET-SELF( Rakudo::QuantHash.ADD-OBJECTS-VALUES-TO-BAG( nqp::create(Rakudo::Internals::IterationSet), objects.iterator, values.iterator, self.keyof ) ) } multi method DELETE-KEY(Bag:D: \k) { X::Immutable.new(method => 'DELETE-KEY', typename => self.^name).throw; } #--- selection methods multi method grabpairs(Bag:D: $count?) { X::Immutable.new( method => 'grabpairs', typename => self.^name ).throw; } #--- coercion methods multi method Bag(Bag:D:) { self } multi method BagHash(Bag:D:) { $!elems && nqp::elems($!elems) ?? nqp::create(BagHash).SET-SELF( Rakudo::QuantHash.BAGGY-CLONE($!elems)) !! nqp::create(BagHash) } multi method Mix(Bag:D:) { $!elems && nqp::elems($!elems) ?? nqp::create(Mix).SET-SELF($!elems) !! mix() } multi method MixHash(Bag:D:) { $!elems && nqp::elems($!elems) ?? nqp::create(MixHash).SET-SELF( Rakudo::QuantHash.BAGGY-CLONE($!elems)) !! nqp::create(MixHash) } multi method Setty(Bag:U:) { Set } multi method Setty(Bag:D:) { self.Set } multi method Baggy(Bag:U:) { Bag } multi method Baggy(Bag:D:) { self } multi method Mixy (Bag:U:) { Mix } multi method Mixy (Bag:D:) { self.Mix } #--- illegal methods proto method classify-list(|) { X::Immutable.new(:method, :typename(self.^name)).throw; } proto method categorize-list(|) { X::Immutable.new(:method, :typename(self.^name)).throw; } } #line 1 SETTING::src/core.c/BagHash.rakumod my class BagHash does Baggy { my role KeyOf[::CONSTRAINT] { method keyof() { CONSTRAINT } } method ^parameterize(Mu \base, Mu \type) { my \what := base.^mixin(KeyOf[type]); what.^set_name(base.^name ~ '[' ~ type.^name ~ ']'); what } #--- interface methods multi method STORE(BagHash:D: Any:D \keys --> BagHash:D) { (my \iterator := keys.iterator).is-lazy ?? self.fail-iterator-cannot-be-lazy('initialize') !! self.SET-SELF( Rakudo::QuantHash.ADD-PAIRS-TO-BAG( nqp::create(Rakudo::Internals::IterationSet), iterator, self.keyof ) ) } multi method STORE(BagHash:D: \objects, \values --> BagHash:D) { self.SET-SELF( Rakudo::QuantHash.ADD-OBJECTS-VALUES-TO-BAG( nqp::create(Rakudo::Internals::IterationSet), objects.iterator, values.iterator, self.keyof ) ) } multi method AT-KEY(BagHash:D: \k) is raw { my \type := self.keyof; Proxy.new( FETCH => { nqp::if( nqp::istrue($!elems) && nqp::existskey($!elems,(my $which := k.WHICH)), nqp::getattr(nqp::atkey($!elems,$which),Pair,'$!value'), # 0 because the value of the condition is returned ) }, STORE => -> $, Int() $value { nqp::if( nqp::istype($value,Failure), # https://github.com/Raku/old-issue-tracker/issues/5567 $value.throw, nqp::if( $!elems, nqp::if( # allocated hash nqp::existskey($!elems,(my $which := k.WHICH)), nqp::if( # existing element $value > 0, nqp::bindattr( nqp::atkey($!elems,$which), Pair, '$!value', nqp::decont($value) ), nqp::stmts( nqp::deletekey($!elems,$which), 0 ) ), nqp::if( $value > 0, # new Rakudo::QuantHash.BIND-TO-TYPED-BAG( $!elems, $which, k, nqp::decont($value), type ) ) ), nqp::if( # no hash allocated yet $value > 0, Rakudo::QuantHash.BIND-TO-TYPED-BAG( nqp::bindattr(self,BagHash,'$!elems', nqp::create(Rakudo::Internals::IterationSet) ), k.WHICH, k, nqp::decont($value), type ) ) ) ) } ) } #--- introspection methods method total() { Rakudo::QuantHash.BAG-TOTAL($!elems) } #--- coercion methods multi method Bag(BagHash:D: :$view) { # :view is implementation-detail $!elems && nqp::elems($!elems) ?? nqp::create(Bag).SET-SELF( # not empty $view ?? $!elems # won't change !! Rakudo::QuantHash.BAGGY-CLONE($!elems) # need deep copy ) !! bag() # empty } multi method BagHash(BagHash:D:) { self } multi method Mix(BagHash:D:) { $!elems && nqp::elems($!elems) ?? nqp::create(Mix).SET-SELF(Rakudo::QuantHash.BAGGY-CLONE($!elems)) !! mix() } multi method MixHash(BagHash:D:) { $!elems && nqp::elems($!elems) ?? nqp::create(MixHash).SET-SELF( Rakudo::QuantHash.BAGGY-CLONE($!elems)) !! nqp::create(MixHash) } multi method Setty(BagHash:U:) { SetHash } multi method Setty(BagHash:D:) { self.SetHash } multi method Baggy(BagHash:U:) { BagHash } multi method Baggy(BagHash:D:) { self } multi method Mixy (BagHash:U:) { MixHash } multi method Mixy (BagHash:D:) { self.MixHash } method clone() { $!elems && nqp::elems($!elems) ?? nqp::create(self).SET-SELF(Rakudo::QuantHash.BAGGY-CLONE($!elems)) !! nqp::create(self) } #--- iterator methods sub proxy(str $key, Mu \elems) is raw { # We are only sure that the key exists when the Proxy # is made, but we cannot be sure of its existence when # either the FETCH or STORE block is executed. So we # still need to check for existence, and handle the case # where we need to (re-create) the key and value. The # logic is therefore basically the same as in AT-KEY, # except for tests for allocated storage and .WHICH # processing. # save object for potential recreation my $pair := nqp::atkey(elems,$key); Proxy.new( FETCH => { nqp::if( nqp::existskey(elems,$key), nqp::getattr(nqp::atkey(elems,$key),Pair,'$!value'), # 0 the value of existskey if the key doesn't exist ) }, STORE => -> $, Int() $value { nqp::if( # https://github.com/Raku/old-issue-tracker/issues/5567 nqp::istype($value,Failure), $value.throw, nqp::if( nqp::existskey(elems,$key), nqp::if( # existing element nqp::isgt_i($value,0), nqp::bindattr( # value ok nqp::atkey(elems,$key), Pair, '$!value', nqp::decont($value) ), nqp::stmts( # goodbye! nqp::deletekey(elems,$key), 0 ) ), nqp::if( # where did it go? nqp::isgt_i($value,0), nqp::bindattr( nqp::bindkey(elems,$key,$pair), Pair, '$!value', nqp::decont($value) ) ) ) ) } ) } my class Iterate does Iterator { has $!elems is built(:bind); has $!keys is built(:bind) is built(False) = Rakudo::Internals.IterationSet2keys($!elems); method pull-one() is raw { nqp::elems($!keys) ?? nqp::p6bindattrinvres( nqp::clone( nqp::atkey($!elems,(my $key := nqp::shift_s($!keys))) ), Pair, '$!value', proxy($key,$!elems) ) !! IterationEnd } method push-all(\target --> IterationEnd) { my $elems := $!elems; my $keys := $!keys; nqp::while( # doesn't sink nqp::elems($keys), target.push(nqp::atkey($elems,nqp::shift_s($keys))) ) } } multi method iterator(BagHash:D:) { Iterate.new(:$!elems) } # also .pairs my class KV does Iterator { has $!elems is built(:bind); has $!keys is built(:bind) is built(False) = Rakudo::Internals.IterationSet2keys($!elems); has str $!on; method pull-one() is raw { nqp::if( $!on, nqp::stmts( (my $proxy := proxy($!on,$!elems)), ($!on = ""), $proxy ), nqp::if( nqp::elems($!keys), nqp::getattr( nqp::atkey($!elems,($!on = nqp::shift_s($!keys))),Pair,'$!key' ), IterationEnd ) ) } method push-all(\target --> IterationEnd) { my $elems := $!elems; my $keys := $!keys; nqp::while( nqp::elems($keys), nqp::stmts( # doesn't sink (my $pair := nqp::atkey($elems,nqp::shift_s($keys))), target.push(nqp::getattr($pair,Pair,'$!key')), target.push(nqp::getattr($pair,Pair,'$!value')) ) ) } } multi method kv(BagHash:D:) { Seq.new(KV.new(:$!elems)) } my class Values does Iterator { has $!elems is built(:bind); has $!keys is built(:bind) is built(False) = Rakudo::Internals.IterationSet2keys($!elems); method pull-one() is raw { nqp::elems($!keys) ?? proxy(nqp::shift_s($!keys),$!elems) !! IterationEnd } method push-all(\target --> IterationEnd) { my $elems := $!elems; my $keys := $!keys; nqp::while( # doesn't sink nqp::elems($keys), target.push(proxy(nqp::shift_s($keys),$elems)) ); } } multi method values(BagHash:D:) { Seq.new(Values.new(:$!elems)) } #---- selection methods multi method grab(BagHash:D:) { $!elems && nqp::elems($!elems) ?? Rakudo::QuantHash.BAG-GRAB($!elems,self.total) !! Nil } multi method grab(BagHash:D: Callable:D $calculate) { self.grab( $calculate(self.total) ) } multi method grab(BagHash:D: Whatever) { self.grab(Inf) } multi method grab(BagHash:D: $count) { Seq.new(nqp::if( (my $todo = Rakudo::QuantHash.TODO($count)) && $!elems && nqp::elems($!elems), nqp::stmts( (my Int $total = self.total), nqp::if($todo > $total,$todo = $total), Rakudo::Iterator.Callable( { nqp::if( $todo, nqp::stmts( --$todo, Rakudo::QuantHash.BAG-GRAB($!elems,$total--) ), IterationEnd ) } ) ), Rakudo::Iterator.Empty )) } #--- convenience methods method add(BagHash:D: \to-add --> Nil) { nqp::bindattr( self,SetHash,'$!elems',nqp::create(Rakudo::Internals::IterationSet) ) unless $!elems; Rakudo::QuantHash.ADD-ITERATOR-TO-BAG( $!elems, to-add.iterator, self.keyof ); } method remove(BagHash:D: \to-remove --> Nil) { Rakudo::QuantHash.SUB-ITERATOR-FROM-BAG( $!elems, to-remove.iterator ) if $!elems; } } #line 1 SETTING::src/core.c/Mixy.rakumod my role Mixy does Baggy { method of() { Real } multi method hash(Mixy:D: --> Hash:D) { self!HASHIFY(Real) } multi method Hash(Mixy:D: --> Hash:D) { self!HASHIFY(Any) } # https://github.com/rakudo/rakudo/issues/5057 multi method deepmap(Mixy:D: &mapper) { my $type := self.WHAT; my $elems := nqp::getattr(self,self.WHAT,'$!elems'); my $clone := nqp::clone($elems); my $iter := nqp::iterator($elems); while $iter { my str $key = nqp::iterkey_s(nqp::shift($iter)); my $pair := nqp::iterval($iter); my $value = nqp::getattr($pair,Pair,'$!value'); # must be Scalar $value := nqp::decont($value) if nqp::istype($type,Mix); # update clone my $returned := nqp::decont(mapper($value).Int); $returned ?? nqp::bindkey( $clone, $key, nqp::p6bindattrinvres( nqp::clone($pair),Pair,'$!value',$returned ) ) !! nqp::deletekey($clone,$key); $value ?? nqp::bindattr($pair,Pair,'$!value',nqp::decont($value)) !! nqp::deletekey($elems,$key) } nqp::p6bindattrinvres(nqp::create($type),$type,'$!elems',$clone) } multi method kxxv(Mixy:D:) { ".kxxv is not supported on a {self.^name}".Failure } multi method grab(Mixy:D: $count?) { ".grab is not supported on a {self.^name}".Failure } multi method pick(Mixy:D: $count?) { ".pick is not supported on a {self.^name}, maybe use .roll instead?".Failure } multi method roll(Mixy:D:) { (my \raw := self.RAW-HASH) && (my \total := self!total-positive) ?? nqp::getattr( nqp::iterval(Rakudo::QuantHash.MIX-ROLL(raw,total)),Pair,'$!key' ) !! Nil } multi method roll(Mixy:D: Whatever) { Seq.new( (my \raw := self.RAW-HASH) && (my \total := self!total-positive) ?? Rakudo::Iterator.Callable( { nqp::getattr( nqp::iterval(Rakudo::QuantHash.MIX-ROLL(raw,total)), Pair, '$!key' ) }, True ) !! Rakudo::Iterator.Empty ) } multi method roll(Mixy:D: Callable:D $calculate) { (my $total := self!total-positive) ?? self.roll($calculate($total)) !! Seq.new(Rakudo::Iterator.Empty) } multi method roll(Mixy:D: $count) { $count == Inf ?? self.roll(*) # let Whatever handle it !! Seq.new( # something else as count (my $todo = $count.Int) < 1 # also handles NaN ?? Rakudo::Iterator.Empty # nothing to do !! (my \raw := self.RAW-HASH) && (my \total := self!total-positive) && ++$todo ?? Rakudo::Iterator.Callable( { # need to do a number of times --$todo ?? nqp::getattr( nqp::iterval( Rakudo::QuantHash.MIX-ROLL(raw,total) ), Pair, '$!key' ) !! IterationEnd } ) !! Rakudo::Iterator.Empty # nothing to roll for ) } #--- object creation methods method new-from-pairs(Mixy:_: *@pairs --> Mixy:D) { (my \iterator := @pairs.iterator).is-lazy ?? self.fail-iterator-cannot-be-lazy('coerce') !! nqp::create(self).SET-SELF( Rakudo::QuantHash.ADD-PAIRS-TO-MIX( nqp::create(Rakudo::Internals::IterationSet), iterator, self.keyof ) ) } #--- coercion methods sub SETIFY(\mixy, \type) { nqp::if( (my \raw := mixy.RAW-HASH) && nqp::elems(raw), nqp::stmts( (my \elems := nqp::clone(raw)), (my \iter := nqp::iterator(elems)), nqp::while( iter, nqp::if( nqp::getattr(nqp::iterval(nqp::shift(iter)),Pair,'$!value') < 0, nqp::deletekey(elems,nqp::iterkey_s(iter)), nqp::bindkey( elems, nqp::iterkey_s(iter), nqp::getattr(nqp::iterval(iter),Pair,'$!key') ) ) ), nqp::create(type).SET-SELF(elems) ), nqp::if( nqp::eqaddr(type,Set), set(), nqp::create(type) ) ) } multi method Set(Mixy:D:) { SETIFY(self,Set) } multi method SetHash(Mixy:D:) { SETIFY(self,SetHash) } sub BAGGIFY(\mixy, \type) { nqp::if( (my \raw := mixy.RAW-HASH) && nqp::elems(raw), nqp::stmts( # something to coerce (my \elems := nqp::clone(raw)), (my \iter := nqp::iterator(elems)), nqp::while( iter, nqp::if( (my \value := nqp::getattr( nqp::iterval(nqp::shift(iter)),Pair,'$!value' ).Int) > 0, # .Int also deconts nqp::bindkey( # ok to keep value.Int elems, nqp::iterkey_s(iter), nqp::p6bindattrinvres( nqp::iterval(iter),Pair,'$!value',value) ), nqp::deletekey(elems,nqp::iterkey_s(iter)) ) ), nqp::create(type).SET-SELF(elems), ), nqp::if( # nothing to coerce nqp::istype(type,Bag), bag(), nqp::create(BagHash) ) ) } multi method Bag(Baggy:D:) { BAGGIFY(self, Bag) } multi method BagHash(Baggy:D:) { BAGGIFY(self, BagHash) } } #line 1 SETTING::src/core.c/Mix.rakumod my class Mix does Mixy { has ValueObjAt $!WHICH; has Real $!total; has Real $!total-positive; my role KeyOf[::CONSTRAINT] { method keyof() { CONSTRAINT } } method ^parameterize(Mu \base, Mu \type) { my \what := base.^mixin(KeyOf[type]); what.^set_name(base.^name ~ '[' ~ type.^name ~ ']'); what } #--- interface methods multi method STORE(Mix:D: Any:D \keys, :INITIALIZE($)! --> Mix:D) { (my \iterator := keys.iterator).is-lazy ?? self.fail-iterator-cannot-be-lazy('.initialize') !! self.SET-SELF(Rakudo::QuantHash.ADD-PAIRS-TO-MIX( nqp::create(Rakudo::Internals::IterationSet),iterator,self.keyof )) } multi method STORE(Mix:D: \objects, \values, :INITIALIZE($)! --> Mix:D) { self.SET-SELF( Rakudo::QuantHash.ADD-OBJECTS-VALUES-TO-MIX( nqp::create(Rakudo::Internals::IterationSet), objects.iterator, values.iterator, self.keyof ) ) } multi method DELETE-KEY(Mix:D: $) { X::Immutable.new(method => 'DELETE-KEY', typename => self.^name).throw; } #--- introspection methods multi method WHICH(Mix:D: --> ValueObjAt:D) { nqp::isconcrete($!WHICH) ?? $!WHICH !! self!WHICH } method !WHICH() { $!WHICH := nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Mix), 'Mix|', nqp::concat(nqp::unbox_s(self.^name), '|') ), nqp::sha1( nqp::join('\0',Rakudo::Sorting.MERGESORT-str( Rakudo::QuantHash.BAGGY-RAW-KEY-VALUES(self) )) ) ), ValueObjAt ) } method total(Mix:D: --> Real:D) { $!total // ($!total := Rakudo::QuantHash.MIX-TOTAL($!elems)) } method !total-positive(Mix:D: --> Real:D) { $!total-positive // ($!total-positive := Rakudo::QuantHash.MIX-TOTAL-POSITIVE($!elems)) } #--- selection methods multi method grab($count? --> Real:D) { X::Immutable.new( method => 'grab', typename => self.^name ).throw; } multi method grabpairs($count? --> Real:D) { X::Immutable.new( method => 'grabpairs', typename => self.^name ).throw; } #--- coercion methods multi method Mix(Mix:D:) { self } multi method MixHash(Mix:D:) { $!elems && nqp::elems($!elems) ?? nqp::create(MixHash).SET-SELF( Rakudo::QuantHash.BAGGY-CLONE($!elems)) !! nqp::create(MixHash) } multi method Setty(Mix:U:) { Set } multi method Setty(Mix:D:) { self.Set } multi method Baggy(Mix:U:) { Bag } multi method Baggy(Mix:D:) { self.Bag } multi method Mixy (Mix:U:) { Mix } multi method Mixy (Mix:D:) { self } #--- illegal methods proto method classify-list(|) { X::Immutable.new(:method, :typename(self.^name)).throw; } proto method categorize-list(|) { X::Immutable.new(:method, :typename(self.^name)).throw; } } #line 1 SETTING::src/core.c/MixHash.rakumod my class MixHash does Mixy { my role KeyOf[::CONSTRAINT] { method keyof() { CONSTRAINT } } method ^parameterize(Mu \base, Mu \type) { my \what := base.^mixin(KeyOf[type]); what.^set_name(base.^name ~ '[' ~ type.^name ~ ']'); what } #--- interface methods method total() { Rakudo::QuantHash.MIX-TOTAL($!elems) } method !total-positive() { Rakudo::QuantHash.MIX-TOTAL-POSITIVE($!elems) } multi method STORE(MixHash:D: Any:D \keys --> MixHash:D) { (my \iterator := keys.iterator).is-lazy ?? self.fail-iterator-cannot-be-lazy('initialize') !! self.SET-SELF( Rakudo::QuantHash.ADD-PAIRS-TO-MIX( nqp::create(Rakudo::Internals::IterationSet), iterator, self.keyof ) ) } multi method STORE(MixHash:D: \objects, \values --> MixHash:D) { self.SET-SELF( Rakudo::QuantHash.ADD-OBJECTS-VALUES-TO-MIX( nqp::create(Rakudo::Internals::IterationSet), objects.iterator, values.iterator, self.keyof ) ) } multi method AT-KEY(MixHash:D: \k) is raw { my \type := self.keyof; Proxy.new( FETCH => { $!elems && nqp::existskey($!elems,(my \which := k.WHICH)) ?? nqp::getattr(nqp::atkey($!elems,which),Pair,'$!value') !! 0 }, STORE => -> $, Real() $value { nqp::if( nqp::istype($value,Failure), # https://github.com/Raku/old-issue-tracker/issues/5567 $value.throw, nqp::if( $!elems, nqp::if( # allocated hash nqp::existskey($!elems,(my $which := k.WHICH)), nqp::if( # existing element $value == 0, nqp::stmts( nqp::deletekey($!elems,$which), 0 ), nqp::bindattr( nqp::atkey($!elems,$which), Pair, '$!value', nqp::decont($value) ), ), nqp::unless( $value == 0, Rakudo::QuantHash.BIND-TO-TYPED-MIX( $!elems, $which, k, nqp::decont($value), type ) ) ), nqp::unless( # no hash allocated yet $value == 0, Rakudo::QuantHash.BIND-TO-TYPED-MIX( nqp::bindattr(self,::?CLASS,'$!elems', nqp::create(Rakudo::Internals::IterationSet) ), k.WHICH, k, nqp::decont($value), type ) ) ) ) } ) } #--- object creation methods multi method new(MixHash:_:) { nqp::create(self) } #--- coercion methods multi method Mix(MixHash:D: :$view) { # :view is implementation-detail $!elems && nqp::elems($!elems) ?? nqp::p6bindattrinvres( nqp::create(Mix),Mix,'$!elems', $view ?? $!elems !! $!elems.clone ) !! mix() } multi method MixHash(MixHash:D:) { self } multi method Setty(MixHash:U:) { SetHash } multi method Setty(MixHash:D:) { self.SetHash } multi method Baggy(MixHash:U:) { BagHash } multi method Baggy(MixHash:D:) { self.BagHash } multi method Mixy (MixHash:U:) { MixHash } multi method Mixy (MixHash:D:) { self } method clone() { $!elems && nqp::elems($!elems) ?? nqp::create(MixHash).SET-SELF( Rakudo::QuantHash.BAGGY-CLONE($!elems) ) !! nqp::create(MixHash) } #--- iterator methods sub proxy(str $key, Mu \elems) is raw { # We are only sure that the key exists when the Proxy # is made, but we cannot be sure of its existence when # either the FETCH or STORE block is executed. So we # still need to check for existence, and handle the case # where we need to (re-create) the key and value. The # logic is therefore basically the same as in AT-KEY, # except for tests for allocated storage and .WHICH # processing. # save for possible object recreation my $pair := nqp::atkey(elems,$key); Proxy.new( FETCH => { nqp::existskey(elems,$key) ?? nqp::getattr(nqp::atkey(elems,$key),Pair,'$!value') !! 0 }, STORE => -> $, Real() \value { nqp::if( # https://github.com/Raku/old-issue-tracker/issues/5567 nqp::istype(value,Failure), value.throw, nqp::if( nqp::existskey(elems,$key), nqp::if( # existing element value == 0, nqp::stmts( # goodbye! nqp::deletekey(elems,$key), 0 ), nqp::bindattr( # value ok nqp::atkey(elems,$key), Pair, '$!value', nqp::decont(value) ) ), nqp::unless( # where did it go? value == 0, nqp::bindattr( nqp::bindkey(elems,$key,$pair), Pair, '$!value', nqp::decont(value) ) ) ) ) } ) } my class Iterate does Iterator { has $!elems is built(:bind); has $!keys is built(:bind) is built(False) = Rakudo::Internals.IterationSet2keys($!elems); method pull-one() is raw { nqp::elems($!keys) ?? nqp::p6bindattrinvres( nqp::clone( nqp::atkey($!elems,(my $key := nqp::shift_s($!keys))) ), Pair, '$!value', proxy($key,$!elems) ) !! IterationEnd } method push-all(\target --> IterationEnd) { my $elems := $!elems; my $keys := $!keys; nqp::while( # doesn't sink nqp::elems($keys), target.push(nqp::atkey($elems,nqp::shift_s($keys))) ) } } multi method iterator(MixHash:D:) { Iterate.new(:$!elems) } # also .pairs my class KV does Iterator { has $!elems is built(:bind); has $!keys is built(:bind) is built(False) = Rakudo::Internals.IterationSet2keys($!elems); has str $!on; method pull-one() is raw { nqp::if( $!on, nqp::stmts( (my $proxy := proxy($!on,$!elems)), ($!on = ""), $proxy ), nqp::if( nqp::elems($!keys), nqp::getattr( nqp::atkey($!elems,($!on = nqp::shift_s($!keys))),Pair,'$!key' ), IterationEnd ) ) } method push-all(\target --> IterationEnd) { my $elems := $!elems; my $keys := $!keys; nqp::while( nqp::elems($keys), nqp::stmts( # doesn't sink (my $pair := nqp::atkey($elems,nqp::shift_s($keys))), target.push(nqp::getattr($pair,Pair,'$!key')), target.push(nqp::getattr($pair,Pair,'$!value')) ) ) } } multi method kv(MixHash:D:) { Seq.new(KV.new(:$!elems)) } my class Values does Iterator { has $!elems is built(:bind); has $!keys is built(:bind) is built(False) = Rakudo::Internals.IterationSet2keys($!elems); method pull-one() is raw { nqp::elems($!keys) ?? proxy(nqp::shift_s($!keys),$!elems) !! IterationEnd } method push-all(\target --> IterationEnd) { my $elems := $!elems; my $keys := $!keys; nqp::while( # doesn't sink nqp::elems($keys), target.push(proxy(nqp::shift_s($keys),$elems)) ) } } multi method values(MixHash:D:) { Seq.new(Values.new(:$!elems)) } } #line 1 SETTING::src/core.c/set_operators.rakumod proto sub set(|) is pure {*} multi sub set() { BEGIN nqp::create(Set) } multi sub set(*@a --> Set:D) { Set.new(@a) } proto sub bag(|) is pure {*} multi sub bag() { BEGIN nqp::create(Bag) } multi sub bag(*@a --> Bag:D) { Bag.new(@a) } proto sub mix(|) is pure {*} multi sub mix() { BEGIN nqp::create(Mix) } multi sub mix(*@a --> Mix:D) { Mix.new(@a) } #line 1 SETTING::src/core.c/set_elem.rakumod # This file implements the following set operators: # (elem) is an element of (ASCII) # ∈ is an element of # ∉ is NOT an element of # (cont) contains (ASCII) # ∋ contains # ∌ does NOT contain proto sub infix:<(elem)>($, $, *% --> Bool:D) is pure {*} multi sub infix:<(elem)>(Str:D $a, Map:D \b --> Bool:D) { nqp::hllbool( nqp::istrue( nqp::elems(my \storage := nqp::getattr(nqp::decont(b),Map,'$!storage')) && nqp::if( nqp::istype(b,Hash::Object), nqp::getattr( # object hash nqp::ifnull( nqp::atkey(storage,$a.WHICH), BEGIN # provide virtual value False # did not exist nqp::p6bindattrinvres(nqp::create(Pair),Pair,'$!value',False) ), Pair, '$!value' ), nqp::atkey(storage,$a) # normal hash ) ) ) } multi sub infix:<(elem)>(Any \a, Map:D \b --> Bool:D) { nqp::hllbool( nqp::istrue( nqp::elems( # haz a haystack my \storage := nqp::getattr(nqp::decont(b),Map,'$!storage') ) && nqp::istype(b,Hash::Object) && nqp::getattr( nqp::ifnull( nqp::atkey(storage,a.WHICH), # exists BEGIN # provide virtual value False # did not exist nqp::p6bindattrinvres(nqp::create(Pair),Pair,'$!value',False) ), Pair, '$!value' ) ) ) } multi sub infix:<(elem)>(Str:D $a, array[str] \b --> Bool:D) { my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems(b)) && nqp::isne_s($a,nqp::atpos_s(b,$i)), nqp::null ); nqp::hllbool(nqp::islt_i($i,nqp::elems(b))) } multi sub infix:<(elem)>(Int:D $a, array[int] \b --> Bool:D) { my int $i = -1; nqp::while( nqp::islt_i(++$i,nqp::elems(b)) && nqp::isne_i($a,nqp::atpos_i(b,$i)), nqp::null ); nqp::hllbool(nqp::islt_i($i,nqp::elems(b))) } multi sub infix:<(elem)>(Int:D $a, Range:D \b --> Bool:D) { b.is-int ?? b.ACCEPTS($a) !! $a (elem) b.iterator } multi sub infix:<(elem)>(Any \a, Iterable:D \b --> Bool:D) { a (elem) b.iterator } multi sub infix:<(elem)>(Any \a, Iterator:D \b --> Bool:D) { nqp::if( b.is-lazy, Any.fail-iterator-cannot-be-lazy('(elem)',''), nqp::stmts( (my str $needle = a.WHICH), nqp::until( nqp::eqaddr( (my \pulled := nqp::decont(b.pull-one)), IterationEnd ), nqp::if( nqp::iseq_s($needle,pulled.WHICH), return True ) ), False ) ) } multi sub infix:<(elem)>(Any \a, QuantHash:D $b --> Bool:D) { nqp::hllbool( (my \elems := $b.RAW-HASH) ?? nqp::existskey(elems,a.WHICH) !! 0 ) } multi sub infix:<(elem)>(Any, Failure:D $b) { $b.throw } multi sub infix:<(elem)>(Failure:D $a, Any) { $a.throw } multi sub infix:<(elem)>(Any \a, Any \b) { a (elem) b.Set } # U+2208 ELEMENT OF my constant &infix:<∈> := &infix:<(elem)>; # U+220A SMALL ELEMENT OF my constant &infix:<∊> := &infix:<(elem)>; # U+2209 NOT AN ELEMENT OF proto sub infix:<∉>($, $, *%) is pure {*} multi sub infix:<∉>(\a, \b --> Bool:D) { not a (elem) b } proto sub infix:<(cont)>($, $, *%) is pure {*} multi sub infix:<(cont)>(\a, \b --> Bool:D) { b (elem) a } # U+220B CONTAINS AS MEMBER my constant &infix:<∋> = &infix:<(cont)>; # U+220D SMALL CONTAINS AS MEMBER my constant &infix:<∍> = &infix:<(cont)>; # U+220C DOES NOT CONTAIN AS MEMBER proto sub infix:<∌>($, $, *%) is pure {*} multi sub infix:<∌>(\a, \b --> Bool:D) { not b (elem) a } #line 1 SETTING::src/core.c/set_union.rakumod # This file implements the following set operators: # (|) union (ASCII) # ∪ union proto sub infix:<(|)>(|) is pure {*} multi sub infix:<(|)>() { set() } multi sub infix:<(|)>(QuantHash:D $a) { $a } # Set/Bag/Mix multi sub infix:<(|)>(Setty:D $a, Setty:D $b) { nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( # first has elems (my \braw := $b.RAW-HASH) && nqp::elems(braw), nqp::stmts( # second has elems (my \elems := nqp::clone(araw)), (my \iter := nqp::iterator(braw)), nqp::while( # loop over keys of second iter, nqp::bindkey( # bind into clone of first elems, nqp::iterkey_s(nqp::shift(iter)), nqp::iterval(iter) ) ), nqp::create($a.WHAT).SET-SELF(elems) # make it a Set(Hash) ), $a # no second, so first ), nqp::if( # no first (my \raw := $b.RAW-HASH) && nqp::elems(raw), nqp::if( # but second nqp::istype($a,Set),$b.Set,$b.SetHash ), $a # both empty ) ) } multi sub infix:<(|)>(Setty:D $a, Mixy:D $b) { $a.Mixy (|) $b } multi sub infix:<(|)>(Setty:D $a, Baggy:D $b) { $a.Baggy (|) $b } multi sub infix:<(|)>(Mixy:D $a, Mixy:D $b) { nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( # first has elems (my \braw := $b.RAW-HASH) && nqp::elems(braw), nqp::stmts( # second has elems (my \elems := nqp::clone(araw)), (my \iter := nqp::iterator(braw)), nqp::while( # loop over keys of second iter, nqp::if( nqp::existskey( araw, (my \key := nqp::iterkey_s(nqp::shift(iter))) ), nqp::if( # must use HLL < because values can be bignums nqp::getattr( nqp::decont(nqp::atkey(araw,key)),Pair,'$!value') < nqp::getattr( # > hl nqp::decont(nqp::atkey(braw,key)),Pair,'$!value'), nqp::bindkey(elems,key,nqp::atkey(braw,key)) ), nqp::bindkey(elems,key,nqp::atkey(braw,key)) ) ), nqp::create($a.WHAT).SET-SELF(elems) # make it a Mix(Hash) ), $a # no second, so first ), nqp::if( # no first (my \raw := $b.RAW-HASH) && nqp::elems(raw), nqp::if( # but second nqp::istype($a,Mix),$b.Mix,$b.MixHash ), $a # both empty ) ) } multi sub infix:<(|)>(Mixy:D $a, Baggy:D $b) { $a (|) $b.Mix } multi sub infix:<(|)>(Mixy:D $a, Setty:D $b) { $a (|) $b.Mix } multi sub infix:<(|)>(Baggy:D $a, Mixy:D $b) { $a.Mixy (|) $b } multi sub infix:<(|)>(Baggy:D $a, Baggy:D $b) { nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( # first has elems (my \braw := $b.RAW-HASH) && nqp::elems(braw), nqp::stmts( # second has elems (my \elems := nqp::clone(araw)), (my \iter := nqp::iterator(braw)), nqp::while( # loop over keys of second iter, nqp::if( nqp::existskey( araw, (my \key := nqp::iterkey_s(nqp::shift(iter))) ), nqp::if( nqp::islt_i( nqp::getattr( nqp::decont(nqp::atkey(araw,key)),Pair,'$!value'), nqp::getattr( nqp::decont(nqp::atkey(braw,key)),Pair,'$!value') ), nqp::bindkey(elems,key,nqp::atkey(braw,key)) ), nqp::bindkey(elems,key,nqp::atkey(braw,key)) ) ), nqp::create($a.WHAT).SET-SELF(elems) # make it a Bag ), $a # no second, so first ), nqp::if( # no first (my \raw := $b.RAW-HASH) && nqp::elems(raw), nqp::if( # but second nqp::istype($a,Bag),$b.Bag,$b.BagHash ), $a # both empty ) ) } multi sub infix:<(|)>(Baggy:D $a, Setty:D $b) { $a (|) $b.Bag } multi sub infix:<(|)>(Map:D \a, Map:D \b) { nqp::create(Set).SET-SELF( Rakudo::QuantHash.ADD-MAP-TO-SET( Rakudo::QuantHash.COERCE-MAP-TO-SET(a), b ) ) } multi sub infix:<(|)>(Iterable:D \a, Iterable:D \b) { (my $aiterator := a.flat.iterator).is-lazy || (my $biterator := b.flat.iterator).is-lazy ?? Any.fail-iterator-cannot-be-lazy('union', 'set') !! nqp::create(Set).SET-SELF( Rakudo::QuantHash.ADD-PAIRS-TO-SET( Rakudo::QuantHash.ADD-PAIRS-TO-SET( nqp::create(Rakudo::Internals::IterationSet), $aiterator, Mu ), $biterator, Mu ) ) } multi sub infix:<(|)>(Failure:D $a, Any) { $a.throw } multi sub infix:<(|)>(Any, Failure:D $b) { $b.throw } multi sub infix:<(|)>(Any \a, Any \b) { nqp::isconcrete(a) ?? nqp::istype(a,Mixy) ?? a (|) b.Mix !! nqp::istype(a,Baggy) ?? a (|) b.Bag !! nqp::istype(a,Setty) ?? a (|) b.Set !! nqp::isconcrete(b) ?? nqp::istype(b,Mixy) ?? a.Mix (|) b !! nqp::istype(b,Baggy) ?? a.Bag (|) b !! a.Set (|) b.Set !! a (|) b.Set !! a.Set (|) b } multi sub infix:<(|)>(+@p) { # also Any my $iterator := @p.iterator; nqp::if( nqp::eqaddr((my $result := $iterator.pull-one),IterationEnd), set(), # nothing to process nqp::if( nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), $result.Set, # only 1 elem to process nqp::stmts( nqp::repeat_until( nqp::eqaddr(($pulled := $iterator.pull-one),IterationEnd), ($result := $result (|) $pulled) ), $result ) ) ) } # U+222A UNION my constant &infix:<∪> := &infix:<(|)>; #line 1 SETTING::src/core.c/set_intersection.rakumod # This file implements the following set operators: # (&) intersection (ASCII) # ∩ intersection proto sub infix:<(&)>(|) is pure {*} multi sub infix:<(&)>() { set() } multi sub infix:<(&)>(QuantHash:D $a) { $a } # Set/Bag/Mix multi sub infix:<(&)>(Setty:D $a, Setty:D $b) { nqp::if( (my $araw := $a.RAW-HASH) && nqp::elems($araw) && (my $braw := $b.RAW-HASH) && nqp::elems($braw), nqp::stmts( # both have elems nqp::if( nqp::islt_i(nqp::elems($araw),nqp::elems($braw)), nqp::stmts( # a smallest, iterate over it (my $iter := nqp::iterator($araw)), (my $base := $braw) ), nqp::stmts( # b smallest, iterate over that ($iter := nqp::iterator($braw)), ($base := $araw) ) ), (my $elems := nqp::create(Rakudo::Internals::IterationSet)), nqp::while( $iter, nqp::if( # bind if in both nqp::existskey($base,nqp::iterkey_s(nqp::shift($iter))), nqp::bindkey($elems,nqp::iterkey_s($iter),nqp::iterval($iter)) ) ), nqp::create($a.WHAT).SET-SELF($elems) ), nqp::if( # one/neither has elems nqp::istype($a,Set), nqp::if(nqp::eqaddr($a.WHAT,Set), set(), nqp::create($a.WHAT)), nqp::create(SetHash) ) ) } multi sub infix:<(&)>(Setty:D $a, Baggy:D $b) { Rakudo::QuantHash.INTERSECT-BAGGIES($a.Baggy, $b, Bag) } multi sub infix:<(&)>(Baggy:D $a, Setty:D $b) { Rakudo::QuantHash.INTERSECT-BAGGIES($a, $b.Bag, Bag) } multi sub infix:<(&)>(Setty:D $a, Mixy:D $b) { Rakudo::QuantHash.INTERSECT-BAGGIES($a.Mixy, $b, Mix) } multi sub infix:<(&)>(Mixy:D $a, Setty:D $b) { Rakudo::QuantHash.INTERSECT-BAGGIES($a, $b.Mix, Mix) } multi sub infix:<(&)>(Baggy:D $a, Baggy:D $b) { Rakudo::QuantHash.INTERSECT-BAGGIES($a, $b, Bag) } multi sub infix:<(&)>(Mixy:D $a, Baggy:D $b) { Rakudo::QuantHash.INTERSECT-BAGGIES($a, $b, Mix) } multi sub infix:<(&)>(Baggy:D $a, Mixy:D $b) { Rakudo::QuantHash.INTERSECT-BAGGIES($a, $b, Mix) } multi sub infix:<(&)>(Mixy:D $a, Mixy:D $b) { Rakudo::QuantHash.INTERSECT-BAGGIES($a, $b, Mix) } multi sub infix:<(&)>(Baggy:D $a, Any:D $b) { nqp::istype((my $bbag := $b.Bag),Bag) ?? Rakudo::QuantHash.INTERSECT-BAGGIES($a, $bbag, Bag) !! $bbag.throw } multi sub infix:<(&)>(Any:D $a, Baggy:D $b) { $b.Bag (&) $a } multi sub infix:<(&)>(Mixy:D $a, Any:D $b) { nqp::istype((my $bmix := $b.Mix),Mix) ?? Rakudo::QuantHash.INTERSECT-BAGGIES($a, $bmix, Mix) !! $bmix.throw } multi sub infix:<(&)>(Any:D $a, Mixy:D $b) { $b.Mix (&) $a } multi sub infix:<(&)>(Map:D \a, Map:D \b) { nqp::if( nqp::istype(a,Hash::Object) || nqp::istype(b,Hash::Object), (a.Set (&) b.Set), # either is object hash, coerce! nqp::if( # both ordinary Str hashes nqp::elems( my \araw := nqp::getattr(nqp::decont(a),Map,'$!storage') ) && nqp::elems( my \braw := nqp::getattr(nqp::decont(b),Map,'$!storage') ), nqp::stmts( # both are initialized nqp::if( nqp::islt_i(nqp::elems(araw),nqp::elems(braw)), nqp::stmts( # a smallest, iterate over it (my $iter := nqp::iterator(araw)), (my $base := braw) ), nqp::stmts( # b smallest, iterate over that ($iter := nqp::iterator(braw)), ($base := araw) ) ), (my $elems := nqp::create(Rakudo::Internals::IterationSet)), nqp::while( $iter, nqp::if( # create if in both nqp::existskey( $base, nqp::iterkey_s(nqp::shift($iter)) ), nqp::bindkey( $elems,nqp::iterkey_s($iter).WHICH,nqp::iterkey_s($iter)) ) ), nqp::create(Set).SET-SELF($elems) ), set() # one/neither has elems ) ) } multi sub infix:<(&)>(Any $, Failure:D $b) { $b.throw } multi sub infix:<(&)>(Failure:D $a, Any $) { $a.throw } # Note that we cannot create a Setty:D,Any candidate because that will result # in an ambiguous dispatch, so we need to hack a check for Setty in here. multi sub infix:<(&)>(Any \a, Any \b) { nqp::isconcrete(a) ?? nqp::istype(a,Mixy) ?? a (&) b.Mix !! nqp::istype(a,Baggy) ?? a (&) b.Bag !! nqp::istype(a,Setty) ?? a (&) b.Set !! nqp::isconcrete(b) ?? nqp::istype(b,Mixy) ?? a.Mix (&) b !! nqp::istype(b,Baggy) ?? a.Bag (&) b !! a.Set (&) b.Set !! a (&) b.Set !! a.Set (&) b } multi sub infix:<(&)>(+@p) { # also Any my $iterator := @p.iterator; nqp::if( nqp::eqaddr((my $result := $iterator.pull-one),IterationEnd), set(), # nothing to process nqp::if( nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), $result.Set, # only 1 elem to process nqp::stmts( nqp::repeat_until( nqp::eqaddr(($pulled := $iterator.pull-one),IterationEnd), ($result := $result (&) $pulled) ), $result ) ) ) } # U+2229 INTERSECTION my constant &infix:<∩> := &infix:<(&)>; #line 1 SETTING::src/core.c/set_difference.rakumod # This file implements the following set operators: # (-) set difference (ASCII) # ∖ set difference proto sub infix:<(-)>(|) is pure {*} multi sub infix:<(-)>() { set() } multi sub infix:<(-)>(QuantHash:D $a) { $a } # Set/Bag/Mix multi sub infix:<(-)>(SetHash:D $a) { $a.Set } multi sub infix:<(-)>(BagHash:D $a) { $a.Bag } multi sub infix:<(-)>(MixHash:D $a) { $a.Mix } multi sub infix:<(-)>(Setty:D $a, Setty:D $b) { (my $araw := $a.RAW-HASH) && nqp::elems($araw) && (my $braw := $b.RAW-HASH) && nqp::elems($braw) ?? nqp::create($a.WHAT).SET-SELF( # both have elems Rakudo::QuantHash.SUB-SET-FROM-SET($araw, $braw) ) !! $a # no elems in a or b } multi sub infix:<(-)>(Setty:D $a, Map:D \b) { nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::create($a.WHAT).SET-SELF( # elems in a nqp::if( nqp::elems(my \braw := nqp::getattr(nqp::decont(b),Map,'$!storage')), Rakudo::QuantHash.SUB-MAP-FROM-SET(araw, b), # both have elems nqp::clone(araw) # no elems in b ) ), $a # no elems in a ) } multi sub infix:<(-)>(Setty:D $a, Iterable:D \b) { nqp::if( (my $iterator := b.iterator).is-lazy, Set.fail-iterator-cannot-be-lazy('set difference'), nqp::if( (my $raw := $a.RAW-HASH) && nqp::elems($raw), nqp::create($a.WHAT).SET-SELF( # elems in b Rakudo::QuantHash.SUB-PAIRS-FROM-SET($raw, $iterator) ), $a # no elems in b ) ) } multi sub infix:<(-)>(Mixy:D $a, Mixy:D $b) { # needed as tie-breaker Rakudo::QuantHash.DIFFERENCE-MIXY-QUANTHASH($a, $b) } multi sub infix:<(-)>(Mixy:D $a, QuantHash:D $b) { Rakudo::QuantHash.DIFFERENCE-MIXY-QUANTHASH($a, $b) } multi sub infix:<(-)>(QuantHash:D $a, Mixy:D $b) { Rakudo::QuantHash.DIFFERENCE-MIXY-QUANTHASH($a.Mixy, $b) } multi sub infix:<(-)>(Mixy:D $a, Map:D \b) { Rakudo::QuantHash.DIFFERENCE-MIXY-QUANTHASH($a, b.Set) } multi sub infix:<(-)>(Mixy:D $a, Any:D \b) { # also Iterable Rakudo::QuantHash.DIFFERENCE-MIXY-QUANTHASH($a, b.Set) } multi sub infix:<(-)>(Any:D \a, Mixy:D $b) { Rakudo::QuantHash.DIFFERENCE-MIXY-QUANTHASH(a.Mix, $b) } multi sub infix:<(-)>(Baggy:D $a, Mixy:D $b) { # needed as tie-breaker Rakudo::QuantHash.DIFFERENCE-MIXY-QUANTHASH($a.Mixy, $b) } multi sub infix:<(-)>(Baggy:D $a, Baggy:D $b) { # needed as tie-breaker Rakudo::QuantHash.DIFFERENCE-BAGGY-QUANTHASH($a, $b) } multi sub infix:<(-)>(Baggy:D $a, QuantHash:D $b) { Rakudo::QuantHash.DIFFERENCE-BAGGY-QUANTHASH($a, $b) } multi sub infix:<(-)>(QuantHash:D $a, Baggy:D $b) { Rakudo::QuantHash.DIFFERENCE-BAGGY-QUANTHASH($a.Baggy, $b) } multi sub infix:<(-)>(Baggy:D $a, Map:D \b) { Rakudo::QuantHash.DIFFERENCE-BAGGY-QUANTHASH($a, b.Bag) } multi sub infix:<(-)>(Baggy:D $a, Any:D \b) { # also Iterable Rakudo::QuantHash.DIFFERENCE-BAGGY-QUANTHASH($a, b.Bag) } multi sub infix:<(-)>(Any \a, Baggy:D $b) { Rakudo::QuantHash.DIFFERENCE-BAGGY-QUANTHASH(a.Bag, $b) } multi sub infix:<(-)>(Any \a, Map:D \b) { infix:<(-)>(a.Set, b) } multi sub infix:<(-)>(Any \a, Iterable:D \b) { infix:<(-)>(a.Set, b) } multi sub infix:<(-)>(Any $, Failure:D $b) { $b.throw } multi sub infix:<(-)>(Failure:D $a, Any $) { $a.throw } multi sub infix:<(-)>(Any \a, Any \b) { infix:<(-)>(a.Set,b.Set) } multi sub infix:<(-)>(+@p) { # also Any sub subtract(Mu \elems, Mu \iter, \clone, \value --> Nil) { my $pair := nqp::ifnull( nqp::atkey(elems, nqp::iterkey_s(iter)), nqp::bindkey( elems, nqp::iterkey_s(iter), nqp::if( clone, nqp::p6bindattrinvres( nqp::clone(nqp::iterval(iter)), Pair, '$!value', 0 ), Pair.new(nqp::iterval(iter),0) ) ) ); nqp::bindattr($pair,Pair,'$!value', nqp::getattr($pair,Pair,'$!value') - value ); } nqp::if( (my $params := @p.iterator).is-lazy, Set.fail-iterator-cannot-be-lazy('set difference'), # bye bye nqp::stmts( # fixed list of things to diff (my $type := nqp::if( nqp::istype((my $p := $params.pull-one),Mixy), Mix, nqp::if(nqp::istype($p,Baggy),Bag,Set) )), (my $mutable := nqp::eqaddr($p.WHAT,MixHash) || nqp::eqaddr($p.WHAT,BagHash) || nqp::eqaddr($p.WHAT,SetHash) ), (my $elems := nqp::if( nqp::istype($p,Baggy), nqp::if( # already have a Baggy, clone (my $raw := $p.RAW-HASH), Rakudo::QuantHash.BAGGY-CLONE($raw), nqp::create(Rakudo::Internals::IterationSet) ), nqp::unless( # something else, Mix it! $p.Set.Mix.RAW-HASH, nqp::create(Rakudo::Internals::IterationSet) ) )), nqp::until( nqp::eqaddr(($p := $params.pull-one),IterationEnd), nqp::if( # not done parsing nqp::istype($p,Baggy), nqp::stmts( # Mixy/Baggy semantics apply nqp::unless( # upgrade type if needed nqp::istype($type,Mix), ($type := nqp::if(nqp::istype($p,Mixy),Mix,Bag)) ), nqp::if( ($raw := $p.RAW-HASH) && (my $iter := nqp::iterator($raw)), nqp::while( # something to process $iter, subtract( $elems, nqp::shift($iter), 1, nqp::getattr(nqp::iterval($iter),Pair,'$!value') ) ) ) ), nqp::stmts( # not a Baggy/Mixy, assume Set ($raw := nqp::if(nqp::istype($p,Setty),$p,$p.Set).RAW-HASH) && ($iter := nqp::iterator($raw)), nqp::while( # something to process $iter, subtract($elems, nqp::shift($iter), 0, 1) ) ) ) ), ($iter := nqp::iterator($elems)), # start post-processing nqp::if( nqp::istype($type,Set), nqp::while( # need to create a Set $iter, nqp::if( nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value') > 0, nqp::bindkey( $elems, nqp::iterkey_s($iter), nqp::getattr(nqp::iterval($iter),Pair,'$!key') ), nqp::deletekey($elems,nqp::iterkey_s($iter)) ) ), nqp::if( nqp::istype($type,Mix), nqp::while( # convert to Mix semantics $iter, nqp::unless( nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value'), nqp::deletekey($elems,nqp::iterkey_s($iter)) # not valid in Mix ) ), nqp::while( # convert to Bag semantics $iter, nqp::unless( nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value') >0, nqp::deletekey($elems,nqp::iterkey_s($iter)) # not valid in Bag ) ) ) ), nqp::if( # set to mutable if so started $mutable, $type := nqp::if( nqp::eqaddr($type,Mix), MixHash, nqp::if(nqp::eqaddr($type,Bag),BagHash,SetHash) ) ), nqp::create($type).SET-SELF($elems) ) ) } # U+2216 SET MINUS my constant &infix:<∖> := &infix:<(-)>; #line 1 SETTING::src/core.c/set_symmetric_difference.rakumod # This test file tests the following set operators: # (^) set symmetric difference (Texas) # ⊖ set symmetric difference proto sub infix:<(^)>(|) is pure {*} multi sub infix:<(^)>() { set() } multi sub infix:<(^)>(QuantHash:D $a) { $a } # Set/Bag/Mix multi sub infix:<(^)>(Setty:D $a, Setty:D $b) { nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( (my \braw := $b.RAW-HASH) && nqp::elems(braw), nqp::stmts( # both are initialized nqp::if( nqp::islt_i(nqp::elems(araw),nqp::elems(braw)), nqp::stmts( # a smallest, iterate over it (my $iter := nqp::iterator(araw)), (my $elems := nqp::clone(braw)) ), nqp::stmts( # b smallest, iterate over that ($iter := nqp::iterator(braw)), ($elems := nqp::clone(araw)) ) ), nqp::while( $iter, nqp::if( # remove if in both nqp::existskey($elems,nqp::iterkey_s(nqp::shift($iter))), nqp::deletekey($elems,nqp::iterkey_s($iter)), nqp::bindkey($elems,nqp::iterkey_s($iter),nqp::iterval($iter)) ) ), nqp::create($a.WHAT).SET-SELF($elems) ), $a # b empty, so a ), nqp::if( # a empty, so b nqp::istype($a,Set), $b.Set, $b.SetHash ) ) } multi sub infix:<(^)>(Setty:D $a, Mixy:D $b) { $a.Mixy (^) $b } multi sub infix:<(^)>(Setty:D $a, Baggy:D $b) { $a.Baggy (^) $b } multi sub infix:<(^)>(Mixy:D $a, Mixy:D $b) { nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( (my \braw := $b.RAW-HASH) && nqp::elems(braw), nqp::stmts( # both are initialized nqp::if( nqp::islt_i(nqp::elems(araw),nqp::elems(braw)), nqp::stmts( # a smallest, iterate over it (my $iter := nqp::iterator(my $base := araw)), (my $elems := nqp::clone(braw)) ), nqp::stmts( # b smallest, iterate over that ($iter := nqp::iterator($base := braw)), ($elems := nqp::clone(araw)) ) ), nqp::while( $iter, nqp::if( nqp::existskey($elems,nqp::iterkey_s(nqp::shift($iter))), nqp::if( (my \diff := nqp::getattr(nqp::iterval($iter),Pair,'$!value') - nqp::getattr( nqp::atkey($elems,nqp::iterkey_s($iter)), Pair, '$!value' ) ), nqp::bindkey( $elems, nqp::iterkey_s($iter), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)),Pair,'$!value',abs(diff) ) ), nqp::deletekey($elems,nqp::iterkey_s($iter)) ), nqp::bindkey( $elems, nqp::iterkey_s($iter), nqp::clone(nqp::iterval($iter)) ) ) ), nqp::create($a.WHAT).SET-SELF($elems) ), nqp::create($a.WHAT).SET-SELF( # b empty, so a Rakudo::QuantHash.MIX-CLONE-ALL-POSITIVE(araw) ) ), nqp::if( (my \raw := $b.RAW-HASH) && nqp::elems(raw), nqp::create($a.WHAT).SET-SELF( # a empty, so b Rakudo::QuantHash.MIX-CLONE-ALL-POSITIVE(raw) ), $a # a and b empty ) ) } multi sub infix:<(^)>(Mixy:D $a, Baggy:D $b) { $a (^) $b.Mix } multi sub infix:<(^)>(Mixy:D $a, Setty:D $b) { $a (^) $b.Mix } multi sub infix:<(^)>(Baggy:D $a, Mixy:D $b) { $a.Mixy (^) $b } multi sub infix:<(^)>(Baggy:D $a, Baggy:D $b) { nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( (my \braw := $b.RAW-HASH) && nqp::elems(braw), nqp::stmts( # both are initialized nqp::if( nqp::islt_i(nqp::elems(araw),nqp::elems(braw)), nqp::stmts( # a smallest, iterate over it (my $iter := nqp::iterator(my $base := araw)), (my $elems := nqp::clone(braw)) ), nqp::stmts( # b smallest, iterate over that ($iter := nqp::iterator($base := braw)), ($elems := nqp::clone(araw)) ) ), nqp::while( $iter, nqp::if( # remove if in both nqp::existskey($elems,nqp::iterkey_s(nqp::shift($iter))), nqp::if( (my int $diff = nqp::sub_i( nqp::getattr(nqp::iterval($iter),Pair,'$!value'), nqp::getattr( nqp::atkey($elems,nqp::iterkey_s($iter)), Pair, '$!value' ) )), nqp::bindkey( $elems, nqp::iterkey_s($iter), nqp::p6bindattrinvres( nqp::clone(nqp::iterval($iter)), Pair, '$!value', nqp::abs_i($diff) ) ), nqp::deletekey($elems,nqp::iterkey_s($iter)) ), nqp::bindkey($elems,nqp::iterkey_s($iter),nqp::iterval($iter)) ) ), nqp::create($a.WHAT).SET-SELF($elems) ), $a # b empty, so a ), nqp::if( # a empty, so b nqp::istype($a,Bag), $b.Bag, $b.BagHash ) ) } multi sub infix:<(^)>(Baggy:D $a, Setty:D $b) { $a (^) $b.Bag } multi sub infix:<(^)>(Map:D \a, Map:D \b) { nqp::if( nqp::elems((my \elems := Rakudo::QuantHash.COERCE-MAP-TO-SET(a))), nqp::if( # $a has elems (my \iter := nqp::iterator(nqp::getattr(nqp::decont(b),Map,'$!storage'))), nqp::stmts( nqp::if( # both have elems nqp::istype(b,Hash::Object), nqp::while( # object hash iter, nqp::if( nqp::getattr(nqp::iterval(nqp::shift(iter)),Pair,'$!value'), nqp::if( # should be checked nqp::existskey(elems,nqp::iterkey_s(iter)), nqp::deletekey(elems,nqp::iterkey_s(iter)),# remove existing nqp::bindkey( # add new elems, nqp::iterkey_s(iter), nqp::getattr(nqp::iterval(iter),Pair,'$!key') ) ) ) ), nqp::while( # ordinary hash iter, nqp::if( nqp::iterval(nqp::shift(iter)), nqp::if( # should be checked nqp::existskey( elems, (my \which := nqp::iterkey_s(iter).WHICH) ), nqp::deletekey(elems,which), # remove existing nqp::bindkey(elems,which,nqp::iterkey_s(iter)) # add new ) ) ) ), nqp::create(Set).SET-SELF(elems) # done ), nqp::create(Set).SET-SELF(elems) # nothing right, so make left ), b.Set # nothing left, coerce right ) } multi sub infix:<(^)>(Failure:D $a, Any) { $a.throw } multi sub infix:<(^)>(Any, Failure:D $b) { $b.throw } multi sub infix:<(^)>(Any \a, Any \b) { nqp::istype(a,Mixy) ?? a (^) b.Mix !! nqp::istype(b,Mixy) ?? a.Mix (^) b !! nqp::istype(a,Baggy) ?? a (^) b.Bag !! nqp::istype(b,Baggy) ?? a.Bag (^) b !! a.Set (^) b.Set } multi sub infix:<(^)>(+@p) { # also Any # positions / size in minmax info my constant COUNT = 0; my constant LOWEST = 1; my constant HIGHEST = 2; my constant SIZE = 3; # basic minmax for new keys my $init-minmax := nqp::setelems(nqp::create(IterationBuffer),SIZE); nqp::bindpos($init-minmax,COUNT,1); # handle key that has been seen before for given value sub handle-existing(Mu \elems, Mu \iter, \value --> Nil) { my \minmax := nqp::getattr( nqp::atkey(elems,nqp::iterkey_s(iter)),Pair,'$!value' ); nqp::bindpos(minmax,COUNT,nqp::add_i(nqp::atpos(minmax,COUNT),1)); nqp::if( value > nqp::atpos(minmax,HIGHEST), nqp::stmts( nqp::bindpos(minmax,LOWEST,nqp::atpos(minmax,HIGHEST)), nqp::bindpos(minmax,HIGHEST,value) ), nqp::if( nqp::not_i(nqp::defined(nqp::atpos(minmax,LOWEST))) || value > nqp::atpos(minmax,LOWEST), nqp::bindpos(minmax,LOWEST,value) ) ); } # handle key that has not yet been seen sub handle-new(Mu \elems, Mu \iter, \pair, \value) { my \minmax := nqp::clone($init-minmax); nqp::bindpos(minmax,HIGHEST,value); nqp::bindkey( elems, nqp::iterkey_s(iter), nqp::p6bindattrinvres(pair,Pair,'$!value',minmax) ) } nqp::if( (my $params := @p.iterator).is-lazy, Any.fail-iterator-cannot-be-lazy('symmetric diff',''), # bye bye nqp::stmts( # fixed list of things to diff (my \elems := nqp::create(Rakudo::Internals::IterationSet)), (my $type := Set), (my int $pseen = 0), nqp::until( nqp::eqaddr((my \p := $params.pull-one),IterationEnd), nqp::stmts( # not done parsing nqp::unless( $pseen, (my $mutable := nqp::eqaddr(p.WHAT,MixHash) || nqp::eqaddr(p.WHAT,BagHash) || nqp::eqaddr(p.WHAT,SetHash) ) ), ++$pseen, nqp::if( nqp::istype(p,Baggy), nqp::stmts( # Mixy/Baggy semantics apply nqp::unless( nqp::istype($type,Mix), ($type := nqp::if(nqp::istype(p,Mixy),Mix,Bag)) ), nqp::if( (my $raw := p.RAW-HASH) && (my $iter := nqp::iterator($raw)), nqp::stmts( # something to process nqp::while( $iter, nqp::if( nqp::existskey( elems, nqp::iterkey_s(nqp::shift($iter)) ), handle-existing( # seen this element before elems, $iter, nqp::getattr(nqp::iterval($iter),Pair,'$!value') ), handle-new( # new element elems, $iter, nqp::clone(nqp::iterval($iter)), nqp::getattr(nqp::iterval($iter),Pair,'$!value') ) ) ) ) ) ), nqp::stmts( # not a Baggy/Mixy, assume Set ($raw := nqp::if(nqp::istype(p,Setty),p,p.Set).RAW-HASH) && ($iter := nqp::iterator($raw)), nqp::while( # something to process $iter, nqp::if( nqp::existskey(elems,nqp::iterkey_s(nqp::shift($iter))), handle-existing( # seen this element before elems, $iter, 1 ), handle-new( # new element elems, $iter, nqp::p6bindattrinvres( nqp::create(Pair),Pair,'$!key',nqp::iterval($iter)), 1 ) ) ) ) ) ) ), ($iter := nqp::iterator(elems)), # start post-processing nqp::if( nqp::istype($type,Set), nqp::while( # need to create a Set $iter, nqp::if( nqp::ifnull( nqp::atpos( nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value'), LOWEST ), 0 ) == 1, nqp::deletekey(elems,nqp::iterkey_s($iter)), # seen > 1 nqp::bindkey( # only once elems, # convert to nqp::iterkey_s($iter), # Setty format nqp::getattr(nqp::iterval($iter),Pair,'$!key') ) ) ), nqp::if( nqp::istype($type,Mix), nqp::while( # convert to Mixy semantics $iter, nqp::stmts( (my $minmax := nqp::getattr(nqp::iterval(nqp::shift($iter)),Pair,'$!value')), nqp::if( nqp::islt_i(nqp::atpos($minmax,COUNT),$pseen), handle-existing(elems,$iter,0) # absentee == value 0 seen ), nqp::if( nqp::ifnull(nqp::atpos($minmax,LOWEST),0) == nqp::atpos($minmax,HIGHEST), nqp::deletekey(elems,nqp::iterkey_s($iter)), # top 2 same nqp::bindattr( # there's a nqp::iterval($iter), # difference Pair, # so convert '$!value', nqp::atpos($minmax,HIGHEST) - nqp::ifnull(nqp::atpos($minmax,LOWEST),0) ) ) ) ), nqp::while( # convert to Baggy semantics $iter, nqp::if( nqp::ifnull( nqp::atpos( ($minmax := nqp::getattr( nqp::iterval(nqp::shift($iter)),Pair,'$!value')), LOWEST ), 0 ) == nqp::atpos($minmax,HIGHEST), nqp::deletekey(elems,nqp::iterkey_s($iter)), # top 2 same nqp::bindattr( # there's a nqp::iterval($iter), # difference Pair, # so convert '$!value', nqp::atpos($minmax,HIGHEST) - nqp::ifnull(nqp::atpos($minmax,LOWEST),0) ) ) ) ) ), nqp::if( # set to mutable if so started $mutable, $type := nqp::if( nqp::eqaddr($type,Mix), MixHash, nqp::if(nqp::eqaddr($type,Bag),BagHash,SetHash) ) ), nqp::create($type).SET-SELF(elems) ) ) } # U+2296 CIRCLED MINUS my constant &infix:<⊖> := &infix:<(^)>; #line 1 SETTING::src/core.c/set_equality.rakumod # This file implements the following set operators: # (==) set equality (ASCII) # ≡ is identical to # ≢ is not identical to proto sub infix:<<(==)>>($, $, *% --> Bool:D) is pure {*} multi sub infix:<<(==)>>(Setty:D $a, Setty:D $b --> Bool:D) { nqp::unless( nqp::eqaddr($a,$b), nqp::stmts( # A and B not same object (my \araw := $a.RAW-HASH), (my \braw := $b.RAW-HASH), nqp::if( araw && braw, nqp::if( # A and B both allocated nqp::isne_i(nqp::elems(araw),nqp::elems(braw)), (return False), # not same number of elems nqp::stmts( # same number of elems in A and B (my \iter := nqp::iterator(araw)), nqp::while( # have something to iterate over iter, nqp::unless( nqp::existskey(braw,nqp::iterkey_s(nqp::shift(iter))), return False # elem in A doesn't exist in B ) ) ) ), nqp::if( # A and B not both allocated (araw && nqp::elems(araw)) || (braw && nqp::elems(braw)), return False # allocated side contains elements ) ) ) ); True } multi sub infix:<<(==)>>(Setty:D $a, Mixy:D $b --> Bool:D) { $a.Mix (==) $b } multi sub infix:<<(==)>>(Setty:D $a, Baggy:D $b --> Bool:D) { $a.Bag (==) $b } multi sub infix:<<(==)>>(Setty:D $a, Any \b --> Bool:D) { $a (==) b.Set } multi sub infix:<<(==)>>(Mixy:D $a, Mixy:D $b --> Bool:D) { Rakudo::QuantHash.MIX-IS-EQUAL($a, $b) } multi sub infix:<<(==)>>(Mixy:D $a, Baggy:D $b --> Bool:D) { Rakudo::QuantHash.MIX-IS-EQUAL($a, $b) } multi sub infix:<<(==)>>(Mixy:D $a, Setty:D $b --> Bool:D) { $a (==) $b.Mix } multi sub infix:<<(==)>>(Mixy:D $a, Any \b --> Bool:D) { $a (==) b.Mix } multi sub infix:<<(==)>>(Baggy:D $a, Mixy:D $b --> Bool:D) { Rakudo::QuantHash.MIX-IS-EQUAL($a, $b) } multi sub infix:<<(==)>>(Baggy:D $a, Baggy:D $b --> Bool:D) { Rakudo::QuantHash.MIX-IS-EQUAL($a, $b) } multi sub infix:<<(==)>>(Baggy:D $a, Setty:D $b --> Bool:D) { $a (==) $b.Bag } multi sub infix:<<(==)>>(Baggy:D $a, Any \b --> Bool:D) { $a (==) b.Bag } multi sub infix:<<(==)>>(Map:D \a, Map:D \b --> Bool:D) { nqp::unless( nqp::eqaddr(nqp::decont(a),nqp::decont(b)), nqp::if( # A and B are different nqp::isne_i( nqp::elems(my \araw := nqp::getattr(nqp::decont(a),Map,'$!storage')), nqp::elems(my \braw := nqp::getattr(nqp::decont(b),Map,'$!storage')) ), (return False), # different number of elements nqp::if( # same size nqp::istype(a,Hash::Object) || nqp::istype(b,Hash::Object), (return a.Set (==) b.Set), # either is objectHash, so coerce nqp::stmts( # both are normal Maps (my \iter := nqp::iterator(araw)), nqp::while( iter, nqp::unless( nqp::iseq_i( nqp::istrue(nqp::iterval(nqp::shift(iter))), nqp::istrue(nqp::atkey(braw,nqp::iterkey_s(iter))) ), (return False) # elem in A hasn't got same validity in B ) ) ) ) ) ); True } multi sub infix:<<(==)>>(Iterable:D \a, Map:D \b --> Bool:D) { my \iterator := a.iterator; my \braw := nqp::getattr(nqp::decont(b),Map,'$!storage'); return False # can never find all values if nqp::istype(iterator,PredictiveIterator) && iterator.count-only < nqp::elems(braw); my $key; my $seen := nqp::hash; nqp::if( nqp::istype(b,Hash::Object), nqp::until( # object hash nqp::eqaddr((my \object := iterator.pull-one),IterationEnd), nqp::if( nqp::istrue( nqp::getattr( nqp::ifnull( nqp::atkey(braw,$key := object.WHICH), BEGIN # provide virtual value 0 nqp::p6bindattrinvres(nqp::create(Pair),Pair,'$!value',0) ), Pair, '$!value' ) ), nqp::bindkey($seen,$key,1), (return False) # not seen or not true ) ), nqp::until( # normal Map nqp::eqaddr(($key := iterator.pull-one),IterationEnd), nqp::if( nqp::istrue(nqp::atkey(braw,$key)), nqp::bindkey($seen,$key,1), (return False) # not seen or not true ) ) ); nqp::hllbool(nqp::iseq_i(nqp::elems($seen),nqp::elems(braw))) } multi sub infix:<<(==)>>(Any \a, Mixy:D $b --> Bool:D) { a.Mix (==) $b } multi sub infix:<<(==)>>(Any \a, Baggy:D $b --> Bool:D) { a.Bag (==) $b } multi sub infix:<<(==)>>(Any \a, Setty:D $b --> Bool:D) { a.Set (==) $b } multi sub infix:<<(==)>>(Failure:D $a, Any) { $a.throw } multi sub infix:<<(==)>>(Any, Failure:D $b) { $b.throw } multi sub infix:<<(==)>>(Any \a, Any \b --> Bool:D) { a.Set (==) b.Set } # U+2261 IDENTICAL TO my constant &infix:<≡> := &infix:<<(==)>>; # U+2262 NOT IDENTICAL TO proto sub infix:<≢>($, $, *%) is pure {*} multi sub infix:<≢>(\a, \b --> Bool:D) { not a (==) b } #line 1 SETTING::src/core.c/set_subset.rakumod # This file implements the following set operators: # (<=) is a subset of (ASCII) # ⊆ is a subset of # ⊈ is NOT a subset of # (>=) is a superset of (ASCII) # ⊇ is a superset of # ⊉ is NOT a superset of proto sub infix:<<(<=)>>($, $, *% --> Bool:D) is pure {*} multi sub infix:<<(<=)>>(Setty:D $a, Setty:D $b --> Bool:D) { nqp::unless( nqp::eqaddr($a,$b), nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( # number of elems in B *always* >= A (my \braw := $b.RAW-HASH) && nqp::isle_i(nqp::elems(araw),nqp::elems(braw)) && (my \iter := nqp::iterator(araw)), nqp::while( # number of elems in B >= A iter, nqp::unless( nqp::existskey(braw,nqp::iterkey_s(nqp::shift(iter))), return False # elem in A doesn't exist in B ) ), return False # number of elems in B smaller than A ) ) ); True } multi sub infix:<<(<=)>>(Setty:D $a, Mixy:D $b --> Bool:D) { $a.Mix (<=) $b } multi sub infix:<<(<=)>>(Setty:D $a, Baggy:D $b --> Bool:D) { $a.Bag (<=) $b } multi sub infix:<<(<=)>>(Setty:D $a, Any \b --> Bool:D) { $a (<=) b.Set } multi sub infix:<<(<=)>>(Mixy:D $a, Mixy:D $b --> Bool:D) { Rakudo::QuantHash.MIX-IS-SUBSET($a, $b) } multi sub infix:<<(<=)>>(Mixy:D $a, Baggy:D $b --> Bool:D) { Rakudo::QuantHash.MIX-IS-SUBSET($a, $b) } multi sub infix:<<(<=)>>(Mixy:D $a, Setty:D $b --> Bool:D) { $a (<=) $b.Mix } multi sub infix:<<(<=)>>(Mixy:D $a, Any \b --> Bool:D) { $a (<=) b.Mix } multi sub infix:<<(<=)>>(Baggy:D $a, Mixy:D $b --> Bool:D) { Rakudo::QuantHash.MIX-IS-SUBSET($a, $b) } multi sub infix:<<(<=)>>(Baggy:D $a, Baggy:D $b --> Bool:D) { nqp::unless( nqp::eqaddr($a,$b), nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( # number of elems in B *always* >= A (my \braw := $b.RAW-HASH) && nqp::isle_i(nqp::elems(araw),nqp::elems(braw)) && (my \iter := nqp::iterator(araw)), nqp::while( # number of elems in B >= A iter, nqp::unless( nqp::getattr(nqp::iterval(nqp::shift(iter)),Pair,'$!value') <= # value in A should be less or equal than B nqp::getattr( nqp::ifnull( nqp::atkey(braw,nqp::iterkey_s(iter)), BEGIN # provide virtual value 0 nqp::p6bindattrinvres(nqp::create(Pair),Pair,'$!value',0) ), Pair, '$!value' ), return False ) ), return False # number of elems in B smaller than A ) ) ); True } multi sub infix:<<(<=)>>(Baggy:D $a, Setty:D $b --> Bool:D) { $a (<=) $b.Bag } multi sub infix:<<(<=)>>(Baggy:D $a, Any \b --> Bool:D) { $a (<=) b.Bag } multi sub infix:<<(<=)>>(Map:D \a, Map:D \b --> Bool:D) { nqp::if( nqp::eqaddr(nqp::decont(a),nqp::decont(b)), True, # B is alias of A nqp::if( # A and B are different nqp::elems(my \araw := nqp::getattr(nqp::decont(a),Map,'$!storage')), nqp::if( # something in A nqp::istype(a,Hash::Object) || nqp::istype(b,Hash::Object), (a.Set (<=) b.Set), # either is objectHash, so coerce nqp::if( # both are normal Maps (my \iter := nqp::iterator(araw)) && nqp::elems( my \braw := nqp::getattr(nqp::decont(b),Map,'$!storage') ), nqp::stmts( # something to check for in B nqp::while( iter, nqp::if( nqp::iterval(nqp::shift(iter)), nqp::unless( # valid in A nqp::atkey(braw,nqp::iterkey_s(iter)), return False # valid elem in A isn't valid elem in B ) ) ), True # all valids in A occur as valids in B ), nqp::stmts( # nothing to check for in B nqp::while( iter, nqp::if( nqp::iterval(nqp::shift(iter)), return False # valid in elem in A (and none in B) ) ), True # no valid elems in A ) ) ), True # nothing in A ) ) } multi sub infix:<<(<=)>>(Iterable:D \a, Map:D \b --> Bool:D) { my \iterator := a.iterator; my \braw := nqp::getattr(nqp::decont(b),Map,'$!storage'); if nqp::istype(b,Hash::Object) { nqp::until( nqp::eqaddr((my \object := iterator.pull-one),IterationEnd), nqp::unless( nqp::existskey(braw,my str $key = object.WHICH) && nqp::istrue( nqp::getattr(nqp::atkey(braw,$key),Pair,'$!value') ), (return False) ) ); } else { nqp::until( nqp::eqaddr((my \string := iterator.pull-one),IterationEnd), nqp::unless( nqp::existskey(braw,my str $key = string.Str) && nqp::istrue(nqp::atkey(braw,$key)), (return False) ) ); } True } multi sub infix:<<(<=)>>(Any \a, Mixy:D $b --> Bool:D) { a.Mix (<=) $b } multi sub infix:<<(<=)>>(Any \a, Baggy:D $b --> Bool:D) { a.Bag (<=) $b } multi sub infix:<<(<=)>>(Any \a, Setty:D $b --> Bool:D) { a.Set (<=) $b } multi sub infix:<<(<=)>>(Failure:D $a, Any $) { $a.throw } multi sub infix:<<(<=)>>(Any $, Failure:D $b) { $b.throw } multi sub infix:<<(<=)>>(Any \a, Any \b --> Bool:D) { a.Set (<=) b.Set } # U+2286 SUBSET OF OR EQUAL TO my constant &infix:<⊆> := &infix:<<(<=)>>; # U+2288 NEITHER A SUBSET OF NOR EQUAL TO proto sub infix:<⊈>($, $, *%) is pure {*} multi sub infix:<⊈>(\a, \b --> Bool:D) { not a (<=) b } proto sub infix:<<(>=)>>($, $, *%) is pure {*} multi sub infix:<<(>=)>>(\a, \b --> Bool:D) { b (<=) a } # U+2287 SUPERSET OF OR EQUAL TO proto sub infix:<⊇>($, $, *%) is pure {*} multi sub infix:<⊇>(\a, \b --> Bool:D) { b (<=) a } # U+2289 NEITHER A SUPERSET OF NOR EQUAL TO proto sub infix:<⊉>($, $, *%) is pure {*} multi sub infix:<⊉>(\a, \b --> Bool:D) { not b (<=) a } #line 1 SETTING::src/core.c/set_proper_subset.rakumod # This file implements the following set operators: # (<) is a proper subset of (ASCII) # ⊂ is a proper subset of # ⊄ is NOT a proper subset of # (>) is a proper superset of (ASCII) # ⊃ is a proper superset of # ⊅ is NOT a proper superset of proto sub infix:<<(<)>>($, $, *% --> Bool:D) is pure {*} multi sub infix:<<(<)>>(Setty:D $a, Setty:D $b --> Bool:D) { nqp::if( nqp::eqaddr($a,$b), False, # X is never a true subset of itself nqp::if( (my $braw := $b.RAW-HASH) && nqp::elems($braw), nqp::if( (my $araw := $a.RAW-HASH) && nqp::elems($araw), nqp::if( nqp::islt_i(nqp::elems($araw),nqp::elems($braw)) && (my $iter := nqp::iterator($araw)), nqp::stmts( # A has fewer elems than B nqp::while( $iter, nqp::unless( nqp::existskey($braw,nqp::iterkey_s(nqp::shift($iter))), return False # elem in A doesn't exist in B ) ), True # all elems in A exist in B ), False # number of elems in B smaller or equal to A ), True # no elems in A, and elems in B ), False # can never have fewer elems in A than in B ) ) } multi sub infix:<<(<)>>(Setty:D $a, Mixy:D $b --> Bool:D) { $a.Mix (<) $b } multi sub infix:<<(<)>>(Setty:D $a, Baggy:D $b --> Bool:D) { $a.Bag (<) $b } multi sub infix:<<(<)>>(Setty:D $a, Any \b --> Bool:D) { $a (<) b.Set } multi sub infix:<<(<)>>(Mixy:D $a, Mixy:D $b --> Bool:D) { Rakudo::QuantHash.MIX-IS-PROPER-SUBSET($a,$b) } multi sub infix:<<(<)>>(Mixy:D $a, Baggy:D $b --> Bool:D) { Rakudo::QuantHash.MIX-IS-PROPER-SUBSET($a,$b) } multi sub infix:<<(<)>>(Mixy:D $a, Any \b --> Bool:D) { $a (<) b.Mix } multi sub infix:<<(<)>>(Baggy:D $a, Mixy:D $b --> Bool:D) { Rakudo::QuantHash.MIX-IS-PROPER-SUBSET($a,$b) } multi sub infix:<<(<)>>(Baggy:D $a, Baggy:D $b --> Bool:D) { nqp::if( nqp::eqaddr($a,$b), False, # never proper subset of self nqp::if( # different objects (my \araw := $a.RAW-HASH) && (my \iter := nqp::iterator(araw)), nqp::if( # elements on left (my \braw := $b.RAW-HASH) && nqp::elems(braw), nqp::if( # elements on both sides nqp::isle_i(nqp::elems(araw),nqp::elems(braw)), nqp::stmts( # equal number of elements on either side (my int $less = 0), nqp::while( iter, nqp::if( (my \left := nqp::getattr( nqp::iterval(nqp::shift(iter)), Pair, '$!value' )) > (my \right := nqp::getattr( nqp::ifnull( nqp::atkey(braw,nqp::iterkey_s(iter)), BEGIN nqp::p6bindattrinvres( # virtual 0 nqp::create(Pair),Pair,'$!value',0) ), Pair, '$!value' )), (return False), # too many on left, we're done nqp::unless($less,$less = left < right) ) ), nqp::hllbool( # ok so far, must have lower total or fewer keys $less || nqp::islt_i(nqp::elems(araw),nqp::elems(braw)) ) ), False # more keys on left ), False # keys on left, no keys on right ), nqp::hllbool( # no keys on left (my \raw := $b.RAW-HASH) ?? nqp::elems(raw) !! 0 ) ) ) } multi sub infix:<<(<)>>(Baggy:D $a, Any \b --> Bool:D) { $a (<) b.Bag } multi sub infix:<<(<)>>(Any \a, Mixy:D $b --> Bool:D) { a.Mix (<) $b } multi sub infix:<<(<)>>(Any \a, Baggy:D $b --> Bool:D) { a.Bag (<) $b } multi sub infix:<<(<)>>(Failure:D $a, Any) { $a.throw } multi sub infix:<<(<)>>(Any, Failure:D $b) { $b.throw } multi sub infix:<<(<)>>(Any \a, Any \b --> Bool:D) { a.Set (<) b.Set } # U+2282 SUBSET OF my constant &infix:<⊂> := &infix:<<(<)>>; # U+2284 NOT A SUBSET OF proto sub infix:<⊄>($, $, *%) is pure {*} multi sub infix:<⊄>(\a, \b --> Bool:D) { not a (<) b } proto sub infix:<<(>)>>($, $, *%) is pure {*} multi sub infix:<<(>)>>(\a, \b --> Bool:D) { b (<) a } # U+2283 SUPERSET OF proto sub infix:<⊃>($, $, *%) is pure {*} multi sub infix:<⊃>(\a, \b --> Bool:D) { b (<) a } # U+2285 NOT A SUPERSET OF proto sub infix:<⊅>($, $, *%) is pure {*} multi sub infix:<⊅>(\a, \b --> Bool:D) { not b (<) a } #line 1 SETTING::src/core.c/set_multiply.rakumod # This file implements the following set operators: # (.) set multiplication (ASCII) # ⊍ set multiplication proto sub infix:<(.)>(|) is pure {*} multi sub infix:<(.)>() { bag() } multi sub infix:<(.)>(Setty:D $a) { $a.Baggy } multi sub infix:<(.)>(Baggy:D $a) { $a } # also Mixy multi sub infix:<(.)>(Setty:D $a, Setty:D $b) { (my $elems := $a.Bag.RAW-HASH) && nqp::elems($elems) ?? nqp::create($a.WHAT.Baggy).SET-SELF( Rakudo::QuantHash.MULTIPLY-SET-TO-BAG($elems,$b.RAW-HASH), ) !! $a.Baggy } multi sub infix:<(.)>(Mixy:D $a, Mixy:D $b) { nqp::if( (my $elems := Rakudo::QuantHash.BAGGY-CLONE-RAW($a.RAW-HASH)) && nqp::elems($elems), nqp::stmts( Rakudo::QuantHash.MULTIPLY-MIX-TO-MIX($elems,$b.RAW-HASH), nqp::create($a.WHAT).SET-SELF($elems) ), $a ) } multi sub infix:<(.)>(Mixy:D $a, Baggy:D $b) { infix:<(.)>($a, $b.Mix) } multi sub infix:<(.)>(Mixy:D $a, Any \b) { infix:<(.)>($a, b.Mix) } multi sub infix:<(.)>(Setty:D $a, Mixy:D $b) { infix:<(.)>($a.Mixy, $b) } multi sub infix:<(.)>(Baggy:D $a, Mixy:D $b) { infix:<(.)>($a.Mixy, $b) } multi sub infix:<(.)>(Any \a, Mixy:D $b) { infix:<(.)>( a.Mix, $b) } multi sub infix:<(.)>(Baggy:D $a, Baggy:D $b) { (my $elems := Rakudo::QuantHash.BAGGY-CLONE-RAW($a.RAW-HASH)) && nqp::elems($elems) ?? nqp::create($a.WHAT).SET-SELF( Rakudo::QuantHash.MULTIPLY-BAG-TO-BAG($elems,$b.RAW-HASH), ) !! $a } multi sub infix:<(.)>(Any, Failure:D $b) { $b.throw } multi sub infix:<(.)>(Failure:D $a, Any) { $a.throw } # Note that we cannot create a Setty|Baggy:D,Any candidate because that will # result in an ambiguous dispatch, so we need to hack a check for Setty|Baggy # in here. multi sub infix:<(.)>(Any \a, Any \b) { infix:<(.)>( nqp::isconcrete(a) ?? nqp::istype(a,Setty) ?? a.Baggy !! nqp::istype(a,Baggy) ?? a !! a.Bag !! a.Bag, b.Bag ) } multi sub infix:<(.)>(+@p) { # also Any my $iterator := @p.iterator; nqp::if( nqp::eqaddr((my $result := $iterator.pull-one),IterationEnd), bag(), # nothing to process nqp::if( nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), $result.Bag, # only 1 elem to process nqp::stmts( nqp::repeat_until( nqp::eqaddr(($pulled := $iterator.pull-one),IterationEnd), ($result := $result (.) $pulled) ), $result ) ) ) } # U+228D MULTISET MULTIPLICATION my constant &infix:<⊍> := &infix:<(.)>; #line 1 SETTING::src/core.c/set_addition.rakumod # This file implements the following set operators: # (+) baggy addition (ASCII) # ⊎ baggy addition proto sub infix:<(+)>(|) is pure {*} multi sub infix:<(+)>() { bag() } multi sub infix:<(+)>(Bag:D $a) { $a } multi sub infix:<(+)>(Mix:D $a) { $a } multi sub infix:<(+)>(MixHash:D $a) { $a.Mix } multi sub infix:<(+)>(Setty:D $a, QuantHash:D $b) { nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( # elems on left (my \braw := $b.RAW-HASH) && nqp::elems(braw), nqp::stmts( # elems on both sides (my \elems := Rakudo::QuantHash.SET-BAGGIFY(araw)), nqp::create( nqp::if( nqp::istype($b,Mixy),$a.WHAT.Mixy,$a.WHAT.Baggy) ).SET-SELF( nqp::if( nqp::istype($b,Mixy), Rakudo::QuantHash.ADD-MIX-TO-MIX(elems, braw), nqp::if( nqp::istype($b,Baggy), Rakudo::QuantHash.ADD-BAG-TO-BAG(elems, braw), Rakudo::QuantHash.ADD-SET-TO-BAG(elems, braw) ) ) ) ), nqp::if(nqp::istype($b,Mixy),$a.Mixy,$a.Baggy) # no elems on right ), nqp::if( # no elems left/either nqp::istype($a,Set), nqp::if(nqp::istype($b,Mixy),$b.Mix, $b.Bag), nqp::if(nqp::istype($b,Mixy),$b.MixHash,$b.BagHash) ) ) } multi sub infix:<(+)>(Setty:D $a, Map:D \b) { nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( # elems on left nqp::elems(nqp::getattr(nqp::decont(b),Map,'$!storage')), nqp::create( nqp::if(nqp::istype($a,Set),Bag,BagHash) ).SET-SELF( # elems on both sides Rakudo::QuantHash.ADD-MAP-TO-BAG( Rakudo::QuantHash.SET-BAGGIFY(araw), b ) ), $a.Baggy # no elems on right ), nqp::if(nqp::istype($a,Set),b.Bag,b.BagHash ) # no elems left/either ) } multi sub infix:<(+)>(Mixy:D $a, QuantHash:D $b) { nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( # elems on left (my \braw := $b.RAW-HASH) && nqp::elems(braw), nqp::stmts( # elems on both sides (my \elems := Rakudo::QuantHash.BAGGY-CLONE(araw)), nqp::create($a.WHAT).SET-SELF( nqp::if( nqp::istype($b,Baggy), Rakudo::QuantHash.ADD-MIX-TO-MIX(elems, braw), Rakudo::QuantHash.ADD-SET-TO-MIX(elems, braw) ) ) ), $a # no elems on right ), nqp::if(nqp::istype($a,Mix),$b.Mix,$b.MixHash ) # no elems left/either ) } multi sub infix:<(+)>(Baggy:D $a, QuantHash:D $b) { nqp::if( (my \araw := $a.RAW-HASH) && nqp::elems(araw), nqp::if( # elems on left (my \braw := $b.RAW-HASH) && nqp::elems(braw), nqp::stmts( # elems on both sides (my \elems := Rakudo::QuantHash.BAGGY-CLONE(araw)), nqp::create( nqp::if( nqp::istype($b,Mixy), nqp::if(nqp::istype($a,Bag),Mix,MixHash), nqp::if(nqp::istype($a,Bag),$a.WHAT,BagHash) ) ).SET-SELF( nqp::if( nqp::istype($b,Mixy), Rakudo::QuantHash.ADD-MIX-TO-MIX(elems, braw), nqp::if( nqp::istype($b,Baggy), Rakudo::QuantHash.ADD-BAG-TO-BAG(elems, braw), Rakudo::QuantHash.ADD-SET-TO-BAG(elems, braw) ) ) ) ), nqp::if(nqp::istype($b,Mixy),$a.Mixy,$a) # no elems on right ), nqp::if( # no elems left/either nqp::istype($a,Mix) || nqp::istype($a,Bag), nqp::if( nqp::istype($b,Mixy),$b.Mix, $b.Bag), nqp::if( nqp::istype($b,Mixy),$b.MixHash,$b.BagHash) ) ) } multi sub infix:<(+)>(Map:D \a, Map:D \b) { nqp::if( nqp::elems(nqp::getattr(nqp::decont(a),Map,'$!storage')), nqp::if( # elems on left nqp::elems(nqp::getattr(nqp::decont(b),Map,'$!storage')), nqp::create(Bag).SET-SELF( # elems on both sides Rakudo::QuantHash.ADD-MAP-TO-BAG( Rakudo::QuantHash.COERCE-MAP-TO-BAG(a), b ) ), a.Bag # no elems on right ), b.Bag # no elems left/either ) } multi sub infix:<(+)>(Iterable:D \a, Iterable:D \b) { nqp::create(Bag).SET-SELF( Rakudo::QuantHash.ADD-PAIRS-TO-BAG( Rakudo::QuantHash.ADD-PAIRS-TO-BAG( nqp::create(Rakudo::Internals::IterationSet), a.iterator, Mu ), b.iterator, Mu ) ) } multi sub infix:<(+)>(Any, Failure:D $b) { $b.throw } multi sub infix:<(+)>(Failure:D $a, Any) { $a.throw } multi sub infix:<(+)>(Any \a, Any \b) { nqp::if( nqp::istype(a,QuantHash) && nqp::isconcrete(a), nqp::if( nqp::istype(a,Mixy) || nqp::istype(b,Mixy), infix:<(+)>(a.Mixy, b.Mix(:view)), # :view is implementation-detail infix:<(+)>(a.Baggy, b.Bag(:view)) # :view is implementation-detail ), nqp::if( nqp::istype(a,Mixy) || nqp::istype(b,Mixy), infix:<(+)>(a.Mix, b.Mix(:view)), # :view is implementation-detail infix:<(+)>(a.Bag, b.Bag(:view)) # :view is implementation-detail ) ) } multi sub infix:<(+)>(+@p) { # also Any my $iterator := @p.iterator; nqp::if( nqp::eqaddr((my $result := $iterator.pull-one),IterationEnd), bag(), # nothing to process nqp::if( nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd), $result.Bag, # only 1 elem to process nqp::stmts( nqp::repeat_until( nqp::eqaddr(($pulled := $iterator.pull-one),IterationEnd), ($result := $result (+) $pulled) ), $result ) ) ) } # U+228E MULTISET UNION my constant &infix:<⊎> := &infix:<(+)>; #line 1 SETTING::src/core.c/set_precedes.rakumod # This file implements the following set operators: # (<+) precedes (ASCII) # ≼ precedes # (>+) succeeds (ASCII) # ≽ succeeds proto sub infix:<<(<+)>>($, $, *% --> Bool:D) is pure { die if $*FOLDING; # not going to constant fold something that's deprecated Rakudo::Deprecations.DEPRECATED( "set operator {$*INSTEAD // "(<=)"}", "", "6.d", :what("Set operator {$*WHAT // "(<+)"}"), :up( 1 + ?$*WHAT ) ) unless $*INTERNAL; {*} } multi sub infix:<<(<+)>>(Setty:D $a, QuantHash:D $b --> Bool:D) { nqp::if( (my \araw := $a.RAW-HASH), nqp::if( (my \braw := $b.RAW-HASH) && nqp::isge_i(nqp::elems(braw),nqp::elems(araw)), nqp::stmts( (my \iter := nqp::iterator(araw)), nqp::while( iter && nqp::existskey(braw,nqp::iterkey_s(nqp::shift(iter))), nqp::null ), nqp::hllbool(nqp::isfalse(iter)) ), False ), True ) } multi sub infix:<<(<+)>>(Mixy:D $a, Baggy:D $b --> Bool:D) { nqp::if( (my \araw := $a.RAW-HASH), nqp::if( (my \braw:= $b.RAW-HASH) && nqp::isge_i(nqp::elems(braw),nqp::elems(araw)), nqp::stmts( (my \iter := nqp::iterator(araw)), nqp::while( iter, nqp::if( nqp::not_i(nqp::existskey( braw, (my \key := nqp::iterkey_s(nqp::shift(iter))) )) || nqp::getattr(nqp::decont(nqp::atkey(araw,key)),Pair,'$!value') > nqp::getattr(nqp::decont(nqp::atkey(braw,key)),Pair,'$!value'), (return False) ) ), True ), False ), True ) } multi sub infix:<<(<+)>>(Baggy:D $a, Baggy:D $b --> Bool:D) { nqp::if( (my \araw := $a.RAW-HASH), nqp::if( (my \braw := $b.RAW-HASH) && nqp::isge_i(nqp::elems(braw),nqp::elems(araw)), nqp::stmts( (my \iter := nqp::iterator(araw)), nqp::while( iter, nqp::if( nqp::not_i(nqp::existskey( braw, (my \key := nqp::iterkey_s(nqp::shift(iter))) )) || nqp::isgt_i( nqp::getattr(nqp::decont(nqp::atkey(araw,key)),Pair,'$!value'), nqp::getattr(nqp::decont(nqp::atkey(braw,key)),Pair,'$!value') ), (return False) ) ), True ), False ), True ) } multi sub infix:<<(<+)>>(QuantHash:U $a, QuantHash:U $b --> True ) { } multi sub infix:<<(<+)>>(QuantHash:U $a, QuantHash:D $b --> True ) { } multi sub infix:<<(<+)>>(QuantHash:D $a, QuantHash:U $b --> Bool:D ) { not $a.elems } multi sub infix:<<(<+)>>(QuantHash:D $a, QuantHash:D $b --> Bool:D ) { return False if $a.AT-KEY($_) > $b.AT-KEY($_) for $a.keys; True } multi sub infix:<<(<+)>>(Any, Failure:D $b) { $b.throw } multi sub infix:<<(<+)>>(Failure:D $a, Any) { $a.throw } multi sub infix:<<(<+)>>(Any \a, Any \b --> Bool:D) { my $*INTERNAL = 1; nqp::istype(a,Mixy) || nqp::istype(b,Mixy) ?? infix:<<(<+)>>(a.Mix, b.Mix) !! infix:<<(<+)>>(a.Bag, b.Bag) } # U+227C PRECEDES OR EQUAL TO proto sub infix:<≼>($, $, *%) is pure {*} multi sub infix:<≼>($a, $b --> Bool:D) { my $*WHAT = "≼"; my $*INSTEAD = "⊆"; infix:<<(<+)>>($a, $b) } # $a (>+) $b === $a R(<+) $b proto sub infix:<<(>+)>>($, $, *%) is pure {*} multi sub infix:<<(>+)>>($a, $b --> Bool:D) { my $*WHAT = "(>+)"; my $*INSTEAD = "(>=)"; infix:<<(<+)>>($b, $a) } # U+227D SUCCEEDS OR EQUAL TO proto sub infix:<≽>($, $, *%) is pure {*} multi sub infix:<≽>($a, $b --> Bool:D) { my $*WHAT = "≽"; my $*INSTEAD = "⊇"; infix:<<(<+)>>($b, $a) } #line 1 SETTING::src/core.c/ObjAt.rakumod my class ObjAt { # declared in BOOTSTRAP # class ObjAt is Any # has str $!value; method new(str $s) { nqp::box_s($s, self.WHAT) } multi method WHICH(ObjAt:D: --> ObjAt:D) { nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,ObjAt), 'ObjAt|', nqp::concat(nqp::unbox_s(self.^name), '|') ), $!value ), self.WHAT ) } multi method Str(ObjAt:D:) { nqp::p6box_s(nqp::unbox_s(self)); } multi method gist(ObjAt:D:) { nqp::p6box_s(nqp::unbox_s(self)); } multi method raku(ObjAt:D:) { self.^name ~ ".new(" ~ nqp::p6box_s(nqp::unbox_s(self)).raku ~ ")" } } my class ValueObjAt { # declared in BOOTSTRAP # class ValueObjAt is ObjAt } multi sub infix:(ObjAt:D $a, ObjAt:D $b --> Bool:D) { nqp::hllbool( nqp::eqaddr(nqp::decont($a),nqp::decont($b)) || (nqp::eqaddr($a.WHAT,$b.WHAT) && nqp::iseq_s($a,$b)) ) } #line 1 SETTING::src/core.c/Version.rakumod my class Version { # class Version { # has $!parts; # has int $!plus; # has str $!string; # } # Define a constant string for Whatever so that we can use # nqp::eqaddr to see whether a part of the version is a Whatever # without actually using the Whatever type object. my constant star = '*'; my constant $v = do { my $version := nqp::create(Version); nqp::bindattr( $version,Version,'$!parts', nqp::list()); nqp::bindattr_s($version,Version,'$!string',""); $version } my constant $vstar = do { my $version := nqp::create(Version); nqp::bindattr( $version,Version,'$!parts', nqp::list(star)); nqp::bindattr_i($version,Version,'$!plus', -1); nqp::bindattr_s($version,Version,'$!string', '*'); $version } my constant $v6 = do { my $version := nqp::create(Version); nqp::bindattr( $version,Version,'$!parts', nqp::list(6)); nqp::bindattr_s($version,Version,'$!string',"6"); $version } my constant $v6c = do { my $version := nqp::create(Version); nqp::bindattr( $version,Version,'$!parts', nqp::list(6,"c")); nqp::bindattr_s($version,Version,'$!string',"6.c"); $version } my constant $v6d = do { my $version := nqp::create(Version); nqp::bindattr( $version,Version,'$!parts', nqp::list(6,"d")); nqp::bindattr_s($version,Version,'$!string',"6.d"); $version } my constant $v6e = do { my $version := nqp::create(Version); nqp::bindattr( $version,Version,'$!parts', nqp::list(6,"e","PREVIEW")); nqp::bindattr_s($version,Version,'$!string',"6.e.PREVIEW"); $version } my constant $v6star = do { my $version := nqp::create(Version); nqp::bindattr( $version,Version,'$!parts', nqp::list(6,star)); nqp::bindattr_s($version,Version,'$!string',"6.*"); $version } my constant $vplus = do { my $version := nqp::create(Version); nqp::bindattr( $version,Version,'$!parts', nqp::list); nqp::bindattr_i($version,Version,'$!plus', 1); nqp::bindattr_s($version,Version,'$!string',""); $version } my constant $vminus = do { my $version := nqp::create(Version); nqp::bindattr( $version,Version,'$!parts', nqp::list); nqp::bindattr_i($version,Version,'$!plus', -1); nqp::bindattr_s($version,Version,'$!string',""); $version } method !SET-SELF(\parts,\plus,\string) { $!parts := nqp::getattr(parts,List,'$!reified'); $!plus = plus; $!string = string; self } multi method new(Version:) { $v } multi method new(Version: '6') { $v6 } multi method new(Version: '6.c') { $v6c } multi method new(Version: '6.d') { $v6d } multi method new(Version: '6.e.PREVIEW') { $v6e } # update on language multi method new(Version: '6.*') { $v6star } # level bump multi method new(Version: '*') { $vstar } multi method new(Version: Whatever) { $vstar } multi method new(Version: @parts, Str:D $string, Int() $plus = 0, $?) { nqp::create(self)!SET-SELF(@parts.List, $plus, $string) } method !SLOW-NEW(str $s) { # we comb the version string for /:r '*' || \d+ || <.alpha>+/, which # will become our parts. Decimal numbers are converted to Ints, and # the rest of the parts remain as strings my int $pos; my int $chars = nqp::chars($s); my int $mark; my $strings := nqp::list_s; my $parts := nqp::list; nqp::while( nqp::islt_i($pos, $chars), nqp::if( nqp::eqat($s, '*', $pos), nqp::stmts( # Whatever portion nqp::push_s($strings, '*'), nqp::push($parts,star), ++$pos ), nqp::if( nqp::iscclass(nqp::const::CCLASS_NUMERIC, $s, $pos), nqp::stmts( # we're at the start of a numeric portion ($mark = $pos++), nqp::while( # seek the end of numeric portion nqp::islt_i($pos, $chars) && nqp::iscclass(nqp::const::CCLASS_NUMERIC, $s, $pos), ++$pos ), nqp::push($parts, # grab numeric portion nqp::atpos( nqp::radix( 10, nqp::push_s( $strings, nqp::substr($s, $mark, nqp::sub_i($pos, $mark)) ), 0, 0 ), 0 ) ) ), nqp::if( # same idea as for numerics, except for <.alpha> class nqp::iscclass(nqp::const::CCLASS_ALPHABETIC, $s, $pos) || nqp::iseq_i(nqp::ord($s, $pos), 95), nqp::stmts( # we're at the start of a alpha portion ($mark = $pos++), nqp::while( # seek the end of alpha portion nqp::islt_i($pos, $chars) && (nqp::iscclass(nqp::const::CCLASS_ALPHABETIC, $s, $pos) || nqp::iseq_i(nqp::ord($s, $pos), 95)), ++$pos ), nqp::push($parts, # grab alpha portion nqp::push_s( $strings, nqp::substr($s, $mark, nqp::sub_i($pos, $mark)) ) ) ), ++$pos ) ) ) ); nqp::if( nqp::elems($strings), nqp::stmts( (my str $last = nqp::substr($s,nqp::sub_i(nqp::chars($s),1))), (my int $plus = nqp::iseq_s($last,'+') - nqp::iseq_s($last,'-')), nqp::create(self)!SET-SELF($parts, $plus, nqp::concat(nqp::join('.',$strings),nqp::if($plus,$last,'')) ) ), Nil # no parts found ) } multi method new(Version: Str() $s) { self!SLOW-NEW($s) // ($s.ends-with('+') ?? $vplus !! $s.ends-with('-') ?? $vminus !! $v) } multi method Str(Version:D:) { nqp::p6box_s($!string) } multi method gist(Version:D:) { nqp::concat("v",$!string) } multi method raku(Version:D:) { if nqp::chars($!string) { my int $first = nqp::ord($!string); nqp::isge_i($first,48) && nqp::isle_i($first,57) # "0" <= x <= "9" ?? nqp::concat("v",$!string) !! self.^name ~ ".new('$!string')" } else { self.^name ~ ".new" } } multi method ACCEPTS(Version:D: Version:D $other) { my \oparts := nqp::getattr($other,Version,'$!parts'); my int $oelems = nqp::isnull(oparts) ?? 0 !! nqp::elems(oparts); my int $elems = nqp::elems($!parts); my int $max-elems = nqp::isge_i($oelems,$elems) ?? $oelems !! $elems; my int $i = -1; nqp::while( nqp::islt_i(++$i,$max-elems), nqp::stmts( (my $v := nqp::if( nqp::isge_i($i,$elems), star, nqp::atpos($!parts,$i) )), # if whatever here, no more check this iteration nqp::unless( nqp::eqaddr($v,star), nqp::stmts( (my $o := nqp::if( nqp::isge_i($i,$oelems), 0, nqp::atpos(oparts,$i) )), # if whatever there, no more to check this iteration nqp::unless( nqp::eqaddr($o,star), nqp::if( nqp::eqaddr((my $order := $o cmp $v),Order::More), (return nqp::hllbool(nqp::isgt_i($!plus,0))), nqp::if( nqp::eqaddr($order,Order::Less), (return nqp::hllbool(nqp::islt_i($!plus,0))) ) ) ) ) ) ) ); True } method Capture() { X::Cannot::Capture.new( :what(self) ).throw } multi method WHICH(Version:D: --> ValueObjAt:D) { nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Version), 'Version|', nqp::concat(nqp::unbox_s(self.^name), '|') ), $!string ), ValueObjAt ) } method parts() { nqp::hllize($!parts) } method plus() { nqp::hllbool(nqp::iseq_i($!plus, 1)) } method minus() { nqp::hllbool(nqp::iseq_i($!plus,-1)) } method whatever() { my int $i = -1; my int $elems = nqp::elems($!parts); nqp::until( nqp::iseq_i(++$i,$elems) || nqp::eqaddr(nqp::atpos($!parts,$i),star), nqp::null ); nqp::hllbool(nqp::isne_i($i,$elems)) } method Version() { self } } multi sub infix:(Version:D $a, Version:D $b --> Bool:D) { nqp::hllbool( nqp::eqaddr($a,$b) || (nqp::eqaddr($a.WHAT,$b.WHAT) && (nqp::iseq_s( nqp::getattr_s($a,Version,'$!string'), nqp::getattr_s($b,Version,'$!string') ) || nqp::eqaddr(($a cmp $b),Order::Same)) ) ) } multi sub infix:(Version:D $a, Version:D $b) { nqp::if( nqp::eqaddr($a,$b), # we're us Same, nqp::stmts( (my \ia := nqp::clone(nqp::getattr($a,Version,'$!parts'))), (my \ib := nqp::clone(nqp::getattr($b,Version,'$!parts'))), (my ($ret, $a-part, $b-part)), nqp::while( ia, # check from left nqp::stmts( ($a-part := nqp::shift(ia)), ($b-part := ib ?? nqp::shift(ib) !! 0), nqp::if( ($ret := nqp::if( nqp::istype($a-part,Str) && nqp::istype($b-part,Int), Less, nqp::if( nqp::istype($a-part,Int) && nqp::istype($b-part,Str), More, ($a-part cmp $b-part) ) )), return $ret ) ) ), nqp::while( ib, # check from right nqp::stmts( ($a-part := 0), ($b-part := nqp::shift(ib)), nqp::if( ($ret := nqp::if( nqp::istype($a-part,Str) && nqp::istype($b-part,Int), Less, nqp::if( nqp::istype($a-part,Int) && nqp::istype($b-part,Str), More, ($a-part cmp $b-part) ) )), return $ret ) ) ), ( nqp::getattr_i($a,Version,'$!plus') cmp nqp::getattr_i($b,Version,'$!plus') ) ) ) } multi sub infix:«<=>»(Version:D $a, Version:D $b) { $a cmp $b } multi sub infix:«<» (Version:D $a, Version:D $b) { $a cmp $b == Less } multi sub infix:«<=» (Version:D $a, Version:D $b) { $a cmp $b != More } multi sub infix:«==» (Version:D $a, Version:D $b) { $a cmp $b == Same } multi sub infix:«!=» (Version:D $a, Version:D $b) { $a cmp $b != Same } multi sub infix:«>=» (Version:D $a, Version:D $b) { $a cmp $b != Less } multi sub infix:«>» (Version:D $a, Version:D $b) { $a cmp $b == More } #line 1 SETTING::src/core.c/ForeignCode.rakumod # Takes a foreign code object and tries to make it feel somewhat like a Raku # one. Note that it doesn't have signature information we can know about. my class ForeignCode does Callable does Rakudo::Internals::ImplementationDetail { # declared in BOOTSTRAP # class ForeignCode # has Code $!do; # Code object we delegate to method arity( --> 0) { } method count( --> Inf) { } method has-phasers(--> False) { } method has-loop-phasers(--> False) { } method signature(ForeignCode:D:) { (sub (|) { }).signature } method name() { (nqp::can($!do, 'name') ?? $!do.name !! nqp::getcodename($!do)) || '' } } my class Rakudo::Internals::EvalIdSource { my Int $count = 0; my Lock $lock = Lock.new; method next-id() { $lock.protect: { $count++ } } } proto sub EVAL( $code is copy where Blob|Cool|Callable|RakuAST::Node, Str() :$lang is copy = 'Raku', PseudoStash :context($ctx), Str() :$filename = Str, Bool() :$check, *%_ ) is raw { die "EVAL() in Raku is intended to evaluate strings or ASTs, did you mean 'try'?" if nqp::istype($code,Callable); # TEMPORARY HACK $lang = 'Raku' if $lang eq 'perl6'; # First look in compiler registry. my $compiler := nqp::getcomp($lang); if nqp::isnull($compiler) { # Try a multi-dispatch to another EVAL candidate. If that fails to # dispatch, map it to a typed exception. CATCH { when X::Multi::NoMatch { X::Eval::NoSuchLang.new(:$lang).throw } } return {*}; } my $*CTXSAVE; # make sure we don't use the EVAL's MAIN context for the # currently compiling compilation unit my $context := nqp::defined($ctx) ?? $ctx !! CALLER::LEXICAL::; my $compiled; my $eval_ctx := nqp::getattr(nqp::decont($context), PseudoStash, '$!ctx'); if nqp::istype($code, RakuAST::Node) { # Wrap as required to get compilation unit. my $comp-unit := do if nqp::istype($code, RakuAST::CompUnit) { $code } else { my $statement-list := do if nqp::istype($code, RakuAST::StatementList) { $code } else { my $statement := do if nqp::istype($code, RakuAST::Statement) { $code } elsif nqp::istype($code, RakuAST::Expression) { RakuAST::Statement::Expression.new(expression => $code) } else { die "Cannot evaluate a $code.^name() node; expected a compilation unit, " ~ "statement list, statement, or expression"; } RakuAST::StatementList.new($statement) } RakuAST::CompUnit.new: :outer-cu($*CU // RakuAST::CompUnit), :eval, :$statement-list, :comp-unit-name($filename // 'EVAL_' ~ Rakudo::Internals::EvalIdSource.next-id) } # Perform symbol resolution, then compile to QAST and in turn bytecode. my $resolver := RakuAST::Resolver::EVAL.new(:context($eval_ctx), :global(GLOBAL)); $comp-unit.check($resolver); if $resolver.has-compilation-errors { $resolver.produce-compilation-exception.throw; } my $from := $compiler.exists_stage('optimize') ?? 'optimize' !! 'qast'; $compiled := $compiler.compile: :$from, $comp-unit.IMPL-TO-QAST-COMP-UNIT; } else { $code = nqp::istype($code,Blob) ?? $code.decode('utf8') !! $code.Str; my $?FILES := $filename // 'EVAL_' ~ Rakudo::Internals::EvalIdSource.next-id; my $LANG := $context<%?LANG>:exists ?? $context<%?LANG> !! Nil; my $*INSIDE-EVAL := 1; $compiled := $compiler.compile: $code, :outer_ctx($eval_ctx), :global(GLOBAL), :language_version(nqp::getcomp('Raku').language_version), |(:optimize($_) with nqp::getcomp('Raku').cli-options), |(%(:grammar($LANG