File Coverage

blib/lib/MarpaX/Languages/Lua/Parser.pm
Criterion Covered Total %
statement 109 134 81.3
branch 20 34 58.8
condition 4 8 50.0
subroutine 17 17 100.0
pod 2 5 40.0
total 152 198 76.7


line stmt bran cond sub pod time code
1             package MarpaX::Languages::Lua::Parser;
2              
3 2     2   82578 use strict;
  2         4  
  2         67  
4 2     2   7 use warnings;
  2         2  
  2         53  
5 2     2   7 use warnings qw(FATAL utf8); # Fatalize encoding glitches.
  2         6  
  2         79  
6 2     2   910 use open qw(:std :utf8); # Undeclared streams in UTF-8.
  2         1960  
  2         10  
7              
8 2     2   1161 use Data::RenderAsTree;
  2         235275  
  2         111  
9              
10 2     2   1292 use Data::Section::Simple 'get_data_section';
  2         1016  
  2         128  
11              
12 2     2   1626 use Log::Handler;
  2         69946  
  2         14  
13              
14 2     2   1294 use Marpa::R2;
  2         244443  
  2         31  
15              
16 2     2   87 use Moo;
  2         4  
  2         18  
17              
18 2     2   1293 use Path::Tiny; # For path().
  2         10388  
  2         125  
19              
20 2     2   11 use Types::Standard qw/Any ArrayRef HashRef Bool Str/;
  2         3  
  2         36  
