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