File Coverage

blib/lib/RPerl/Parser.pm
Criterion Covered Total %
statement 145 146 99.3
branch 28 32 87.5
condition 9 18 50.0
subroutine 13 13 100.0
pod n/a
total 195 209 93.3


line stmt bran cond sub pod time code
1             # [[[ HEADER ]]]
2             package RPerl::Parser;
3 3     3   1054 use strict;
  3         5  
  3         71  
4 3     3   13 use warnings;
  3         5  
  3         59  
5 3     3   14 use RPerl::AfterSubclass;
  3         4  
  3         370  
6             our $VERSION = 0.009_000;
7              
8             # [[[ OO INHERITANCE ]]]
9             #use RPerl::CompileUnit::Module::Class;
10             #use parent qw(RPerl::CompileUnit::Module::Class);
11              
12             # [[[ CRITICS ]]]
13             ## no critic qw(ProhibitUselessNoCritic ProhibitMagicNumbers RequireCheckedSyscalls) # USER DEFAULT 1: allow numeric values & print operator
14             ## no critic qw(ProhibitConstantPragma ProhibitMagicNumbers) # USER DEFAULT 3: allow constants
15             ## no critic qw(ProhibitPostfixControls) # SYSTEM SPECIAL 6: PERL CRITIC FILED ISSUE #639, not postfix foreach or if
16             ## no critic qw(ProhibitBacktickOperators) ## SYSTEM SPECIAL 11: allow system command execution
17             ## no critic qw(RequireCarping) # SYSTEM SPECIAL 13: allow die instead of croak
18              
19             # [[[ INCLUDES ]]]
20 3     3   996 use Perl::Critic;
  3         2077453  
  3         132  
21 3     3   3003 use RPerl::Grammar;
  3         10  
  3         176  
22              
23             # [[[ CONSTANTS ]]]
24 3     3   30 use constant MAX_SINGLE_ERROR_LINE_LENGTH => my integer $TYPED_MAX_SINGLE_ERROR_LINE_LENGTH = 120;
  3         5  
  3         4519  
