File Coverage

blib/lib/MDOM/Document/Gmake.pm
Criterion Covered Total %
statement 255 279 91.4
branch 132 152 86.8
condition 52 66 78.7
subroutine 22 24 91.6
pod 1 2 50.0
total 462 523 88.3


line stmt bran cond sub pod time code
1             package MDOM::Document::Gmake;
2              
3 15     15   10026 use strict;
  15         27  
  15         471  
4 15     15   62 use warnings;
  15         17  
  15         401  
5              
6             #use Smart::Comments;
7             #use Smart::Comments '###', '####';
8              
9 15     15   10816 use Text::Balanced qw( gen_extract_tagged );
  15         247168  
  15         1456  
10 15     15   7360 use Makefile::DOM;
  15         56  
  15         510  
11             #use Data::Dump::Streamer;
12 15     15   84 use base 'MDOM::Node';
  15         21  
  15         61  
13 15     15   85 use List::MoreUtils qw( before all any );
  15         22  
  15         1663  
14 15     15   86 use List::Util qw( first );
  15         25  
  15         1576  
15              
16             my %_map;
17             BEGIN {
18 15     15   403 %_map = (
19             COMMENT => 1, # context for parsing multi-line comments
20             COMMAND => 2, # context for parsing multi-line commands
21             RULE => 3, # context for parsing rules
22             VOID => 4, # void context
23             UNKNOWN => 5, # context for parsing unexpected constructs
24             );
25             }
26              
27 15     15   81 use constant \%_map;
  15         24  
  15         52348  
