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