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