28              
29             my %_rev_map = reverse %_map;
30              
31             my @keywords = qw(
32             vpath include sinclude
33             ifdef ifndef else endif
34             define endef export unexport
35             );
36              
37             my $extract_interp_1 = gen_extract_tagged('\$[(]', '[)]', '');
38             my $extract_interp_2 = gen_extract_tagged('\$[{]', '[}]', '');
39              
40             sub extract_interp {
41 1523     1523 1 3095 my ($res) = $extract_interp_1->($_[0]);
42 1523 100       67101 if (!$res) {
43 1501         2814 ($res) = $extract_interp_2->($_[0]);
44             }
45 1523         61673 $res;
46             }
47              
48             my ($context, $saved_context);
49              
50             sub new {
51 75 50   75 0 476 my $class = ref $_[0] ? ref shift : shift;
52 75         91 my $input = shift;
53 75 50       167 return undef if !defined $input;
54 75         72 my $in;
55 75 50       168 if (ref $input) {
56 15 50   15   100 open $in, '<', $input or die;
  15         20  
  15         114  
  75         1346  
57             } else {
58 0 0       0 open $in, $input or
59             die "Can't open $input for reading: $!";
60             }
61 75         15010 my $self = $class->SUPER::new;
62 75         246 $self->_tokenize($in);
63 75         480 $self;
64             }
65              
66             sub _tokenize {
67 75     75   104 my ($self, $fh) = @_;
68 75         165 $context = VOID;
69 75         79 my @tokens;
70 75         327 while (<$fh>) {
71             ### Tokenizing : $_
72             ### ...with context : $_rev_map{$context}
73 165         296 s/\r\n/\n/g;
74 165 50       652 $_ .= "\n" if !/\n$/s;
75 165 100 100     600 if ($context == VOID || $context == RULE) {
    100          
    100          
    50          
76 131 100 100     888 if ($context == VOID && s/(?x) ^ (\t\s*) (?= \# ) //) {
    100 100        
77             ### Found comment in VOID context...
78 4         21 @tokens = (
79             MDOM::Token::Whitespace->new($1),
80             _tokenize_comment($_)
81             );
82 4 100       25 if ($tokens[-1]->isa('MDOM::Token::Continuation')) {
83             ### Switching context to COMMENT...
84 1         1 $saved_context = $context;
85 1         2 $context = COMMENT;
86 1         3 $tokens[-2]->add_content("\\\n");
87 1         1 pop @tokens;
88             }
89 4         10 $self->__add_elements( @tokens );
90             }
91             elsif ($context == RULE and s/^\t//) {
92             ### Found a command in RULE context...
93 33         69 @tokens = _tokenize_command($_);
94             #warn "*@tokens*";
95             ### Tokens for the command: @tokens
96 33         104 unshift @tokens, MDOM::Token::Separator->new("\t");
97 33 100       202 if ($tokens[-1]->isa('MDOM::Token::Continuation')) {
98             ### Switching context to COMMAND...
99 4         7 $saved_context = $context;
100 4         5 $context = COMMAND;
101 4         5 pop @tokens;
102 4 100       13 if ($tokens[-1]->class =~ /Bare$/) {
103 3         13 $tokens[-1]->add_content("\\\n");
104             } else {
105 1         3 push @tokens, MDOM::Token::Bare->new("\\\n");
106             }
107             }
108 33         120 my $cmd = MDOM::Command->new;
109 33         91 $cmd->__add_elements(@tokens);
110 33         81 $self->__add_element($cmd);
111             ### command (post): $cmd
112 33         143 next;
113             } else {
114 94         227 @tokens = _tokenize_normal($_);
115 94 100 100     1366 if (@tokens >= 2 &&
    100 100        
116             $tokens[-1]->isa('MDOM::Token::Continuation') &&
117             $tokens[-2]->isa('MDOM::Token::Comment')) {
118             ### Found a trailing comment...
119             ### Switching conext to COMMENT...
120 2         4 $saved_context = $context;
121 2         3 $context = COMMENT;
122 2         8 $tokens[-2]->add_content("\\\n");
123 2         2 pop @tokens;
124 2         7 $self->__add_elements( _parse_normal(@tokens) );
125             } elsif ($tokens[-1]->isa('MDOM::Token::Continuation')) {
126             ### Found a line continuation...
127             ### Switching context to UNKNOWN...
128 12         22 $saved_context = $context;
129 12         57 $context = UNKNOWN;
130             } else {
131             ### Parsing it as a normal line...
132 80         207 $self->__add_elements( _parse_normal(@tokens) );
133             }
134             }
135             } elsif ($context == COMMENT) {
136 6         12 @tokens = _tokenize_comment($_);
137 6 100       23 if ($tokens[-1]->isa('MDOM::Token::Continuation')) {
138             ### Slurping one more continued comment line...
139 3         7 $tokens[-2]->add_content("\\\n");
140 3         2 pop @tokens;
141 3         7 $self->last_token->add_content(join '', @tokens);
142             } else {
143             ### Completing comment slurping...
144             ### Switching back to context: _state_str($saved_context)
145 3         2 $context = $saved_context;
146 3         5 my $last = pop @tokens;
147 3         6 $self->last_token->add_content(join '', @tokens);
148 3         8 $self->last_token->parent->__add_element($last);
149             }
150             } elsif ($context == COMMAND) {
151 8         16 @tokens = _tokenize_command($_);
152             ### more tokens for the cmd: @tokens
153 8 100       45 if ($tokens[-1]->isa('MDOM::Token::Continuation')) {
154             ### Slurping one more continued command line...
155 4         12 $tokens[-2]->add_content("\\\n");
156 4         6 pop @tokens;
157 4         9 for my $token (@tokens) {
158 12 100 100     34 if ($token->class =~ /Interpolation/ or
159             $self->last_token->class =~ /Interpolation/) {
160 6         12 $self->last_token->parent->__add_element($token);
161             } else {
162 6         14 $self->last_token->add_content($token);
163             }
164             }
165             } else {
166             ### Completing command slurping: @tokens
167             ### Switching back to context: _state_str($saved_context)
168 4         4 $context = RULE;
169 4         4 my $last = pop @tokens;
170             ### last_token: $self->last_token
171 4         11 for my $token (@tokens) {
172 6 100 66     17 if ($token->class =~ /Interpolation/ or
173             $self->last_token->class =~ /Interpolation/) {
174 2         5 $self->last_token->parent->__add_element($token);
175             } else {
176 4         12 $self->last_token->add_content($token);
177             }
178             }
179 4         10 $self->last_token->parent->__add_element($last);
180             }
181             } elsif ($context == UNKNOWN) {
182 20         40 push @tokens, _tokenize_normal($_);
183 20 50 66     304 if (@tokens >= 2 && $tokens[-1]->isa('MDOM::Token::Continuation') &&
    100 66        
184             $tokens[-2]->isa('MDOM::Token::Comment')) {
185 0         0 $context = COMMENT;
186 0         0 $tokens[-2]->add_content("\\\n");
187 0         0 pop @tokens;
188 0         0 $self->__add_elements( _parse_normal(@tokens) );
189             } elsif ($tokens[-1]->isa('MDOM::Token::Continuation')) {
190             ### Do nothing here...stay in the UNKNOWN context...
191             } else {
192 12         46 $self->__add_elements( _parse_normal(@tokens) );
193 12         69 $context = $saved_context;
194             }
195             } else {
196 0         0 die "Unkown state: $context";
197             }
198             }
199 75 50 66     415 if ($context != RULE && $context != VOID) {
200 0         0 warn "unexpected end of input at line $.";
201             }
202             }
203              
204             sub _tokenize_normal {
205 114     114   217 local $_ = shift;
206 114         120 my @tokens;
207 114         131 my $pending_token = '';
208 114         89 my $next_token;
209             ### TOKENIZING: $_
210 114         110 while (1) {
211             # "token = $pending_token";
212             #warn pos;
213             #warn '@tokens = ', _dump_tokens2(@tokens);
214 1327 100       5004 if (/(?x) \G [\s\n]+ /gc) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
215 286         988 $next_token = MDOM::Token::Whitespace->new($&);
216             #push @tokens, $next_token;
217             }
218             elsif (/(?x) \G (?: :: | := | \?= | \+= | [=:;] )/gc) {
219 77         433 $next_token = MDOM::Token::Separator->new($&);
220             }
221             elsif (/(?x) \G \| /gc) {
222             # XXX This should be a separator...
223 3         15 $next_token = MDOM::Token::Bare->new($&);
224             }
225             elsif (my $res = extract_interp($_)) {
226 15         88 $next_token = MDOM::Token::Interpolation->new($res);
227             }
228             elsif (/(?x) \G \$. /gc) {
229 7         58 $next_token = MDOM::Token::Interpolation->new($&);
230             }
231             elsif (/(?x) \G \\ ([\#\\\n:]) /gcs) {
232 24         54 my $c = $1;
233 24 100       56 if ($c eq "\n") {
234 20 100       59 push @tokens, MDOM::Token::Bare->new($pending_token)
235             if $pending_token ne '';
236 20         107 push @tokens, MDOM::Token::Continuation->new("\\\n");
237 20         75 return @tokens;
238             } else {
239 4         5 $pending_token .= "\\$c";
240             }
241             }
242             elsif (/(?x) \G (\# [^\n]*) \\ \n/sgc) {
243 2         4 my $s = $1;
244 2 50       6 push @tokens, MDOM::Token::Bare->new($pending_token) if $pending_token ne '';
245 2         11 push @tokens, MDOM::Token::Comment->new($s);
246 2         12 push @tokens, MDOM::Token::Continuation->new("\\\n");
247 2         7 return @tokens;
248             } elsif (/(?x) \G \# [^\n]* /gc) {
249 12         85 $next_token = MDOM::Token::Comment->new($&);
250             } elsif (/(?x) \G . /gc) {
251 809         1311 $pending_token .= $&;
252             } else {
253 92         168 last;
254             }
255 1213 100       5622 if ($next_token) {
256 400 100       698 if ($pending_token ne '') {
257 204         608 push @tokens, MDOM::Token::Bare->new($pending_token);
258 204         261 $pending_token = '';
259             }
260 400         394 push @tokens, $next_token;
261 400         418 $next_token = undef;
262             }
263             }
264             ### parse_normal result: @tokens
265 92         346 @tokens;
266             }
267              
268             sub _tokenize_command {
269 56     56   77 my $s = shift;
270 56         62 my @tokens;
271 56         71 my $pending_token = '';
272 56         53 my $next_token;
273 56         77 my $strlen = length $s;
274 56         222 while ($s =~ /(?x) \G (\s*) ([\@+\-]) /gc) {
275 16         40 my ($whitespace, $modifier) = ($1, $2);
276 16 100       43 if ($whitespace) {
277 7         18 push @tokens, MDOM::Token::Whitespace->new($whitespace);
278             }
279 16         73 push @tokens, MDOM::Token::Modifier->new($modifier);
280             }
281 56         52 while (1) {
282 607         484 my $last = 0;
283 607 100       1100 if ($s =~ /(?x) \G \n /gc) {
    100          
    100          
    100          
    100          
284 45         149 $next_token = MDOM::Token::Whitespace->new("\n");
285             #push @tokens, $next_token;
286             }
287             elsif (my $res = extract_interp($s)) {
288 11         52 $next_token = MDOM::Token::Interpolation->new($res);
289             }
290             elsif ($s =~ /(?x) \G \$. /gc) {
291 11         63 $next_token = MDOM::Token::Interpolation->new($&);
292             }
293             elsif ($s =~ /(?x) \G \\ ([\#\\\n:]) /gcs) {
294 12         20 my $c = $1;
295 12 100 100     52 if ($c eq "\n" && pos $s == $strlen) {
296 8         34 $next_token = MDOM::Token::Continuation->new("\\\n");
297             } else {
298 4         6 $pending_token .= "\\$c";
299             }
300             }
301             elsif ($s =~ /(?x) \G . /gc) {
302 472         735 $pending_token .= $&;
303             } else {
304 56         67 $last = 1;
305             }
306 607 100       1354 if ($next_token) {
307 75 100       133 if ($pending_token) {
308 60         144 push @tokens, MDOM::Token::Bare->new($pending_token);
309 60         72 $pending_token = '';
310             }
311 75         82 push @tokens, $next_token;
312 75         73 $next_token = undef;
313             }
314 607 100       884 last if $last;
315             }
316 56 50       115 if ($pending_token) {
317 0         0 push @tokens, MDOM::Token::Bare->new($pending_token);
318 0         0 $pending_token = '';
319             }
320 56         165 @tokens;
321             }
322              
323             sub _tokenize_comment {
324 10     10   21 local $_ = shift;
325 10         10 my @tokens;
326 10         14 my $pending_token = '';
327 10         8 while (1) {
328 134 100       302 if (/(?x) \G \n /gc) {
    100          
    50          
329 6 50       34 push @tokens, MDOM::Token::Comment->new($pending_token) if $pending_token ne '';
330 6         18 push @tokens, MDOM::Token::Whitespace->new("\n");
331 6         22 return @tokens;
332             #push @tokens, $next_token;
333             }
334             elsif (/(?x) \G \\ ([\\\n#:]) /gcs) {
335 4         4 my $c = $1;
336 4 50       8 if ($c eq "\n") {
337 4 50       14 push @tokens, MDOM::Token::Comment->new($pending_token) if $pending_token ne '';
338 4         12 push @tokens, MDOM::Token::Continuation->new("\\\n");
339 4         11 return @tokens;
340             } else {
341 0         0 $pending_token .= "\\$c";
342             }
343             }
344             elsif (/(?x) \G . /gc) {
345 124         121 $pending_token .= $&;
346             }
347             else {
348 0         0 last;
349             }
350             }
351 0         0 @tokens;
352             }
353              
354             sub _parse_normal {
355 94     94   258 my @tokens = @_;
356             ### fed to _parse_normal: @tokens
357 94         171 my @sep = grep { $_->isa('MDOM::Token::Separator') } @tokens;
  627         1975  
358             #### Separators: @sep
359 94 100       228 if (@tokens == 1) {
360 8         29 return $tokens[0];
361             }
362             # filter out significant tokens:
363 86         148 my ($fst, $snd) = grep { $_->significant } @tokens;
  619         1248  
364 86         102 my $is_directive;
365 86 100       1412 if ($fst) {
366 81 100 66     219 if ($fst eq '-include') {
    100 66        
      66        
367 2         17 $fst->set_content('include');
368 2         17 unshift @tokens, MDOM::Token::Modifier->new('-');
369 2         13 $is_directive = 1;
370             }
371             elsif ($fst eq 'override' && $snd && $snd eq 'define' ||
372             _is_keyword($fst)) {
373 12         14 $is_directive = 1;
374             }
375 81 100       363 if ($is_directive) {
376             ##### Found directives...
377 14         88 my $node = MDOM::Directive->new;
378 14         59 $node->__add_elements(@tokens);
379 14         61 return $node;
380             }
381             }
382 72 100 66     700 if (@sep >= 2 && $sep[0] =~ /^::?$/ and $sep[1] eq ';') {
    100 100        
    100 66        
    100 66        
    100 100        
      66        
383             #### Found simple rule with inlined command...
384 9         66 my $rule = MDOM::Rule::Simple->new;
385 9     42   103 my @t = before { $_ eq ';' } @tokens;
  42         72  
386 9         66 $rule->__add_elements(@t);
387 9         21 splice @tokens, 0, scalar(@t);
388              
389 9         30 my @prefix = shift @tokens;
390 9 100 66     85 if ($tokens[0] && $tokens[0]->isa('MDOM::Token::Whitespace')) {
391 6         14 push @prefix, shift @tokens;
392             }
393              
394 9         34 @tokens = (@prefix, _tokenize_command(join '', @tokens));
395 9 50       62 if ($tokens[-1]->isa('MDOM::Token::Continuation')) {
396 0         0 $saved_context = $context;
397 0         0 $context = COMMAND;
398             }
399 9         59 my $cmd = MDOM::Command->new;
400 9         30 $cmd->__add_elements(@tokens);
401 9         26 $rule->__add_elements($cmd);
402 9         12 $saved_context = RULE;
403 9 100       26 $context = RULE if $context == VOID;
404 9         46 return $rule;
405             }
406             elsif (@sep >= 2 && $sep[0] eq ':' and $sep[1] =~ /^::?$/) {
407             #### Found static pattern rule...
408 2         31 my $rule = MDOM::Rule::StaticPattern->new;
409 2     21   24 my @t = before { $_ eq ';' } @tokens;
  21         46  
410 2         23 $rule->__add_elements(@t);
411 2         9 splice @tokens, 0, scalar(@t);
412 2 100       8 if (@tokens) {
413 1         4 my @prefix = shift @tokens;
414 1 50 33     13 if ($tokens[0] && $tokens[0]->isa('MDOM::Token::Whitespace')) {
415 1         3 push @prefix, shift @tokens;
416             }
417              
418 1         5 @tokens = (@prefix, _tokenize_command(join '', @tokens));
419 1 50       13 if ($tokens[-1]->isa('MDOM::Token::Continuation')) {
420 0         0 $saved_context = $context;
421 0         0 $context = COMMAND;
422             }
423 1         12 my $cmd = MDOM::Command->new;
424 1         8 $cmd->__add_elements(@tokens);
425 1         4 $rule->__add_elements($cmd);
426             }
427 2         3 $saved_context = RULE;
428 2 50       8 $context = RULE if $context == VOID;
429 2         15 return $rule;
430             }
431             elsif (@sep == 1 && $sep[0] =~ /^::?$/) {
432             #### Found simple rule without inlined command...
433 36         217 my $rule = MDOM::Rule::Simple->new;
434 36         141 $rule->__add_elements(@tokens);
435 36         155 $saved_context = RULE;
436 36 100       87 $context = RULE if $context == VOID;
437 36         140 return $rule;
438             }
439             elsif (@sep && $sep[0] =~ /(?x) ^ (?: = | := | \+= | \?= ) $/) {
440 15         82 my $assign = MDOM::Assignment->new;
441             ### Assignment tokens: @tokens
442 15         57 $assign->__add_elements(@tokens);
443 15         21 $saved_context = VOID;
444 15 100       36 $context = VOID if $context == RULE;
445 15         53 return $assign;
446             }
447             elsif (all {
448 20 100   20   140 $_->isa('MDOM::Token::Comment') ||
449             $_->isa('MDOM::Token::Whitespace')
450             } @tokens) {
451 5         21 @tokens;
452             }
453             else {
454             #### Found unkown token sequence: @tokens
455 5         20 @tokens = _tokenize_command(join '', @tokens);
456 5         46 my $node = MDOM::Unknown->new;
457 5         25 $node->__add_elements(@tokens);
458 5         28 $node;
459             }
460             }
461              
462             sub _dump_tokens {
463 0     0   0 my @tokens = map { $_->clone } @_;
  0         0  
464 0         0 warn "??? ", (join ' ',
465 0         0 map { s/\\/\\\\/g; s/\n/\\n/g; s/\t/\\t/g; "[$_]" } @tokens
  0         0  
  0         0  
  0         0  
466             ), "\n";
467             }
468              
469             sub _state_str {
470 0     0   0 $_rev_map{$saved_context}
471             }
472              
473             sub _is_keyword {
474 777     777   1164 any { $_[0] eq $_ } @keywords;
  79     79   628  
475             }
476              
477             1;
478              
479             __END__