File Coverage

blib/lib/macro.pm
Criterion Covered Total %
statement 171 172 99.4
branch 70 74 94.5
condition 47 57 82.4
subroutine 30 30 100.0
pod 4 6 66.6
total 322 339 94.9


line stmt bran cond sub pod time code
1             package macro;
2              
3 9     9   1468802 use 5.008_001;
  9         178  
  9         487  
4              
5 9     9   54 use strict;
  9         18  
  9         503  
6 9     9   52 use warnings;
  9         20  
  9         390  
7 9     9   48 use warnings::register;
  9         16  
  9         2476  
8              
9             our $VERSION = '0.06';
10              
11 9 100   9   51 use constant DEBUG => $ENV{PERL_MACRO_DEBUG} ? 1 : 0;
  9         26  
  9         927  
12              
13 9     9   57 use Scalar::Util (); # tainted()
  9         21  
  9         147  
14 9     9   45 use Carp ();
  9         20  
  9         176  
15              
16 9     9   13116 use PPI::Document ();
  9         2133801  
  9         288  
17 9     9   118 use PPI::Lexer ();
  9         22  
  9         292  
18             my $lexer = PPI::Lexer->new();
19              
20 9     9   98 use B ();
  9         18  
  9         139  
21 9     9   48 use B::Deparse ();
  9         19  
  9         25483  
