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