]> git.vpit.fr Git - perl/modules/Sub-Prototype-Util.git/blobdiff - lib/Sub/Prototype/Util.pm
Better be on irc.perl.org
[perl/modules/Sub-Prototype-Util.git] / lib / Sub / Prototype / Util.pm
index f090f87be84c8460f2bc030e535d097b1868248e..68ac767034d309c575945a2fb85a893e8a91d53a 100644 (file)
@@ -8,29 +8,33 @@ use Scalar::Util qw/reftype/;
 
 =head1 NAME
 
-Sub::Prototype::Util - Prototypes-related utility routines.
+Sub::Prototype::Util - Prototype-related utility routines.
 
 =head1 VERSION
 
-Version 0.01
+Version 0.08
 
 =cut
 
-our $VERSION = '0.01';
+use vars qw/$VERSION/;
+
+$VERSION = '0.08';
 
 =head1 SYNOPSIS
 
-    use Sub::Prototype::Util qw/flatten recall/;
+    use Sub::Prototype::Util qw/flatten recall wrap/;
 
     my @a = qw/a b c/;
     my @args = ( \@a, 1, { d => 2 }, undef, 3 );
 
     my @flat = flatten '\@$;$', @args; # ('a', 'b', 'c', 1, { d => 2 })
     recall 'CORE::push', @args; # @a contains 'a', 'b', 'c', 1, { d => 2 }, undef, 3
+    my $splice = wrap 'CORE::splice', compile => 1;
+    my @b = $splice->(\@a, 4, 2); # @a is now ('a', 'b', 'c', 1, 3) and @b is ({ d => 2 }, undef)
 
 =head1 DESCRIPTION
 
-Prototypes are evil, but sometimes you just have to bear with them, especially when messing with core functions. This module provides several utilities aimed at faciliting "overloading" of prototyped functions.
+Prototypes are evil, but sometimes you just have to bear with them, especially when messing with core functions. This module provides several utilities aimed at facilitating "overloading" of prototyped functions.
 
 They all handle C<5.10>'s C<_> prototype.
 
@@ -39,6 +43,7 @@ They all handle C<5.10>'s C<_> prototype.
 =cut
 
 my %sigils = qw/SCALAR $ ARRAY @ HASH % GLOB * CODE &/;
