Skip to content

Commit

Permalink
Allow Nil to represent holes in sparse arrays
Browse files Browse the repository at this point in the history
This commit affects the way arrays are rendered with .raku, and it
affects the way Nil is interpreted as a value when initializing an
array.

This is achieved by the following conceptual changes:

1. in .raku, output a hole (nqp::isnull) as `Nil`

To achieve this, several Iterator classes have their private method
`!hole` turned into a outside visible method `hole` with the
"is implementation-detail" trait.  This allows Array.raku to override
the handling of a hole by mixing in a "hole" method into the iterator
that produces "Nil".

2. in STORE and circumfix:<[ ]> (and its helpers), interprete Nil as
   *skipping* an element to be initialized.  If there was only one
   element specified, and it was Nil, then return an empty Array.

3. fix some Rakudo::Internals helper methods to recognize nqp::null
   values without throwing a tantrum.

This breaks a few tests in roast, that expect that [Nil] will return
[Any], where now it will return [Nil].  There is no further test breakage.

This does *not* fix the case of Nil in arrays constrained with a type
smiley.  Generating the .raku of these is fine, but it won't roundtrip
because `my Int:D @A = 1,Nil,3` is a runtime error.  FWIW, I think this
is actually incorrect behaviour, or at least unproductive behaviour.

This work was done in reply to

  Raku/problem-solving#279

One of the proposed solutions was to introduce a Hole class with special
semantics.  Should that proposal be accepted, then it is mostly replacing
`Nil` by `Hole` in most of this commit.
  • Loading branch information
lizmat committed Jun 15, 2021
1 parent 168da39 commit 484baa6
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 48 deletions.
79 changes: 58 additions & 21 deletions src/core.c/Array.pm6
Expand Up @@ -20,14 +20,26 @@ my class Array { # declared in BOOTSTRAP
}

method push(Mu \value --> Nil) {
nqp::push($!target, nqp::p6scalarwithvalue($!descriptor, value));
my \cont := nqp::p6scalarwithvalue($!descriptor,value); # typecheck
nqp::eqaddr(value,Nil)
?? nqp::setelems($!target,nqp::add_i(nqp::elems($!target),1))
!! nqp::push($!target,cont)
}

