File Coverage

blib/lib/Devel/Declare/Parser.pm
Criterion Covered Total %
statement 273 298 91.6
branch 90 122 73.7
condition 18 20 90.0
subroutine 67 74 90.5
pod 47 48 97.9
total 495 562 88.0


line stmt bran cond sub pod time code
1             package Devel::Declare::Parser;
2 5     5   26513 use strict;
  5         13  
  5         217  
3 5     5   23 use warnings;
  5         7  
  5         180  
4              
5             require Devel::Declare::Interface;
6 5     5   2822 use Devel::Declare;
  5         27120  
  5         25  
7 5     5   3055 use B::Compiling;
  5         68530  
  5         34  
8 5     5   4391 use B::Hooks::EndOfScope;
  5         61271  
  5         40  
9 5     5   467 use Scalar::Util qw/blessed/;
  5         10  
  5         249  
10 5     5   53 use Carp;
  5         5  
  5         3294  
11              
12             our $VERSION = '0.020';
13              
14             sub new {
15 35     35 1 67 my $class = shift;
16 35         58 my ( $name, $dec, $offset ) = @_;
17 35         167 return bless( [ $name, $dec, $offset, $offset ], $class );
18             }
19              
20             sub process {
21 35     35 1 47 my $self = shift;
22 35 100       100 return unless $self->pre_parse();
23 28 50       83 return unless $self->parse();
24 28 50       1027 return unless $self->post_parse();
25 28 50       101 return unless $self->rewrite();
26 24 100       68 return unless $self->write_line();
27 22 50       52 return unless $self->edit_line();
28 22         324 return 1;
29             }
30              
31             ###############
32             # Abstractable
33             #
34              
35 166     166 1 384 sub quote_chars {( qw/ [ ( ' " / )};
36 15     15 1 25 sub end_chars {( qw/ { ; / )};
37              
38 14     14 1 53 sub inject {()}
39              
40             sub pre_parse {
41 35     35 1 42 my $self = shift;
42 35         103 $self->skip_declarator;
43 35         89 $self->skipspace;
44              
45 35 50       90 return if $self->is_defenition;
46 35 100       112 return if $self->is_contained;
47 29 100       94 return if $self->is_arrow_contained;
48 28         70 return 1;
49             }
50              
51             sub parse {
52 28     28 1 31 my $self = shift;
53 28         83 $self->parts( $self->strip_remaining_items );
54 28         112 $self->end_char( $self->peek_num_chars(1));
55 28 100       47 $self->strip_length(1) if $self->end_char eq '{';
56 28         75 return 1;
57             }
58              
59 28     28 1 66 sub post_parse { 1 }
60              
61             sub rewrite {
62 0     0 1 0 my $self = shift;
63 0         0 $self->new_parts( $self->parts );
64 0         0 1;
65             }
66              
67             sub write_line {
68 24     24 1 28 my $self = shift;
69 24         71 my $newline = $self->open_line();
70              
71 34         60 $newline .= join( ', ',
72 24 100       43 map { $self->format_part($_) }
73 24         31 @{ $self->new_parts || [] }
74             );
75              
76 24         79 $newline .= $self->close_line();
77              
78 24         61 my $line = $self->line;
79 24         49 substr( $line, $self->offset, 0 ) = $newline;
80 24         50 $self->line( $line );
81 24 50       72 $self->diag( "New Line: " . $line . "\n" )
82             if $self->DEBUG;
83              
84 24         51 1;
85             }
86              
87 22     22 1 42 sub edit_line { 1 }
88              
89 24     24 1 37 sub open_line { "(" }
90              
91             sub close_line {
92 24     24 1 32 my $self = shift;
93 24         46 my $end = $self->end_char();
94 24 100       55 return ")" if $end ne '{';
95 23 100       25 return ( @{$self->new_parts || []} ? ', ' : '' )
  23 100       37  
    50          
96             . 'sub'
97             . ( $self->prototype ? $self->prototype : '' )
98             .' { '
99             . join( '; ',
100             $self->inject,
101             $self->_block_end_injection,
102             )
103             . '; ';
104             }
105              
106             ##############
107             # Stash
108             #
109              
110             our %STASH;
111              
112             sub _stash {
113 23     23   29 my ( $item ) = @_;
114 23         48 my $id = "$item";
115 23         58 $STASH{$id} = $item;
116 23         51 return $id;
117             }
118              
119             sub _unstash {
120 23     23   23 my ( $id ) = @_;
121 23         70 return delete $STASH{$id};
122             }
123              
124             ##############
125             # Accessors
126             #
127              
128             my @ACCESSORS = qw/parts new_parts end_char prototype contained/;
129              
130             {
131             my $count = 0;
132             for my $accessor ( qw/name declarator original_offset offset/, @ACCESSORS ) {
133             my $idx = $count++;
134 5     5   29 no strict 'refs';
  5         9  
  5         450  
135             *$accessor = sub {
136 2197     2197   1777 my $self = shift;
137 2197 100       3345 ( $self->[$idx] ) = @_ if @_;
138 2197         7127 return $self->[$idx];
139             };
140             }
141 5     5   27 no strict 'refs';
  5         7  
  5         350  
142 1     1   4 *{ __PACKAGE__ . '::_last_index' } = sub { $count };
143             }
144              
145             sub add_accessor {
146 1     1 0 3 my $class = shift;
147 1         3 my ( $accessor ) = @_;
148 5     5   32 no strict 'refs';
  5         7  
  5         12470  
149 1         7 my $idx = $class->_last_index + ${ $class . '::_LAST_INDEX' }++;
  1         7  
150 1         7 *{ $class . '::' . $accessor } = sub {
151 387     387   325 my $self = shift;
152 387 100       626 ( $self->[$idx] ) = @_ if @_;
153 387         819 return $self->[$idx];
154 1         7 };
155             }
156              
157             ###############
158             # Informational
159             #
160              
161             our %QUOTEMAP = (
162             '(' => ')',
163             '{' => '}',
164             '[' => ']',
165             '<' => '>',
166             );
167              
168             sub end_quote {
169 20     20 1 24 my $self = shift;
170 20         30 my ( $start ) = @_;
171 20   66     128 return $QUOTEMAP{ $start } || $start;
172             }
173              
174 4     4 1 138 sub linenum { PL_compiling->line }
175 4     4 1 49 sub filename { PL_compiling->file }
176              
177             sub has_comma {
178 0     0 1 0 my $self = shift;
179 0         0 grep { $_ eq ',' } $self->has_non_string_or_quote_parts;
  0         0  
180             }
181              
182             sub has_fat_comma {
183 0     0 1 0 my $self = shift;
184 0         0 grep { $_ eq '=>' } $self->has_non_string_or_quote_parts;
  0         0  
185             }
186              
187             sub has_non_string_or_quote_parts {
188 0     0 1 0 my $self = shift;
189 0         0 grep { !ref($_) } @{ $self->parts };
  0         0  
  0         0  
190             }
191              
192             sub has_string_or_quote_parts {
193 0     0 1 0 my $self = shift;
194 0         0 grep { ref($_) } @{ $self->parts };
  0         0  
  0         0  
195             }
196              
197             sub has_keyword {
198 0     0 1 0 my $self = shift;
199 0         0 my ( $word ) = @_;
200 0 0       0 return unless $word;
201 0 0       0 grep {
202 0         0 ref( $_ ) ? ($_->[1] eq $word) : ($_ eq $word)
203 0         0 } @{ $self->parts };
204             }
205              
206             ################
207             # Debug
208             #
209              
210             our $DEBUG = 0;
211 51 50   51 1 44 sub DEBUG {shift; ( $DEBUG ) = @_ if @_; $DEBUG }
  51         95  
  51         309  
212              
213 0     0 1 0 sub diag { warn( _debug(@_)) }
214 4     4 1 11 sub bail { die( _debug(@_)) }
215              
216             sub _debug {
217 4 50   4   40 shift if blessed( $_[0] );
218              
219 4         28 my @caller = caller(1);
220 4 50       14 my @msgs = (
221             @_,
222             DEBUG() ? (
223             "\nCaller: " . $caller[0] . "\n",
224             "Caller file: " . $caller[1] . "\n",
225             "Caller Line: " . $caller[2] . "\n",
226             ) : (),
227             );
228 4         11 return ( @msgs, " at " . filename() . " line " . linenum() . "\n" );
229             }
230              
231             ################
232             # Line manipulation and advancement
233             #
234              
235             sub line {
236 871     871 1 775 my $self = shift;
237 871 100       1509 Devel::Declare::set_linestr($_[0]) if @_;
238 871         2396 return Devel::Declare::get_linestr();
239             }
240              
241             sub advance {
242 337     337 1 282 my $self = shift;
243 337         296 my ( $len ) = @_;
244 337 100       577 return unless $len;
245 126         172 $self->offset( $self->offset + $len );
246             }
247              
248             sub strip_length {
249 90     90 1 92 my $self = shift;
250 90         96 my ($len) = @_;
251 90 50       154 return unless $len;
252              
253 90         140 my $linestr = $self->line();
254 90         150 substr($linestr, $self->offset, $len) = '';
255 90         150 $self->line($linestr);
256             }
257              
258             sub skip_declarator {
259 35     35 1 34 my $self = shift;
260 35         108 my $item = $self->peek_is_other;
261 35         120 my $name = $self->name;
262 35 50       337 if ( $item =~ m/^(.*)$name/ ) {
263 35         101 $self->original_offset(
264             $self->original_offset + length($1)
265             );
266             }
267 35         107 $self->advance( length($item) );
268             }
269              
270             sub skipspace {
271 282     282 1 235 my $self = shift;
272 282         359 $self->advance(
273             Devel::Declare::toke_skipspace( $self->offset )
274             );
275             }
276              
277             ################
278             # Public parsing interface
279             #
280              
281             sub is_defenition {
282 35     35 1 36 my $self = shift;
283 35         96 my $name = $self->declarator;
284 35 50       67 return 1 if $self->line =~ m/sub[\s\n]+$name/sm;
285 35         90 return 0;
286             }
287              
288             sub is_contained {
289 35     35 1 48 my $self = shift;
290 35 50       92 return 0 unless $self->peek_num_chars(1);
291 35 100       109 return 0 if $self->peek_num_chars(1) ne '(';
292 6         19 $self->contained(1);
293 6         102 return 1;
294             }
295              
296             sub is_arrow_contained {
297 29     29 1 35 my $self = shift;
298 29         54 $self->skipspace;
299              
300             #Strip first item
301 29         91 my $first = $self->strip_item;
302 29         53 my $offset = $self->offset;
303              
304             # look at whats next
305 29         49 $self->skipspace;
306 29         59 my $stuff = $self->peek_remaining();
307              
308             # Put first back.
309 29         56 my $line = $self->line;
310 29   100     129 substr( $line, $offset, 0 ) = $self->format_part( $first, 1 ) || "";
311 29         61 $self->offset( $offset );
312 29         50 $self->line( $line );
313              
314 29 100       135 return 1 if $stuff =~ m/^=>[\s\n]*\(/sm;
315             }
316              
317             sub peek_item_type {
318 98     98 1 90 my $self = shift;
319 98         149 $self->skipspace;
320 98 100       193 return 'quote' if $self->peek_is_quote;
321 86 100       176 return 'word' if $self->peek_is_word;
322 42 100       730 return 'block' if $self->peek_is_block;
323 8 100       34 return 'end' if $self->peek_is_end;
324 7 50       17 return 'other' if $self->peek_is_other;
325 0         0 return undef;
326             }
327              
328             sub peek_item {
329 98     98 1 80 my $self = shift;
330 98         154 $self->skipspace;
331              
332 98         196 my $type = $self->peek_item_type;
333 98 50       792 return unless $type;
334              
335 98         146 my $method = "peek_$type";
336 98 100       462 return unless $self->can( $method );
337              
338 63         123 my $item = $self->$method();
339 63 50       119 return unless $item;
340              
341 63 50       140 return $item unless wantarray;
342 63         110 return ( $item, $type );
343             }
344              
345             sub peek_quote {
346 12     12 1 17 my $self = shift;
347 12         33 $self->skipspace;
348              
349 12         61 my $start = substr($self->line, $self->offset, 3);
350 12         23 my $charstart = substr($start, 0, 1);
351 12 50       27 return unless $self->peek_is_quote( $start, $charstart );
352              
353 12         49 my ( $length, $quoted ) = $self->_quoted_from_dd();
354              
355 12         35 return [ $quoted, $charstart ];
356             }
357              
358             sub peek_word {
359 44     44 1 49 my $self = shift;
360 44         70 $self->skipspace;
361 44         73 my $len = $self->peek_is_word;
362 44 50       97 return unless $len;
363              
364 44         70 my $linestr = $self->line();
365 44         74 my $name = substr($linestr, $self->offset, $len);
366 44         93 return [ $name, undef ];
367             }
368              
369             sub peek_other {
370 7     7 1 9 my $self = shift;
371 7         18 $self->skipspace;
372 7 50       13 return if $self->peek_is_word;
373 7 50       12 return if $self->peek_is_quote;
374 7 50       14 return if $self->peek_is_end;
375 7 50       12 return if $self->peek_is_block;
376 7         13 return $self->peek_is_other;
377             }
378              
379             sub peek_is_quote {
380 117     117 1 108 my $self = shift;
381 117         180 my ( $start ) = $self->peek_num_chars(1);
382 117   100     190 return (grep { $_ eq $start } $self->quote_chars )
383             || undef;
384             }
385              
386             sub peek_is_word {
387 181     181 1 166 my $self = shift;
388 181   100     273 return $self->_peek_is_package
389             || $self->_peek_is_word;
390             }
391              
392             sub peek_is_block {
393 63     63 1 65 my $self = shift;
394 63         89 my ( $start ) = $self->peek_num_chars(1);
395 63   100     283 return ($start eq '{')
396             || undef;
397             }
398              
399             sub peek_is_end {
400 15     15 1 15 my $self = shift;
401 15         23 my ( $start ) = $self->peek_num_chars(1);
402 15         34 my ($end) = grep { $start eq $_ } $self->end_chars;
  30         44  
403 15   66     47 return $end
404             || $self->peek_is_block;
405             }
406              
407             sub peek_is_other {
408 49     49 1 48 my $self = shift;
409 49         123 my $linestr = $self->line;
410 49         120 substr( $linestr, 0, $self->offset ) = '';
411 49         124 my $quote = join( '', $self->quote_chars );
412 49 50       562 return unless $linestr =~ m/^([^\s;{$quote]+)/;
413 49         181 return $1;
414             }
415              
416             sub peek_num_chars {
417 519     519 1 439 my $self = shift;
418 519         591 my @out = map { substr($self->line, $self->offset, $_) } @_;
  519         759  
419 519 100       1113 return @out if wantarray;
420 296         622 return $out[0];
421             }
422              
423             sub strip_item {
424 98     98 1 92 my $self = shift;
425 98         173 return $self->_item_via_( 'strip_length' );
426             }
427              
428             sub strip_remaining_items {
429 28     28 1 57 my $self = shift;
430 28         28 my @parts;
431 28         53 while ( my $part = $self->strip_item ) {
432 41         103 push @parts => $part;
433             }
434 28         114 return \@parts;
435             }
436              
437             sub peek_remaining {
438 260     260 1 253 my $self = shift;
439 260         407 return substr( $self->line, $self->offset );
440             }
441              
442             ###############
443             # Private parser interface
444             #
445              
446             sub _peek_is_word {
447 141     141   131 my $self = shift;
448 141   100     170 return Devel::Declare::toke_scan_word($self->offset, 1)
449             || undef;
450             }
451              
452             sub _peek_is_package {
453 181     181   147 my $self = shift;
454 181         227 my $start = $self->peek_num_chars(1);
455 181 100       627 return unless $start =~ m/^[A-Za-z_]$/;
456 132 100       219 return unless $self->peek_remaining =~ m/^(\w+::[\w:]+)/;
457 3         19 return length($1);
458             }
459              
460             sub _linestr_offset_from_dd {
461 22     22   25 my $self = shift;
462 22         63 return Devel::Declare::get_linestr_offset()
463             }
464              
465             sub _quoted_from_dd {
466 16     16   15 my $self = shift;
467 16         23 my $length = Devel::Declare::toke_scan_str($self->offset);
468 16         36 my $quoted = Devel::Declare::get_lex_stuff();
469 16         22 Devel::Declare::clear_lex_stuff();
470              
471 16         32 return ( $length, $quoted );
472             }
473              
474             sub _item_via_ {
475 98     98   88 my $self = shift;
476 98         141 my ( $move_method ) = @_;
477              
478 98         173 my ( $item, $type ) = $self->peek_item;
479 98 100       337 return unless $item;
480              
481 63         148 $self->_move_via_( $move_method, $type, $item );
482 63         186 return $item;
483             }
484              
485             sub _move_via_ {
486 63     63   53 my $self = shift;
487 63         82 my ( $method, $type, $item ) = @_;
488              
489 63 50       195 croak( "$method is not a valid move method" )
490             unless $self->can( $method );
491              
492 63 100       186 if ( $type eq 'word' ) {
    100          
    50          
493 44         65 $self->$method( $self->peek_is_word );
494             }
495             elsif ( $type eq 'quote' ) {
496 12         30 my ( $len ) = $self->_quoted_from_dd();
497 12         42 $self->$method( $len );
498             }
499             elsif ( $type eq 'other' ) {
500 7         16 $self->$method( length( $item ));
501             }
502             }
503              
504             #############
505             # Rewriting interface
506             #
507              
508             sub format_part {
509 71     71 1 92 my $self = shift;
510 71         74 my ( $part, $no_added_quotes ) = @_;
511 71 100       152 return unless $part;
512 64 100       143 return $part unless ref($part);
513 54 100 100     802 return $part->[0] if $no_added_quotes && !$part->[1];
514 36 100       177 return "'" . $part->[0] . "'"
515             unless $part->[1];
516 12         61 return $part->[1] . $part->[0] . $self->end_quote( $part->[1] );
517             }
518              
519             #############
520             # Codeblock munging
521             #
522              
523             sub _block_end_injection {
524 23     23   26 my $self = shift;
525 23         173 my $class = blessed( $self );
526              
527 23         70 my $id = _stash( $self );
528              
529 23         224 return "BEGIN { $class\->_edit_block_end('$id') }";
530             }
531              
532             sub _edit_block_end {
533 22     22   1295 my $class = shift;
534 22         31 my ( $id ) = @_;
535              
536             on_scope_end {
537 22     22   1047 $class->_scope_end($id);
538 22         131 };
539             }
540              
541             sub _scope_end {
542 23     23   37 my $class = shift;
543 23         30 my ( $id ) = @_;
544 23         47 my $self = _unstash( $id );
545              
546 23         48 my $oldlinestr = $self->line;
547 23         72 my $linestr = $oldlinestr;
548 23         62 $self->offset( $self->_linestr_offset_from_dd() );
549 23 100       151 if ( $linestr =~ m/}\s*$/ ) {
550 17         35 substr($linestr, $self->offset, 0) = ' );';
551             }
552             else {
553 6         13 substr($linestr, $self->offset, 0) = ' ) ';
554             }
555 23         50 $self->line($linestr);
556 23 50       42 $self->diag(
557             "Old Line: " . $oldlinestr . "\n",
558             "New Line: " . $linestr . "\n",
559             ) if $self->DEBUG;
560              
561             }
562              
563             1;
564              
565             __END__