+my %reftypes = reverse %sigils;
 
 sub _check_ref {
  my ($a, $p) = @_;
@@ -78,7 +83,7 @@ sub flatten {
   } elsif ($p =~ /[\@\%]/) {
    push @args, @_;
    last;
-  } elsif ($p eq '_') {
+  } elsif ($p eq '_' && @_ == 0) {
    push @args, $_;
   } else {
    push @args, shift;
@@ -96,66 +101,200 @@ Calls the function C<$name> with the prototyped argument list C<@args>. That is,
 
 will call C<push @$a, 1, 2, 3> and so fill the arrayref C<$a> with C<1, 2, 3>. This is especially needed for core functions because you can't C<goto> into them.
 
+You can also force the use of a specific prototype. In this case, C<$name> must be a hash reference that holds exactly one key/value pair, the key being the function name and the value the prototpye that should be used to call it.
+
+    recall { 'CORE::push' => '\@$' }, $a, 1, 2, 3; # will only push 1
+
+This allows you to recall into C<CORE::grep> and C<CORE::map> by using the C<\&@> prototype :
+
+    sub mygrep (&@) { recall { 'CORE::grep' => '\&@' }, @_ } # the prototypes are intentionally different
+
 =cut
 
-sub recall {
- my ($name, @a) = @_;
- croak 'Wrong subroutine name' unless $name;
+sub _check_name {
+ my $name = $_[0];
+ croak 'No subroutine specified' unless $name;
+ my $proto;
+ my $r = ref $name;
+ if (!$r) {
+  $proto = prototype $name;
+ } elsif ($r eq 'HASH') {
+  croak 'Forced prototype hash reference must contain exactly one key/value pair' unless keys %$name == 1;
+  ($name, $proto) = %$name;
+ } else {
+  croak 'Unhandled ' . $r . ' reference as first argument';
+ }
  $name =~ s/^\s+//;
  $name =~ s/[\s\$\@\%\*\&;].*//;
- my $proto = prototype $name;
- my @args;
+ return $name, $proto;
+}
+
+sub recall {
+ my ($name, $proto) = _check_name shift;
+ my $call = $name . '(';
+ my @cr;
  if (defined $proto) {
   my $i = 0;
   while ($proto =~ /(\\?)(\[[^\]]+\]|[^\];])/g) {
    my $p = $2;
    if ($1) {
-    my $r = _check_ref $a[$i], $p;
-    push @args, join '', $sigils{$r}, '{$a[', $i, ']}';
+    my $r = _check_ref $_[$i], $p;
+    $call .= $sigils{$r} . '{$_[' . $i . ']},';
    } elsif ($p =~ /[\@\%]/) {
-    push @args, join '', '@a[', $i, '..', (@a - 1), ']';
+    $call .= '@_[' . $i . '..' . (@_ - 1) . ']';
     last;
    } elsif ($p =~ /\&/) {
-    push @args, 'sub{&{$a[' . $i . ']}}';
-   } elsif ($p eq '_') {
-    push @args, '$_';
+    push @cr, $_[$i];
+    $call .= 'sub{&{$cr[' . $#cr . ']}},';
+   } elsif ($p eq '_' && $i >= @_) {
+    $call .= '$_,';
    } else {
-    push @args, '$a[' . $i . ']';
+    $call .= '$_[' . $i . '],';
    }
    ++$i; 
   }
+  $call =~ s/,$//;
  } else {
-  @args = map '$a[' . $_ . ']', 0 .. @a - 1;
+  $call .= join ',', map '$_[' . $_ . ']', 0 .. @_ - 1;
  }
- my @ret = eval $name . '(' . join(',', @args) . ');';
+ $call .= ');';
+ my @ret = eval $call;
  croak $@ if $@;
  return @ret;
 }
 
+=head2 C<wrap $name, %opts>
+
+Generates a wrapper that does the same thing as L</recall>, but specialized for a given function. This wrapper can be compiled once for all to avoid calling C<eval> at each run (like L</recall> does). You can still force the prototype by passing C<< { $name => $proto } >> as the first argument. Others arguments are seen as key / value pairs and tune the code generated by L</wrap>. Valid keys are :
+
+=over 4
+
+=item C<< ref => $func >>
+
+Specifies the function used in the generated code to test the reference type of scalars. Defaults to C<'ref'>. You may also want to use C<Scalar::Util::reftype>.
+
+=item C<< wrong_ref => $code >>
+
+The code executed when a reference of incorrect type is encountered. The result of this snippet is also the result of the generated code, hence it defaults to C<'undef'>. It's a good place to C<croak> or C<die> too.
+
+=item C<< sub => $bool >>
+
+Encloses the code into a C<sub { }> block. Default is true.
+
+=item C<< compile => $bool >>
+
+Makes L</wrap> compile the code generated and return the resulting code reference. Implies C<< sub => 1 >>. Be careful that in this case C<ref> must be a fully qualified function name. Defaults to false.
+
+=back
+
+This is how you make your own C<push> that pushes into array references :
+
+    my @a = (0 .. 2);
+    my $push = wrap 'CORE::push', compile => 1;
+    $push->(\@a, 3 .. 7); # returns 3 + 5 = 8, and @a now contains 0 .. 7
+
+=cut
+
+sub _wrap {
+ my ($name, $proto, $i, $args, $cr, $opts) = @_;
+ if ($proto =~ /(\\?)(\[[^\]]+\]|[^\];])(.*)/g) {
+  my ($ref, $p) = ($1, $2);
+  $proto = $3;
+  $p = $1 if $p =~ /^\[([^\]]+)\]/;
+  my $cur = '$_[' . $i . ']';
+  if ($ref) {
+   if (length $p > 1) {
+    return 'my $r = ' . $opts->{ref} . '(' . $cur . '); ' 
+           . join ' els',
+              map( {
+               "if (\$r eq '" . $reftypes{$_} ."') { "
+               . _wrap($name, $proto, ($i + 1),
+                              $args . $_ . '{' . $cur . '}, ',
+                              $cr, $opts)
+               . ' }'
+              } split //, $p),
+              'e { ' . $opts->{wrong_ref} . ' }'
+   } else {
+    $args .= $p . '{' . $cur . '}, ';
+   }
+  } elsif ($p =~ /[\@\%]/) {
+   $args .= '@_[' . $i . '..$#_]';
+  } elsif ($p =~ /\&/) {
+   my %h = do { my $c; map { $_ => $c++ } @$cr };
+   my $j;
+   if (not exists $h{$i}) {
+    push @$cr, $i;
+    $j = $#{$cr};
+   } else {
+    $j = int $h{$i};
+   }
+   $args .= 'sub{&{$c[' . $j . ']}}, ';
+  } elsif ($p eq '_') {
+   $args .= '((@_ > ' . $i . ') ? ' . $cur . ' : $_), ';
+  } else {
+   $args .= $cur . ', ';
+  }
+  return _wrap($name, $proto, ($i + 1), $args, $cr, $opts);
+ } else {
+  $args =~ s/,\s*$//;
+  return $name . '(' . $args . ')';
+ }
+}
+
+sub wrap {
+ my ($name, $proto) = _check_name shift;
+ croak 'Optional arguments must be passed as key => value pairs' if @_ % 2;
+ my %opts = @_;
+ $opts{ref}     ||= 'ref';
+ $opts{sub}       = 1       if not exists $opts{sub} or $opts{compile};
+ $opts{wrong_ref} = 'undef' if not defined $opts{wrong_ref};
+ my @cr;
+ my $call;
+ if (defined $proto) {
+  $call = _wrap $name, $proto, 0, '', \@cr, \%opts;
+ } else {
+  $call = _wrap $name, '', 0, '@_';
+ }
+ if (@cr) {
+  $call = 'my @c; '
+        . join('', map { 'push @c, $_[' . $_ . ']; ' } @cr)
+        . $call
+ }
+ $call = '{ ' . $call . ' }';
+ $call = 'sub ' . $call if $opts{sub};
+ if ($opts{compile}) {
+  $call = eval $call;
+  croak $@ if $@;
+ }
+ return $call;
+}
+
 =head1 EXPORT
 
