| File: | blib/lib/Sub/Nary.pm |
| Coverage: | 97.4% |
| line | stmt | bran | path | cond | sub | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Sub::Nary; | ||||||
| 2 | |||||||
| 3 | 13 13 13 | 328 78 161 | use 5.008001; | ||||
| 4 | |||||||
| 5 | 13 13 13 | 162 55 214 | use strict; | ||||
| 6 | 13 13 13 | 168 49 193 | use warnings; | ||||
| 7 | |||||||
| 8 | 13 13 13 | 177 78 267 | use Carp qw/croak/; | ||||
| 9 | |||||||
| 10 | 13 13 13 | 197 53 249 | use B qw/class ppname svref_2object OPf_KIDS/; | ||||
| 11 | |||||||
| 12 - 20 | =head1 NAME Sub::Nary - Try to count how many elements a subroutine can return in list context. =head1 VERSION Version 0.02 =cut | ||||||
| 21 | |||||||
| 22 | our $VERSION; | ||||||
| 23 | BEGIN { | ||||||
| 24 | 13 | 140 | $VERSION = '0.02'; | ||||
| 25 | } | ||||||
| 26 | |||||||
| 27 - 137 | =head1 SYNOPSIS
use Sub::Nary;
my $sn = Sub::Nary->new();
my $r = $sn->nary(\&hlagh);
=head1 DESCRIPTION
This module uses the L<B> framework to walk into subroutines and try to guess how many scalars are likely to be returned in list context. It's not always possible to give a definitive answer to this question at compile time, so the results are given in terms of "probability of return" (to be understood in a sense described below).
=head1 METHODS
=head2 C<new>
The usual constructor. Currently takes no argument.
=head2 C<nary $coderef>
Takes a code reference to a named or anonymous subroutine, and returns a hash reference whose keys are the possible numbers of returning scalars, and the corresponding values the "probability" to get them. The special key C<'list'> is used to denote a possibly infinite number of returned arguments. The return value hence would look at
{ 1 => 0.2, 2 => 0.4, 4 => 0.3, list => 0.1 }
that is, we should get C<1> scalar C<1> time over C<5> and so on. The sum of all values is C<1>. The returned result, and all the results obtained from intermediate subs, are cached into the object.
=head2 C<flush>
Flushes the L<Sub::Nary> object cache. Returns the object itself.
=head1 PROBABILITY OF RETURN
The probability is computed as such :
=over 4
=item * When branching, each branch is considered equally possible.
For example, the subroutine
sub simple {
if (rand < 0.1) {
return 1;
} else {
return 2, 3;
}
}
is seen returning one or two arguments each with probability C<1/2>.
As for
sub hlagh {
my $x = rand;
if ($x < 0.1) {
return 1, 2, 3;
} elsif ($x > 0.9) {
return 4, 5;
}
}
it is considered to return C<3> scalars with probability C<1/2>, C<2> with probability C<1/2 * 1/2 = 1/4> and C<1> (when the two tests fail, the last computed value is returned, which here is C<< $x > 0.9 >> evaluated in the scalar context of the test) with remaining probability C<1/4>.
=item * The total probability law for a given returning point is the convolution product of the probabilities of its list elements.
As such,
sub notsosimple {
return 1, simple(), 2
}
returns C<3> or C<4> arguments with probability C<1/2> ; and
sub double {
return simple(), simple()
}
never returns C<1> argument but returns C<2> with probability C<1/2 * 1/2 = 1/4>, C<3> with probability C<1/2 * 1/2 + 1/2 * 1/2 = 1/2> and C<4> with probability C<1/4> too.
=item * If a core function may return different numbers of scalars, each kind is considered equally possible.
For example, C<stat> returns C<13> elements on success and C<0> on error. The according probability will then be C<< { 0 => 0.5, 13 => 0.5 } >>.
=item * The C<list> state is absorbing in regard of all the other ones.
This is just a pedantic way to say that "list + fixed length = list".
That's why
sub listy {
return 1, simple(), @_
}
is considered as always returning an unbounded list.
Also, the convolution law does not behave the same when C<list> elements are involved : in the following example,
sub oneorlist {
if (rand < 0.1) {
return 1
} else {
return @_
}
}
sub composed {
return oneorlist(), oneorlist()
}
C<composed> returns C<2> scalars with probability C<1/2 * 1/2 = 1/4> and a C<list> with probability C<3/4>.
=back
=cut | ||||||
| 138 | |||||||
| 139 | BEGIN { | ||||||
| 140 | 13 | 216 | require XSLoader; | ||||
| 141 | 13 | 154 | XSLoader::load(__PACKAGE__, $VERSION); | ||||
| 142 | } | ||||||
| 143 | |||||||
| 144 | sub _check_self { | ||||||
| 145 | 3 | 116 | croak 'First argument isn\'t a valid ' . __PACKAGE__ . ' object' | ||||
| 146 | unless ref $_[0] and $_[0]->isa(__PACKAGE__); | ||||||
| 147 | } | ||||||
| 148 | |||||||
| 149 | sub new { | ||||||
| 150 | 10 | 193 | my $class = shift; | ||||
| 151 | 10 | 279 | $class = ref($class) || $class || __PACKAGE__; | ||||
| 152 | 10 | 289 | bless { cache => { } }, $class; | ||||
| 153 | } | ||||||
| 154 | |||||||
| 155 | sub flush { | ||||||
| 156 | 3 | 22 | my $self = shift; | ||||
| 157 | 3 | 26 | _check_self($self); | ||||
| 158 | 1 | 11 | $self->{cache} = { }; | ||||
| 159 | 1 | 8 | $self; | ||||
| 160 | } | ||||||
| 161 | |||||||
| 162 | sub nary { | ||||||
| 163 | 241 | 18609 | my $self = shift; | ||||
| 164 | 241 | 1655 | my $sub = shift; | ||||
| 165 | |||||||
| 166 | 241 | 2642 | $self->{cv} = [ ]; | ||||
| 167 | 241 | 5876 | return ($self->enter(svref_2object($sub)))[1]; | ||||
| 168 | } | ||||||
| 169 | |||||||
| 170 | sub name ($) { | ||||||
| 171 | 4879 | 64783 | local $SIG{__DIE__} = \&Carp::confess; | ||||
| 172 | 4879 | 57153 | my $n = $_[0]->name; | ||||
| 173 | 4879 | 73115 | $n eq 'null' ? substr(ppname($_[0]->targ), 3) : $n | ||||
| 174 | } | ||||||
| 175 | |||||||
| 176 | sub power { | ||||||
| 177 | 20 | 166 | my ($p, $n, $c) = @_; | ||||
| 178 | 20 | 162 | return unless defined $p; | ||||
| 179 | 19 | 202 | return { 0 => $c } unless $n; | ||||
| 180 | 16 | 134 | if ($n eq 'list') { | ||||
| 181 | 3 | 27 | my $z = delete $p->{0}; | ||||
| 182 | 3 | 47 | return { 'list' => $c } unless $z; | ||||
| 183 | 2 | 51 | return { 0 => $c } if $z == 1; | ||||
| 184 | 1 | 39 | return { 0 => $c * $z, list => $c * (1 - $z) }; | ||||
| 185 | } | ||||||
| 186 | 13 23 | 137 450 | my $r = combine map { { %$p } } 1 .. $n; | ||||
| 187 | 13 13 | 96 347 | $r->{$_} *= $c for keys %$r; | ||||
| 188 | 13 | 275 | return $r; | ||||
| 189 | } | ||||||
| 190 | |||||||
| 191 | my %ops; | ||||||
| 192 | |||||||
| 193 | $ops{$_} = 1 for scalops; | ||||||
| 194 | $ops{$_} = 0 for qw/stub nextstate pushmark iter unstack/; | ||||||
| 195 | $ops{$_} = 1 for qw/padsv/; | ||||||
| 196 | $ops{$_} = 'list' for qw/padav/; | ||||||
| 197 | $ops{$_} = 'list' for qw/padhv rv2hv/; | ||||||
| 198 | $ops{$_} = 'list' for qw/padany/; | ||||||
| 199 | $ops{$_} = 'list' for qw/match entereval readline/; | ||||||
| 200 | |||||||
| 201 | $ops{each} = { 0 => 0.5, 2 => 0.5 }; | ||||||
| 202 | $ops{stat} = { 0 => 0.5, 13 => 0.5 }; | ||||||
| 203 | |||||||
| 204 | $ops{caller} = sub { my @a = caller 0; scalar @a }->(); | ||||||
| 205 | $ops{localtime} = do { my @a = localtime; scalar @a }; | ||||||
| 206 | $ops{gmtime} = do { my @a = gmtime; scalar @a }; | ||||||
| 207 | |||||||
| 208 | $ops{$_} = { 0 => 0.5, 10 => 0.5 } for map "gpw$_", qw/nam uid ent/; | ||||||
| 209 | $ops{$_} = { 0 => 0.5, 4 => 0.5 } for map "ggr$_", qw/nam gid ent/; | ||||||
| 210 | $ops{$_} = 'list' for qw/ghbyname ghbyaddr ghostent/; | ||||||
| 211 | $ops{$_} = { 0 => 0.5, 4 => 0.5 } for qw/gnbyname gnbyaddr gnetent/; | ||||||
| 212 | $ops{$_} = { 0 => 0.5, 3 => 0.5 } for qw/gpbyname gpbynumber gprotoent/; | ||||||
| 213 | $ops{$_} = { 0 => 0.5, 4 => 0.5 } for qw/gsbyname gsbyport gservent/; | ||||||
| 214 | |||||||
| 215 | sub enter { | ||||||
| 216 | 310 | 2537 | my ($self, $cv) = @_; | ||||
| 217 | |||||||
| 218 | 310 | 2813 | return undef, 'list' if class($cv) ne 'CV'; | ||||
| 219 | 307 | 17903 | my $op = $cv->ROOT; | ||||
| 220 | 307 | 3412 | my $tag = tag($op); | ||||
| 221 | |||||||
| 222 | 307 46 | 4308 1034 | return undef, { %{$self->{cache}->{$tag}} } if exists $self->{cache}->{$tag}; | ||||
| 223 | |||||||
| 224 | # Anything can happen with recursion | ||||||
| 225 | 261 261 | 1274 3306 | for (@{$self->{cv}}) { | ||||
| 226 | 25 | 576 | return undef, 'list' if $tag == tag($_->ROOT); | ||||
| 227 | } | ||||||
| 228 | |||||||
| 229 | 259 259 | 1306 2728 | unshift @{$self->{cv}}, $cv; | ||||
| 230 | 259 | 4653 | my $r = add $self->inspect($op->first); | ||||
| 231 | 259 259 | 1644 4150 | shift @{$self->{cv}}; | ||||
| 232 | |||||||
| 233 | 259 | 2276 | $r = { $r => 1 } unless ref $r; | ||||
| 234 | 259 | 4317 | $self->{cache}->{$tag} = { %$r }; | ||||
| 235 | 259 | 3415 | return undef, $r; | ||||
| 236 | } | ||||||
| 237 | |||||||
| 238 | sub inspect { | ||||||
| 239 | 1893 | 14477 | my ($self, $op) = @_; | ||||
| 240 | |||||||
| 241 | 1893 | 12577 | my $n = name($op); | ||||
| 242 | 1893 | 16495 | return add($self->inspect_kids($op)), undef if $n eq 'return'; | ||||
| 243 | |||||||
| 244 | 1768 | 23108 | my $meth = $self->can('pp_' . $n); | ||||
| 245 | 1768 | 15605 | return $self->$meth($op) if $meth; | ||||
| 246 | |||||||
| 247 | 1169 | 9799 | if (exists $ops{$n}) { | ||||
| 248 | 234 | 1695 | my $l = $ops{$n}; | ||||
| 249 | 234 | 1960 | $l = { %$l } if ref $l; | ||||
| 250 | 234 | 2559 | return undef, $l; | ||||
| 251 | } | ||||||
| 252 | |||||||
| 253 | 935 | 7375 | if (class($op) eq 'LOGOP' and not null $op->first) { | ||||
| 254 | 50 | 3494 | my @res; | ||||
| 255 | |||||||
| 256 | 50 | 590 | my $op = $op->first; | ||||
| 257 | 50 | 546 | my ($r1, $l1) = $self->inspect($op); | ||||
| 258 | 50 | 591 | return $r1, $l1 if defined $r1 and zero $l1; | ||||
| 259 | 49 | 462 | my $c = count $l1; | ||||
| 260 | |||||||
| 261 | 49 | 668 | $op = $op->sibling; | ||||
| 262 | 49 | 497 | my ($r2, $l2) = $self->inspect($op); | ||||
| 263 | |||||||
| 264 | 49 | 665 | $op = $op->sibling; | ||||
| 265 | 49 | 310 | my ($r3, $l3); | ||||
| 266 | 49 | 519 | if (null $op) { | ||||
| 267 | # If the logop has no else branch, it can also return the *scalar* result of | ||||||
| 268 | # the conditional | ||||||
| 269 | 26 | 293 | $l3 = { 1 => 1 }; | ||||
| 270 | } else { | ||||||
| 271 | 23 | 210 | ($r3, $l3) = $self->inspect($op); | ||||
| 272 | } | ||||||
| 273 | |||||||
| 274 | 49 | 1207 | my $r = add $r1, scale $c / 2, add $r2, $r3; | ||||
| 275 | 49 | 1031 | my $l = scale $c / 2, add $l2, $l3; | ||||
| 276 | 49 | 685 | return $r, $l | ||||
| 277 | } | ||||||
| 278 | |||||||
| 279 | 885 | 47050 | return $self->inspect_kids($op); | ||||
| 280 | } | ||||||
| 281 | |||||||
| 282 | sub inspect_kids { | ||||||
| 283 | 1010 | 7980 | my ($self, $op) = @_; | ||||
| 284 | |||||||
| 285 | 1010 | 15064 | return undef, 0 unless $op->flags & OPf_KIDS; | ||||
| 286 | |||||||
| 287 | 886 | 10255 | $op = $op->first; | ||||
| 288 | 886 | 9457 | return undef, 0 if null $op; | ||||
| 289 | 886 | 5957 | if (name($op) eq 'pushmark') { | ||||
| 290 | 272 | 3254 | $op = $op->sibling; | ||||
| 291 | 272 | 3321 | return undef, 0 if null $op; | ||||
| 292 | } | ||||||
| 293 | |||||||
| 294 | 877 | 5414 | my ($r, @l); | ||||
| 295 | 877 | 4409 | my $c = 1; | ||||
| 296 | 877 | 10918 | for (; not null $op; $op = $op->sibling) { | ||||
| 297 | 1701 | 11430 | my $n = name($op); | ||||
| 298 | 1701 | 14436 | if ($n eq 'nextstate') { | ||||
| 299 | 443 | 2995 | @l = (); | ||||
| 300 | 443 | 10622 | next; | ||||
| 301 | } | ||||||
| 302 | 1258 | 9262 | if ($n eq 'lineseq') { | ||||
| 303 | 2 | 14 | @l = (); | ||||
| 304 | 2 | 24 | $op = $op->first; | ||||
| 305 | 2 | 13 | redo; | ||||
| 306 | } | ||||||
| 307 | 1256 | 11652 | my ($rc, $lc) = $self->inspect($op); | ||||
| 308 | 1256 | 11930 | $c = 1 - count $r; | ||||
| 309 | 1256 | 13250 | $r = add $r, scale $c, $rc if defined $rc; | ||||
| 310 | 1256 | 9896 | if (not defined $lc) { | ||||
| 311 | 204 | 1152 | @l = (); | ||||
| 312 | 204 | 1092 | last; | ||||
| 313 | } | ||||||
| 314 | 1052 | 34801 | push @l, scale $c, $lc; | ||||
| 315 | } | ||||||
| 316 | |||||||
| 317 | 877 | 25342 | my $l = scale +(1 - count $r), normalize combine @l; | ||||
| 318 | |||||||
| 319 | 877 | 14724 | return $r, $l; | ||||
| 320 | } | ||||||
| 321 | |||||||
| 322 | # Stolen from B::Deparse | ||||||
| 323 | |||||||
| 324 | 378 | 13353 | sub padval { $_[0]->{cv}->[0]->PADLIST->ARRAYelt(1)->ARRAYelt($_[1]) } | ||||
| 325 | |||||||
| 326 | sub gv_or_padgv { | ||||||
| 327 | 61 | 451 | my ($self, $op) = @_; | ||||
| 328 | 61 | 467 | if (class($op) eq 'PADOP') { | ||||
| 329 | 61 | 3241 | return $self->padval($op->padix) | ||||
| 330 | } else { # class($op) eq "SVOP" | ||||||
| 331 | 0 | 0 | return $op->gv; | ||||
| 332 | } | ||||||
| 333 | } | ||||||
| 334 | |||||||
| 335 | sub const_sv { | ||||||
| 336 | 317 | 2288 | my ($self, $op) = @_; | ||||
| 337 | 317 | 3764 | my $sv = $op->sv; | ||||
| 338 | # the constant could be in the pad (under useithreads) | ||||||
| 339 | 317 | 5286 | $sv = $self->padval($op->targ) unless $$sv; | ||||
| 340 | 317 | 2633 | return $sv; | ||||
| 341 | } | ||||||
| 342 | |||||||
| 343 | sub pp_entersub { | ||||||
| 344 | 74 | 575 | my ($self, $op) = @_; | ||||
| 345 | |||||||
| 346 | 74 | 3219 | $op = $op->first while $op->flags & OPf_KIDS; | ||||
| 347 | 74 | 758 | return undef, 0 if null $op; | ||||
| 348 | 74 | 510 | if (name($op) eq 'pushmark') { | ||||
| 349 | 74 | 844 | $op = $op->sibling; | ||||
| 350 | 74 | 857 | return undef, 0 if null $op; | ||||
| 351 | } | ||||||
| 352 | |||||||
| 353 | 74 | 327 | my $r; | ||||
| 354 | 74 | 357 | my $c = 1; | ||||
| 355 | 74 | 1315 | for (; not null $op->sibling; $op = $op->sibling) { | ||||
| 356 | 11 | 153 | my ($rc, $lc) = $self->inspect($op); | ||||
| 357 | 11 | 186 | return $rc, $lc if defined $rc and not defined $lc; | ||||
| 358 | 10 | 179 | $r = add $r, scale $c, $rc; | ||||
| 359 | 10 | 398 | $c *= count $lc; | ||||
| 360 | } | ||||||
| 361 | |||||||
| 362 | 73 | 639 | if (name($op) eq 'rv2cv') { | ||||
| 363 | 71 | 271 | my $n; | ||||
| 364 | 42 | 667 | do { | ||||
| 365 | 81 | 1032 | $op = $op->first; | ||||
| 366 | 81 | 919 | my $next = $op->sibling; | ||||
| 367 | 81 | 987 | while (not null $next) { | ||||
| 368 | 18 | 93 | $op = $next; | ||||
| 369 | 18 | 346 | $next = $next->sibling; | ||||
| 370 | } | ||||||
| 371 | 81 | 554 | $n = name($op) | ||||
| 372 | 71 | 317 | } while ($op->flags & OPf_KIDS and { map { $_ => 1 } qw/null leave/ }->{$n}); | ||||
| 373 | 71 142 | 549 1898 | return 'list', undef unless { map { $_ => 1 } qw/gv refgen/ }->{$n}; | ||||
| 374 | 68 | 710 | local $self->{sub} = 1; | ||||
| 375 | 68 | 646 | my ($rc, $lc) = $self->inspect($op); | ||||
| 376 | 68 | 1765 | return $r, scale $c, $lc; | ||||
| 377 | } else { | ||||||
| 378 | # Method call ? | ||||||
| 379 | 2 | 40 | return $r, { 'list' => $c }; | ||||
| 380 | } | ||||||
| 381 | } | ||||||
| 382 | |||||||
| 383 | sub pp_gv { | ||||||
| 384 | 71 | 533 | my ($self, $op) = @_; | ||||
| 385 | |||||||
| 386 | 71 | 1147 | return $self->{sub} ? $self->enter($self->gv_or_padgv($op)->CV) : (undef, 1) | ||||
| 387 | } | ||||||
| 388 | |||||||
| 389 | sub pp_anoncode { | ||||||
| 390 | 9 | 77 | my ($self, $op) = @_; | ||||
| 391 | |||||||
| 392 | 9 | 139 | return $self->{sub} ? $self->enter($self->const_sv($op)) : (undef, 1) | ||||
| 393 | } | ||||||
| 394 | |||||||
| 395 | sub pp_goto { | ||||||
| 396 | 3 | 25 | my ($self, $op) = @_; | ||||
| 397 | |||||||
| 398 | 3 | 21 | my $n = name($op); | ||||
| 399 | 3 | 48 | while ($op->flags & OPf_KIDS) { | ||||
| 400 | 12 | 142 | my $nop = $op->first; | ||||
| 401 | 12 | 79 | my $nn = name($nop); | ||||
| 402 | 12 | 101 | if ($nn eq 'pushmark') { | ||||
| 403 | 4 | 45 | $nop = $nop->sibling; | ||||
| 404 | 4 | 30 | $nn = name($nop); | ||||
| 405 | } | ||||||
| 406 | 12 | 161 | if ($n eq 'rv2cv' and $nn eq 'gv') { | ||||
| 407 | 1 | 12 | return $self->enter($self->gv_or_padgv($nop)->CV); | ||||
| 408 | } | ||||||
| 409 | 11 | 53 | $op = $nop; | ||||
| 410 | 11 | 159 | $n = $nn; | ||||
| 411 | } | ||||||
| 412 | |||||||
| 413 | 2 | 25 | return undef, 'list'; | ||||
| 414 | } | ||||||
| 415 | |||||||
| 416 | sub pp_const { | ||||||
| 417 | 306 | 2324 | my ($self, $op) = @_; | ||||
| 418 | |||||||
| 419 | 306 | 4181 | return undef, 0 unless $op->isa('B::SVOP'); | ||||
| 420 | |||||||
| 421 | 293 | 2324 | my $sv = $self->const_sv($op); | ||||
| 422 | 293 | 1552 | my $n = 1; | ||||
| 423 | 293 | 2219 | my $c = class($sv); | ||||
| 424 | 293 | 16376 | if ($c eq 'AV') { | ||||
| 425 | 19 | 204 | $n = $sv->FILL + 1 | ||||
| 426 | } elsif ($c eq 'HV') { | ||||||
| 427 | 0 | 0 | $n = 2 * $sv->KEYS | ||||
| 428 | } | ||||||
| 429 | |||||||
| 430 | 293 | 3268 | return undef, $n | ||||
| 431 | } | ||||||
| 432 | |||||||
| 433 | 23 | 591 | sub pp_aslice { $_[0]->inspect($_[1]->first->sibling) } | ||||
| 434 | |||||||
| 435 | sub pp_hslice; | ||||||
| 436 | *pp_hslice = *pp_aslice{CODE}; | ||||||
| 437 | |||||||
| 438 | 2 | 45 | sub pp_lslice { $_[0]->inspect($_[1]->first) } | ||||
| 439 | |||||||
| 440 | sub pp_rv2av { | ||||||
| 441 | 29 | 249 | my ($self, $op) = @_; | ||||
| 442 | 29 | 404 | $op = $op->first; | ||||
| 443 | |||||||
| 444 | 29 | 207 | if (name($op) eq 'gv') { | ||||
| 445 | 9 | 144 | return undef, { list => 1 }; | ||||
| 446 | } | ||||||
| 447 | |||||||
| 448 | 20 | 213 | $self->inspect($op); | ||||
| 449 | } | ||||||
| 450 | |||||||
| 451 | sub pp_aassign { | ||||||
| 452 | 10 | 92 | my ($self, $op) = @_; | ||||
| 453 | |||||||
| 454 | 10 | 141 | $op = $op->first; | ||||
| 455 | |||||||
| 456 | # Can't assign to return | ||||||
| 457 | 10 | 182 | my $l = ($self->inspect($op->sibling))[1]; | ||||
| 458 | 10 | 133 | return undef, $l if not exists $l->{list}; | ||||
| 459 | |||||||
| 460 | 9 | 80 | $self->inspect($op); | ||||
| 461 | } | ||||||
| 462 | |||||||
| 463 | sub pp_leaveloop { | ||||||
| 464 | 22 | 186 | my ($self, $op) = @_; | ||||
| 465 | |||||||
| 466 | 22 | 318 | $op = $op->first; | ||||
| 467 | 22 | 137 | my ($r1, $l1); | ||||
| 468 | 22 | 85 | my $for; | ||||
| 469 | 22 | 145 | if (name($op) eq 'enteriter') { # for loop ? | ||||
| 470 | 12 | 64 | $for = 1; | ||||
| 471 | 12 | 107 | ($r1, $l1) = $self->inspect($op); | ||||
| 472 | 12 | 183 | return $r1, $l1 if defined $r1 and zero $l1; | ||||
| 473 | } | ||||||
| 474 | |||||||
| 475 | 21 | 267 | $op = $op->sibling; | ||||
| 476 | 21 | 133 | my ($r2, $l2); | ||||
| 477 | 21 | 266 | if (name($op->first) eq 'and') { | ||||
| 478 | 14 | 288 | ($r2, $l2) = $self->inspect($op->first->first); | ||||
| 479 | 14 | 209 | return $r2, $l2 if defined $r2 and zero $l2; | ||||
| 480 | 13 | 118 | my $c = count $l2; | ||||
| 481 | 13 | 180 | return { list => 1 }, undef if !$for and defined $r2; | ||||
| 482 | 12 | 300 | my ($r3, $l3) = $self->inspect($op->first->first->sibling); | ||||
| 483 | 12 | 235 | return { list => 1 }, undef if defined $r3 and defined $l3; | ||||
| 484 | 11 | 187 | $r2 = add $r2, scale $c, $r3; | ||||
| 485 | } else { | ||||||
| 486 | 7 | 67 | ($r2, $l2) = $self->inspect($op); | ||||
| 487 | 7 | 137 | return { list => 1 }, undef if defined $r2 and defined $l2; | ||||
| 488 | } | ||||||
| 489 | |||||||
| 490 | 17 | 191 | my $r = (defined $r1) ? add $r1, scale +(1 - count $r1), $r2 | ||||
| 491 | : $r2; | ||||||
| 492 | 17 | 173 | my $c = 1 - count $r; | ||||
| 493 | 17 | 286 | return $r, $c ? { 0 => $c } : undef; | ||||
| 494 | } | ||||||
| 495 | |||||||
| 496 | sub pp_flip { | ||||||
| 497 | 18 | 144 | my ($self, $op) = @_; | ||||
| 498 | |||||||
| 499 | 18 | 235 | $op = $op->first; | ||||
| 500 | 18 | 124 | return $self->inspect($op) if name($op) ne 'range'; | ||||
| 501 | |||||||
| 502 | 18 | 106 | my ($r, $l); | ||||
| 503 | 18 | 228 | my $begin = $op->first; | ||||
| 504 | 18 | 123 | if (name($begin) eq 'const') { | ||||
| 505 | 12 | 132 | my $end = $begin->sibling; | ||||
| 506 | 12 | 87 | if (name($end) eq 'const') { | ||||
| 507 | 8 | 72 | $begin = $self->const_sv($begin); | ||||
| 508 | 8 | 69 | $end = $self->const_sv($end); | ||||
| 509 | { | ||||||
| 510 | 13 13 13 8 | 291 75 209 38 | no warnings 'numeric'; | ||||
| 511 | 8 8 | 32 124 | $begin = int ${$begin->object_2svref}; | ||||
| 512 | 8 8 | 34 108 | $end = int ${$end->object_2svref}; | ||||
| 513 | } | ||||||
| 514 | 8 | 124 | return undef, $end - $begin + 1; | ||||
| 515 | } else { | ||||||
| 516 | 4 | 38 | ($r, $l) = $self->inspect($end); | ||||
| 517 | } | ||||||
| 518 | } else { | ||||||
| 519 | 6 | 59 | ($r, $l) = $self->inspect($begin); | ||||
| 520 | } | ||||||
| 521 | |||||||
| 522 | 10 | 117 | my $c = 1 - count $r; | ||||
| 523 | 10 | 180 | return $r, $c ? { 'list' => $c } : undef | ||||
| 524 | } | ||||||
| 525 | |||||||
| 526 | sub pp_grepwhile { | ||||||
| 527 | 11 | 86 | my ($self, $op) = @_; | ||||
| 528 | |||||||
| 529 | 11 | 129 | $op = $op->first; | ||||
| 530 | 11 | 73 | return $self->inspect($op) if name($op) ne 'grepstart'; | ||||
| 531 | 11 | 202 | $op = $op->first->sibling; | ||||
| 532 | |||||||
| 533 | 11 | 166 | my ($r2, $l2) = $self->inspect($op->sibling); | ||||
| 534 | 11 | 207 | return $r2, $l2 if defined $r2 and zero $l2; | ||||
| 535 | 8 | 83 | my $c2 = count $l2; # First one to happen | ||||
| 536 | |||||||
| 537 | 8 | 73 | my ($r1, $l1) = $self->inspect($op); | ||||
| 538 | 8 | 292 | return (add $r2, scale $c2, $r1), undef if defined $r1 and zero $l1 | ||||
| 539 | and not zero $l2; | ||||||
| 540 | 5 | 44 | my $c1 = count $l1; | ||||
| 541 | |||||||
| 542 | 5 | 68 | $l2 = { $l2 => 1 } unless ref $l2; | ||||
| 543 | 5 | 156 | my $r = add $r2, | ||||
| 544 | scale $c2, | ||||||
| 545 | 5 | 64 | add map { scale $l2->{$_}, cumulate $r1, $_, $c1 } keys %$l2; | ||||
| 546 | 5 | 56 | my $c = 1 - count $r; | ||||
| 547 | 5 | 125 | return $r, $c ? { ((zero $l2) ? 0 : 'list') => $c } : undef; | ||||
| 548 | } | ||||||
| 549 | |||||||
| 550 | sub pp_mapwhile { | ||||||
| 551 | 21 | 166 | my ($self, $op) = @_; | ||||
| 552 | |||||||
| 553 | 21 | 245 | $op = $op->first; | ||||
| 554 | 21 | 202 | return $self->inspect($op) if name($op) ne 'mapstart'; | ||||
| 555 | 21 | 366 | $op = $op->first->sibling; | ||||
| 556 | |||||||
| 557 | 21 | 303 | my ($r2, $l2) = $self->inspect($op->sibling); | ||||
| 558 | 21 | 320 | return $r2, $l2 if defined $r2 and zero $l2; | ||||
| 559 | 18 | 178 | my $c2 = count $l2; # First one to happen | ||||
| 560 | |||||||
| 561 | 18 | 153 | my ($r1, $l1) = $self->inspect($op); | ||||
| 562 | 18 | 455 | return (add $r2, scale $c2, $r1), undef if defined $r1 and zero $l1 | ||||
| 563 | and not zero $l2; | ||||||
| 564 | 15 | 197 | my $c1 = count $l1; | ||||
| 565 | |||||||
| 566 | 15 | 172 | $l2 = { $l2 => 1 } unless ref $l2; | ||||
| 567 | 20 | 455 | my $r = add $r2, | ||||
| 568 | scale $c2, | ||||||
| 569 | 15 | 178 | add map { scale $l2->{$_}, cumulate $r1, $_, $c1 } keys %$l2; | ||||
| 570 | 15 | 158 | my $c = 1 - count $r; | ||||
| 571 | 15 20 | 145 198 | my $l = scale $c, normalize add map { power $l1, $_, $l2->{$_} } keys %$l2; | ||||
| 572 | 15 | 266 | return $r, $l; | ||||
| 573 | } | ||||||
| 574 | |||||||
| 575 - 619 | =head1 EXPORT
An object-oriented module shouldn't export any function, and so does this one.
=head1 CAVEATS
The algorithm may be pessimistic (things seen as C<list> while they are of fixed length) but not optimistic (the opposite, duh).
C<wantarray> isn't specialized when encountered in the optree.
=head1 DEPENDENCIES
L<perl> 5.8.1.
L<Carp> (standard since perl 5), L<B> (since perl 5.005) and L<XSLoader> (since perl 5.006).
=head1 AUTHOR
Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
You can contact me by mail or on #perl @ FreeNode (vincent or Prof_Vince).
=head1 BUGS
Please report any bugs or feature requests to C<bug-b-nary at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Sub-Nary>. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Sub::Nary
Tests code coverage report is available at L<http://www.profvince.com/perl/cover/Sub-Nary>.
=head1 ACKNOWLEDGEMENTS
Thanks to Sebastien Aperghis-Tramoni for helping to name this module.
=head1 COPYRIGHT & LICENSE
Copyright 2008 Vincent Pit, all rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
=cut | ||||||
| 620 | |||||||
| 621 | 1; # End of Sub::Nary | ||||||