File Coverage

blib/lib/Parse/Eyapp/Output.pm
Criterion Covered Total %
statement 163 226 72.1
branch 43 82 52.4
condition 5 12 41.6
subroutine 23 27 85.1
pod 0 7 0.0
total 234 354 66.1


line stmt bran cond sub pod time code
1             #
2             # Module Parse::Eyapp::Output
3             #
4             # This module is based on Francois Desarmenien Parse::Yapp distribution
5             #
6             # Copyright © 2006, 2007, 2008, 2009, 2010, 2011, 2012 Casiano Rodriguez-Leon.
7             # Copyright © 2017 William N. Braswell, Jr.
8             # All Rights Reserved.
9             #
10             # Parse::Yapp is Copyright © 1998, 1999, 2000, 2001, Francois Desarmenien.
11             # Parse::Yapp is Copyright © 2017 William N. Braswell, Jr.
12             # All Rights Reserved.
13             #
14              
15             package Parse::Eyapp::Output;
16 61     61   336 use strict;
  61         125  
  61         2843  
17              
18             our @ISA=qw ( Parse::Eyapp::Lalr );
19              
20             require 5.004;
21              
22 61     61   19579 use Parse::Eyapp::Base qw(compute_lines);
  61         159  
  61         3428  
23 61     61   25327 use Parse::Eyapp::Lalr;
  61         246  
  61         2123  
24 61     61   354 use Parse::Eyapp::Driver;
  61         137  
  61         1202  
25 61     61   294 use Parse::Eyapp::Node; # required to have access to $Parse::Eyapp::Node::FILENAME
  61         126  
  61         1142  
26 61     61   285 use File::Basename;
  61         170  
  61         4676  
27             #use Data::Dumper;
28 61     61   413 use List::Util qw(first);
  61         123  
  61         3025  
29              
30              
31 61     61   405 use Carp;
  61         125  
  61         115085  
