File Coverage

blib/lib/RPerl/Parser.pm
Criterion Covered Total %
statement 18 18 100.0
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 24 24 100.0


line stmt bran cond sub pod time code
1             # [[[ HEADER ]]]
2             package RPerl::Parser;
3 4     4   1265 use strict;
  4         10  
  4         98  
4 4     4   18 use warnings;
  4         9  
  4         82  
5 4     4   19 use RPerl::AfterSubclass;
  4         8  
  4         460  
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 4     4   1815 use Perl::Critic;
  4         3082255  
  4         181  
21 4     4   4242 use RPerl::Grammar;
  4         19  
  4         255  
22              
23             # [[[ CONSTANTS ]]]
24 4     4   42 use constant MAX_SINGLE_ERROR_LINE_LENGTH => my integer $TYPED_MAX_SINGLE_ERROR_LINE_LENGTH = 120;
  4         12  
  4         5963  
25              
26             # [[[ SUBROUTINES ]]]
27              
28             # Parse from Human-Readable RPerl Source Code File to Eyapp-Parsed RPerl AST Object
29             our object $rperl_to_ast__parse = sub {
30             ( my string $rperl_source__file_name) = @_;
31              
32             # [[[ PARSE PHASE 0: CHECK PERL SYNTAX ]]]
33             # [[[ PARSE PHASE 0: CHECK PERL SYNTAX ]]]
34             # [[[ PARSE PHASE 0: CHECK PERL SYNTAX ]]]
35              
36             rperl_source__check_syntax($rperl_source__file_name);
37              
38             # [[[ PARSE PHASE 1: CRITICIZE PERL SYNTAX ]]]
39             # [[[ PARSE PHASE 1: CRITICIZE PERL SYNTAX ]]]
40             # [[[ PARSE PHASE 1: CRITICIZE PERL SYNTAX ]]]
41              
42             rperl_source__criticize($rperl_source__file_name);
43              
44             # [[[ PARSE PHASE 2: PARSE RPERL SYNTAX ]]]
45             # [[[ PARSE PHASE 2: PARSE RPERL SYNTAX ]]]
46             # [[[ PARSE PHASE 2: PARSE RPERL SYNTAX ]]]
47              
48             return ( rperl_source__parse($rperl_source__file_name) );
49             };
50              
51             # Check Perl Syntax Using Perl Interpreter
52             our void $rperl_source__check_syntax = sub {
53             ( my string $rperl_source__file_name) = @_;
54              
55             RPerl::verbose('PARSE PHASE 0: Check Perl syntax... ');
56              
57             my string $nul = $OSNAME eq 'MSWin32' ? 'NUL' : '/dev/null';
58             my string $rperl_source__perl_syntax_command
59             # DEV NOTE: inclusion of '-Mstrict' alters propagation of error messages through eval() to die()
60             = $EXECUTABLE_NAME . q{ -Iblib/lib -M"warnings FATAL=>q(all)" -Mstrict -cw }
61             # = $EXECUTABLE_NAME . q{ -Iblib/lib -M"warnings FATAL=>q(all)" -cw }
62             . $rperl_source__file_name;
63             my string $rperl_source__perl_syntax_command__no_output
64             = $rperl_source__perl_syntax_command . ' > '.$nul.' 2> '.$nul;
65             my string $rperl_source__perl_syntax_command__all_output
66             = $rperl_source__perl_syntax_command . ' 2>&1';
67              
68             #my string $rperl_source__perl_syntax_command = q{perl -Iblib/lib -cw } . $rperl_source__file_name;
69              
70             #RPerl::diag("in rperl_source__check_syntax(), have \$rperl_source__perl_syntax_command =\n$rperl_source__perl_syntax_command\n");
71             #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");
72             #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");
73              
74             #my integer $rperl_source__perl_syntax_retval = system $rperl_source__perl_syntax_command;
75             my integer $rperl_source__perl_syntax_retval
76             = system $rperl_source__perl_syntax_command__no_output; # don't want any messages printed here
77              
78             #my string $rperl_source__perl_syntax_retstring = `echo HOWDY`;
79             #my string $rperl_source__perl_syntax_retstring = `$rperl_source__perl_syntax_command`;
80             my string $rperl_source__perl_syntax_retstring
81             = `$rperl_source__perl_syntax_command__all_output`;
82              
83             #RPerl::diag("in rperl_source__check_syntax(), have \$rperl_source__perl_syntax_retval = $rperl_source__perl_syntax_retval\n");
84             #RPerl::diag("in rperl_source__check_syntax(), have \$rperl_source__perl_syntax_retstring =\n$rperl_source__perl_syntax_retstring\n");
85             #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?
86             #RPerl::diag("in rperl_source__check_syntax(), have \$? = $?\n");
87              
88             # NEED ADD ERROR CHECKING: ECOPAPL00 FILE DOES NOT EXIST, ECOPAPL01 FILE IS EMPTY
89              
90             if ( $rperl_source__perl_syntax_retval != 0 ) {
91             my $error_pretty = "\n\n"
92             . 'ERROR ECOPAPL02, RPERL PARSER, PERL SYNTAX ERROR' . "\n"
93             . 'Failed normal Perl strictures-and-fatal-warnings syntax check with the following information:' . "\n\n"
94             . ' File Name: ' . $rperl_source__file_name . "\n"
95             . ' Return Value: ' . ( $rperl_source__perl_syntax_retval >> 8 ) . "\n"
96             . ' Error Message(s): ';
97             if ( (length $rperl_source__perl_syntax_retstring) < MAX_SINGLE_ERROR_LINE_LENGTH() ) {
98             $error_pretty .= $rperl_source__perl_syntax_retstring . "\n\n";
99             }
100             else {
101             $error_pretty .= "\n\n" . $rperl_source__perl_syntax_retstring . "\n\n";
102             }
103            
104             die $error_pretty;
105             }
106              
107             my string_arrayref $rperl_source__perl_syntax_retstring_lines;
108             @{$rperl_source__perl_syntax_retstring_lines} = split /\n/xms,
109             $rperl_source__perl_syntax_retstring;
110              
111             # RPerl::diag('in rperl_source__check_syntax(), have $rperl_source__perl_syntax_retstring_lines = ' . "\n" . Dumper($rperl_source__perl_syntax_retstring_lines) . "\n");
112             my string_arrayref $rperl_source__perl_syntax_retstring_warnings = [];
113             foreach my string $rperl_source__perl_syntax_retstring_line (
114             @{$rperl_source__perl_syntax_retstring_lines} )
115             {
116             if (( $rperl_source__perl_syntax_retstring_line !~ m/WARNING\sW/xms ) # RPerl Warning
117             and
118             ( $rperl_source__perl_syntax_retstring_line !~ m/ERROR\sE/xms ) # RPerl Error
119             and
120             ( $rperl_source__perl_syntax_retstring_line !~ m/\[\[\[\ BEGIN\s/xms ) # RPerl Non-Error Debug Info
121             and
122             ( $rperl_source__perl_syntax_retstring_line !~ m/\[\[\[\ END\s/xms ) # RPerl Non-Error Debug Info
123             and
124             ( $rperl_source__perl_syntax_retstring_line !~ m/syntax\sOK/xms ) # Perl Non-Error
125             )
126             {
127             push @{$rperl_source__perl_syntax_retstring_warnings},
128             $rperl_source__perl_syntax_retstring_line;
129             }
130             }
131              
132             if ( ( scalar @{$rperl_source__perl_syntax_retstring_warnings} ) != 0 ) {
133             my $error_pretty = "\n"
134             . 'ERROR ECOPAPL03, RPERL PARSER, PERL SYNTAX WARNING' . "\n"
135             . 'Failed normal Perl strictures-and-fatal-warnings syntax check with the following information:' . "\n\n"
136             . ' File Name: ' . $rperl_source__file_name . "\n"
137             . ' Warning Message(s): ';
138            
139             if ( ( ( scalar @{$rperl_source__perl_syntax_retstring_warnings} ) == 1 )
140             and ( (length $rperl_source__perl_syntax_retstring_warnings->[0]) < MAX_SINGLE_ERROR_LINE_LENGTH() ) ) {
141             $error_pretty .= $rperl_source__perl_syntax_retstring_warnings->[0] . "\n\n";
142             }
143             else {
144             $error_pretty .= "\n\n" . ( join "\n", @{$rperl_source__perl_syntax_retstring_warnings} ) . "\n\n";
145             }
146             die $error_pretty;
147             }
148              
149             RPerl::verbose(' done.' . "\n");
150             };
151              
152             # Criticize Perl Syntax Using Perl::Critic
153             our void $rperl_source__criticize = sub {
154             ( my string $rperl_source__file_name) = @_;
155              
156             RPerl::verbose('PARSE PHASE 1: Criticize Perl syntax... ');
157              
158             # pre-critic error, begin check to ensure file ends with newline character or all-whitespace line
159             if ( not -f $rperl_source__file_name ) {
160             die 'ERROR ECOPAPC10, RPERL PARSER, PERL CRITIC VIOLATION: File not found, ' . q{'} . $rperl_source__file_name . q{'} . ', dying' . "\n";
161             }
162              
163             open my filehandleref $FILE_HANDLE, '<', $rperl_source__file_name
164             or die 'ERROR ECOPAPC11, RPERL PARSER, PERL CRITIC VIOLATION: Cannot open file ' . q{'} . $rperl_source__file_name . q{'} . ' for reading, ' . $OS_ERROR . ', dying' . "\n";
165              
166             my string $file_line = undef;
167             my string $file_line_last = undef;
168              
169             while ( $file_line = <$FILE_HANDLE> ) {
170             # RPerl::diag('in rperl_source__criticize(), top of while loop, have $file_line = ' . q{'} . $file_line . q{'} . "\n");
171             $file_line_last = $file_line;
172             }
173              
174             # RPerl::diag('in rperl_source__criticize(), have last $file_line = ' . q{'} . $file_line . q{'} . "\n");
175             # RPerl::diag('in rperl_source__criticize(), have $file_line_last = ' . q{'} . $file_line_last . q{'} . "\n");
176              
177             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";
178              
179             # DEV NOTE: the last line of all RPerl input files must either end with a newline character or be all-whitespace characters,
180             # in order to avoid false positives triggered by Perl::Critic
181             if (((substr $file_line_last, -1, 1) ne "\n") and ( $file_line_last !~ m/^\s+$/xms )) {
182             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";
183             }
184              
185             # DEV NOTE: disable RequireTidyCode because perltidy may not be stable
186             # my object $rperl_source__critic = Perl::Critic->new( -severity => 'brutal' );
187             # my object $rperl_source__critic = Perl::Critic->new( -exclude => ['RequireTidyCode'] -severity => 'brutal' ); # DEV NOTE: Perl::Critic's own docs-recommended syntax throws a violation
188             my object $rperl_source__critic = Perl::Critic->new(
189             # DEV NOTE: disable RequireTidyCode because Perl::Tidy is not perfect and may complain even if the code is tidy;
190             # disable PodSpelling because calling the external spellchecker can cause errors such as aspell's "No word lists can be found for the language FOO";
191             # disable RequireExplicitPackage because 'use RPerl;' comes before package name(s), and Grammar.eyp will catch any other violations
192             # NEED REMOVE HARD-CODED TEMPORARY WORK-AROUND: https://github.com/autinitysystems/Perl-Critic-Policy-Documentation-RequirePod/issues/1
193             # NEED REMOVE HARD-CODED TEMPORARY WORK-AROUND: https://github.com/petdance/perl-critic-bangs/issues/16
194             # disable RequirePod because it is not part of Perl::Critic & wrongly includes itself in themes 'core' & 'php' & 'maintenance'
195             # disable all non-core additional policies which may be installed, such as Perlsecret, etc.
196             '-exclude' => ['RequireTidyCode', 'PodSpelling', 'RequireExplicitPackage', 'RequirePod', 'ProhibitBitwiseOperators'],
197             '-severity' => 'brutal',
198             '-theme' => 'core'
199             );
200             my @rperl_source__critic_violations
201             = $rperl_source__critic->critique($rperl_source__file_name);
202              
203             my integer $rperl_source__critic_num_violations
204             = scalar @rperl_source__critic_violations;
205              
206             #RPerl::diag("in rperl_source__criticize(), have \$rperl_source__critic_num_violations = $rperl_source__critic_num_violations\n");
207             # my string $rperl_source__critic_dumperified_violations = Dumper( \@rperl_source__critic_violations );
208             #RPerl::diag("in rperl_source__criticize(), have Dumper(\\\@rperl_source__critic_violations) =\n" . $rperl_source__critic_dumperified_violations . "\n");
209              
210             # 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?
211              
212             if ( $rperl_source__critic_num_violations > 0 ) {
213             my string $violation_pretty = q{};
214             foreach my object $violation (@rperl_source__critic_violations) {
215             $violation_pretty .= ' File Name: ' . $rperl_source__file_name . "\n";
216             $violation_pretty .= ' Line number: ' . $violation->{_location}->[0] . "\n";
217             $violation_pretty .= ' Policy: ' . $violation->{_policy} . "\n";
218             $violation_pretty .= ' Description: ' . $violation->{_description} . "\n";
219             if ( ref( $violation->{_explanation} ) eq 'ARRAY' ) {
220             $violation_pretty .= ' Explanation: See Perl Best Practices page(s) ' . join( ', ', @{ $violation->{_explanation} } ) . "\n\n";
221             }
222             else {
223             $violation_pretty .= ' Explanation: ' . $violation->{_explanation} . "\n\n";
224             }
225             }
226             die "\n"
227             . 'ERROR ECOPAPC02, RPERL PARSER, PERL CRITIC VIOLATION'
228             . "\n"
229             . 'Failed Perl::Critic brutal review with the following information:'
230             . "\n\n"
231             . $violation_pretty;
232             }
233             else {
234             RPerl::verbose(' done.' . "\n");
235             }
236             };
237              
238             # Die On RPerl Grammar Error
239             our void $rperl_grammar_error = sub {
240             ( my array $argument ) = @_;
241              
242             my string $value = $argument->YYCurval;
243             if ( not( defined $value ) ) {
244             $value = '<<< NO TOKEN FOUND >>>';
245             }
246             my string $helpful_hint = q{};
247             if ( $value =~ /\d/xms ) {
248             $helpful_hint
249             = q{ Helpful Hint: Possible case of PBP RequireNumberSeparators (see below)} . "\n"
250             . q{ Policy: Perl::Critic::Policy::ValuesAndExpressions::RequireNumberSeparators} . "\n"
251             . q{ Description: Long number not separated with underscores} . "\n"
252             . q{ Explanation: See Perl Best Practices page(s) 59} . "\n";
253             }
254              
255             my integer $line_number = $argument->{TOKENLINE};
256             my string $rperl_source__file_name = $argument->{rperl_source__file_name};
257              
258             # die( "\n" . 'ERROR ECOPARP00, RPERL PARSER, SYNTAX ERROR; have $argument =' . "\n" . Dumper($argument) . "\n" );
259             # die( "\n" . 'ERROR ECOPARP00, RPERL PARSER, SYNTAX ERROR; have $argument->{rperl_source__file_name} = ' . $argument->{rperl_source__file_name} . "\n" );
260              
261             my $current_state_num = $argument->{STACK}[-1][0];
262             my $current_state = $argument->{STATES}[$current_state_num];
263             my $expected_tokens = q{};
264             my number $is_first_expected = 1;
265              
266             foreach my $expected_token ( sort keys %{ $current_state->{ACTIONS} } ) {
267             if ($is_first_expected) {
268             $is_first_expected = 0;
269             $expected_tokens .= $expected_token . "\n";
270             }
271             else {
272             $expected_tokens
273             .= q{ } . $expected_token . "\n";
274             }
275             }
276              
277             die "\n"
278             . 'ERROR ECOPARP00, RPERL PARSER, RPERL SYNTAX ERROR' . "\n"
279             . 'Failed RPerl grammar syntax check with the following information:'
280             . "\n\n"
281             . ' File Name: ' . $rperl_source__file_name . "\n"
282             . ' Line Number: ' . $line_number . "\n"
283             . ' Unexpected Token: ' . $value . "\n"
284             . ' Expected Token(s): ' . $expected_tokens
285             . $helpful_hint . "\n";
286             };
287              
288             # Parse RPerl Syntax Using Eyapp Grammar
289             our void $rperl_source__parse = sub {
290             ( my string $rperl_source__file_name) = @_;
291              
292             RPerl::verbose('PARSE PHASE 2: Parse RPerl syntax... ');
293              
294             my object $eyapp_parser = RPerl::Grammar->new();
295             $eyapp_parser->{rperl_source__file_name} = $rperl_source__file_name;
296             $eyapp_parser->YYSlurpFile($rperl_source__file_name);
297             my object $rperl_ast = $eyapp_parser->YYParse(
298             yydebug => 0x00, # disable eyapp DBG DEBUGGING
299              
300             # yydebug => 0xFF, # full eyapp DBG DEBUGGING, USE FOR DEBUGGING GRAMMAR
301             yyerror => $rperl_grammar_error
302             );
303              
304             RPerl::verbose(' done.' . "\n");
305              
306             # RPerl::diag("in rperl_source__parse(), have \$rperl_ast->str() =\n" . $rperl_ast->str() . "\n\n");
307             # RPerl::diag("in rperl_source__parse(), have \$rperl_ast =\n" . rperl_ast__dump($rperl_ast) . "\n\n");
308             # die 'TMP DEBUG';
309              
310             return ($rperl_ast);
311             };
312              
313             # condense AST dump, replace all instances of RPerl rule(s) with more meaningful RPerl class(es)
314             our string $rperl_ast__dump = sub {
315             ( my object $rperl_ast) = @_;
316             $Data::Dumper::Indent = 1; # do not attempt to align hash values based on hash key length
317             my string $rperl_ast_dumped = Dumper($rperl_ast);
318             $Data::Dumper::Indent = 2; # restore default
319              
320             # $rperl_ast_dumped =~ s/\ \ /\ \ \ \ /gxms; # set tabs from 2 to 4 spaces
321             $rperl_ast_dumped =~ s/[ ]{2}/ /gxms; # set tabs from 2 to 4 spaces
322             my string $replacee;
323             my string $replacer;
324             foreach my string $rule ( sort keys %{$RPerl::Grammar::RULES} ) {
325             $replacee = q{'} . $rule . q{'};
326             $replacer
327             = q{'} . $rule . ' ISA ' . $RPerl::Grammar::RULES->{$rule} . q{'};
328             $rperl_ast_dumped =~ s/$replacee/$replacer/gxms;
329             }
330             return $rperl_ast_dumped;
331             };
332              
333             # replace all instances of RPerl rule(s) with more meaningful RPerl class(es)
334             our string $rperl_rule__replace = sub {
335             ( my string $rperl_rule_string) = @_;
336             my string $replacer;
337             foreach my string $rule ( sort keys %{$RPerl::Grammar::RULES} ) {
338             if ( $RPerl::Grammar::RULES->{$rule} ne 'RPerl::NonGenerator' ) {
339             $replacer
340             = q{(}
341             . $rule . ' ISA '
342             . $RPerl::Grammar::RULES->{$rule} . q{)};
343             $replacer =~ s/RPerl:://gxms;
344             $rperl_rule_string =~ s/$rule/$replacer/gxms;
345             }
346             }
347             return $rperl_rule_string;
348             };
349              
350             1; # end of class