22             my $deparser = B::Deparse->new('-si0', '-x9');
23              
24             my $backend;
25              
26             if(DEBUG >= 1 && !$^C){
27             require macro::filter;
28             $backend = 'macro::filter';
29             }
30             else{
31             require macro::compiler;
32             $backend = 'macro::compiler';
33             }
34             sub import{
35 20     20   26219 my $class = shift;
36              
37 20 100       224 return unless @_;
38              
39 17         162 $backend->import(@_);
40              
41 10         961 return;
42             }
43              
44             sub backend{
45 2     2 1 41 return $backend;
46             }
47              
48             sub new :method{
49 26     26 1 96 my($class) = @_;
50              
51 26         123 return bless {} => $class;
52             }
53              
54             sub defmacro :method{
55 28     28 1 2252 my $self = shift;
56              
57 28         264 while(my($name, $macro) = splice @_, 0, 2){
58 46 100 66     973 if( !defined($name) || !defined($macro) ){
59 2         76 warnings::warnif('Illigal declaration of macro');
60 2         9278 next;
61             }
62 44 100 100     394 if(Scalar::Util::tainted($name) || Scalar::Util::tainted($macro)){
63 2         313 Carp::croak('Insecure dependency in macro::defmacro()');
64 0         0 return;
65             }
66              
67 42 100       194 if(exists $self->{$name}){
68 1         59 warnings::warnif(qq{Macro "$name" redefined});
69             }
70              
71 42         2098 my $optimize;
72 42 100       152 if(ref($macro) eq 'CODE'){
73 40         137 $macro = _deparse($macro);
74 38         88 $optimize = 1;
75             }
76              
77 40         219 my $mdoc = $lexer->lex_source( $self->process($macro) );
78              
79 40         163368 $mdoc->prune(\&_want_useless_element);
80 40 50       2070 die $@ if $@;
81              
82 40 100       310 $self->{$name} = $optimize ? $self->_optimize($mdoc) : $mdoc;
83             }
84              
85 24         1416 return;
86             }
87              
88             sub _deparse{
89 40     40   75 my($coderef) = @_;
90 40         369 my $cv = B::svref_2object($coderef);
91              
92 40 100       368 if(ref($cv->START) eq 'B::NULL'){
93 2 100       75 my $subr = sprintf '%s &%s::%s',
94             ($cv->XSUB ? 'XSUB' : 'undefined subroutine'),
95             $cv->GV->STASH->NAME, $cv->GV->SAFENAME;
96 2         81 Carp::croak("Cannot use $subr as macro entity");
97             }
98             else{
99 38         37636 my $src = $deparser->coderef2text($coderef);
100 38 100       376 if($src =~ s/\A ( [^\{]+ ) //xms){ # remove prototype and attributes
101 2         9 my $s = $1;
102 2 100       16 if($s =~ /( \( .+ \) )/xms){
103 1         49 warnings::warnif("Subroutine prototype $1 ignored");
104             }
105 2 100       2308 if($s =~ /(: \s+ \w+)/xms){
106 1         46 warnings::warnif("Subroutine attribute $1 ignored");
107             }
108             }
109 38         3940 return 'do' . $src;
110             }
111             }
112              
113             my %rm_module = map{ $_ => 1 } qw(strict warnings diagnostics);
114             sub _want_useless_element{
115 571     571   40332 my(undef, $it) = @_;
116              
117             # newline
118 571 100 100     3307 return 1 if $it->isa('PPI::Token::Whitespace') && $it->content eq "\n";
119              
120             # semi-colon at the end of the block
121 409 100 66     2417 return 1 if $it->isa('PPI::Token::Structure') && $it->content eq ';'
      100        
122             && !$it->parent->snext_sibling;
123              
124             # package statements created by B::Deparse
125 370 100       1894 return 1 if $it->isa('PPI::Statement::Package');
126              
127             # BEGIN {} created by B::Deparse
128 350 50       1716 return 1 if $it->isa('PPI::Statement::Scheduled');
129              
130             # use VERSION || strict || warnings || diagnostics
131 350 100 66     3647 return 0 unless $it->isa('PPI::Statement::Include') && $it->type eq 'use';
132 58   33     1873 return $it->version || $rm_module{ $it->module };
133             }
134              
135             sub _optimize{
136 38     38   91 my(undef, $md) = @_;
137              
138             # do{ single-statement; } -> +(single-statement)
139              
140 38         167 my @stmt = $md->schild(0)->schild(0)->snext_sibling->schildren;
141              
142 38 100 100     2407 if(@stmt == 1 && (ref($stmt[0]) eq 'PPI::Statement')
      100        
143             && !$stmt[0]->find_any(\&_want_not_simple)){
144              
145 31         739 my $expr = PPI::Statement::Expression->new();
146 31         518 $expr->add_element(PPI::Token::Operator->new('+'));
147 31         2359 $expr->add_element(_list( $stmt[0]->clone() ));
148 31         1168 return $expr;
149             }
150              
151 7         92 return $md;
152             }
153             my %not_simple = map{ $_ => 1 }
154             qw(my our local state for foreach while until);
155              
156             sub _want_not_simple{
157 96     96   1430 my(undef, $it) = @_;
158              
159 96   100     773 return $it->isa('PPI::Token::Word') && $not_simple{$it->content};
160             }
161              
162             ############################ process ############################
163              
164             sub preprocess{
165 40     40 0 114 return $_[1]; # noop
166             }
167             sub postprocess{
168 58     58 0 396 return $_[1]; # noop
169             }
170              
171             sub process :method{
172 58     58 1 213 my($self, $src, $caller) = @_;
173              
174 58         357 my $document = $lexer->lex_source($src);
175              
176 58         711430 my $d = $self->preprocess($document);
177              
178 58         921 foreach my $macrocall( reverse _ppi_find($d, \&_want_macrocall, $self) ){
179 59         4393 $self->_expand($macrocall, $caller);
180             }
181              
182 58         760 return $self->postprocess($d)->top->serialize();
183             }
184              
185             # customized find routine (PPI::Node::find is original)
186             # * dies on fail
187             # * returns found element list, instead of array reference (or false if fails)
188             # * supplies the wanted subroutine with other arguments
189             sub _ppi_find{
190 117     117   272 my($top, $wanted, @others) = @_;
191              
192 117         219 my @found = ();
193 117         489 my @queue = $top->children;
194 117         1532 while ( my $elem = shift @queue ) {
195 4394         18539 my $rv = $wanted->( $top, $elem, @others );
196              
197 4394 100       11086 if(defined $rv){
198 4388 100       8625 push @found, $elem if $rv;
199              
200 4388 100       30613 if($elem->can('children')){
201              
202 908 100       3926 if($elem->can('start')){
203 341         1127 unshift @queue,
204             $elem->start,
205             $elem->children,
206             $elem->finish;
207             }
208             else{
209 567         1588 unshift @queue, $elem->children;
210             }
211             }
212             }
213             else{
214 6         15 last;
215             }
216             }
217 117         592 return @found;
218             }
219              
220              
221             # find 'foo(...)', but not 'Foo->foo(...)'
222             sub _want_macrocall{
223 3573     3573   5044 my($doc, $elem, $macro) = @_;
224              
225              
226 3573 100       10808 if($elem->{enable}){
227 6         16 delete $doc->{skip};
228             }
229 3573 100       9901 if($doc->{skip}){
230 382         658 return 0; # end of _ppi_find()
231             }
232              
233             # 'foo(...); bar(...); }'
234             # ~ <- UnmatchedBrace
235 3191 100       17494 if($elem->isa('PPI::Statement::UnmatchedBrace')){
236 6         20 return; # end of _ppi_find()
237             }
238              
239             # 'foo(...)'
240             # ~~~ <- Word
241             # ~~~~~ <- List
242             # ~~~ <- Expression (or nothing)
243 3185 100 100     21079 if($elem->isa('PPI::Token::Word') && exists $macro->{ $elem->content }){
244              
245             # check "->foo" pattern
246 64         787 my $sibling = $elem->sprevious_sibling;
247 64 100 100     2230 return 0 if $sibling && $sibling->isa('PPI::Token::Operator')
      100        
248             && $sibling->content eq q{->};
249              
250             # check argument list, e.g. "foo(...)"
251 63         339 $sibling = $elem->snext_sibling;
252 63   100     1700 return $sibling && $sibling->isa('PPI::Structure::List');
253             }
254 3121         9291 return 0;
255             }
256              
257             sub _list{
258 92     92   2485 my($element) = @_;
259              
260 92         321 my $open = PPI::Token::Structure->new( q{(} );
261 92         963 my $list = PPI::Structure::List->new($open);
262              
263 92         2126 $list->{finish} = PPI::Token::Structure->new( q{)} );
264              
265 92 50       1831 $list->add_element($element) if $element;
266              
267 92         1639 return $list;
268             }
269              
270              
271              
272             sub _expand{
273 59     59   122 my($self, $word, $caller) = @_;
274              
275             # extracting arguments
276 59         81 my @args;
277 59         189 my $args_list = $word->snext_sibling->clone(); # Structure::List
278              
279 59 100       10651 if(my $expr = $args_list->schild(0)){ # Statement::Expression
280 25         413 my $arg = PPI::Statement::Expression->new();
281              
282             # split $expr by ','
283 25         333 foreach my $it($expr->schildren){
284 89 100 100     3901 if($it->isa('PPI::Token::Operator')
      66        
285             && ( $it->content eq q{,} || $it->content eq q{=>}) ){
286 31         204 push @args, _list($arg);
287              
288 31         106 $arg = PPI::Statement::Expression->new();
289             }
290             else{
291 58         198 $arg->add_element($it->clone());
292             }
293             }
294 25 50       2135 if($arg != $args[-1]){
295 25         220 push @args, _list($arg);
296             }
297             }
298              
299             # replacing parameters
300 59         539 my $md = $self->{ $word->content }->clone(); # copy the macro body
301 59         13510 foreach my $param( _ppi_find($md, \&_want_param) ){
302 45         104 _param_replace($param, \@args, $args_list);
303             }
304              
305 59         757 if(DEBUG >= 2){
306             my $funcall = $word->content . $word->snext_sibling->content;
307             my $replaced = $md->content;
308              
309             my $line = $word->location->[0] + $caller->[2];
310             $funcall =~ s/^/#$line /msxg;
311             print STDERR "$funcall => $replaced\n";
312             }
313              
314 59         376 _funcall_replace($word, $md);
315              
316 59         181 return;
317             }
318              
319             # $_[...]
320             sub _want_param{
321 821     821   936 my $elem = $_[1];
322              
323 821 100 66     4402 return 1 if $elem->isa('PPI::Token::ArrayIndex') && $elem->content eq q{$#_};
324              
325 817 100       7899 return 0 unless $elem->isa('PPI::Token::Magic'); # @_ is a magic variable
326              
327 47 100       124 return 1 if $elem->content eq q{@_};
328              
329 42   33     258 return $elem->content eq q{$_}
330              
331             && ($elem = $elem->snext_sibling)
332             && $elem->isa('PPI::Structure::Subscript')
333              
334             && ($elem = $elem->schild(0))
335             && $elem->isa('PPI::Statement::Expression')
336              
337             && ($elem = $elem->schild(0))
338             && $elem->isa('PPI::Token::Number');
339             }
340             sub _param_idx{
341 36     36   48 my($elem) = @_;
342              
343             # Token::Magic Structure::SubScript Statement::Expression Token::Number
344 36         85 return $elem->snext_sibling->schild(0)->schild(0)->content;
345             }
346              
347             # $_[0] -> (expr)
348             # @_ -> (expr, expr, ...)
349             sub _param_replace{
350 45     45   68 my($param, $args, $args_list) = @_;
351              
352             # XXX: insert_before() requires $arg->isa('PPI::Token'),
353             # but not ($args[$i] / $args_list)->isa('PPI::Token')
354              
355 45         143 $param->__insert_before(PPI::Token::Operator->new(q{+}));
356              
357 45 100       1514 if($param->content eq q{@_}){
    100          
358 5         33 $param->__insert_before($args_list);
359             }
360             elsif($param->content eq q{$#_}){
361 4         41 my $expr = PPI::Statement::Expression->new();
362 4         41 $expr->add_element( PPI::Token::Number->new($#{$args}) );
  4         19  
363 4         90 $param->__insert_before(_list($expr));
364             }
365             else{ # $_[index]
366 36   66     360 my $arg = $args->[_param_idx $param] || _list(PPI::Token::Word->new('undef'));
367 36         1331 $param->__insert_before( $arg );
368 36         902 $param->snext_sibling->remove(); # remove Structure::Subscript
369             }
370              
371              
372 45         1834 $param->remove();
373 45         2712 return;
374             }
375              
376             # word(...) -> do{ ... }
377             sub _funcall_replace{
378 59     59   103 my($word, $block) = @_;
379              
380 59         206 $word->__insert_before($block);
381 59         2473 $word->snext_sibling->remove(); # arglist
382 59         3392 $word->remove(); # word
383 59         5236 return;
384             }
385              
386             1;
387             __END__