]> git.vpit.fr Git - perl/modules/Sub-Prototype-Util.git/blob - lib/Sub/Prototype/Util.pm
Importing Sub-Prototype-Util-0.07.tar.gz
[perl/modules/Sub-Prototype-Util.git] / lib / Sub / Prototype / Util.pm
1 package Sub::Prototype::Util;
2
3 use strict;
4 use warnings;
5
6 use Carp qw/croak/;
7 use Scalar::Util qw/reftype/;
8
9 =head1 NAME
10
11 Sub::Prototype::Util - Prototype-related utility routines.
12
13 =head1 VERSION
14
15 Version 0.07
16
17 =cut
18
19 use vars qw/$VERSION/;
20
21 $VERSION = '0.07';
22
23 =head1 SYNOPSIS
24
25     use Sub::Prototype::Util qw/flatten recall wrap/;
26
27     my @a = qw/a b c/;
28     my @args = ( \@a, 1, { d => 2 }, undef, 3 );
29
30     my @flat = flatten '\@$;$', @args; # ('a', 'b', 'c', 1, { d => 2 })
31     recall 'CORE::push', @args; # @a contains 'a', 'b', 'c', 1, { d => 2 }, undef, 3
32     my $splice = wrap 'CORE::splice', compile => 1;
33     my @b = $splice->(\@a, 4, 2); # @a is now ('a', 'b', 'c', 1, 3) and @b is ({ d => 2 }, undef)
34
35 =head1 DESCRIPTION
36
37 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.
38
39 They all handle C<5.10>'s C<_> prototype.
40
41 =head1 FUNCTIONS
42
43 =cut
44
45 my %sigils = qw/SCALAR $ ARRAY @ HASH % GLOB * CODE &/;
46 my %reftypes = reverse %sigils;
47
48 sub _check_ref {
49  my ($a, $p) = @_;
50  my $r;
51  if (!defined $a || !defined($r = reftype $a)) { # not defined or plain scalar
52   croak 'Got ' . ((defined $a) ? 'a plain scalar' : 'undef')
53                . ' where a reference was expected';
54  }
55  croak 'Unexpected ' . $r . ' reference' unless exists $sigils{$r}
56                                             and $p =~ /\Q$sigils{$r}\E/;
57  return $r;
58 }
59
60 =head2 C<flatten $proto, @args>
61
62 Flattens the array C<@args> according to the prototype C<$proto>. When C<@args> is what C<@_> is after calling a subroutine with prototype C<$proto>, C<flatten> returns the list of what C<@_> would have been if there were no prototype.
63
64 =cut
65
66 sub flatten {
67  my $proto = shift;
68  return @_ unless defined $proto;
69  my @args; 
70  while ($proto =~ /(\\?)(\[[^\]]+\]|[^\];])/g) {
71   my $p = $2;
72   if ($1) {
73    my $a = shift;
74    my $r = _check_ref $a, $p;
75    my %deref = (
76     SCALAR => sub { push @args, $$a },
77     ARRAY  => sub { push @args, @$a },
78     HASH   => sub { push @args, %$a },
79     GLOB   => sub { push @args, *$a },
80     CODE   => sub { push @args, &$a }
81    );
82    $deref{$r}->();
83   } elsif ($p =~ /[\@\%]/) {
84    push @args, @_;
85    last;
86   } elsif ($p eq '_' && @_ == 0) {
87    push @args, $_;
88   } else {
89    push @args, shift;
90   }
91  }
92  return @args;
93 }
94
95 =head2 C<recall $name, @args>
96
97 Calls the function C<$name> with the prototyped argument list C<@args>. That is, C<@args> should be what C<@_> is when you define a subroutine with the same prototype as C<$name>. For example,
98
99     my $a = [ ];
100     recall 'CORE::push', $a, 1, 2, 3;
101
102 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.
103
104 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.
105
106     recall { 'CORE::push' => '\@$' }, $a, 1, 2, 3; # will only push 1
107
108 This allows you to recall into C<CORE::grep> and C<CORE::map> by using the C<\&@> prototype :
109
110     sub mygrep (&@) { recall { 'CORE::grep' => '\&@' }, @_ } # the prototypes are intentionally different
111
112 =cut
113
114 sub _check_name {
115  my $name = $_[0];
116  croak 'No subroutine specified' unless $name;
117  my $proto;
118  my $r = ref $name;
119  if ($r eq 'HASH') {
120   croak 'Forced prototype hash reference must contain exactly one key/value pair' unless keys %$name == 1;
121   ($name, $proto) = %$name;
122  } elsif (length $r) {
123   croak 'Unhandled ' . $r . ' reference as first argument';
124  }
125  $name =~ s/^\s+//;
126  $name =~ s/[\s\$\@\%\*\&;].*//;
127  $proto = prototype $name unless $proto;
128  return $name, $proto;
129 }
130
131 sub recall {
132  my ($name, $proto) = _check_name shift;
133  my $call = $name . '(';
134  my @cr;
135  if (defined $proto) {
136   my $i = 0;
137   while ($proto =~ /(\\?)(\[[^\]]+\]|[^\];])/g) {
138    my $p = $2;
139    if ($1) {
140     my $r = _check_ref $_[$i], $p;
141     $call .= $sigils{$r} . '{$_[' . $i . ']},';
142    } elsif ($p =~ /[\@\%]/) {
143     $call .= '@_[' . $i . '..' . (@_ - 1) . ']';
144     last;
145    } elsif ($p =~ /\&/) {
146     push @cr, $_[$i];
147     $call .= 'sub{&{$cr[' . $#cr . ']}},';
148    } elsif ($p eq '_' && $i >= @_) {
149     $call .= '$_,';
150    } else {
151     $call .= '$_[' . $i . '],';
152    }
153    ++$i; 
154   }
155   $call =~ s/,$//;
156  } else {
157   $call .= join ',', map '$_[' . $_ . ']', 0 .. @_ - 1;
158  }
159  $call .= ');';
160  my @ret = eval $call;
161  croak $@ if $@;
162  return @ret;
163 }
164
165 =head2 C<wrap $name, %opts>
166
167 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 :
168
169 =over 4
170
171 =item C<< ref => $func >>
172
173 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>.
174
175 =item C<< wrong_ref => $code >>
176
177 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.
178
179 =item C<< sub => $bool >>
180
181 Encloses the code into a C<sub { }> block. Default is true.
182
183 =item C<< compile => $bool >>
184
185 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.
186
187 =back
188
189 This is how you make your own C<push> that pushes into array references :
190
191     my @a = (0 .. 2);
192     my $push = wrap 'CORE::push', compile => 1;
193     $push->(\@a, 3 .. 7); # returns 3 + 5 = 8, and @a now contains 0 .. 7
194
195 =cut
196
197 sub _wrap {
198  my ($name, $proto, $i, $args, $cr, $opts) = @_;
199  if ($proto =~ /(\\?)(\[[^\]]+\]|[^\];])(.*)/g) {
200   my ($ref, $p) = ($1, $2);
201   $proto = $3;
202   $p = $1 if $p =~ /^\[([^\]]+)\]/;
203   my $cur = '$_[' . $i . ']';
204   if ($ref) {
205    if (length $p > 1) {
206     return 'my $r = ' . $opts->{ref} . '(' . $cur . '); ' 
207            . join ' els',
208               map( {
209                "if (\$r eq '" . $reftypes{$_} ."') { "
210                . _wrap($name, $proto, ($i + 1),
211                               $args . $_ . '{' . $cur . '}, ',
212                               $cr, $opts)
213                . ' }'
214               } split //, $p),
215               'e { ' . $opts->{wrong_ref} . ' }'
216    } else {
217     $args .= $p . '{' . $cur . '}, ';
218    }
219   } elsif ($p =~ /[\@\%]/) {
220    $args .= '@_[' . $i . '..$#_]';
221   } elsif ($p =~ /\&/) {
222    my %h = do { my $c; map { $_ => $c++ } @$cr };
223    my $j;
224    if (not exists $h{$i}) {
225     push @$cr, $i;
226     $j = $#{$cr};
227    } else {
228     $j = int $h{$i};
229    }
230    $args .= 'sub{&{$c[' . $j . ']}}, ';
231   } elsif ($p eq '_') {
232    $args .= '((@_ > ' . $i . ') ? ' . $cur . ' : $_), ';
233   } else {
234    $args .= $cur . ', ';
235   }
236   return _wrap($name, $proto, ($i + 1), $args, $cr, $opts);
237  } else {
238   $args =~ s/,\s*$//;
239   return $name . '(' . $args . ')';
240  }
241 }
242
243 sub wrap {
244  my ($name, $proto) = _check_name shift;
245  croak 'Optional arguments must be passed as key => value pairs' if @_ % 2;
246  my %opts = @_;
247  $opts{ref}     ||= 'ref';
248  $opts{sub}       = 1       if not exists $opts{sub} or $opts{compile};
249  $opts{wrong_ref} = 'undef' if not defined $opts{wrong_ref};
250  my @cr;
251  my $call;
252  if (defined $proto) {
253   $call = _wrap $name, $proto, 0, '', \@cr, \%opts;
254  } else {
255   $call = _wrap $name, '', 0, '@_';
256  }
257  if (@cr) {
258   $call = 'my @c; '
259         . join('', map { 'push @c, $_[' . $_ . ']; ' } @cr)
260         . $call
261  }
262  $call = '{ ' . $call . ' }';
263  $call = 'sub ' . $call if $opts{sub};
264  if ($opts{compile}) {
265   $call = eval $call;
266   croak $@ if $@;
267  }
268  return $call;
269 }
270
271 =head1 EXPORT
272
273 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.
274
275 =cut
276
277 use base qw/Exporter/;
278
279 use vars qw/@EXPORT @EXPORT_OK %EXPORT_TAGS/;
280
281 @EXPORT             = ();
282 %EXPORT_TAGS        = (
283  'funcs' =>  [ qw/flatten recall wrap/ ]
284 );
285 @EXPORT_OK          = map { @$_ } values %EXPORT_TAGS;
286 $EXPORT_TAGS{'all'} = [ @EXPORT_OK ];
287
288 =head1 DEPENDENCIES
289
290 L<Carp>, L<Exporter> (core modules since perl 5), L<Scalar::Util> (since 5.7.3).
291
292 =head1 AUTHOR
293
294 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
295
296 You can contact me by mail or on #perl @ FreeNode (vincent or Prof_Vince).
297
298 =head1 BUGS
299
300 Please report any bugs or feature requests to C<bug-sub-prototype-util at rt.cpan.org>, or through the web interface at L<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.
301
302 =head1 SUPPORT
303
304 You can find documentation for this module with the perldoc command.
305
306     perldoc Sub::Prototype::Util
307
308 Tests code coverage report is available at L<http://www.profvince.com/perl/cover/Sub-Prototype-Util>.
309
310 =head1 COPYRIGHT & LICENSE
311
312 Copyright 2008 Vincent Pit, all rights reserved.
313
314 This program is free software; you can redistribute it and/or modify it
315 under the same terms as Perl itself.
316
317 =cut
318
319 1; # End of Sub::Prototype::Util