File Coverage

blib/lib/Attribute/Handlers/Prospective.pm
Criterion Covered Total %
statement 83 93 89.2
branch 41 48 85.4
condition 16 25 64.0
subroutine 12 14 85.7
pod 0 6 0.0
total 152 186 81.7


line stmt bran cond sub pod time code
1             package Attribute::Handlers::Prospective;
2             $VERSION = '0.01';
3 1     1   11313 use Filter::Simple;
  1         45312  
  1         10  
4 1     1   84 use Text::Balanced ':ALL';
  1         2  
  1         260  
5 1     1   6 use Carp;
  1         7  
  1         943  
6              
7             our $id = qr/(?>[a-z_]\w*(?:::[a-z_]\w*)*)/i;
8             our $parens = qr/[(](?:(?>[^()]+)|(??{$parens}))*[)]/;
9             our $attr = qr/$id(?:$parens)?/;
10             our $decl = qr/my|our|local/;
11             our $sigil = qr/[\$\@%*]/;
12             our $comments = qr/(?-sm:\s*#.*\n)*\s*/;
13              
14             our $attr_list5 = qr/:$comments($attr$comments(?::?$comments$attr$comments)*)/;
15             our $sub_decl5 = qr/\bsub\s+($id)\s*(?:$attr_list5)?\s*($parens)?/;
16             our $sub_anon5 = qr/\bsub\s*(?:$attr_list5)?\s*($parens)?/;
17             our $var_decl5 = qr/\b($decl)\s*($id?)\s*($sigil)($id)\s*$attr_list5\s*(\S)/;
18             our $var_noattr5 = qr/\b($decl\s*$id?\s*$sigil$id\s*(?=\S)(?!:))/;
19              
20             our $attr_list6 = qr/\bis\s+($attr(?:\s*(?:(?:\bis\b)?\s*$attr|#[^\n]\n))*)/;
21             our $sub_decl6 = qr/\bsub\s+($id)\s*(?:$attr_list6)?\s*($parens)?(?=\s*[{])/;
22             our $sub_anon6 = qr/\bsub\s*(?:$attr_list6)?\s*($parens)?/;
23             our $var_decl6 = qr/\b($decl)\s*($id?)\s*($sigil)($id)\s*$attr_list6\s*(\S)/;
24             our $var_noattr6 = qr/\b($decl\s+$id?\s*$sigil$id\s*(?=\S)(?!is\b))/;
25              
26             our ($attr_list, $sub_decl, $sub_anon, $var_decl, $var_noattrs);
27              
28             our %ATTRS = ( ATTR => {} );
29              
30             our @PHASES = qw(BEGIN CHECK INIT RUN END);
31              
32             sub get_attr {
33 0     0 0 0 my $attr = shift;
34 0   0     0 my $package = shift || caller;
35 0 0       0 return $ATTRS{$package} unless $attr;
36 0         0 return $ATTRS{$package}{$attr};
37             }
38              
39             sub def_handler {
40 341     341 0 475 my ($location, $type, $phase, $attr, $ATTR) = @_;
41 1     1   1315 use Data::Dumper 'Dumper';
  1         9096  
  1         2767  
42 341 50 66     738 return $ATTR->{rawdata} ? "*{'$attr (RAWDATA)'} = \\&$attr;" : ""
    100          
43             if !$phase && !$type;
44 330 100 100     1012 return "*{'$attr ($type)'} = \\&$attr;"
45             if !$phase && $ATTR->{types}{$type};
46 299 100 100     1252 return "*{'$attr ($type $phase)'} = \\&$attr;"
47             if $ATTR->{types}{$type} && $ATTR->{phases}{$phase};
48 268         632 return "";
49             }
50              
51             sub def_attr {
52 11     11 0 23 my ($sub, $data, $pkg) = @_;
53 11         49 my $ATTR = $ATTRS{$pkg}{$sub} = {};
54 11         1260 $ATTR->{phases}{$_} = $data =~ s/\s*,?\s*($_)\s*,?\s*// for @PHASES;
55 11 100       87 $ATTR->{phases}{INIT} = 1 unless grep $ATTR->{phases}{$_}, @PHASES;
56 11 100       60 $ATTR->{handler} = $sub =~ /::/ ? $sub : $pkg."::".$sub;
57 11 100       59 $data .= ',ANY' unless $data =~/\b(SCALAR|ARRAY|HASH|GLOB|CODE|VAR)\b/;
58 11 100       74 $ATTR->{types}{SCALAR} = 1 if $data =~ /\b(undef|ANY|VAR|SCALAR)\b/;
59 11 100       70 $ATTR->{types}{ARRAY} = 1 if $data =~ /\b(undef|ANY|VAR|ARRAY)\b/;
60 11 100       60 $ATTR->{types}{HASH} = 1 if $data =~ /\b(undef|ANY|VAR|HASH)\b/;
61 11 100       52 $ATTR->{types}{GLOB} = 1 if $data =~ /\b(undef|ANY|GLOB)\b/;
62 11 100       69 $ATTR->{types}{CODE} = 1 if $data =~ /\b(undef|ANY|CODE)\b/;
63 11 50       31 $ATTR->{rawdata} = 1 if $data =~ /\b(RAWDATA)\b/;
64 11         27 return $ATTR;
65             }
66              
67             sub def_call {
68 34     34 0 75 my ($impl, $owner, $reftype, $attr, $args) = @_;
69 34         81 foreach my $phase ( qw(BEGIN CHECK INIT RUN END) ) {
70 170         1016 $impl->{$phase} .=
71             "eval{$owner->\${\\'$attr ($reftype $phase)'}($args,'$phase');1} || " .
72             "eval{$owner->\${\\'AUTOATTR ($reftype $phase)'}($args,'$phase');1};";
73             }
74             }
75              
76             sub def_call_prepost {
77 45     45 0 99 my ($impl, $owner, $reftype, $args, $arglist) = @_;
78 45         90 foreach my $handler ( qw(PREATTR POSTATTR) ) {
79 90         131 foreach my $phase ( qw(BEGIN CHECK INIT RUN END) ) {
80 450         1941 $impl->{$phase} .=
81             "eval{$owner->\${\\'$handler ($reftype $phase)'}($args, '$handler',$arglist,'$phase');1}; ";
82             }
83             }
84             }
85              
86             my %sigil_to_type = (
87             '$' => 'SCALAR',
88             '@' => 'ARRAY',
89             '%' => 'HASH',
90             '&' => 'CODE',
91             '*' => 'GLOB',
92             );
93              
94             sub impl_attrs {
95 56     56 0 141 my ($attrs,$name,$pkg,$sigil,$decl,$type) = @_;
96 56         83 my %impl;
97 56         97 my $prepostargs = "";
98 56         83 my $noprepost = 0;
99 56 50 33     351 my $glob = ($decl eq 'my') ? "'LEXICAL($sigil$name)'"
    100          
100             : ($decl eq 'sub' && !$name) ? "'ANON'"
101             : "\\*$name";
102 56 50       145 my $referent = $name ? "\\$sigil$name" : '$_';
103 56 100 66     323 my $location = $name && $name =~ /^(.*::)+/ ? $1
104             : '__PACKAGE__';
105 56 100       117 my $owner = $type ? $type : $location;
106 56         138 my $reftype = $sigil_to_type{$sigil};
107 56         68 while (1) {
108 101 100       12052 $attrs =~ m/\G:?$comments\s*($id)($parens)?$comments/gc or last;
109 45   100     527 my ($attr, $data) = ($1, $2||"");
110 45         328 $data =~ s/^[(]|[)]$//g;
111 45   100     135 $data ||= 'undef';
112 45 100       118 if ($attr eq 'ATTR') {
113 11         41 my $ATTR = def_attr($name, $data, $pkg);
114 11         16 $noprepost=1;
115 11         41 $impl{BEGIN} .= def_handler($location, undef, undef, $name, $ATTR);
116 11         26 foreach my $type ( qw(SCALAR ARRAY HASH CODE GLOB) ) {
117 55         114 $impl{BEGIN} .= def_handler($location, $type, undef, $name, $ATTR);
118 55         87 foreach my $phase ( qw(BEGIN CHECK INIT RUN END) ) {
119 275         500 $impl{BEGIN} .= def_handler($location, $type, $phase, $name, $ATTR);
120             }}
121 11         25 next;
122             }
123 34   33     228 $data &&= "$owner->can('$attr (RAWDATA)') ? q($data) : eval q([$data])";
124 34         85 my $args = "$glob,$referent,'$attr',$data";
125 34         189 $impl{BEGIN} .= "die 'No such $reftype attribute: '.${owner}.'::$attr' unless $owner->can('$attr ($reftype)') || $owner->can('AUTOATTR ($reftype)');";
126 34         114 def_call(\%impl, $owner, $reftype, $attr, $args);
127 34         121 $prepostargs .= "[$owner,$args],";
128             }
129 56 100       686 def_call_prepost(\%impl, $owner, $reftype, "$glob, $referent", "[$prepostargs]")
130             unless $noprepost;
131 236 100       3769 return join " ",
132 280         630 map { ($_ eq 'RUN' ? "" : $_) . "{ $impl{$_} }" }
133 56         159 grep { defined $impl{$_} }
134             qw(BEGIN CHECK INIT RUN END);
135             }
136              
137             sub _usage_AH_ {
138 0     0   0 croak "Usage: use $_[0] autotie => {AttrName => TieClassName,...}";
139             }
140              
141             FILTER {
142             my $caller = shift;
143             my $classname = shift;
144             my $autotied = "";
145             while (@_) {
146             my $cmd = shift;
147             next if $cmd =~ /^Perl\s*6$/;
148             if ($cmd =~ /^autotie((?:ref)?)$/) {
149             my $tiedata = $1 ? '$ref, @$data' : '@$data';
150             my $mapping = shift;
151             _usage_AHI_ $class unless ref($mapping) eq 'HASH';
152             while (my($attr, $tieclass) = each %$mapping) {
153             $tieclass =~ s/^($id)(.*)/$1/is;
154             my $args = $2||'()';
155             _usage_AH_ $class unless $attr =~ $id
156             && $tieclass =~ $id;
157             $attr =~ s/__CALLER__/$caller/e;
158             $attr = $caller."::".$attr unless $attr =~ /::/;
159             $autotied .= qq(
160             eval { require $tieclass and $tieclass->import($args) };
161             sub $attr : ATTR(VAR,RUN) {
162             my (\$ref, \$data) = \@_[2,4];
163             my \$type = ref(\$ref);
164             if (\$type eq 'SCALAR') {
165             tie \$\$ref,'$tieclass',$tiedata
166             }
167             elsif (\$type eq 'ARRAY') {
168             tie \@\$ref,'$tieclass',$tiedata
169             }
170             elsif (\$type eq 'HASH') {
171             tie \%\$ref,'$tieclass',$tiedata
172             }
173             else {
174             print STDERR "Can't autotie a \$type\n" and exit
175             }
176             }
177             );
178             }
179             }
180             else {
181             print STDERR "Can't understand $cmd\n" and exit;
182             }
183             }
184             $_ = $autotied . $_;
185             pos() ||= 0;
186             my $newcode;
187             my $extracted;
188             while (pos() < length()) {
189             my @found;
190             if (($extracted) = extract_quotelike($_,q//) and $extracted or
191             ($extracted) = extract_variable($_,q//) and $extracted ) {
192             $newcode .= $extracted;
193             }
194             elsif (m/\G$sub_decl/gc) {
195             my ($name, $attrs, $params) = ($1,$2||"",$3||"");
196             my ($block) = extract_codeblock;
197             $DB::single = 1;
198             $newcode .= "sub $name $params $block ;"
199             . impl_attrs($attrs,$name,$caller,'&','sub');
200             }
201             elsif (m/\G$sub_anon/gc) {
202             my ($attrs, $params) = ($1||"",$2||"");
203             my ($block) = extract_codeblock;
204             $newcode .= "do { local \$_ = sub $params $block; "
205             . impl_attrs($attrs,undef,$caller,'&','sub')
206             . ' ; $_ }';
207             }
208             elsif (m/\G$var_noattrs/gc) {
209             $newcode .= $1;
210             }
211             elsif (m/\G$var_decl/gc) {
212             my ($decl, $type, $sigil, $name, $attrs, $nextchar)
213             = ($1, $2||"",$3, $4, $5||"", $6);
214             $newcode .= "$decl $type $sigil$name; "
215             . impl_attrs($attrs,$name,$caller,$sigil,$decl,$type)
216             . "; "
217             . ($nextchar eq '=' ? "$sigil$name " : "")
218             . $nextchar;
219             }
220             elsif (m/\G($id|$parens|.)/gcs) {
221             $newcode .= $1;
222             }
223             else {
224             die "Internal error";
225             }
226             }
227             $_ = $newcode;
228             # print STDERR if $_;
229             }
230             qr/^__(END|DATA)__$/m;
231              
232 1     1   14 no warnings;
  1         3  
  1         380  
233             my $filterer = *import{CODE};
234 2     2   7 my $mod_filterer = sub { unshift @_, scalar caller; goto &$filterer };
  2         10  
235             *import = sub {
236 2 50   2   53747 if (grep /Perl\s*6/, @_) {
237 0         0 $attr_list = $attr_list6;
238 0         0 $sub_decl = $sub_decl6;
239 0         0 $sub_anon = $sub_anon6;
240 0         0 $var_decl = $var_decl6;
241 0         0 $var_noattrs = $var_noattr6;
242             }
243             else {
244 2         6 $attr_list = $attr_list5;
245 2         4 $sub_decl = $sub_decl5;
246 2         3 $sub_anon = $sub_anon5;
247 2         4 $var_decl = $var_decl5;
248 2         13 $var_noattrs = $var_noattr5;
249             }
250 2         5 *{caller()."::import"} = $mod_filterer;
  2         13  
251 2         12 goto &$mod_filterer
252             };
253              
254             1;
255              
256             __END__