21              
22             has attributes =>
23             (
24             default => sub{return 0},
25             is => 'rw',
26             isa => Bool,
27             required => 0,
28             );
29              
30             has grammar =>
31             (
32             default => sub{return ''},
33             is => 'rw',
34             isa => Any,
35             required => 0,
36             );
37              
38             has input_file_name =>
39             (
40             default => sub{return ''},
41             is => 'rw',
42             isa => Str,
43             required => 1,
44             );
45              
46             has input_text =>
47             (
48             default => sub{return []},
49             is => 'rw',
50             isa => ArrayRef,
51             required => 1,
52             );
53              
54             has keywords =>
55             (
56             default => sub
57             {
58             return
59             {
60             map { $_ => 1 }
61             qw
62             {
63             and break do else elseif
64             end false for function if
65             in local nil not or
66             repeat return then true until while
67             }
68             }
69             },
70             is => 'ro',
71             isa => HashRef,
72             required => 0,
73             );
74              
75             has logger =>
76             (
77             default => sub{return undef},
78             is => 'rw',
79             isa => Any,
80             required => 0,
81             );
82              
83             has maxlevel =>
84             (
85             default => sub{return 'notice'},
86             is => 'rw',
87             isa => Str,
88             required => 0,
89             );
90              
91             has minlevel =>
92             (
93             default => sub{return 'error'},
94             is => 'rw',
95             isa => Str,
96             required => 0,
97             );
98              
99             has output_file_name =>
100             (
101             default => sub{return ''},
102             is => 'rw',
103             isa => Str,
104             required => 1,
105             );
106              
107             has output_tokens =>
108             (
109             default => sub{return []},
110             is => 'rw',
111             isa => ArrayRef,
112             required => 1,
113             );
114              
115             has recce =>
116             (
117             default => sub{return ''},
118             is => 'rw',
119             isa => Any,
120             required => 0,
121             );
122              
123             has renderer =>
124             (
125             default => sub{return ''},
126             is => 'rw',
127             isa => Any,
128             required => 0,
129             );
130              
131             has value =>
132             (
133             default => sub{return ''},
134             is => 'rw',
135             isa => Any,
136             required => 0,
137             );
138              
139             our $VERSION = '1.01';
140              
141             # ------------------------------------------------
142              
143             sub BUILD
144             {
145 21     21 0 605 my($self) = @_;
146              
147 21 50       397 if (! defined $self -> logger)
148             {
149 0         0 $self -> logger(Log::Handler -> new);
150 0         0 $self -> logger -> add
151             (
152             screen =>
153             {
154             maxlevel => $self -> maxlevel,
155             message_layout => '%m',
156             minlevel => $self -> minlevel,
157             utf8 => 1,
158             }
159             );
160             }
161              
162 21         1553 $self -> input_text([path($self -> input_file_name) -> lines_utf8]);
163 21         35523 $self -> grammar
164             (
165             Marpa::R2::Scanless::G -> new({source => \get_data_section('Lua.bnf')})
166             );
167 21         4123042 $self -> recce
168             (
169             Marpa::R2::Scanless::R -> new
170             ({
171             grammar => $self -> grammar,
172             })
173             );
174 21         22833 $self -> renderer
175             (
176             Data::RenderAsTree -> new(clean_nodes => 1)
177             );
178              
179             } # End of BUILD.
180              
181             # --------------------------------------------------
182              
183             sub log
184             {
185 25030     25030 1 1644873 my($self, $level, $s) = @_;
186 25030 50       35524 $level = 'notice' if (! defined $level);
187 25030 50       31550 $s = '' if (! defined $s);
188              
189 25030 50       398526 $self -> logger -> $level($s) if ($self -> logger);
190              
191             } # End of log.
192              
193             # ------------------------------------------------
194              
195             sub process
196             {
197 21     21 0 42 my($self) = @_;
198 21         76 my($input) = join('', @{$self -> input_text});
  21         505  
199 21         360 my($input_ref) = \$input;
200 21         124 my($input_length) = length $input;
201 21         413 my($pos) = $self -> recce -> read($input_ref);
202              
203 21         14596 READ: while (1)
204             {
205 1374         34854 EVENT:
206 1374         361962 for my $event (@{$self -> recce -> events})
207             {
208 1355         11824 my($name) = @{$event};
  1355         2152  
209              
210 1355 50       3373 if ($name eq 'multiline string' )
211             {
212 0         0 my($start, $length) = $self -> recce -> pause_span;
213 0         0 my($string_terminator) = $self -> recce -> literal($start, $length);
214 0         0 $string_terminator =~ tr/\[/\]/;
215 0         0 my($terminator_pos) = index($$input_ref, $string_terminator, $start);
216              
217 0 0       0 die "Died looking for $string_terminator. \n" if ($terminator_pos < 0);
218              
219             # The string terminator has the same length as the start of string marker.
220              
221 0         0 my($string_length) = $terminator_pos + $length - $start;
222              
223 0         0 $self -> recce -> lexeme_read('multiline string', $start, $string_length);
224              
225 0         0 $pos = $terminator_pos + $length;
226              
227 0         0 next EVENT;
228             }
229              
230 1355 50       2258 if ($name eq 'multiline comment')
231             {
232             # This is a discard event.
233              
234 0         0 my(undef, $start, $end) = @{$event};
  0         0  
235 0         0 my($length) = $end - $start;
236 0         0 my($comment_terminator) = $self -> recce -> literal($start, $length);
237 0         0 $comment_terminator =~ tr/-//;
238 0         0 $comment_terminator =~ tr/\[/\]/;
239 0         0 my($terminator_pos) = index( $$input_ref, $comment_terminator, $start);
240              
241 0 0       0 die "Died looking for $comment_terminator. \n" if ($terminator_pos < 0);
242              
243             # Don't read anything into G1 -- just throw the comment away.
244              
245 0         0 $pos = $terminator_pos + $length;
246              
247 0         0 next EVENT;
248             }
249              
250 1355 100       2573 if ($name eq 'singleline comment')
251             {
252             # This is a discard event.
253              
254 84         75 my(undef, $start, $end) = @{$event};
  84         193  
255 84         192 my($length) = $end-$start;
256 84         417 pos($$input_ref) = $end - 1;
257 84         585 $$input_ref =~ /[\r\n]/gxms;
258 84         217 my($new_pos) = pos($$input_ref);
259              
260 84 50       254 die "Died looking for singleline comment terminator. \n" if (! defined $new_pos);
261              
262 84         108 $pos = $new_pos;
263              
264 84         252 next EVENT;
265             }
266              
267 1271 50       2580 if ($name eq 'Name')
268             {
269             # This is an event to check if a keyword is used as an identifier
270             # and die if it is.
271              
272 1271         23542 my($start, $length) = $self -> recce -> pause_span;
273 1271         32030 my($line, $column) = $self -> recce -> line_column($start);
274 1271         31814 my($literal) = $self -> recce -> literal($start, $length);
275              
276 1271 100       14643 if ( exists $self -> keywords -> { $literal } )
277             {
278 296   100     5846 $self -> recce -> lexeme_read(qq{keyword $literal}, $start, $length)
279             // die $self->input_file_name . qq{:$line:$column: keyword '$literal' used as \n};
280             }
281             else
282             {
283 975         17145 $self -> recce -> lexeme_read('Name', $start, $length);
284             }
285              
286 1270         92991 $pos = $self -> recce -> pos();
287              
288 1270         13760 next EVENT;
289             }
290              
291 0         0 die "Unexpected event '$name'\n";
292              
293             }
294              
295 1373 100       4216 last READ if ($pos >= $input_length);
296              
297 1353         24805 $pos = $self -> recce -> resume($pos);
298             }
299              
300             # Warning: Don't use if (my($ambiguous_status) = $self -> recce -> ambiguous),
301             # since then the 'if' always returns true.
302              
303 20 50       331 if (my $ambiguous_status = $self -> recce -> ambiguous)
304             {
305 0         0 die "The Lua source is ambiguous: $ambiguous_status. \n";
306             }
307              
308 20         9918 return $self -> recce -> value;
309              
310             } # End of process.
311              
312             # --------------------------------------------------
313              
314             sub render
315             {
316 20     20 0 42 my($self) = @_;
317 20         51 my($slim_list) = [];
318              
319 20         28 my($attributes);
320             my($name);
321 0         0 my($s);
322 0         0 my($type);
323              
324             $self -> renderer -> root -> walk_down
325             ({
326             callback => sub
327             {
328 15782     15782   293941 my($node, $opt) = @_;
329              
330             # Ignore the root, and keep walking.
331              
332 15782 100       28872 return 1 if ($node -> is_root);
333              
334 15762         109086 $name = $node -> name;
335 15762         112100 $name =~ s/^\s*\d+\s=\s(.+)/$1/;
336 15762         46401 $name =~ s/\s\[[A-Z]+\s\d+\]//;
337 15762         28486 $attributes = $node -> attributes;
338 15762         64948 $type = $$attributes{type};
339              
340 15762 100       27754 if ($type eq 'SCALAR')
341             {
342 9248         12385 push @$slim_list, $name;
343              
344 9248         24042 $self -> log(info => ' ' x $$opt{_depth} . $name);
345             }
346              
347 15762         65868 return 1; # Keep walking.
348             },
349 20         523 _depth => 0,
350             });
351              
352 20         986 $self -> output_tokens($slim_list);
353              
354             } # End of render.
355              
356             # ------------------------------------------------
357              
358             sub run
359             {
360 21     21 1 23706 my($self, %args) = @_;
361 21   33     494 my($file_name) = $args{input_file_name} || $self -> input_file_name;
362              
363 21         256 $self -> value($self -> process($file_name) );
364 20         447062 $self -> renderer -> run(${$self -> value});
  20         466  
365 20         9035922 $self -> log(debug => $_) for @{$self -> renderer -> root -> tree2string({no_attributes => 1 - $self -> attributes})};
  20         447  
366 20         2385 $self -> render;
367              
368 20   33     1664 my($output_file_name) = $args{output_file_name} || $self -> output_file_name;
369              
370 20 50       743 if ($output_file_name)
371             {
372 20         112 path($output_file_name) -> spew_utf8(map{"$_\n"} @{$self -> output_tokens});
  9248         13689  
  20         1206  
373             }
374              
375             # Return 0 for success and 1 for failure.
376              
377 20         226390 return 0;
378              
379             } # End of run.
380              
381             #-------------------------------------------------
382              
383             1;
384              
385             =pod
386              
387             =head1 NAME
388              
389             C - A Lua source code parser
390              
391             =head1 Synopsis
392              
393             #!/usr/bin/env perl
394              
395             use strict;
396             use warnings;
397              
398             use MarpaX::Languages::Lua::Parser;
399              
400             # ---------------------------------
401              
402             my($input_file_name) = shift || die "Usage: $0 a_lua_source_file_name\n";
403             my($parser) = MarpaX::Languages::Lua::Parser -> new(input_file_name => $input_file_name);
404              
405             $parser -> run;
406              
407             print map{"$_\n"} @{$parser -> output_tokens};
408              
409             This script ships as scripts/synopsis.pl. Run it as:
410              
411             shell> perl -Ilib scripts/synopsis.pl lua.sources/echo.lua
412              
413             See also scripts/parse.file.pl for code which takes command line parameters. For help, run:
414              
415             shell> perl -Ilib scripts/parse.file.pl -h
416              
417             =head1 Description
418              
419             C parses Lua source code files.
420              
421             The result is stored in a tree managed by L.
422              
423             A list of scalar tokens from this tree is stored in an arrayref.
424              
425             See the FAQ question L for details.
426              
427             =head1 Installation
428              
429             Install C as you would for any C module:
430              
431             Run:
432              
433             cpanm MarpaX::Languages::Lua::Parser
434              
435             or run:
436              
437             sudo cpan MarpaX::Languages::Lua::Parser
438              
439             or unpack the distro, and then:
440              
441             perl Makefile.PL
442             make (or dmake or nmake)
443             make test
444             make install
445              
446             =head1 Constructor and Initialization
447              
448             C is called as C<< my($parser) = MarpaX::Languages::Lua::Parser -> new(k1 => v1, k2 => v2, ...) >>.
449              
450             It returns a new object of type C.
451              
452             Key-value pairs accepted in the parameter list (see also the corresponding methods
453             [e.g. L]):
454              
455             =over 4
456              
457             =item o attributes => $Boolean
458              
459             When set to 1, metadata attached to each tree node is included in the output.
460              
461             If you set the L to 'debug', this tree is printed to the log.
462              
463             Default: 0.
464              
465             =item o input_file_name => $string
466              
467             The name the input file to be parsed.
468              
469             This option is mandatory.
470              
471             Default: ''.
472              
473             =item o logger => aLog::HandlerObject
474              
475             By default, an object of type L is created which prints to STDOUT,
476             but given the default setting (maxlevel => 'notice'), nothing is actually printed.
477              
478             See C and C below.
479              
480             Set C to '' (the empty string) to stop a logger being created.
481              
482             Default: undef.
483              
484             =item o maxlevel => logOption1
485              
486             This option affects L objects.
487              
488             See the L docs.
489              
490             Typical values: 'info', 'debug'.
491              
492             See the FAQ question L for details.
493              
494             See also the help output by scripts/parse.file.pl -h.
495              
496             Default: 'notice'.
497              
498             =item o minlevel => logOption2
499              
500             This option affects L object.
501              
502             See the L docs.
503              
504             Default: 'error'.
505              
506             No lower levels are used.
507              
508             =item o output_file_name => $string
509              
510             The name of the text file to be written.
511              
512             If not set, nothing is written.
513              
514             The items written, one per line, are as returned by L.
515              
516             Default: ''.
517              
518             =back
519              
520             =head1 Methods
521              
522             =head2 attributes([$Boolean])
523              
524             Here, the [] indicate an optional parameter.
525              
526             Gets or sets the attributes option.
527              
528             Note: The value passed to L's C method is (1 - $Boolean).
529              
530             See the FAQ question L for details.
531              
532             C is a parameter to L.
533              
534             =head2 input_file_name([$string])
535              
536             Here, the [] indicate an optional parameter.
537              
538             Get or set the name of the file to parse.
539              
540             See lua.sources/*.lua for sample input.
541              
542             Note: C is a parameter to new().
543              
544             =head2 log($level, $s)
545              
546             Calls $self -> logger -> log($level => $s) if ($self -> logger).
547              
548             =head2 logger([$log_object])
549              
550             Here, the [] indicate an optional parameter.
551              
552             Get or set the log object.
553              
554             C<$log_object> must be a L-compatible object.
555              
556             To disable logging, just set logger to the empty string.
557              
558             Note: C is a parameter to new().
559              
560             =head2 maxlevel([$string])
561              
562             Here, the [] indicate an optional parameter.
563              
564             Get or set the value used by the logger object.
565              
566             This option is only used if an object of type L is created. See L.
567              
568             Typical values: 'info', 'debug'.
569              
570             See the FAQ question L for details.
571              
572             Note: C is a parameter to new().
573              
574             =head2 minlevel([$string])
575              
576             Here, the [] indicate an optional parameter.
577              
578             Get or set the value used by the logger object.
579              
580             This option is only used if an object of type L is created. See L.
581              
582             Note: C is a parameter to new().
583              
584             =head2 new()
585              
586             This method is auto-generated by L.
587              
588             =head2 output_file_name([$string])
589              
590             Here, the [] indicate an optional parameter.
591              
592             Get or set the name of the file to write.
593              
594             The tokens written are as returned from L.
595              
596             Note: C is a parameter to new().
597              
598             =head2 output_tokens()
599              
600             Returns an arrayref of tokens output by the parse, one per line. These tokens are pushed onto the
601             stack by walking the tree returned by the renderer, which is an object of type
602             L. The renderer is run by passing it the output from the call to Marpa's
603             C method. See L.
604              
605             If you set the L to 'info', these tokens are printed to the log.
606              
607             See scripts/synopsis.pl for accessing this arrayref.
608              
609             See lua.output/*.txt for sample output.
610              
611             =head2 renderer()
612              
613             Returns the object of type L, which takes the output from the call to Marpa's
614             C method and converts it into an object of type L.
615              
616             If you set the L to 'debug', this tree is printed to the log.
617              
618             =head2 run([%args])
619              
620             The method which does all the work.
621              
622             C<%args> is a hash with this optional (key => value) pair:
623              
624             =over 4
625              
626             =item o input_file_name => $in_file_name
627              
628             =item o output_file_name => $out_file_name
629              
630             =back
631              
632             File names specified in the call to C take precedence over file names specified to L.
633              
634             Returns 0 for a successful parse and 1 for failure.
635              
636             The code dies if L itself can't parse the given input file.
637              
638             Note: C and C are parameters to L.
639              
640             =head1 FAQ
641              
642             =head2 Why did you store Lua's BNF in a __DATA__ section?
643              
644             This avoids problems with single- and double-quotes in the BNF, and the allegedly unknown escape
645             sequences \v etc too.
646              
647             =head2 How do I get output from this module?
648              
649             In various ways:
650              
651             =over 4
652              
653             =item o Call the L method
654              
655             Then, process the arrayref returned.
656              
657             =item o Call the L method
658              
659             This will return an object of type L, and from there you can call that object's
660             C method, to get access to the tree itself. See this module's C method for sample
661             code.
662              
663             =item o Set maxlevel to 'info'.
664              
665             This writes the output tokens to the log, one per line.
666              
667             See the C method for sample code.
668              
669             =item o Set maxlevel to 'debug'.
670              
671             This writes the output tokens to the log, one per line, and also writes to the log the tree
672             returned by passing the return value of Marpa's C method to the renderer. The renderer
673             is an object of type L, and outputs a tree managed by L.
674              
675             See the L method for sample code.
676              
677             =item o Set the output_file_name to a non-empty string
678              
679             In this case the code will walk the tree just mentioned, and output the scalar items, one per line,
680             to this file.
681              
682             =item o All of the above
683              
684             =back
685              
686             =head2 How do I interpret the output?
687              
688             For help with this, try the IRC channel irc.freenode.net#marpa.
689              
690             What that really means is that neither Jeffrey no anyone else imposes any kind of restriction on
691             what you may do with the output, or with how you may interpret it.
692              
693             =head2 Where is Marpa's home page?
694              
695             L.
696              
697             =head1 Machine-Readable Change Log
698              
699             The file Changes was converted into Changelog.ini by L.
700              
701             =head1 Version Numbers
702              
703             Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.
704              
705             =head1 Repository
706              
707             L
708              
709             =head1 Support
710              
711             Email the author, or log a bug on RT:
712              
713             L.
714              
715             =head1 Credits
716              
717             Jeffrey Kegler wrote the code, and posted a link on the IRC chat channel mentioned above.
718              
719             See L.
720              
721             =head1 Author
722              
723             L was packaged by Ron Savage Iron@savage.net.auE> in 2015.
724              
725             Homepage: L.
726              
727             =head1 Copyright
728              
729             Australian copyright (c) 2015, Ron Savage.
730              
731             All Programs of mine are 'OSI Certified Open Source Software';
732             you can redistribute them and/or modify them under the terms of
733             The Artistic License 2.0, a copy of which is available at:
734             http://www.opensource.org/licenses/index.html
735              
736             =cut
737              
738             __DATA__