25              
26             # [[[ SUBROUTINES ]]]
27              
28             # Parse from Human-Readable RPerl Source Code File to Eyapp-Parsed RPerl AST Object
29             sub rperl_to_ast__parse {
30 2244     2244   5157 { my object $RETURN_TYPE };
  2244         4505  
31 2244         5649 ( my string $rperl_source__file_name) = @ARG;
32              
33             # [[[ PARSE PHASE 0: CHECK PERL SYNTAX ]]]
34             # [[[ PARSE PHASE 0: CHECK PERL SYNTAX ]]]
35             # [[[ PARSE PHASE 0: CHECK PERL SYNTAX ]]]
36              
37 2244         47731 rperl_source__check_syntax($rperl_source__file_name);
38              
39             # [[[ PARSE PHASE 1: CRITICIZE PERL SYNTAX ]]]
40             # [[[ PARSE PHASE 1: CRITICIZE PERL SYNTAX ]]]
41             # [[[ PARSE PHASE 1: CRITICIZE PERL SYNTAX ]]]
42              
43 1916         156178 rperl_source__criticize($rperl_source__file_name);
44              
45             # [[[ PARSE PHASE 2: PARSE RPERL SYNTAX ]]]
46             # [[[ PARSE PHASE 2: PARSE RPERL SYNTAX ]]]
47             # [[[ PARSE PHASE 2: PARSE RPERL SYNTAX ]]]
48              
49 1744         76284 return ( rperl_source__parse($rperl_source__file_name) );
50             }
51              
52             # Check Perl Syntax Using Perl Interpreter
53             sub rperl_source__check_syntax {
54 2244     2244   4898 { my void $RETURN_TYPE };
  2244         5326  
55 2244         5399 ( my string $rperl_source__file_name) = @ARG;
56              
57 2244         12212 RPerl::verbose('PARSE PHASE 0: Check Perl syntax... ');
58              
59 2244 50       13429 my string $nul = $OSNAME eq 'MSWin32' ? 'NUL' : '/dev/null';
60 2244         9461 my string $rperl_source__perl_syntax_command
61             # DEV NOTE: inclusion of '-Mstrict' alters propagation of error messages through eval() to die()
62             = $EXECUTABLE_NAME . q{ -Iblib/lib -M"warnings FATAL=>q(all)" -Mstrict -cw }
63             # = $EXECUTABLE_NAME . q{ -Iblib/lib -M"warnings FATAL=>q(all)" -cw }
64             . $rperl_source__file_name;
65 2244         9168 my string $rperl_source__perl_syntax_command__no_output
66             = $rperl_source__perl_syntax_command . ' > '.$nul.' 2> '.$nul;
67 2244         6049 my string $rperl_source__perl_syntax_command__all_output
68             = $rperl_source__perl_syntax_command . ' 2>&1';
69              
70             #my string $rperl_source__perl_syntax_command = q{perl -Iblib/lib -cw } . $rperl_source__file_name;
71              
72             #RPerl::diag("in rperl_source__check_syntax(), have \$rperl_source__perl_syntax_command =\n$rperl_source__perl_syntax_command\n");
73             #RPerl::diag("in rperl_source__check_syntax(), have \$rperl_source__perl_syntax_command__no_output =\n$rperl_source__perl_syntax_command__no_output\n\n");
74             #RPerl::diag("in rperl_source__check_syntax(), have \$rperl_source__perl_syntax_command__all_output =\n$rperl_source__perl_syntax_command__all_output\n\n");
75              
76             #my integer $rperl_source__perl_syntax_retval = system $rperl_source__perl_syntax_command;
77 2244         505521422 my integer $rperl_source__perl_syntax_retval
78             = system $rperl_source__perl_syntax_command__no_output; # don't want any messages printed here
79              
80             #my string $rperl_source__perl_syntax_retstring = `echo HOWDY`;
81             #my string $rperl_source__perl_syntax_retstring = `$rperl_source__perl_syntax_command`;
82 2244         499640380 my string $rperl_source__perl_syntax_retstring
83             = `$rperl_source__perl_syntax_command__all_output`;
84              
85             #RPerl::diag("in rperl_source__check_syntax(), have \$rperl_source__perl_syntax_retval = $rperl_source__perl_syntax_retval\n");
86             #RPerl::diag("in rperl_source__check_syntax(), have \$rperl_source__perl_syntax_retstring =\n$rperl_source__perl_syntax_retstring\n");
87             #RPerl::diag("in rperl_source__check_syntax(), have \$OS_ERROR = $OS_ERROR\n"); # $OS_ERROR seems to contain random error messages that I can't trace?
88             #RPerl::diag("in rperl_source__check_syntax(), have \$? = $?\n");
89              
90             # NEED ADD ERROR CHECKING: ECOPAPL00 FILE DOES NOT EXIST, ECOPAPL01 FILE IS EMPTY
91              
92 2244 100       45260 if ( $rperl_source__perl_syntax_retval != 0 ) {
93 324         4666 my $error_pretty = "\n\n"
94             . 'ERROR ECOPAPL02, RPERL PARSER, PERL SYNTAX ERROR' . "\n"
95             . 'Failed normal Perl strictures-and-fatal-warnings syntax check with the following information:' . "\n\n"
96             . ' File Name: ' . $rperl_source__file_name . "\n"
97             . ' Return Value: ' . ( $rperl_source__perl_syntax_retval >> 8 ) . "\n"
98             . ' Error Message(s): ';
99 324 100       2754 if ( (length $rperl_source__perl_syntax_retstring) < MAX_SINGLE_ERROR_LINE_LENGTH() ) {
100 51         395 $error_pretty .= $rperl_source__perl_syntax_retstring . "\n\n";
101             }
102             else {
103 273         2024 $error_pretty .= "\n\n" . $rperl_source__perl_syntax_retstring . "\n\n";
104             }
105            
106 324         14804 die $error_pretty;
107             }
108              
109 1920         7194 my string_arrayref $rperl_source__perl_syntax_retstring_lines;
110 1920         14387 @{$rperl_source__perl_syntax_retstring_lines} = split /\n/xms,
  1920         31639  
111             $rperl_source__perl_syntax_retstring;
112              
113             # RPerl::diag('in rperl_source__check_syntax(), have $rperl_source__perl_syntax_retstring_lines = ' . "\n" . Dumper($rperl_source__perl_syntax_retstring_lines) . "\n");
114 1920         12119 my string_arrayref $rperl_source__perl_syntax_retstring_warnings = [];
115 1920         4941 foreach my string $rperl_source__perl_syntax_retstring_line (
116 1920         11501 @{$rperl_source__perl_syntax_retstring_lines} )
117             {
118 1924 100 33     86229 if (( $rperl_source__perl_syntax_retstring_line !~ m/WARNING\sW/xms ) # RPerl Warning
      33        
      33        
      66        
119             and
120             ( $rperl_source__perl_syntax_retstring_line !~ m/ERROR\sE/xms ) # RPerl Error
121             and
122             ( $rperl_source__perl_syntax_retstring_line !~ m/\[\[\[\ BEGIN\s/xms ) # RPerl Non-Error Debug Info
123             and
124             ( $rperl_source__perl_syntax_retstring_line !~ m/\[\[\[\ END\s/xms ) # RPerl Non-Error Debug Info
125             and
126             ( $rperl_source__perl_syntax_retstring_line !~ m/syntax\sOK/xms ) # Perl Non-Error
127             )
128             {
129 4         26 push @{$rperl_source__perl_syntax_retstring_warnings},
  4         42  
130             $rperl_source__perl_syntax_retstring_line;
131             }
132             }
133              
134 1920 100       6231 if ( ( scalar @{$rperl_source__perl_syntax_retstring_warnings} ) != 0 ) {
  1920         8360  
135 4         51 my $error_pretty = "\n"
136             . 'ERROR ECOPAPL03, RPERL PARSER, PERL SYNTAX WARNING' . "\n"
137             . 'Failed normal Perl strictures-and-fatal-warnings syntax check with the following information:' . "\n\n"
138             . ' File Name: ' . $rperl_source__file_name . "\n"
139             . ' Warning Message(s): ';
140            
141 4 100 66     19 if ( ( ( scalar @{$rperl_source__perl_syntax_retstring_warnings} ) == 1 )
  4         80  
142             and ( (length $rperl_source__perl_syntax_retstring_warnings->[0]) < MAX_SINGLE_ERROR_LINE_LENGTH() ) ) {
143 2         22 $error_pretty .= $rperl_source__perl_syntax_retstring_warnings->[0] . "\n\n";
144             }
145             else {
146 2         16 $error_pretty .= "\n\n" . ( join "\n", @{$rperl_source__perl_syntax_retstring_warnings} ) . "\n\n";
  2         25  
147             }
148 4         149 die $error_pretty;
149             }
150              
151 1916         40190 RPerl::verbose(' done.' . "\n");
152             }
153              
154             # Criticize Perl Syntax Using Perl::Critic
155             sub rperl_source__criticize {
156 1916     1916   5474 { my void $RETURN_TYPE };
  1916         5734  
157 1916         6068 ( my string $rperl_source__file_name) = @ARG;
158              
159 1916         7991 RPerl::verbose('PARSE PHASE 1: Criticize Perl syntax... ');
160              
161             # pre-critic error, begin check to ensure file ends with newline character or all-whitespace line
162 1916 50       70041 if ( not -f $rperl_source__file_name ) {
163 0         0 die 'ERROR ECOPAPC10, RPERL PARSER, PERL CRITIC VIOLATION: File not found, ' . q{'} . $rperl_source__file_name . q{'} . ', dying' . "\n";
164             }
165              
166 1916 50       202163 open my filehandleref $FILE_HANDLE, '<', $rperl_source__file_name
167             or die 'ERROR ECOPAPC11, RPERL PARSER, PERL CRITIC VIOLATION: Cannot open file ' . q{'} . $rperl_source__file_name . q{'} . ' for reading, ' . $OS_ERROR . ', dying' . "\n";
168              
169 1916         8645 my string $file_line = undef;
170 1916         6276 my string $file_line_last = undef;
171              
172 1916         33523 while ( $file_line = <$FILE_HANDLE> ) {
173             # RPerl::diag('in rperl_source__criticize(), top of while loop, have $file_line = ' . q{'} . $file_line . q{'} . "\n");
174 45039         97634 $file_line_last = $file_line;
175             }
176              
177             # RPerl::diag('in rperl_source__criticize(), have last $file_line = ' . q{'} . $file_line . q{'} . "\n");
178             # RPerl::diag('in rperl_source__criticize(), have $file_line_last = ' . q{'} . $file_line_last . q{'} . "\n");
179              
180 1916 50       16394 close $FILE_HANDLE or die 'ERROR ECOPAPC12, RPERL PARSER, PERL CRITIC VIOLATION: Cannot close file ' . q{'} . $rperl_source__file_name . q{'} . ' after reading, ' . $OS_ERROR . ', dying' . "\n";
181              
182             # DEV NOTE: the last line of all RPerl input files must either end with a newline character or be all-whitespace characters,
183             # in order to avoid false positives triggered by Perl::Critic
184 1916 100 66     14546 if (((substr $file_line_last, -1, 1) ne "\n") and ( $file_line_last !~ m/^\s+$/xms )) {
185 1         50 die 'ERROR ECOPAPC13, RPERL PARSER, PERL CRITIC VIOLATION: RPerl source code input file ' . q{'} . $rperl_source__file_name . q{'} . ' does not end with newline character or line of all-whitespace characters, dying' . "\n";
186             }
187              
188             # DEV NOTE: disable RequireTidyCode because perltidy may not be stable
189             # my object $rperl_source__critic = Perl::Critic->new( -severity => 'brutal' );
190             # my object $rperl_source__critic = Perl::Critic->new( -exclude => ['RequireTidyCode'] -severity => 'brutal' ); # DEV NOTE: Perl::Critic's own docs-recommended syntax throws a violation
191 1915         55977 my object $rperl_source__critic = Perl::Critic->new(
192             # DEV NOTE: disable RequireTidyCode because Perl::Tidy is not perfect and may complain even if the code is tidy;
193             # disable PodSpelling because calling the external spellchecker can cause errors such as aspell's "No word lists can be found for the language FOO";
194             # disable RequireExplicitPackage because 'use RPerl;' comes before package name(s), and Grammar.eyp will catch any other violations
195             # NEED REMOVE HARD-CODED TEMPORARY WORK-AROUND: https://github.com/autinitysystems/Perl-Critic-Policy-Documentation-RequirePod/issues/1
196             # NEED REMOVE HARD-CODED TEMPORARY WORK-AROUND: https://github.com/petdance/perl-critic-bangs/issues/16
197             # disable RequirePod because it is not part of Perl::Critic & wrongly includes itself in themes 'core' & 'php' & 'maintenance'
198             # disable all non-core additional policies which may be installed, such as Perlsecret, etc.
199             '-exclude' => ['RequireTidyCode', 'PodSpelling', 'RequireExplicitPackage', 'RequirePod', 'ProhibitBitwiseOperators'],
200             '-severity' => 'brutal',
201             '-theme' => 'core'
202             );
203             my @rperl_source__critic_violations
204 1915         939222349 = $rperl_source__critic->critique($rperl_source__file_name);
205              
206 1915         159196802 my integer $rperl_source__critic_num_violations
207             = scalar @rperl_source__critic_violations;
208              
209             #RPerl::diag("in rperl_source__criticize(), have \$rperl_source__critic_num_violations = $rperl_source__critic_num_violations\n");
210             # my string $rperl_source__critic_dumperified_violations = Dumper( \@rperl_source__critic_violations );
211             #RPerl::diag("in rperl_source__criticize(), have Dumper(\\\@rperl_source__critic_violations) =\n" . $rperl_source__critic_dumperified_violations . "\n");
212              
213             # NEED ADD ERROR CHECKING: ECOPAPC00 FILE DOES NOT EXIST, ECOPAPC01 FILE IS EMPTY; or would that be redundant with ECOPAPL0x error checking when added above?
214              
215 1915 100       11190 if ( $rperl_source__critic_num_violations > 0 ) {
216 171         505 my string $violation_pretty = q{};
217 171         987 foreach my object $violation (@rperl_source__critic_violations) {
218 192         1178 $violation_pretty .= ' File Name: ' . $rperl_source__file_name . "\n";
219 192         869 $violation_pretty .= ' Line number: ' . $violation->{_location}->[0] . "\n";
220 192         645 $violation_pretty .= ' Policy: ' . $violation->{_policy} . "\n";
221 192         658 $violation_pretty .= ' Description: ' . $violation->{_description} . "\n";
222 192 100       972 if ( ref( $violation->{_explanation} ) eq 'ARRAY' ) {
223 148         386 $violation_pretty .= ' Explanation: See Perl Best Practices page(s) ' . join( ', ', @{ $violation->{_explanation} } ) . "\n\n";
  148         976  
224             }
225             else {
226 44         187 $violation_pretty .= ' Explanation: ' . $violation->{_explanation} . "\n\n";
227             }
228             }
229 171         196999 die "\n"
230             . 'ERROR ECOPAPC02, RPERL PARSER, PERL CRITIC VIOLATION'
231             . "\n"
232             . 'Failed Perl::Critic brutal review with the following information:'
233             . "\n\n"
234             . $violation_pretty;
235             }
236             else {
237 1744         12276 RPerl::verbose(' done.' . "\n");
238             }
239             }
240              
241             # Die On RPerl Grammar Error
242             sub rperl_grammar_error {
243 396     396   841 { my void $RETURN_TYPE };
  396         780  
244 396         1154 ( my array $argument ) = @ARG;
245              
246 396         1605 my string $value = $argument->YYCurval;
247 396 100       4384 if ( not( defined $value ) ) {
248 96         283 $value = '<<< NO TOKEN FOUND >>>';
249             }
250 396         1082 my string $helpful_hint = q{};
251 396 100       2297 if ( $value =~ /\d/xms ) {
252 14         40 $helpful_hint
253             = q{ Helpful Hint: Possible case of PBP RequireNumberSeparators (see below)} . "\n"
254             . q{ Policy: Perl::Critic::Policy::ValuesAndExpressions::RequireNumberSeparators} . "\n"
255             . q{ Description: Long number not separated with underscores} . "\n"
256             . q{ Explanation: See Perl Best Practices page(s) 59} . "\n";
257             }
258              
259 396         909 my integer $line_number = $argument->{TOKENLINE};
260 396         842 my string $rperl_source__file_name = $argument->{rperl_source__file_name};
261              
262             # die( "\n" . 'ERROR ECOPARP00, RPERL PARSER, SYNTAX ERROR; have $argument =' . "\n" . Dumper($argument) . "\n" );
263             # die( "\n" . 'ERROR ECOPARP00, RPERL PARSER, SYNTAX ERROR; have $argument->{rperl_source__file_name} = ' . $argument->{rperl_source__file_name} . "\n" );
264              
265 396         1132 my $current_state_num = $argument->{STACK}[-1][0];
266 396         857 my $current_state = $argument->{STATES}[$current_state_num];
267 396         686 my $expected_tokens = q{};
268 396         604 my number $is_first_expected = 1;
269              
270 396         761 foreach my $expected_token ( sort keys %{ $current_state->{ACTIONS} } ) {
  396         6627  
271 6752 100       8932 if ($is_first_expected) {
272 396         776 $is_first_expected = 0;
273 396         1277 $expected_tokens .= $expected_token . "\n";
274             }
275             else {
276 6356         9065 $expected_tokens
277             .= q{ } . $expected_token . "\n";
278             }
279             }
280              
281 396         682015 die "\n"
282             . 'ERROR ECOPARP00, RPERL PARSER, RPERL SYNTAX ERROR' . "\n"
283             . 'Failed RPerl grammar syntax check with the following information:'
284             . "\n\n"
285             . ' File Name: ' . $rperl_source__file_name . "\n"
286             . ' Line Number: ' . $line_number . "\n"
287             . ' Unexpected Token: ' . $value . "\n"
288             . ' Expected Token(s): ' . $expected_tokens
289             . $helpful_hint . "\n";
290             }
291              
292             # Parse RPerl Syntax Using Eyapp Grammar
293             sub rperl_source__parse {
294 1744     1744   4571 { my void $RETURN_TYPE };
  1744         4137  
295 1744         4864 ( my string $rperl_source__file_name) = @ARG;
296              
297 1744         6773 RPerl::verbose('PARSE PHASE 2: Parse RPerl syntax... ');
298              
299 1744         28486 my object $eyapp_parser = RPerl::Grammar->new();
300 1744         6467 $eyapp_parser->{rperl_source__file_name} = $rperl_source__file_name;
301 1744         8023 $eyapp_parser->YYSlurpFile($rperl_source__file_name);
302 1744         392483 my object $rperl_ast = $eyapp_parser->YYParse(
303             yydebug => 0x00, # disable eyapp DBG DEBUGGING
304              
305             # yydebug => 0xFF, # full eyapp DBG DEBUGGING, USE FOR DEBUGGING GRAMMAR
306             yyerror => \&rperl_grammar_error
307             );
308              
309 1348         125598 RPerl::verbose(' done.' . "\n");
310              
311             # RPerl::diag("in rperl_source__parse(), have \$rperl_ast->str() =\n" . $rperl_ast->str() . "\n\n");
312             # RPerl::diag("in rperl_source__parse(), have \$rperl_ast =\n" . rperl_ast__dump($rperl_ast) . "\n\n");
313             # die 'TMP DEBUG';
314              
315 1348         2409190 return ($rperl_ast);
316             }
317              
318             # condense AST dump, replace all instances of RPerl rule(s) with more meaningful RPerl class(es)
319             sub rperl_ast__dump {
320 5     5   12 { my string $RETURN_TYPE };
  5         18  
321 5         16 ( my object $rperl_ast) = @ARG;
322 5         20 $Data::Dumper::Indent = 1; # do not attempt to align hash values based on hash key length
323 5         46 my string $rperl_ast_dumped = Dumper($rperl_ast);
324 5         657 $Data::Dumper::Indent = 2; # restore default
325              
326             # $rperl_ast_dumped =~ s/\ \ /\ \ \ \ /gxms; # set tabs from 2 to 4 spaces
327 5         46 $rperl_ast_dumped =~ s/[ ]{2}/ /gxms; # set tabs from 2 to 4 spaces
328 5         19 my string $replacee;
329             my string $replacer;
330 5         12 foreach my string $rule ( sort keys %{$RPerl::Grammar::RULES} ) {
  5         414  
331 840         1312 $replacee = q{'} . $rule . q{'};
332             $replacer
333 840         1329 = q{'} . $rule . ' ISA ' . $RPerl::Grammar::RULES->{$rule} . q{'};
334 840         4274 $rperl_ast_dumped =~ s/$replacee/$replacer/gxms;
335             }
336 5         75 return $rperl_ast_dumped;
337             }
338              
339             # replace all instances of RPerl rule(s) with more meaningful RPerl class(es)
340             sub rperl_rule__replace {
341 2     2   6 { my string $RETURN_TYPE };
  2         6  
342 2         7 ( my string $rperl_rule_string) = @ARG;
343 2         5 my string $replacer;
344 2         6 foreach my string $rule ( sort keys %{$RPerl::Grammar::RULES} ) {
  2         181  
345 336 100       612 if ( $RPerl::Grammar::RULES->{$rule} ne 'RPerl::NonGenerator' ) {
346             $replacer
347             = q{(}
348             . $rule . ' ISA '
349 256         447 . $RPerl::Grammar::RULES->{$rule} . q{)};
350 256         517 $replacer =~ s/RPerl:://gxms;
351 256         1486 $rperl_rule_string =~ s/$rule/$replacer/gxms;
352             }
353             }
354 2         68 return $rperl_rule_string;
355             }
356              
357             1; # end of class