File Coverage

blib/lib/Module/AnyEvent/Helper/PPI/Transform.pm
Criterion Covered Total %
statement 199 208 95.6
branch 100 128 78.1
condition 80 144 55.5
subroutine 27 27 100.0
pod 9 9 100.0
total 415 516 80.4


line stmt bran cond sub pod time code
1             package Module::AnyEvent::Helper::PPI::Transform;
2              
3 10     10   740469 use strict;
  10         38  
  10         366  
4 10     10   55 use warnings;
  10         19  
  10         926  
5              
6             # ABSTRACT: PPI::Transform subclass for AnyEvent-ize helper
7             our $VERSION = 'v0.0.5'; # VERSION
8              
9              
10             BEGIN {
11 10     10   77 require Exporter;
12 10         204 our (@ISA) = qw(Exporter);
13 10         266 our (@EXPORT_OK) = qw(
14             function_name is_function_declaration delete_function_declaration
15             copy_children
16             emit_cv emit_cv_into_function
17             replace_as_async
18             );
19             }
20              
21 10     10   12860 use parent qw(PPI::Transform);
  10         2970  
  10         68  
22              
23 10     10   766487 use Carp;
  10         23  
  10         740  
24 10     10   80 use Scalar::Util qw(blessed);
  10         17  
  10         41294  