method append(IterationBuffer:D \buffer --> Nil) {
nqp::while(
nqp::elems(buffer),
nqp::push($!target,
nqp::p6scalarwithvalue($!descriptor,nqp::shift(buffer))
nqp::stmts(
# make sure we typecheck any value being stored
(my \cont := nqp::p6scalarwithvalue(
$!descriptor,
(my \value := nqp::shift(buffer))
)),
nqp::if(
nqp::eqaddr(value,Nil),
nqp::setelems($!target,nqp::add_i(nqp::elems($!target),1)),
nqp::push($!target,cont)
)
)
);
}
Expand Down Expand Up @@ -106,7 +118,7 @@ my class Array { # declared in BOOTSTRAP
nqp::atpos($!reified,$!i = nqp::add_i($!i,1)),
nqp::if(
nqp::islt_i($!i,nqp::elems($!reified)),
self!hole($!i),
self.hole($!i),
nqp::if(
nqp::isconcrete($!todo),
nqp::if(
Expand All @@ -122,7 +134,7 @@ my class Array { # declared in BOOTSTRAP
)
)
}
method !hole(int $i) is raw {
method hole(int $i) is raw is implementation-detail {
nqp::p6scalarfromcertaindesc(
ContainerDescriptor::BindArrayPos.new($!descriptor,$!reified,$i)
)
Expand Down Expand Up @@ -160,7 +172,7 @@ my class Array { # declared in BOOTSTRAP
nqp::while( # doesn't sink
nqp::islt_i($i = nqp::add_i($i,1),$elems),
target.push(
nqp::ifnull(nqp::atpos($!reified,$i),self!hole($i))
nqp::ifnull(nqp::atpos($!reified,$i),self.hole($i))
)
),
($!i = $i),
Expand Down Expand Up @@ -213,11 +225,14 @@ my class Array { # declared in BOOTSTRAP
my \reified := nqp::create(IterationBuffer);
nqp::while(
nqp::islt_i(($i = nqp::add_i($i,1)),$elems),
nqp::bindpos(
reified, $i,
nqp::p6scalarwithvalue(
(BEGIN nqp::getcurhllsym('default_cont_spec')),
nqp::decont(nqp::atpos(params,$i))
nqp::unless(
nqp::isnull(my \value := nqp::decont(nqp::atpos(params,$i)))
|| nqp::eqaddr(value,Nil),
nqp::bindpos(reified,$i,
nqp::p6scalarwithvalue(
(BEGIN nqp::getcurhllsym('default_cont_spec')),
value
)
)
)
);
Expand Down Expand Up @@ -308,10 +323,10 @@ my class Array { # declared in BOOTSTRAP
nqp::p6bindattrinvres(self,List,'$!reified',buffer)
}
multi method STORE(Array:D: Mu \item --> Array:D) {
nqp::push(
(my \buffer = nqp::create(IterationBuffer)),
nqp::p6scalarwithvalue($!descriptor, item)
);
my \buffer = nqp::create(IterationBuffer);
# Make sure we typecheck always
my \cont := nqp::p6scalarwithvalue($!descriptor,item);
nqp::push(buffer,cont) unless nqp::eqaddr(nqp::decont(item),Nil);
nqp::bindattr(self,List,'$!todo',Mu);
nqp::p6bindattrinvres(self,List,'$!reified',buffer)
}
Expand All @@ -324,7 +339,7 @@ my class Array { # declared in BOOTSTRAP

multi method Slip(Array:D: --> Slip:D) {

# A Slip-With-Descripto is a special kind of Slip that also has a
# A Slip-With-Descriptor 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 {
Expand Down Expand Up @@ -1280,13 +1295,35 @@ my class Array { # declared in BOOTSTRAP
method dynamic() {
nqp::isnull($!descriptor) ?? False !! so $!descriptor.dynamic
}

multi method raku(Array:D \SELF: --> Str:D) {
my role NilHole { method hole(int $ --> Nil) { } }

SELF.rakuseen('Array', {
'$' x nqp::iscont(SELF) # self is always deconted
~ '['
~ self.map({nqp::decont($_).raku}).join(', ')
~ ',' x (self.elems == 1 && nqp::istype(self.AT-POS(0),Iterable))
~ ']'
my $iterator := SELF.iterator but NilHole;

my $parts := nqp::list_s;
nqp::push_s($parts,'$') if nqp::iscont(SELF);
nqp::push_s($parts,'[');
nqp::until(
nqp::eqaddr(
(my \value := nqp::decont($iterator.pull-one)),
IterationEnd
),
nqp::stmts(
nqp::push_s(
$parts,
nqp::if(nqp::isnull(value),'Nil',value.raku)
),
nqp::push_s($parts,', ')
)
);
nqp::pop_s($parts) unless nqp::isle_i(nqp::elems($parts),2);
nqp::push_s($parts,',')
if self.elems == 1 && nqp::istype(self.AT-POS(0),Iterable);
nqp::push_s($parts,']');

nqp::join('',$parts)
})
}
multi method WHICH(Array:D: --> ObjAt:D) { self.Mu::WHICH }
Expand Down
18 changes: 13 additions & 5 deletions src/core.c/Rakudo/Internals.pm6
Expand Up @@ -706,11 +706,19 @@ my class Rakudo::Internals {

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
!! '?'
nqp::if(
nqp::isnull(decont),
'Nil',
nqp::if(
nqp::can(decont,nqp::decont_s($method)),
decont."$method"(),
nqp::if(
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);
Expand Down
20 changes: 10 additions & 10 deletions src/core.c/Rakudo/Iterator.pm6
Expand Up @@ -3310,7 +3310,7 @@ class Rakudo::Iterator {
}
method new(\arr, Mu \des) { nqp::create(self)!SET-SELF(arr, des) }

method !hole(int $i) is raw {
method hole(int $i) is raw is implementation-detail {
nqp::p6scalarfromdesc(ContainerDescriptor::BindArrayPos.new(
$!descriptor, $!reified, $i))
}
Expand All @@ -3319,7 +3319,7 @@ class Rakudo::Iterator {
nqp::atpos($!reified,$!i = nqp::add_i($!i,1)),
nqp::if(
nqp::islt_i($!i,nqp::elems($!reified)), # found a hole
self!hole($!i),
self.hole($!i),
IterationEnd
)
)
Expand All @@ -3333,7 +3333,7 @@ class Rakudo::Iterator {
($todo = nqp::sub_i($todo,1))
&& nqp::islt_i(($i = nqp::add_i($i,1)),$elems),
target.push(
nqp::ifnull(nqp::atpos($!reified,$i),self!hole($i))
nqp::ifnull(nqp::atpos($!reified,$i),self.hole($i))
)
);

Expand All @@ -3349,7 +3349,7 @@ class Rakudo::Iterator {
nqp::while( # doesn't sink
nqp::islt_i($i = nqp::add_i($i,1),$elems),
target.push(
nqp::ifnull(nqp::atpos($!reified,$i),self!hole($i))
nqp::ifnull(nqp::atpos($!reified,$i),self.hole($i))
)
);
$!i = $i;
Expand Down Expand Up @@ -3485,7 +3485,7 @@ class Rakudo::Iterator {
}
method new(\list, Mu \des) { nqp::create(self)!SET-SELF(list, des) }

method !hole(int $i) is raw {
method hole(int $i) is raw is implementation-detail {
nqp::isnull($!descriptor)
?? Nil
!! nqp::p6scalarfromdesc(
Expand All @@ -3497,15 +3497,15 @@ class Rakudo::Iterator {

method pull-one() is raw {
nqp::isge_i(($!i = nqp::sub_i($!i,1)),0)
?? nqp::ifnull(nqp::atpos($!reified,$!i),self!hole($!i))
?? 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 = nqp::sub_i($i,1)),0),
target.push(nqp::ifnull(nqp::atpos($reified,$i),self!hole($i)))
target.push(nqp::ifnull(nqp::atpos($reified,$i),self.hole($i)))
);
$!i = $i;
}
Expand Down Expand Up @@ -3551,7 +3551,7 @@ class Rakudo::Iterator {
nqp::create(self)!SET-SELF(rotate, list, des)
}

method !hole(int $i) is raw {
method hole(int $i) is raw is implementation-detail {
nqp::isnull($!descriptor)
?? Nil
!! nqp::p6scalarfromdesc(
Expand All @@ -3568,7 +3568,7 @@ class Rakudo::Iterator {
$!reified,
($!i = nqp::mod_i(nqp::add_i($!i,1),nqp::elems($!reified)))
),
self!hole($!i)
self.hole($!i)
)
!! IterationEnd
}
Expand All @@ -3586,7 +3586,7 @@ class Rakudo::Iterator {
$reified,
($i = nqp::mod_i(nqp::add_i($i,1),$elems))
),
self!hole($i)
self.hole($i)
)
)
);
Expand Down
28 changes: 16 additions & 12 deletions src/core.c/array_operators.pm6
Expand Up @@ -27,18 +27,22 @@ multi sub circumfix:<[ ]>(Iterable:D \iterable) {
)
}
multi sub circumfix:<[ ]>(Mu \x) { # really only for [$foo]
nqp::p6bindattrinvres(
nqp::create(Array),List,'$!reified',
nqp::stmts(
nqp::bindpos(
(my \reified := nqp::create(IterationBuffer)),
0,
nqp::p6scalarwithvalue(
(BEGIN nqp::getcurhllsym('default_cont_spec')),
nqp::decont(x)
)
),
reified
nqp::if(
nqp::eqaddr(nqp::decont(x),Nil),
Array.new,
nqp::p6bindattrinvres(
nqp::create(Array),List,'$!reified',
nqp::stmts(
nqp::bindpos(
(my \reified := nqp::create(IterationBuffer)),
0,
nqp::p6scalarwithvalue(
(BEGIN nqp::getcurhllsym('default_cont_spec')),
nqp::decont(x)
)
),
reified
)
)
)
}
Expand Down

0 comments on commit 484baa6

Please sign in to comment.