File Coverage

blib/lib/Sub/PatMat.pm
Criterion Covered Total %
statement 92 136 67.6
branch 10 46 21.7
condition 7 33 21.2
subroutine 20 22 90.9
pod 0 7 0.0
total 129 244 52.8


line stmt bran cond sub pod time code
1             package Sub::PatMat;
2 2     2   84877 use 5.8.2;
  2         8  
  2         98  
3 2     2   11 use strict;
  2         3  
  2         71  
4 2     2   11 use warnings;
  2         10  
  2         108  
5 2     2   11 use B;
  2         4  
  2         144  
6 2     2   2611 use B::Utils qw/walkoptree_filtered opgrep/;
  2         18357  
  2         231  
7 2     2   135 use Carp;
  2         6  
  2         242  
8              
9 2     2   14 use vars qw($VERSION);
  2         4  
  2         359  
10             $VERSION = 0.01;
11              
12             my %whens;
13             my %names;
14             my $redefine_bitch;
15             my @redefinitions;
16              
17             sub import
18             {
19 2     2   13 no strict 'refs';
  2         5  
  2         776  
20 3     3   164 my $pkg = caller(0);
21 3         9 *{$pkg."::MODIFY_CODE_ATTRIBUTES"} = \&modify_code_attributes;
  3         22  
22 2     2   6366 eval "package $pkg; CHECK { Sub::PatMat::do_check(\"\Q$pkg\E\") }";
  3         179  
23 2     2   13 eval "package $pkg; INIT { Sub::PatMat::do_init() }";
  3         221  
24             }
25              
26             sub modify_code_attributes {
27 11     11 0 4847 my ($pkg, $sub, @attr) = @_;
28 11         14 my @rest;
29             my $when;
30 11         20 for (@attr) {
31 11 50       46 if (/^when(.*)$/) {
32 11         33 $when = $1;
33             } else {
34 0         0 push @rest, $_;
35             }
36             }
37 11 50       28 if (defined $when) {
38 11         10 push @{$whens{$pkg}}, {
  11         51  
39             func => $sub,
40             when => $when,
41             };
42             }
43 11         34 return @rest;
44             }
45              
46             BEGIN {
47 2     2   7 my $old_warn_handler = $SIG{__WARN__};
48             $SIG{__WARN__} = sub {
49 1 50       6 return if $_[0] =~ /package attribute may clash with future reserved word: when/;
50 1 50 33     16 if (!$redefine_bitch && $_[0] =~ /^Subroutine (.*) redefined/) {
51 1         6 push @redefinitions, { func => $1, bitch => $_[0] };
52 1         12 return;
53             }
54 0 0       0 goto &$old_warn_handler if $old_warn_handler;
55 0         0 warn(@_);
56 2         16533 };
57             }
58              
59             sub create_pat_mat
60             {
61 3     3 0 6 my ($pkg, $name, $info) = @_;
62 3         8 my $code = "package $pkg; \*$name = sub {\n";
63 3         4 my $op = "if";
64 3         3 my $n = 0;
65 3         155 my $cv = eval "*$pkg\::$name\{CODE}";
66 3 100 33     40 if ($cv && @$info && $info->[-1]{func} ne $cv) {
      66        
67             # print "fallback for $name: $cv\n";
68 2         7 push @$info, { func => $cv, when => "()" };
69             }
70 3         7 for my $i (@$info) {
71 6         8 my $cond = $i->{when};
72 6 100       16 $cond = "(1)" if $cond eq "()";
73 6         17 $cond = replace_aliases($cond, $info->[$n]{func});
74 6         22 $code .= "$op $cond { &{\$info->[$n]{func}} }\n";
75 6         7 $op = "elsif";
76 6         14 $n++;
77             }
78 3         8 $code .= "else { use Carp; local \$Carp::CarpLevel = 1; croak \"Bad match calling \Q$name\E\" } }\n";
79             # print $code;
80 3 100   1   2334 eval $code or die $@;
  1         10  
  1         2  
  1         127  
81             }
82              
83             sub padname
84             {
85 0     0 0 0 my ($padlist, $op) = @_;
86              
87 0         0 my $padname = $padlist->[0]->ARRAYelt($op->targ);
88 0 0 0     0 if ($padname && !$padname->isa("B::SPECIAL")) {
89 0 0       0 return if $padname->FLAGS & B::SVf_FAKE;
90 0         0 return $padname->PVX;
91             }
92 0         0 return;
93             }
94              
95             sub get_gv_name
96             {
97 0     0 0 0 my ($padlist, $op) = @_;
98              
99 0         0 my ($gv_on_pad, $gv_idx);
100 0 0       0 if ($op->isa("B::SVOP")) {
    0          
101 0         0 $gv_idx = $op->targ;
102             } elsif ($op->isa("B::PADOP")) {
103 0         0 $gv_idx = $op->padix;
104 0         0 $gv_on_pad = 1;
105             } else {
106 0         0 return "";
107             }
108              
109 0 0       0 my $gv = $gv_on_pad ? "" : $op->sv;
110 0 0 0     0 if (!$gv || !$$gv) {
111 0         0 $gv = $padlist->[1]->ARRAYelt($gv_idx);
112             }
113 0 0       0 return "" unless $gv->isa("B::GV");
114 0         0 $gv->NAME;
115             }
116              
117             sub replace_aliases
118             {
119 6     6 0 8 my ($cond, $sub) = @_;
120 6         129 my $cv = B::svref_2object($sub);
121 6         20 my $root = $cv->ROOT;
122 6         40 my $padlist = [$cv->PADLIST->ARRAY];
123 6         8 my %vars;
124             walkoptree_filtered($root,
125 62     62   3985 sub { opgrep({ name => "aassign"}, @_) },
126             sub {
127 4     4   254 my ($op) = (@_);
128             return unless
129 4 0 33     67 $op->first->name eq "null" &&
      33        
      33        
      0        
      0        
      0        
130             $op->first->first->name eq "pushmark" &&
131             $op->first->first->sibling->name eq "rv2av" &&
132             $op->first->first->sibling->first->name eq "gv" &&
133             get_gv_name($padlist, $op->first->first->sibling->first) eq "_" &&
134             $op->last->name eq "null" &&
135             $op->last->first->name eq "pushmark";
136 0         0 my %v;
137 0         0 $op = $op->last->first->sibling;
138 0         0 my $n = 0;
139 0         0 my $ok = 1;
140 0         0 while (1) {
141 0 0       0 if ($op->name eq "padsv") {
    0          
    0          
142 0         0 my $name = padname($padlist, $op);
143 0 0       0 last unless $name;
144 0         0 $v{$name} = "\$_[$n]";
145 0         0 $n++;
146             } elsif ($op->name eq "padav") {
147 0         0 last;
148             } elsif ($op->name eq "padhv") {
149 0         0 last;
150             } else {
151 0         0 $ok = 0; last;
  0         0  
152             }
153 0         0 $op = $op->sibling;
154 0 0       0 last if $op->isa("B::NULL");
155             }
156 0 0       0 return unless $ok;
157 0         0 %vars = %v;
158 6         57 });
159 6         491 for my $name (keys %vars) {
160 0         0 $cond =~ s/\Q$name\E(?![\[\{])/$vars{$name}/g;
161             }
162 6         24 $cond;
163             }
164              
165             sub do_check {
166 3     3 0 7 my ($pkg) = @_;
167 3         4 my %byname;
168 3         5 for my $info (@{$whens{$pkg}}) {
  3         12  
169 11         15 my $sub = $info->{func};
170 11         37 my $cv = B::svref_2object($sub);
171 11         29 my $gv = $cv->GV;
172 11         24 my $name = $gv->NAME;
173 11         15 $names{$name} = 1;
174 11         21 $names{"$pkg\::$name"} = 1;
175 11         9 push @{$byname{$name}}, $info;
  11         48  
176             }
177 3         1044 for my $name (keys %byname) {
178 3         9 create_pat_mat($pkg, $name, $byname{$name});
179             }
180             }
181              
182             sub do_init {
183 1     1 0 5 for my $r (@redefinitions) {
184 0 0       0 unless ($names{$r->{func}}) {
185 0         0 $redefine_bitch = 1;
186 0         0 warn $r->{bitch};
187 0         0 $redefine_bitch = 0;
188             }
189             }
190 1         125 @redefinitions = ();
191             }
192              
193             1;
194             __END__