Sub::Prototype::Util - Prototype-related utility routines.


Sub-Prototype-Util documentation Contained in the Sub-Prototype-Util distribution.

Index


Code Index:

NAME

Top

Sub::Prototype::Util - Prototype-related utility routines.

VERSION

Top

Version 0.09

SYNOPSIS

Top

    use Sub::Prototype::Util qw/flatten wrap recall/;

    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';
    my @b = $splice->(\@a, 4, 2); # @a is now ('a', 'b', 'c', 1, 3) and @b is ({ d => 2 }, undef)

DESCRIPTION

Top

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 5.10's _ prototype.

FUNCTIONS

Top

flatten $proto, @args

Flattens the array @args according to the prototype $proto. When @args is what @_ is after calling a subroutine with prototype $proto, flatten returns the list of what @_ would have been if there were no prototype. It croaks if the arguments can't possibly match the required prototype, e.g. when a reference type is wrong or when not enough elements were provided.

wrap $name, %opts

Generates a wrapper that calls the function $name with a prototyped argument list. That is, the wrapper's arguments should be what @_ is when you define a subroutine with the same prototype as $name.

    my $a = [ 0 .. 2 ];
    my $push = wrap 'CORE::push';
    $push->($a, 3, 4); # returns 3 + 2 = 5 and $a now contains 0 .. 4

You can force the use of a specific prototype. In this case, $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.

    my $push = wrap { 'CORE::push' => '\@$' }; # only pushes 1 arg

Others arguments are seen as key / value pairs that are meant to tune the code generated by wrap. Valid keys are :

ref => $func

Specifies the function used in the generated code to test the reference type of scalars. Defaults to 'ref'. You may also want to use Scalar::Util::reftype.

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 'undef'. It's a good place to croak or die too.

sub => $bool

Encloses the code into a sub { } block. Default is true.

compile => $bool

Makes wrap compile the code generated and return the resulting code reference. Be careful that in this case ref must be a fully qualified function name. Defaults to true, but turned off when sub is false.

For example, this allows you to recall into CORE::grep and CORE::map by using the \&@ prototype :

    my $grep = wrap { 'CORE::grep' => '\&@' };
    sub mygrep (&@) { $grep->(@_) } # the prototypes are intentionally different

recall $name, @args

Calls the function $name with the prototyped argument list @args. That is, @args should be what @_ is when you call a subroutine with $name as prototype. You can still force the prototype by passing { $name => $proto } as the first argument.

    my $a = [ ];
    recall { 'CORE::push' => '\@$' }, $a, 1, 2, 3; # $a just contains 1

It's implemented in terms of wrap, and hence calls eval at each run. If you plan to recall several times, consider using wrap instead.

EXPORT

Top

The functions flatten, wrap and recall are only exported on request, either by providing their name or by the ':funcs' and ':all' tags.

DEPENDENCIES

Top

Carp, Exporter (core modules since perl 5), Scalar::Util (since 5.7.3).

AUTHOR

Top

Vincent Pit, <perl at profvince.com>, http://www.profvince.com.

You can contact me by mail or on irc.perl.org (vincent).

BUGS

Top

Please report any bugs or feature requests to bug-sub-prototype-util at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Sub-Prototype-Util. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

Top

You can find documentation for this module with the perldoc command.

    perldoc Sub::Prototype::Util

Tests code coverage report is available at http://www.profvince.com/perl/cover/Sub-Prototype-Util.

COPYRIGHT & LICENSE

Top


Sub-Prototype-Util documentation Contained in the Sub-Prototype-Util distribution.
package Sub::Prototype::Util;

use 5.006;

use strict;
use warnings;

use Carp qw/croak/;
use Scalar::Util qw/reftype/;

use vars qw/$VERSION/;

$VERSION = '0.09';

my %sigils = qw/SCALAR $ ARRAY @ HASH % GLOB * CODE &/;
my %reftypes = reverse %sigils;

sub _check_ref {
 my ($a, $p) = @_;
 my $r;
 if (!defined $a || !defined($r = reftype $a)) { # not defined or plain scalar
  croak 'Got ' . ((defined $a) ? 'a plain scalar' : 'undef')
               . ' where a reference was expected';
 }
 croak 'Unexpected ' . $r . ' reference' unless exists $sigils{$r}
                                            and $p =~ /\Q$sigils{$r}\E/;
 return $r;
}

sub _clean_msg {
 my ($msg) = @_;
 $msg =~ s/(?:\s+called)?\s+at\s+.*$//s;
 return $msg;
}

sub flatten {
 my $proto = shift;
 return @_ unless defined $proto;
 my @args; 
 while ($proto =~ /(\\?)(\[[^\]]+\]|[^\];])/g) {
  my $p = $2;
  if ($1) {
   my $a = shift;
   my $r = _check_ref $a, $p;
   push @args, $r eq 'SCALAR'
               ? $$a
               : ($r eq 'ARRAY'
                  ? @$a
                  : ($r eq 'HASH'
                     ? %$a
                     : ($r eq 'GLOB'
                        ? *$a
                        : &$a # _check_ref ensures this must be a code ref
                       )
                    )
                 );
  } elsif ($p =~ /[\@\%]/) {
   push @args, @_;
   last;
  } else {
   croak 'Not enough arguments to match this prototype' unless @_;
   push @args, shift;
  }
 }
 return @args;
}

sub _wrap {
 my ($name, $proto, $i, $args, $cr, $opts) = @_;
 while ($proto =~ s/(\\?)(\[[^\]]+\]|[^\];])//) {
  my ($ref, $p) = ($1, $2);
  $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 . ', ';
  }
  ++$i;
 }
 $args =~ s/,\s*$//;
 return $name . '(' . $args . ')';
}

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\$\@\%\*\&;].*//;
 return $name, $proto;
}

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 defined $opts{sub};
 $opts{compile}   = 1       if not defined $opts{compile} and $opts{sub};
 $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 _clean_msg $@ if $@;
 }
 return $call;
}

sub recall {
 my $wrap = eval { wrap shift };
 croak _clean_msg $@ if $@;
 return $wrap->(@_);
}

use base qw/Exporter/;

use vars qw/@EXPORT @EXPORT_OK %EXPORT_TAGS/;

@EXPORT             = ();
%EXPORT_TAGS        = (
 'funcs' =>  [ qw/flatten wrap recall/ ]
);
@EXPORT_OK          = map { @$_ } values %EXPORT_TAGS;
$EXPORT_TAGS{'all'} = [ @EXPORT_OK ];

1; # End of Sub::Prototype::Util