32              
33             # Remove tokens that not appear in the right hand side
34             # of any production
35             # Check if not quote tokens aren't defined
36             sub deleteNotUsedTokens {
37 54     54 0 166 my ($self, $term, $termDef) = @_;
38            
39 54         136 my $rules = $self->{GRAMMAR}{RULES};
40 54         162 my @usedSymbols = map { @{$_->[1]} } @$rules;
  691         977  
  691         1962  
41 54         189 my %usedSymbols;
42 54         404 @usedSymbols{@usedSymbols} = ();
43              
44 54         106 for (@{$self->{GRAMMAR}{DUMMY}}) {
  54         240  
45 0         0 delete $usedSymbols{$_};
46 0         0 delete $termDef->{$_};
47             }
48              
49 54         252 for my $token (keys %$term) {
50 514 100       1199 delete $term->{$token} unless exists $usedSymbols{$token};
51             }
52              
53             # Emit a warning if exists a non '' token in %usedSymbols that is not in %termdef
54 54 50 33     434 if ($self->{GRAMMAR}{STRICT} && %$termDef) {
55 0         0 my @undefined = grep { ! exists $termDef->{$_} } grep { m{^[^']} } keys %$term;
  0         0  
  0         0  
56 0 0       0 if (@undefined) {
57 0         0 @undefined = map { "Warning: may be you forgot to define token '$_'?: %token $_ = /someRegExp/" } @undefined;
  0         0  
58              
59 0         0 local $" = "\n";
60 0         0 warn "@undefined\n";
61             }
62             }
63             }
64              
65             # builds a trivial lexer
66             sub makeLexer {
67 54     54 0 140 my $self = shift;
68              
69 54         131 my $WHITES = 'm{\G(\s+)}gc and $self->tokenline($1 =~ tr{\n}{})';
70 54         165 my $w = $self->{GRAMMAR}{WHITES}[0];
71 54 50       223 if (defined $w) {
72             # if CODE then literally
73 0 0       0 if ($self->{GRAMMAR}{WHITES}[2] eq 'CODE') {
74 0         0 $WHITES = $w;
75             }
76             else {
77 0         0 $w =~ s{^/}{/\\G};
78 0         0 $WHITES = $w.'gc and $self->tokenline($1 =~ tr{\n}{})';
79             }
80             }
81              
82 54 50       220 my $INCREMENTAL = defined($self->{GRAMMAR}{INCREMENTAL}) ? _incrementalLexerText() : '';
83              
84 54         121 my %term = %{$self->{GRAMMAR}{TERM}};
  54         455  
85 54         170 delete $term{"\c@"};
86 54         123 delete $term{'error'};
87              
88 54         112 my %termdef = %{$self->{GRAMMAR}{TERMDEF}};
  54         207  
89              
90 54         415 $self->deleteNotUsedTokens(\%term, \%termdef);
91              
92              
93             # remove from %term the tokens that were explictly defined
94 54         189 my @index = grep { !(exists $termdef{$_}) } keys %term;
  478         964  
95 54         192 %term = map { ($_, $term{$_}) } @index;
  402         783  
96              
97 54         210 my @term = map { s/'$//; s/^'//; $_ } keys %term;
  402         1025  
  402         826  
  402         836  
98              
99 54         300 @term = sort { length($b) <=> length($a) } @term;
  917         1336  
100 54         141 @term = map { quotemeta } @term;
  402         825  
101              
102             # Keep escape characters as \n \r, etc.
103 54         181 @term = map { s/\\\\(.)/\\$1/g; $_ } @term;
  402         613  
  402         731  
104              
105 54         155 my $TERM = '';
106 54 100       215 if (@term) {
107 42         167 $TERM = join '|', @term;
108 42         181 $TERM = "\\G($TERM)";
109             }
110            
111             # Translate defined tokens
112             # sort by line number
113 54         234 my @termdef = sort { $termdef{$a}->[1] <=> $termdef{$b}->[1] } keys %termdef;
  156         285  
114              
115 54         142 my $DEFINEDTOKENS = '';
116 54         160 for my $t (@termdef) {
117 76 50       385 if ($termdef{$t}[2] eq 'REGEXP') {
    50          
    50          
    50          
    50          
    0          
118 0         0 my $reg = $termdef{$t}[0];
119 0         0 $reg =~ s{^/}{/\\G}; # add \G at the begining of the regexp
120 0         0 $DEFINEDTOKENS .= << "EORT";
121             ${reg}gc and return ('$t', \$1);
122             EORT
123             }
124             elsif ($termdef{$t}[2] eq 'CONTEXTUAL_REGEXP') {
125 0         0 my $reg = $termdef{$t}[0];
126 0         0 $reg =~ s{^/}{/\\G}; # add \G at the begining of the regexp
127 0         0 $DEFINEDTOKENS .= << "EORT";
128             \$self->expects('$t') and ${reg}gc and return ('$t', \$1);
129             EORT
130             }
131             elsif ($termdef{$t}[2] eq 'CONTEXTUAL_REGEXP_MATCH') {
132 0         0 my $reg = $termdef{$t}[0];
133 0         0 my $parser = $termdef{$t}[3][0];
134 0         0 $reg =~ s{^/}{/\\G}; # add \G at the begining of the regexp
135 0         0 $DEFINEDTOKENS .= << "EORT";
136             \$pos = pos();
137             if (${reg}gc) {
138             if (\$self->expects('$t')) {
139             my \$oldselfpos = \$self->{POS};
140             \$self->{POS} = pos();
141             if (\$self->YYPreParse('$parser')) {
142             \$self->{POS} = \$oldselfpos;
143             return ('$t', \$1);
144             }
145             else {
146             \$self->{POS} = \$oldselfpos;
147             }
148             }
149             }
150             pos(\$_) = \$pos;
151             EORT
152             }
153             elsif ($termdef{$t}[2] eq 'CONTEXTUAL_REGEXP_NOMATCH') {
154 0         0 my $reg = $termdef{$t}[0];
155 0         0 my $parser = $termdef{$t}[3][0];
156 0         0 $reg =~ s{^/}{/\\G}; # add \G at the begining of the regexp
157             # factorize, factorize!!!! ohh!!!!
158 0         0 $DEFINEDTOKENS .= << "EORT";
159             \$pos = pos();
160             if (${reg}gc) {
161             if (\$self->expects('$t')) {
162             my \$oldselfpos = \$self->{POS};
163             \$self->{POS} = pos();
164             if (!\$self->YYPreParse('$parser')) {
165             \$self->{POS} = \$oldselfpos;
166             return ('$t', \$1);
167             }
168             else {
169             \$self->{POS} = \$oldselfpos;
170             }
171             }
172             }
173             pos(\$_) = \$pos;
174             EORT
175             }
176             elsif ($termdef{$t}[2] eq 'LITERAL') { # %token without regexp or code definition
177 76         131 my $reg = $termdef{$t}[0];
178 76         258 $reg =~ s{^'?}{}; # $reg =~ s{^'?}{/\\G(};
179 76         217 $reg =~ s{'?$}{}; # $reg =~ s{'?$}{)/};
180 76         140 $reg = quotemeta($reg);
181 76         195 $DEFINEDTOKENS .= << "EORT";
182             /\\G(${reg})/gc and return (\$1, \$1);
183             EORT
184             }
185             elsif ($termdef{$t}[2] eq 'CODE') { # token definition is code
186 0         0 $DEFINEDTOKENS .= "$termdef{$t}[0]\n";
187             }
188             }
189              
190 54         218 my $frame = _lexerFrame();
191 54         278 $frame =~ s/<>/$INCREMENTAL/;
192 54         244 $frame =~ s/<>/$WHITES/;
193              
194 54 100       206 if (@term) {
195 42         374 $frame =~ s/<>/m{$TERM}gc and return (\$1, \$1);/
196             }
197             else {
198 12         48 $frame =~ s/<>//
199             }
200              
201 54         292 $frame =~ s/<>/$DEFINEDTOKENS/;
202              
203 54         294 return $frame;
204             }
205              
206             sub _incrementalLexerText {
207              
208 0     0   0 return << 'ENDOFINCREMENTAL';
209             if ($self->YYEndOfInput) {
210             print $a if defined($a = $self->YYPrompt);
211             my $file = $self->YYInputFile;
212             $_ = <$file>;
213             return ('', undef) unless $_;
214             }
215             ENDOFINCREMENTAL
216             }
217              
218             sub _lexerFrame {
219 54     54   148 return << 'EOLEXER';
220             # Default lexical analyzer
221             our $LEX = sub {
222             my $self = shift;
223             my $pos;
224              
225             for (${$self->input}) {
226             <>
227              
228             <>;
229              
230             <>
231              
232             <>
233              
234             return ('', undef) if ($_ eq '') || (defined(pos($_)) && (pos($_) >= length($_)));
235             /\G\s*(\S+)/;
236             my $near = substr($1,0,10);
237              
238             return($near, $near);
239              
240             # die( "Error inside the lexical analyzer near '". $near
241             # ."'. Line: ".$self->line()
242             # .". File: '".$self->YYFilename()."'. No match found.\n");
243             }
244             }
245             ;
246             EOLEXER
247             }
248              
249             ####################################################################
250             # Returns : The string '{\n file contents }\n' with pre and post comments
251             # Parameters : a file name
252             sub _CopyModule {
253 0     0   0 my ($module, $function, $file) = @_;
254              
255 0 0       0 open(DRV,$file) or die "BUG: could not open $file";
256 0         0 my $text = join('',);
257 0         0 close(DRV);
258              
259 0         0 my $label = $module;
260 0         0 $label =~ s/::/_/g;
261 0         0 return << "EOCODE";
262             # Loading $module
263             BEGIN {
264             unless ($module->can('$function')) {
265             eval << 'MODULE_$label'
266             $text
267             MODULE_$label
268             }; # Unless $module was loaded
269             } ########### End of BEGIN { load $file }
270              
271             EOCODE
272             }
273              
274             ## This sub gives support to the "%tree alias" directive
275             ## Builds the text for the named accessors to the children
276             sub make_accessors {
277 54     54 0 122 my $accessors = shift; # hash reference: like left => 0
278              
279 54         130 my $text = '{';
280 54         220 for (keys(%$accessors)) {
281 52         148 $text .= "\n '$_' => $accessors->{$_},";
282             }
283 54         187 return "$text\n }";
284             }
285              
286             # Compute line numbers for the outputfile. Need for debugging
287             our $pattern = '################ @@@@@@@@@ End of User Code @@@@@@@@@ ###################';
288              
289             sub Output {
290 54     54 0 142 my($self)=shift;
291              
292 54         293 $self->Options(@_);
293              
294 54         128 my ($GRAMMAR, $TERMS, $FILENAME, $PACKAGES, $LABELS); # Cas
295 54         218 my($package)=$self->Option('classname');
296              
297 54         194 my $modulino = $self->Option('modulino'); # prompt or undef
298              
299 54 50       212 if (defined($modulino)) {
300 0         0 $modulino = <<"MODULINO";
301             unless (caller) {
302             exit !__PACKAGE__->main('$modulino');
303             }
304             MODULINO
305             }
306             else {
307 54         147 $modulino = '';
308             }
309              
310 54   33     204 my $lexerisdefined = $self->Option('lexerisdefined') || $self->{GRAMMAR}{LEXERISDEFINED};
311 54 50       518 my $defaultLexer = $lexerisdefined ? q{} : $self->makeLexer();
312              
313 54         175 my($head,$states,$rules,$tail,$driver, $bypass, $accessors, $buildingtree, $prefix, $conflict_handlers, $state_conflict);
314 54         145 my($version)=$Parse::Eyapp::Driver::VERSION;
315 54         100 my($datapos);
316 54         109 my $makenodeclasses = '';
317 54         116 $driver='';
318              
319 54 50       263 defined($package)
320             or $package='Parse::Eyapp::Default'; # may be the caller package?
321              
322 54         507 $head= $self->Head();
323 54         394 $rules=$self->RulesTable();
324 54         480 $states=$self->DfaTable();
325 54         547 $tail= $self->Tail();
326              
327 54         525 my $prompt = $self->Prompt();
328              
329             # In case the file ends with documentation and without a
330             # =cut
331             #
332 54 50       424 $tail = $tail."\n\n=for None\n\n=cut\n\n" unless $tail =~ /\n\n=cut\n/;
333             #local $Data::Dumper::Purity = 1;
334              
335 54         400 ($GRAMMAR, $PACKAGES, $LABELS) = $self->Rules();
336 54         215 $bypass = $self->Bypass;
337 54         370 $prefix = $self->Prefix;
338              
339 54         363 $conflict_handlers = $self->conflictHandlers;
340 54         397 $state_conflict = $self->stateConflict;
341              
342 54         413 $buildingtree = $self->Buildingtree;
343 54         346 $accessors = $self->Accessors;
344 54         232 my $accessors_hash = make_accessors($accessors);
345 54         375 $TERMS = $self->Terms();
346              
347             # Thanks Tom! previous double-quote use produced errors in windows
348 54         242 $FILENAME = q{'}.$self->Option('inputfile').q{'};
349              
350 54 50       199 if ($self->Option('standalone')) {
351             # Copy Driver, Node and YATW
352            
353 0         0 $driver .=_CopyModule('Parse::Eyapp::Driver','YYParse', $Parse::Eyapp::Driver::FILENAME);
354 0         0 $driver .= _CopyModule('Parse::Eyapp::Node', 'm', $Parse::Eyapp::Node::FILENAME);
355              
356             # Remove the line use Parse::Eyapp::YATW
357 0         0 $driver =~ s/\n\s*use Parse::Eyapp::YATW;\n//g;
358 0         0 $driver .= _CopyModule('Parse::Eyapp::YATW', 'm', $Parse::Eyapp::YATW::FILENAME);
359              
360 0         0 $makenodeclasses = '$self->make_node_classes('.$PACKAGES.');';
361             }
362             else {
363 54         196 $driver = make_header_for_driver_pm();
364 54         231 $makenodeclasses = '$self->make_node_classes('.$PACKAGES.');';
365             }
366              
367 54   33     212 my($text)=$self->Option('template') || Driver_pm();
368              
369 54         499 $text=~s/<<(\$.+)>>/$1/gee;
  1350         42010  
370              
371 54         1441 $text;
372             }
373              
374             sub make_header_for_driver_pm {
375 54     54 0 145 return q{
376             BEGIN {
377             # This strange way to load the modules is to guarantee compatibility when
378             # using several standalone and non-standalone Eyapp parsers
379              
380             require Parse::Eyapp::Driver unless Parse::Eyapp::Driver->can('YYParse');
381             require Parse::Eyapp::Node unless Parse::Eyapp::Node->can('hnew');
382             }
383             };
384             }
385              
386             sub Driver_pm {
387 54     54 0 222 return <<'EOT';
388             ########################################################################################
389             #
390             # This file was generated using Parse::Eyapp version <<$version>>.
391             #
392             # Copyright © 2006, 2007, 2008, 2009, 2010, 2011, 2012 Casiano Rodriguez-Leon.
393             # Copyright © 2017 William N. Braswell, Jr.
394             # All Rights Reserved.
395             #
396             # Parse::Yapp is Copyright © 1998, 1999, 2000, 2001, Francois Desarmenien.
397             # Parse::Yapp is Copyright © 2017 William N. Braswell, Jr.
398             # All Rights Reserved.
399             #
400             # Don't edit this file, use source file <<$FILENAME>> instead.
401             #
402             # ANY CHANGE MADE HERE WILL BE LOST !
403             #
404             ########################################################################################
405             package <<$package>>;
406             use strict;
407              
408             push @<<$package>>::ISA, 'Parse::Eyapp::Driver';
409              
410             <<$prompt>>
411              
412             <<$driver>>
413              
414             sub unexpendedInput { defined($_) ? substr($_, (defined(pos $_) ? pos $_ : 0)) : '' }
415              
416             <<$head>>
417              
418             <<$defaultLexer>>
419              
420             ################ @@@@@@@@@ End of User Code @@@@@@@@@ ###################
421              
422             my $warnmessage =<< "EOFWARN";
423             Warning!: Did you changed the \@<<$package>>::ISA variable inside the header section of the eyapp program?
424             EOFWARN
425              
426             sub new {
427             my($class)=shift;
428             ref($class) and $class=ref($class);
429              
430             warn $warnmessage unless __PACKAGE__->isa('Parse::Eyapp::Driver');
431             my($self)=$class->SUPER::new(
432             yyversion => '<<$version>>',
433             yyGRAMMAR =>
434             <<$GRAMMAR>>,
435             yyLABELS =>
436             <<$LABELS>>,
437             yyTERMS =>
438             <<$TERMS>>,
439             yyFILENAME => <<$FILENAME>>,
440             yystates =>
441             <<$states>>,
442             yyrules =>
443             <<$rules>>,
444             ################ @@@@@@@@@ End of User Code @@@@@@@@@ ###################
445             yybypass => <<$bypass>>,
446             yybuildingtree => <<$buildingtree>>,
447             yyprefix => '<<$prefix>>',
448             yyaccessors => <<$accessors_hash>>,
449             yyconflicthandlers => <<$conflict_handlers>>,
450             yystateconflict => <<$state_conflict>>,
451             @_,
452             );
453             bless($self,$class);
454              
455             <<$makenodeclasses>>
456             $self;
457             }
458              
459             <<$tail>>
460             ################ @@@@@@@@@ End of User Code @@@@@@@@@ ###################
461              
462             <<$modulino>>
463              
464             1;
465             EOT
466             }
467              
468             ####################################################################
469             # Usage :
470             # my $warnings = Parse::Eyapp->new_grammar(
471             # input=>$translationscheme,
472             # classname=>'main',
473             # firstline => 6,
474             # outputfile => 'main.pm'
475             # );
476             # die "$warnings\nSolve Ambiguities. See file main.output\n" if $warnings;
477             #
478             # Returns : string reporting about the ambiguities and conflicts or ''
479             # Throws : croaks if invalid arguments, if the grammar has errors, if can not open
480             # files or if the semantic actions have errors
481             #
482             # Parameters :
483             my %_new_grammar = (
484             input => undef,
485             classname => undef,
486             firstline => undef,
487             linenumbers => undef,
488             outputfile => undef,
489             );
490             my $validkeys = do { local $" = ", "; my @validkeys = keys(%_new_grammar); "@validkeys" };
491              
492             sub new_grammar {
493 57     57 0 24675 my $class = shift;
494              
495 57 100       416 croak "Error in new_grammar: Use named arguments" if (@_ %2);
496 56         262 my %arg = @_;
497 56 100   151   781 if (defined($a = first { !exists($_new_grammar{$_}) } keys(%arg))) {
  151         484  
498 1         222 croak("Parse::Eyapp::Output::new_grammar Error!: unknown argument $a. Valid arguments are: $validkeys")
499             }
500            
501 55 50       331 my $grammar = $arg{input} or croak "Error in new_grammar: Specify a input grammar";
502              
503 55 100       320 my $name = $arg{classname} or croak 'Error in new_grammar: Please provide a name for the grammar';
504              
505 54         204 my ($package, $filename, $line) = caller;
506              
507 54 100 66     536 $line = $arg{firstline} if defined($arg{firstline}) and ($arg{firstline} =~ /\d+/);
508              
509 54         137 my $linenumbers = $arg{linenumbers};
510 54 100       195 $linenumbers = 1 unless defined($linenumbers);
511              
512 54 50       456 croak "Bad grammar."
513             unless my $p = Parse::Eyapp->new(
514             input => $grammar,
515             inputfile => $filename,
516             firstline => $line,
517             linenumbers => $linenumbers,
518             );
519              
520 54 50       397 my $text = $p->Output(classname => $name) or croak "Can't generate parser.";
521              
522 54         186 my $outputfile = $arg{outputfile};
523 54 50       319 croak "Error in new_grammar: Invalid option for parameter linenumber" unless $linenumbers =~ m{[01]};
524              
525 54 50       215 if (defined($outputfile)) {
526 0         0 my($base,$path,$sfx)=fileparse($outputfile,'\..*$');
527 0         0 $p->outputtables($path, $base);
528 0         0 my($outfile)="$path$base.pm";
529 0 0       0 open(my $OUT,">$outfile")
530             or die "Cannot open $outfile for writing.\n";
531              
532 0         0 compute_lines(\$text, $outfile, $pattern);
533 0         0 print $OUT $text; #$p->Output(classname => $name, linenumbers => $linenumbers);
534 0         0 close $OUT;
535             }
536              
537 54 50   54   477 my $x = eval $text;
  54 50   54   117  
  54 50   19   4354  
  54 50   0   497  
  54 0   2   46141  
  54 0   0   4673  
  19 100   7   1076  
  19 50   1   76  
  19 0       162  
  1         28  
  1         5  
  1         7  
  1         4  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
538 54 100       1010 $@ and die "Error while compiling your parser: $@\n";
539 53         4852 return $p;
540             }
541              
542             1;
543              
544             __END__