25              
26             ########################################################################
27             # Functions
28             # can be called as class methods
29              
30             sub function_name
31             {
32 901 100   901 1 9196 shift unless blessed($_[0]);
33 901         1323 my $word = shift;
34 901 100 66     5464 croak "function_name: MUST be called with an argument of PPI::Element object" unless blessed($word) && $word->isa('PPI::Element');
35 899         4229 my $st = $word->statement;
36 899 100       15490 return if ! $st;
37 788 100       2037 return function_name($st->parent) if $st->class ne 'PPI::Statement::Sub';
38 189         1125 return $st->schild(1);
39             }
40              
41             sub is_function_declaration
42             {
43 294 100   294 1 3587 shift unless blessed($_[0]);
44 294         512 my $word = shift;
45 294 100 66     2032 croak "is_function_declaration: MUST be called with an argument of PPI::Token::Word object" unless blessed($word) && $word->isa('PPI::Token::Word');
46 292   100     1114 return defined $word->parent && $word->parent->class eq 'PPI::Statement::Sub';
47             }
48              
49             sub delete_function_declaration
50             {
51 23 100   23 1 2320 shift unless blessed($_[0]);
52 23         46 my $word = shift;
53 23 100 66     313 croak "delete_function_declaration: MUST be called with an argument of PPI::Token::Word object" unless blessed($word) && $word->isa('PPI::Token::Word');
54 21         80 return $word->parent->delete;
55             }
56              
57             sub copy_children
58             {
59 59 100 100 59 1 6092 shift unless !defined($_[0]) || $_[0] eq '' || blessed($_[0]);
      100        
60 59         5401 my ($prev, $next, $target) = @_;
61              
62 59 100 66     622 croak 'copy_children: Both of prev and next are not PPI::Element objects' unless blessed($prev) && $prev->isa('PPI::Element') || blessed($next) && $next->isa('PPI::Element');
      66        
      66        
63 57 100 66     533 croak 'copy_children: target is not a PPI::Element object' unless blessed($target) && $target->isa('PPI::Element');
64              
65 55         345 for my $elem ($target->children) {
66 193 50       1361 my $new_elem = $elem->clone or confess 'Cloning element failed';
67 193 100       9275 if($prev) {
68 183 50       868 $prev->insert_after($new_elem) or confess 'Insertion failed';
69             } else {
70 10 50       108 $next->insert_before($new_elem) or confess 'Insertion failed';
71             }
72 193         9870 $prev = $new_elem;
73             }
74             }
75              
76             my $cv_decl = PPI::Document->new(\'my $___cv___ = AE::cv;')->first_element->remove;
77             my $cv_ret = PPI::Document->new(\'return $___cv___;'); #->first_element->remove;
78              
79             sub emit_cv
80             {
81 11 100   11 1 3122 shift unless blessed($_[0]);
82 11         73 my $block = shift;
83 11 100 66     220 croak 'emit_cv: target is not a PPI::Structure::Block object' unless blessed($block) && $block->isa('PPI::Structure::Block');
84 9         197 copy_children($block->first_element, undef, $cv_decl);
85 9         124 copy_children($block->schild($block->schildren-1), undef, $cv_ret);
86             }
87              
88             sub emit_cv_into_function
89             {
90 11 100   11 1 2258 shift unless blessed($_[0]);
91 11         25 my $word = shift;
92 11 100 66     126 croak 'emit_cv_into_function: the first argument is not a PPI::Token::Word object' unless blessed($word) && $word->isa('PPI::Token::Word');
93 9         53 my $block = $word->parent->find_first('PPI::Structure::Block');
94 9         3870 emit_cv($block);
95             }
96              
97             my $shift_recv = PPI::Document->new(\'shift->recv()')->first_element->remove;
98              
99             sub _find_one_call
100             {
101 19 50   19   107 shift unless blessed($_[0]);
102 19         43 my ($word) = @_;
103 19 50 33     181 croak '_find_one_call: the first argument is not a PPI::Element object' unless blessed($word) && $word->isa('PPI::Element');
104 19         52 my ($pre) = [];
105 19         138 my $sprev_orig = $word->sprevious_sibling;
106 19         538 my ($prev, $sprev) = ($word->previous_sibling, $word->sprevious_sibling);
107 19         672 my $state = 'INIT';
108              
109             # TODO: Probably, this is wrong
110 19         28 while(1) {
111             #print STDERR "$state : $sprev\n";
112 27 100       258 last unless $sprev;
113 26 100 66     1434 if(($state eq 'INIT' || $state eq 'LIST' || $state eq 'TERM' || $state eq 'SUBTERM') && $sprev->isa('PPI::Token::Operator') && $sprev->content eq '->') {
    50 100        
    100 66        
    50 66        
    50 66        
    50 33        
    50 66        
    50 33        
      0        
      33        
      33        
      0        
      33        
      66        
      66        
      33        
      66        
      33        
114 4         40 $state = 'OP';
115             } elsif($state eq 'OP' && $sprev->isa('PPI::Structure::List')) {
116 0         0 $state = 'LIST';
117             } elsif(($state eq 'OP' || $state eq 'LIST') && ($sprev->isa('PPI::Token::Word') || $sprev->isa('PPI::Token::Symbol'))) {
118 4         8 $state = 'TERM';
119             } elsif(($state eq 'OP' || $state eq 'SUBTERM') &&
120             ($sprev->isa('PPI::Structure::Constructor') || $sprev->isa('PPI::Structure::List') || $sprev->isa('PPI::Structure::Subscript'))) {
121 0         0 $state = 'SUBTERM';
122             } elsif(($state eq 'OP' || $state eq 'SUBTERM') &&
123             ($sprev->isa('PPI::Token::Word') || $sprev->isa('PPI::Token::Symbol'))) {
124 0         0 $state = 'TERM';
125             } elsif(($state eq 'OP' || $state eq 'TERM') && $sprev->isa('PPI::Structure::Block')) {
126 0         0 $state = 'BLOCK';
127             } elsif($state eq 'BLOCK' && $sprev->isa('PPI::Token::Cast')) {
128 0         0 $state = 'TERM';
129             } elsif($state eq 'INIT' || $state eq 'TERM' || $state eq 'SUBTERM') {
130 18         42 last;
131             } else {
132 0         0 $state = 'ERROR'; last;
  0         0  
133             }
134 8         64 $prev = $sprev->previous_sibling;
135 8         178 $sprev = $sprev->sprevious_sibling;
136             }
137 19 50 66     120 confess "Unexpected token sequence" unless $state eq 'INIT' || $state eq 'TERM' || $state eq 'SUBTERM';
      33        
138 19 100       57 if($state ne 'INIT') {
139 4         17 while($sprev ne $sprev_orig) {
140 8         230 my $sprev_ = $sprev_orig->sprevious_sibling;
141 8         195 unshift @$pre , $sprev_orig->remove;
142 8         284 $sprev_orig = $sprev_;
143             }
144             }
145 19         152 return [$prev, $pre];
146             }
147              
148             sub _replace_as_shift_recv
149             {
150 19 50   19   126 shift unless blessed($_[0]);
151 19         47 my ($word) = @_;
152 19 50 33     173 croak '_replace_as_shift_recv: the first argument is not a PPI::Element object' unless blessed($word) && $word->isa('PPI::Element');
153              
154 19         46 my $args;
155 19         77 my $next = $word->snext_sibling;
156              
157 19         389 my ($prev, $pre) = @{_find_one_call($word)};
  19         76  
158              
159 19 50 33     149 if($next && $next->isa('PPI::Structure::List')) {
160 19         293 my $next_ = $next->next_sibling;
161 19         425 $args = $next->remove;
162 19         767 $next = $next_;
163             }
164 19         110 $word->delete;
165 19         724 copy_children($prev, $next, $shift_recv);
166 19         82 return [$pre, $args];
167             }
168              
169             my $bind_scalar = PPI::Document->new(\('Module::AnyEvent::Helper::bind_scalar($___cv___, MARK(), sub {'."\n});"))->first_element->remove;
170             my $bind_array = PPI::Document->new(\('Module::AnyEvent::Helper::bind_array($___cv___, MARK(), sub {'."\n});"))->first_element->remove;
171              
172             sub replace_as_async
173             {
174 21 100   21 1 2384 shift unless blessed($_[0]);
175 21         57 my ($word, $name, $is_array) = @_;
176 21 100 66     239 croak 'replace_as_async: the first argument is not a PPI::Element object' unless blessed($word) && $word->isa('PPI::Element');
177              
178 19         71 my $st = $word->statement;
179 19         419 my $prev = $word->previous_sibling;
180 19         521 my $next = $word->next_sibling;
181              
182 19         353 my ($pre, $args) = @{_replace_as_shift_recv($word)}; # word and prefixes are removed
  19         68  
183              
184             # Setup binder
185 19 100       106 my $bind_ = $is_array ? $bind_array->clone : $bind_scalar->clone;
186 19 100   152   7091 my $mark = $bind_->find_first(sub { $_[1]->class eq 'PPI::Token::Word' && $_[1]->content eq 'MARK'});
  152         3983  
187 19 50       408 if(defined $args) {
188 19         77 $mark->next_sibling->delete;
189 19         1764 $mark->insert_after($args);
190             }
191 19         1144 $mark->set_content($name);
192 19         127 while(@$pre) {
193 8         16 my $entry = pop @$pre;
194 8         37 $mark->insert_before($entry);
195 8         355 $mark = $entry;
196             }
197              
198             # Insert
199 19         161 $st->insert_before($bind_);
200              
201             # Move statements into bound closure
202 19         1523 my $block = $bind_->find_first('PPI::Structure::Block');
203 19         16734 do { # Move statements into bound closure
204 119         399 $next = $st->next_sibling;
205 119         2261 $block->add_element($st->remove);
206 119         5276 $st = $next;
207             } while($st);
208             }
209              
210             my $use = PPI::Document->new(\"use AnyEvent;use Module::AnyEvent::Helper;");
211              
212             sub _emit_use
213             {
214 9 50   9   97 shift unless blessed($_[0]);
215 9         28 my ($doc) = @_;
216 9 50 33     130 croak '_emit_use: the first argument is not a PPI::Element object' unless blessed($doc) && $doc->isa('PPI::Element');
217 9         76 my $first = $doc->first_element;
218 9 50       185 $first = $first->snext_sibling if ! $first->significant;
219 9         49 copy_children(undef, $first, $use);
220             }
221              
222             my $strip_tmpl = 'Module::AnyEvent::Helper::strip_async_all(-exclude => [qw(%s)]);1;';
223              
224             sub _emit_strip
225             {
226 9 50   9   61 shift unless blessed($_[0]);
227 9         21 my ($doc, @exclude) = @_;
228 9 50 33     111 croak '_emit_strip: the first argument is not a PPI::Element object' unless blessed($doc) && $doc->isa('PPI::Element');
229 9         70 my $strip_ = sprintf($strip_tmpl, join ' ', @exclude);
230 9         66 my $strip = PPI::Document->new(\$strip_);
231 9         26886 my $pkgs = $doc->find('PPI::Statement::Package');
232 9         56129 shift @{$pkgs};
  9         29  
233 9         44 for my $pkg (@$pkgs) {
234 0         0 copy_children(undef, $pkg, $strip);
235             }
236 9         125 my $last = $doc->last_element;
237 9 50       217 $last = $last->sprevious_sibling if ! $last->significant;
238 9         467 copy_children($last, undef, $strip);
239             }
240              
241             ########################################################################
242             # Methods
243              
244             sub new
245             {
246 9     9 1 220 my $self = shift;
247 9   33     80 my $class = ref($self) || $self;
248 9         49 my %arg = @_;
249 9         36 $self = bless {
250             }, $class;
251 9 100       59 $self->{_PFUNC} = { map { $_, 1 } @{$arg{-replace_func}} } if exists $arg{-replace_func};
  3         38  
  6         39  
252 9 50       54 $self->{_RFUNC} = { map { $_, 1 } @{$arg{-remove_func}} } if exists $arg{-remove_func};
  18         103  
  9         34  
253 9 100       46 $self->{_DFUNC} = { map { $_, 1 } @{$arg{-delete_func}} } if exists $arg{-delete_func};
  3         13  
  6         20  
254 9 50       55 $self->{_TFUNC} = { map { my $func = $_; $func =~ s/^@//; $func, 1 } @{$arg{-translate_func}} } if exists $arg{-translate_func};
  9         19  
  9         43  
  9         46  
  9         30  
255 2         5 $self->{_AFUNC} = {
256 2         7 map { my $func = $_; $func =~ s/^@//; $func, 1 }
  2         7  
  9         46  
257 9 50       50 exists $arg{-translate_func} ? grep { /^@/ } @{$arg{-translate_func}} : (),
  9         28  
258             };
259 9 100       45 $self->{_XFUNC} = { map { $_, 1 } @{$arg{-exclude_func}} } if exists $arg{-exclude_func};
  0         0  
  5         18  
260 9         42 return $self;
261             }
262              
263             sub _is_translate_func
264             {
265 208     208   1642 my ($self, $name) = @_;
266 208         935 return exists $self->{_TFUNC}{$name};
267             }
268              
269             sub _is_remove_func
270             {
271 144     144   537 my ($self, $name) = @_;
272 144         822 return exists $self->{_RFUNC}{$name};
273             }
274              
275             sub _is_replace_func
276             {
277 54     54   102 my ($self, $name) = @_;
278 54         461 return exists $self->{_PFUNC}{$name};
279             }
280              
281             sub _is_delete_func
282             {
283 54     54   242 my ($self, $name) = @_;
284 54         306 return exists $self->{_DFUNC}{$name};
285             }
286              
287             sub _is_replace_target
288             {
289 72     72   124 my ($self, $name) = @_;
290 72   100     149 return $self->_is_translate_func($name) || $self->_is_remove_func($name) || $self->_is_replace_func($name);
291             }
292              
293             sub _is_array_func
294             {
295 19     19   432 my ($self, $name) = @_;
296 19         73 return exists $self->{_AFUNC}{$name};
297             }
298              
299             sub _is_calling
300             {
301 73     73   123 my ($self, $word) = @_;
302 73 50 66     280 return 0 if ! $word->snext_sibling && ! $word->sprevious_sibling &&
      66        
      66        
      33        
      33        
303             $word->parent && $word->parent->isa('PPI::Statement::Expression') &&
304             $word->parent->parent && $word->parent->parent->isa('PPI::Structure::Subscript');
305 72 50 33     2537 return 0 if $word->snext_sibling && $word->snext_sibling->isa('PPI::Token::Operator') && $word->snext_sibling->content eq '=>';
      33        
306 72         3547 return 1;
307             }
308              
309             sub document
310             {
311 9     9 1 168741 my ($self, $doc) = @_;
312 9         81 $doc->prune('PPI::Token::Comment');
313              
314 9         47798 _emit_use($doc);
315 9 100       57 _emit_strip($doc, exists $self->{_XFUNC} ? keys %{$self->{_XFUNC}} : ());
  5         73  
316              
317 9         1769 my @decl;
318 9         140 my $words = $doc->find('PPI::Token::Word');
319 9         64537 for my $word (@$words) {
320 292 50       5085 next if !defined($word);
321 292 100       612 if(is_function_declaration($word)) { # declaration
322 72 100 100     1101 if($self->_is_remove_func($word->content) || $self->_is_delete_func($word->content)) {
    100          
323 21         61 delete_function_declaration($word);
324             } elsif($self->_is_translate_func($word->content)) {
325 9         38 push @decl, $word; # postpone declaration transform because other parts depend on this name
326             }
327             } else {
328 220 100       3476 next if ! defined $word->document; # Detached element
329 196 100       4677 next if ! defined function_name($word); # Not inside functions / methods
330 85 100       1539 next if ! $self->_is_translate_func(function_name($word)); # Not inside target functions / methods
331 73 100       600 next if ! $self->_is_calling($word); # Not calling
332 72         280 my $name = $word->content;
333 72 100       533 if($self->_is_replace_target($name)) {
334 19         93 replace_as_async($word, $name . '_async', $self->_is_array_func(function_name($word)));
335             }
336             }
337             }
338 9         34 foreach my $decl (@decl) {
339 9         54 $decl->set_content($decl->content . '_async');
340 9         158 emit_cv_into_function($decl);
341             }
342 9         168 return 1;
343             }
344              
345             1;
346              
347             __END__