File Coverage

lib/Sub/Signatures.pm
Criterion Covered Total %
statement 30 47 63.8
branch 15 24 62.5
condition n/a
subroutine 10 10 100.0
pod 0 3 0.0
total 55 84 65.4


line stmt bran cond sub pod time code
1             package Sub::Signatures;
2             $REVISION = '$Id: Signatures.pm,v 1.3 2004/12/05 21:19:33 ovid Exp $';
3             $VERSION = '0.21';
4              
5 10     10   306936 use 5.006;
  10         39  
  10         483  
6 10     10   85 use strict;
  10         27  
  10         383  
7 10     10   58 use warnings;
  10         21  
  10         323  
8 10     10   12001 use Filter::Simple;
  10         337040  
  10         86  
9              
10             my $CALLPACK;
11             my %SIG;
12              
13             my %METHODS;
14              
15             sub import {
16             my $class = shift;
17             my %props = map { $_ => 1 } @_;
18             ($CALLPACK) = caller;
19             $METHODS{$CALLPACK} = exists $props{methods} ? 1 : 0;
20             if ( $ENV{SS_DEBUG} ) {
21             require Data::Dumper;
22             Data::Dumper->import;
23             $Data::Dumper::Indent = 1;
24             }
25             }
26              
27             my $signature = sub {
28             my ( $subname, $parameters ) = @_;
29             if ( 'fallback' eq $parameters ) {
30             return ( "_${subname}_fallback", '', 0 );
31             }
32             else {
33             my @args =
34             map { /\s*(\S*)\s*(\$\w+)/; [ $1 || 'SCALAR', $2 ] }
35             split /(?:,|=>)/ => $parameters;
36             my $count = @args;
37             $parameters = join ', ' => map { $_->[1] } @args;
38             return ( "__${subname}_$count", $parameters, scalar @args );
39             }
40             };
41              
42             my $make_subs = sub {
43             while ( my ( $pack, $subs ) = each %SIG ) {
44             while ( my ( $sub, $counts ) = each %$subs ) {
45             my @build =
46             sort { $a->[1] cmp $b->[1] }
47             map { [ $_ => $counts->{$_} ] } keys %$counts;
48             foreach my $item (@build) {
49             my ( $count, $target ) = @$item;
50             next if 0 <= index +( $subs->{$sub}{body} || '' ), $target;
51             next if 'body' eq $count;
52             $subs->{$sub}{body} ||= '';
53             $subs->{$sub}{body} .= $target =~ /_fallback$/
54             ? " goto \&$target;\n"
55             : " goto \&$target if $count == \@_;\n";
56             }
57             }
58             }
59             print Dumper(%SIG) if $ENV{SS_DEBUG};
60             };
61              
62             my $install_subs = sub {
63             while ( my ( $pack, $subs ) = each %SIG ) {
64             foreach my $sub ( keys %$subs ) {
65             my $body = $subs->{$sub}{body};
66             my $type = $METHODS{$pack} ? 'method' : 'sub';
67             unless ( $body =~ /_fallback;/ ) {
68             $body .= <<" END_BODY";
69             # if we got to here, there was no $type to dispatch to
70             require Carp;
71             shift if 'method' eq '$type';
72             my \$types = join ', ' => map { ref \$_ || 'SCALAR' } \@_;
73             Carp::croak "Could not find a $type matching your signature: $sub(\$types)";
74             END_BODY
75             }
76 10     10   8469 no warnings 'redefine';
  10         21  
  10         8475  
77             my $installed_sub = "package $pack;\nsub $sub {\n$body}";
78 18 100   18 0 18921 eval $installed_sub;
  7 100   2 0 45  
  2 0   11 0 8  
  1 100   2   3  
  2 50   3   13  
  1 100       27  
  0 100       0  
  2 50       683  
  1 50       8  
  0 50       0  
  0 50       0  
  0 0       0  
  0         0  
  7         4325  
  10         4773  
  1         6  
  0         0  
  0         0  
  0         0  
  1         819  
  1         730  
  0         0  
  0         0  
  0         0  
  0         0  
  2         1399  
  1         435  
  0            
  0            
  0            
  0            
  0            
79             warn
80             "Installing &${pack}::$sub\n----------\n$installed_sub\n----------\n"
81             if !$@ && $ENV{SS_DEBUG};
82             die
83             "Failed to install &${pack}::$sub\n----------\n$installed_sub\n----------\nReason: $@"
84             if $@;
85             }
86             }
87             };
88              
89             # each regex can capture itself!
90             my $sub_name_re = qr/([_[:alpha:]][[:word:]]*)/;
91             my $parameters_re = qr/\(([^)]+)\)/;
92             FILTER {
93             warn "Calling package: $CALLPACK ****" if $ENV{SS_DEBUG};
94             while (/(sub\s+$sub_name_re?\s*$parameters_re[^{]*\{)/) {
95             my ( $sub_with_sig, $oldname, $parameters ) = ( $1, $2, $3 );
96              
97             # the following line doesn't work. For some reason, using prototypes
98             # with this module causes an infinite while loop here.
99             # I'm probably overlooking something really obvious.
100             # next if $parameters =~ /^\s*[\\\$@%*;\[\]]*\s*$/; # ignore prototypes
101              
102             my ( $newname, $newparams, $count );
103             if ($oldname) {
104              
105             # named sub
106             ( $newname, $newparams, $count ) =
107             $signature->( $oldname, $parameters );
108             if ( exists $SIG{$CALLPACK}{$oldname}
109             && exists $SIG{$CALLPACK}{$oldname}{$count} )
110             {
111             my $args = $newname;
112             $args =~ s/^_\w+_//;
113             $args =~ s/_/, /g;
114              
115             # how do I get the line number?
116             die "$oldname($args) redefined in package '$CALLPACK'";
117             }
118             $SIG{$CALLPACK}{$oldname}{$count} = $newname;
119             }
120             else {
121              
122             # anonymous sub
123             $newname = '';
124             $newparams = $parameters;
125             }
126             if ($newparams) {
127             s/\Q$sub_with_sig\E/sub $newname \{ my ($newparams) = \@_;/;
128             }
129             else {
130             s/\Q$sub_with_sig\E/sub $newname \{/;
131             }
132             }
133             $make_subs->();
134             $install_subs->();
135             print $_ if $ENV{SS_DEBUG};
136             };
137              
138             1;
139              
140             __END__