-The functions L</flatten> and L</recall> are only exported on request, either by providing their name or by the C<':consts'> and C<':all'> tags.
+The functions L</flatten>, L</recall> and L</wrap> are only exported on request, either by providing their name or by the C<':funcs'> and C<':all'> tags.
 
 =cut
 
 use base qw/Exporter/;
 
-our @EXPORT         = ();
-our %EXPORT_TAGS    = (
- 'funcs' =>  [ qw/flatten recall/ ]
+use vars qw/@EXPORT @EXPORT_OK %EXPORT_TAGS/;
+
+@EXPORT             = ();
+%EXPORT_TAGS        = (
+ 'funcs' =>  [ qw/flatten recall wrap/ ]
 );
-our @EXPORT_OK      = map { @$_ } values %EXPORT_TAGS;
+@EXPORT_OK          = map { @$_ } values %EXPORT_TAGS;
 $EXPORT_TAGS{'all'} = [ @EXPORT_OK ];
 
 =head1 DEPENDENCIES
 
-L<Carp> (core module since perl 5), L<Scalar::Util> (since 5.7.3).
+L<Carp>, L<Exporter> (core modules since perl 5), L<Scalar::Util> (since 5.7.3).
 
 =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).
+You can contact me by mail or on C<irc.perl.org> (vincent).
 
 =head1 BUGS