]> git.vpit.fr Git - perl/modules/Sub-Nary.git/blob - lib/Sub/Nary.pm
Switch qw delimiters to <>
[perl/modules/Sub-Nary.git] / lib / Sub / Nary.pm
1 package Sub::Nary;
2
3 use 5.008_001;
4
5 use strict;
6 use warnings;
7
8 use Carp qw<croak>;
9
10 use B qw<class ppname svref_2object OPf_KIDS>;
11
12 =head1 NAME
13
14 Sub::Nary - Try to count how many elements a subroutine can return in list context.
15
16 =head1 VERSION
17
18 Version 0.03
19
20 =cut
21
22 our $VERSION;
23 BEGIN {
24  $VERSION  = '0.03';
25 }
26
27 =head1 SYNOPSIS
28
29     use Sub::Nary;
30
31     my $sn = Sub::Nary->new;
32     my $r  = $sn->nary(\&hlagh);
33
34 =head1 DESCRIPTION
35
36 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.
37 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).
38
39 =head1 METHODS
40
41 =head2 C<new>
42
43     my $sn = Sub::Nary->new;
44
45 The usual constructor.
46 Currently takes no argument.
47
48 =head2 C<nary>
49
50     my $res = $sn->nary($coderef);
51
52 Takes a 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.
53 The special key C<'list'> is used to denote a possibly infinite number of returned arguments.
54 The return value hence would look at
55
56     { 1 => 0.2, 2 => 0.4, 4 => 0.3, list => 0.1 }
57
58 that is, we should get C<1> scalar C<1> time over C<5> and so on.
59 The sum of all values is C<1>.
60 The returned result, and all the results obtained from intermediate subs, are cached into the object.
61
62 =head2 C<flush>
63
64 Flushes the L<Sub::Nary> object cache.
65 Returns the object itself.
66
67 =head1 PROBABILITY OF RETURN
68
69 The probability is computed as such :
70
71 =over 4
72
73 =item *
74
75 When branching, each branch is considered equally possible.
76
77 For example, the subroutine
78
79     sub simple {
80      if (rand < 0.1) {
81       return 1;
82      } else {
83       return 2, 3;
84      }
85     }
86
87 is seen returning one or two arguments each with probability C<1/2>.
88 As for
89
90     sub hlagh {
91      my $x = rand;
92      if ($x < 0.1) {
93       return 1, 2, 3;
94      } elsif ($x > 0.9) {
95       return 4, 5;
96      }
97     }
98
99 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>.
100
101 =item *
102
103 The total probability law for a given returning point is the convolution product of the probabilities of its list elements.
104
105 As such,
106
107     sub notsosimple {
108      return 1, simple(), 2
109     }
110
111 returns C<3> or C<4> arguments with probability C<1/2> ; and
112
113     sub double {
114      return simple(), simple()
115     }
116
117 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.
118
119 =item *
120
121 If a core function may return different numbers of scalars, each kind is considered equally possible.
122
123 For example, C<stat> returns C<13> elements on success and C<0> on error.
124 The according probability will then be C<< { 0 => 0.5, 13 => 0.5 } >>.
125
126 =item *
127
128 The C<list> state is absorbing in regard of all the other ones.
129
130 This is just a pedantic way to say that "list + fixed length = list".
131 That's why
132
133     sub listy {
134      return 1, simple(), @_
135     }
136
137 is considered as always returning an unbounded list.
138
139 Also, the convolution law does not behave the same when C<list> elements are involved : in the following example,
140
141     sub oneorlist {
142      if (rand < 0.1) {
143       return 1
144      } else {
145       return @_
146      }
147     }
148
149     sub composed {
150      return oneorlist(), oneorlist()
151     }
152
153 C<composed> returns C<2> scalars with probability C<1/2 * 1/2 = 1/4> and a C<list> with probability C<3/4>.
154
155 =back
156
157 =cut
158
159 BEGIN {
160  require XSLoader;
161  XSLoader::load(__PACKAGE__, $VERSION);
162 }
163
164 sub _check_self {
165  croak 'First argument isn\'t a valid ' . __PACKAGE__ . ' object'
166   unless ref $_[0] and $_[0]->isa(__PACKAGE__);
167 }
168
169 sub new {
170  my $class = shift;
171  $class = ref($class) || $class || __PACKAGE__;
172  bless { cache => { } }, $class;
173 }
174
175 sub flush {
176  my $self = shift;
177  _check_self($self);
178  $self->{cache} = { };
179  $self;
180 }
181
182 sub nary {
183  my $self = shift;
184  my $sub  = shift;
185
186  $self->{cv} = [ ];
187  return ($self->enter(svref_2object($sub)))[1];
188 }
189
190 sub name ($) {
191  local $SIG{__DIE__} = \&Carp::confess;
192  my $n = $_[0]->name;
193  $n eq 'null' ? substr(ppname($_[0]->targ), 3) : $n
194 }
195
196 sub power {
197  my ($p, $n, $c) = @_;
198  return unless defined $p;
199  return { 0 => $c } unless $n;
200  if ($n eq 'list') {
201   my $z = delete $p->{0};
202   return { 'list' => $c } unless $z;
203   return { 0      => $c } if $z == 1;
204   return { 0 => $c * $z, list => $c * (1 - $z) };
205  }
206  my $r = combine map { { %$p } } 1 .. $n;
207  $r->{$_} *= $c for keys %$r;
208  return $r;
209 }
210
211 my %ops;
212
213 $ops{$_} = 1      for scalops;
214 $ops{$_} = 0      for qw<stub nextstate pushmark iter unstack>;
215 $ops{$_} = 1      for qw<padsv>;
216 $ops{$_} = 'list' for qw<padav>;
217 $ops{$_} = 'list' for qw<padhv rv2hv>;
218 $ops{$_} = 'list' for qw<padany>;
219 $ops{$_} = 'list' for qw<match entereval readline>;
220
221 $ops{each}      = { 0 => 0.5, 2 => 0.5 };
222 $ops{stat}      = { 0 => 0.5, 13 => 0.5 };
223
224 $ops{caller}    = sub { my @a = caller 0; scalar @a }->();
225 $ops{localtime} = do { my @a = localtime; scalar @a };
226 $ops{gmtime}    = do { my @a = gmtime; scalar @a };
227
228 $ops{$_} = { 0 => 0.5, 10 => 0.5 } for map "gpw$_", qw<nam uid ent>;
229 $ops{$_} = { 0 => 0.5, 4 => 0.5 }  for map "ggr$_", qw<nam gid ent>;
230 $ops{$_} = 'list'                  for qw<ghbyname ghbyaddr ghostent>;
231 $ops{$_} = { 0 => 0.5, 4 => 0.5 }  for qw<gnbyname gnbyaddr gnetent>;
232 $ops{$_} = { 0 => 0.5, 3 => 0.5 }  for qw<gpbyname gpbynumber gprotoent>;
233 $ops{$_} = { 0 => 0.5, 4 => 0.5 }  for qw<gsbyname gsbyport gservent>;
234
235 sub enter {
236  my ($self, $cv) = @_;
237
238  return undef, 'list' if class($cv) ne 'CV';
239  my $op  = $cv->ROOT;
240  my $tag = tag($op);
241
242  return undef, { %{$self->{cache}->{$tag}} } if exists $self->{cache}->{$tag};
243
244  # Anything can happen with recursion
245  for (@{$self->{cv}}) {
246   return undef, 'list' if $tag == tag($_->ROOT);
247  }
248
249  unshift @{$self->{cv}}, $cv;
250  my $r = add $self->inspect($op->first);
251  shift @{$self->{cv}};
252
253  $self->{cache}->{$tag} = { %$r };
254  return undef, $r;
255 }
256
257 sub inspect {
258  my ($self, $op) = @_;
259
260  my $n = name($op);
261  return add($self->inspect_kids($op)), undef if $n eq 'return';
262
263  my $meth = $self->can('pp_' . $n);
264  return $self->$meth($op) if $meth;
265
266  if (exists $ops{$n}) {
267   my $l = $ops{$n};
268   $l = { %$l } if ref $l;
269   return undef, $l;
270  }
271
272  if (class($op) eq 'LOGOP' and not null $op->first) {
273   my @res;
274
275   my $op = $op->first;
276   my ($r1, $l1) = $self->inspect($op);
277   return $r1, $l1 if defined $r1 and zero $l1;
278   my $c = count $l1;
279
280   $op = $op->sibling;
281   my ($r2, $l2) = $self->inspect($op);
282
283   $op = $op->sibling;
284   my ($r3, $l3);
285   if (null $op) {
286    # If the logop has no else branch, it can also return the *scalar* result of
287    # the conditional
288    $l3 = { 1 => 1 };
289   } else {
290    ($r3, $l3) = $self->inspect($op);
291   }
292
293   my $r = add $r1, scale $c / 2, add $r2, $r3;
294   my $l = scale $c / 2, add $l2, $l3;
295   return $r, $l
296  }
297
298  return $self->inspect_kids($op);
299 }
300
301 sub inspect_kids {
302  my ($self, $op) = @_;
303
304  return undef, 0 unless $op->flags & OPf_KIDS;
305
306  $op = $op->first;
307  return undef, 0 if null $op;
308  if (name($op) eq 'pushmark') {
309   $op = $op->sibling;
310   return undef, 0 if null $op;
311  }
312
313  my ($r, @l);
314  my $c = 1;
315  for (; not null $op; $op = $op->sibling) {
316   my $n = name($op);
317   if ($n eq 'nextstate') {
318    @l  = ();
319    next;
320   }
321   if ($n eq 'lineseq') {
322    @l  = ();
323    $op = $op->first;
324    redo;
325   }
326   my ($rc, $lc) = $self->inspect($op);
327   $c = 1 - count $r;
328   $r = add $r, scale $c, $rc if defined $rc;
329   if (not defined $lc) {
330    @l = ();
331    last;
332   }
333   push @l, scale $c, $lc;
334  }
335
336  my $l = scale +(1 - count $r), normalize combine @l;
337
338  return $r, $l;
339 }
340
341 # Stolen from B::Deparse
342
343 sub padval { $_[0]->{cv}->[0]->PADLIST->ARRAYelt(1)->ARRAYelt($_[1]) }
344
345 sub gv_or_padgv {
346  my ($self, $op) = @_;
347  if (class($op) eq 'PADOP') {
348   return $self->padval($op->padix)
349  } else { # class($op) eq "SVOP"
350   return $op->gv;
351  }
352 }
353
354 sub const_sv {
355  my ($self, $op) = @_;
356  my $sv = $op->sv;
357  # the constant could be in the pad (under useithreads)
358  $sv = $self->padval($op->targ) unless $$sv;
359  return $sv;
360 }
361
362 sub pp_entersub {
363  my ($self, $op) = @_;
364
365  $op = $op->first while $op->flags & OPf_KIDS;
366  # First must be a pushmark
367  $op = $op->sibling;
368  # Next must be non null - at worse it's the rv2cv
369
370  my $r;
371  my $c = 1;
372  for (; not null $op->sibling; $op = $op->sibling) {
373   my ($rc, $lc) = $self->inspect($op);
374   return $rc, $lc if defined $rc and not defined $lc;
375   $r = add $r, scale $c, $rc;
376   $c *= count $lc;
377  }
378
379  if (name($op) eq 'rv2cv') {
380   my $n;
381   do {
382    $op = $op->first;
383    my $next = $op->sibling;
384    while (not null $next) {
385     $op   = $next;
386     $next = $next->sibling;
387    }
388    $n  = name($op)
389   } while ($op->flags & OPf_KIDS and { map { $_ => 1 } qw<null leave> }->{$n});
390   return 'list', undef unless { map { $_ => 1 } qw<gv refgen> }->{$n};
391   local $self->{sub} = 1;
392   my ($rc, $lc) = $self->inspect($op);
393   return $r, scale $c, $lc;
394  } else {
395   # Method call ?
396   return $r, { 'list' => $c };
397  }
398 }
399
400 sub pp_gv {
401  my ($self, $op) = @_;
402
403  return $self->{sub} ? $self->enter($self->gv_or_padgv($op)->CV) : (undef, 1)
404 }
405
406 sub pp_anoncode {
407  my ($self, $op) = @_;
408
409  return $self->{sub} ? $self->enter($self->const_sv($op)) : (undef, 1)
410 }
411
412 sub pp_goto {
413  my ($self, $op) = @_;
414
415  my $n = name($op);
416  while ($op->flags & OPf_KIDS) {
417   my $nop = $op->first;
418   my $nn  = name($nop);
419   if ($nn eq 'pushmark') {
420    $nop = $nop->sibling;
421    $nn  = name($nop);
422   }
423   if ($n eq 'rv2cv' and $nn eq 'gv') {
424    return $self->enter($self->gv_or_padgv($nop)->CV);
425   }
426   $op = $nop;
427   $n  = $nn;
428  }
429
430  return undef, 'list';
431 }
432
433 sub pp_const {
434  my ($self, $op) = @_;
435
436  return undef, 0 unless $op->isa('B::SVOP');
437
438  my $sv = $self->const_sv($op);
439  my $n  = 1;
440  my $c  = class($sv);
441  if ($c eq 'AV') {
442   $n = $sv->FILL + 1
443  } elsif ($c eq 'HV') {
444   $n = 2 * $sv->KEYS
445  }
446
447  return undef, $n
448 }
449
450 sub pp_aslice { $_[0]->inspect($_[1]->first->sibling) }
451
452 sub pp_hslice;
453 *pp_hslice = *pp_aslice{CODE};
454
455 sub pp_lslice { $_[0]->inspect($_[1]->first) }
456
457 sub pp_rv2av {
458  my ($self, $op) = @_;
459  $op = $op->first;
460
461  if (name($op) eq 'gv') {
462   return undef, { list => 1 };
463  }
464
465  $self->inspect($op);
466 }
467
468 sub pp_sassign {
469  my ($self, $op) = @_;
470
471  my $r = ($self->inspect($op->first))[0];
472
473  my $c = 1 - count $r;
474  return $r, $c ? { 1 => $c } : undef
475 }
476
477 sub pp_aassign {
478  my ($self, $op) = @_;
479
480  $op = $op->first;
481
482  # Can't assign to return
483  my $l = ($self->inspect($op->sibling))[1];
484  return undef, $l if not exists $l->{list};
485
486  $self->inspect($op);
487 }
488
489 sub pp_leaveloop {
490  my ($self, $op) = @_;
491
492  $op = $op->first;
493  my ($r1, $l1);
494  my $for;
495  if (name($op) eq 'enteriter') { # for loop ?
496   $for = 1;
497   ($r1, $l1) = $self->inspect($op);
498   return $r1, $l1 if defined $r1 and zero $l1;
499  }
500
501  $op = $op->sibling;
502  my ($r2, $l2);
503  if (name($op->first) eq 'and') {
504   ($r2, $l2) = $self->inspect($op->first->first);
505   return $r2, $l2 if defined $r2 and zero $l2;
506   my $c = count $l2;
507   return { list => 1 }, undef if !$for and defined $r2;
508   my ($r3, $l3) = $self->inspect($op->first->first->sibling);
509   return { list => 1 }, undef if defined $r3 and defined $l3;
510   $r2 = add $r2, scale $c, $r3;
511  } else {
512   ($r2, $l2) = $self->inspect($op);
513   return { list => 1 }, undef if defined $r2 and defined $l2;
514  }
515
516  my $r = (defined $r1) ? add $r1, scale +(1 - count $r1), $r2
517                        : $r2;
518  my $c = 1 - count $r;
519  return $r, $c ? { 0 => $c } : undef;
520 }
521
522 sub pp_flip {
523  my ($self, $op) = @_;
524
525  $op = $op->first;
526  return $self->inspect($op) if name($op) ne 'range';
527
528  my ($r, $l);
529  my $begin = $op->first;
530  if (name($begin) eq 'const') {
531   my $end = $begin->sibling;
532   if (name($end) eq 'const') {
533    $begin = $self->const_sv($begin);
534    $end   = $self->const_sv($end);
535    {
536     no warnings 'numeric';
537     $begin = int ${$begin->object_2svref};
538     $end   = int ${$end->object_2svref};
539    }
540    return undef, $end - $begin + 1;
541   } else {
542    ($r, $l) = $self->inspect($end);
543   }
544  } else {
545   ($r, $l) = $self->inspect($begin);
546  }
547
548  my $c = 1 - count $r;
549  return $r, $c ? { 'list' => $c } : undef
550 }
551
552 sub pp_grepwhile {
553  my ($self, $op) = @_;
554
555  $op = $op->first;
556  return $self->inspect($op) if name($op) ne 'grepstart';
557  $op = $op->first->sibling;
558
559  my ($r2, $l2) = $self->inspect($op->sibling);
560  return $r2, $l2 if defined $r2 and zero $l2;
561  my $c2 = count $l2; # First one to happen
562
563  my ($r1, $l1) = $self->inspect($op);
564  return (add $r2, scale $c2, $r1), undef if defined $r1 and zero $l1
565                                                         and not zero $l2;
566  my $c1 = count $l1;
567
568  $l2 = { $l2 => 1 } unless ref $l2;
569  my $r = add $r2,
570           scale $c2,
571             add map { scale $l2->{$_}, cumulate $r1, $_, $c1 } keys %$l2;
572  my $c = 1 - count $r;
573  return $r, $c ? { ((zero $l2) ? 0 : 'list') => $c } : undef;
574 }
575
576 sub pp_mapwhile {
577  my ($self, $op) = @_;
578
579  $op = $op->first;
580  return $self->inspect($op) if name($op) ne 'mapstart';
581  $op = $op->first->sibling;
582
583  my ($r2, $l2) = $self->inspect($op->sibling);
584  return $r2, $l2 if defined $r2 and zero $l2;
585  my $c2 = count $l2; # First one to happen
586
587  my ($r1, $l1) = $self->inspect($op);
588  return (add $r2, scale $c2, $r1), undef if defined $r1 and zero $l1
589                                                         and not zero $l2;
590  my $c1 = count $l1;
591
592  $l2 = { $l2 => 1 } unless ref $l2;
593  my $r = add $r2,
594           scale $c2,
595             add map { scale $l2->{$_}, cumulate $r1, $_, $c1 } keys %$l2;
596  my $c = 1 - count $r;
597  my $l = scale $c, normalize add map { power $l1, $_, $l2->{$_} } keys %$l2;
598  return $r, $l;
599 }
600
601 =head1 EXPORT
602
603 An object-oriented module shouldn't export any function, and so does this one.
604
605 =head1 CAVEATS
606
607 The algorithm may be pessimistic (things seen as C<list> while they are of fixed length) but not optimistic (the opposite, duh).
608
609 C<wantarray> isn't specialized when encountered in the optree.
610
611 =head1 DEPENDENCIES
612
613 L<perl> 5.8.1.
614
615 A C compiler.
616 This module may happen to build with a C++ compiler as well, but don't rely on it, as no guarantee is made in this regard.
617
618 L<Carp> (standard since perl 5), L<B> (since perl 5.005) and L<XSLoader> (since perl 5.6.0).
619
620 =head1 AUTHOR
621
622 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
623
624 You can contact me by mail or on C<irc.perl.org> (vincent).
625
626 =head1 BUGS
627
628 Please report any bugs or feature requests to C<bug-sub-nary at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Sub-Nary>.
629 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
630
631 =head1 SUPPORT
632
633 You can find documentation for this module with the perldoc command.
634
635     perldoc Sub::Nary
636
637 Tests code coverage report is available at L<http://www.profvince.com/perl/cover/Sub-Nary>.
638
639 =head1 ACKNOWLEDGEMENTS
640
641 Thanks to Sebastien Aperghis-Tramoni for helping to name this module.
642
643 =head1 COPYRIGHT & LICENSE
644
645 Copyright 2008 Vincent Pit, all rights reserved.
646
647 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
648
649 =cut
650
651 1; # End of Sub::Nary