File Coverage

blib/lib/MarpaX/Grammar/Parser.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package MarpaX::Grammar::Parser;
2              
3 1     1   75117 use strict;
  1         3  
  1         28  
4 1     1   1238 use utf8;
  1         12  
  1         7  
5 1     1   39 use warnings;
  1         7  
  1         47  
6 1     1   6 use warnings qw(FATAL utf8); # Fatalize encoding glitches.
  1         3  
  1         47  
7 1     1   911 use open qw(:std :utf8); # Undeclared streams in UTF-8.
  1         1523  
  1         6  
8 1     1   2282 use charnames qw(:full :short); # Unneeded in v5.16.
  1         45636  
  1         8  
9              
10 1     1   1727 use Data::TreeDumper (); # For DumpTree().
  0            
  0            
11             use Data::TreeDumper::Renderer::Marpa; # Used by DumpTree().
12              
13             use File::Slurp; # For read_file().
14              
15             use Log::Handler;
16              
17             use Marpa::R2;
18              
19             use Moo;
20              
21             use Tree::DAG_Node;
22              
23             has bind_attributes =>
24             (
25             default => sub{return 0},
26             is => 'rw',
27             #isa => 'Bool',
28             required => 0,
29             );
30              
31             has cooked_tree =>
32             (
33             default => sub{return ''},
34             is => 'rw',
35             #isa => 'Tree::DAG_Node',
36             required => 0,
37             );
38              
39             has cooked_tree_file =>
40             (
41             default => sub{return ''},
42             is => 'rw',
43             #isa => 'Str',
44             required => 0,
45             );
46              
47             has logger =>
48             (
49             default => sub{return undef},
50             is => 'rw',
51             # isa => 'Str',
52             required => 0,
53             );
54              
55             has marpa_bnf_file =>
56             (
57             default => sub{return ''},
58             is => 'rw',
59             #isa => 'Str',
60             required => 0,
61             );
62              
63             has maxlevel =>
64             (
65             default => sub{return 'info'},
66             is => 'rw',
67             # isa => 'Str',
68             required => 0,
69             );
70              
71             has minlevel =>
72             (
73             default => sub{return 'error'},
74             is => 'rw',
75             # isa => 'Str',
76             required => 0,
77             );
78              
79             has raw_tree =>
80             (
81             default => sub{return ''},
82             is => 'rw',
83             #isa => 'Tree::DAG_Node',
84             required => 0,
85             );
86              
87             has raw_tree_file =>
88             (
89             default => sub{return ''},
90             is => 'rw',
91             #isa => 'Str',
92             required => 0,
93             );
94              
95             has user_bnf_file =>
96             (
97             default => sub{return ''},
98             is => 'rw',
99             #isa => 'Str',
100             required => 0,
101             );
102              
103             # Warning: There's another $VERSION in package MarpaX::Grammar::Parser::Dummy below.
104              
105             our $VERSION = '1.03';
106              
107             # ------------------------------------------------
108              
109             sub BUILD
110             {
111             my($self) = @_;
112              
113             die "No Marpa SLIF-DSL file found\n" if (! -e $self -> marpa_bnf_file);
114             die "No user SLIF-DSL file found\n" if (! -e $self -> user_bnf_file);
115              
116             if (! defined $self -> logger)
117             {
118             $self -> logger(Log::Handler -> new);
119             $self -> logger -> add
120             (
121             screen =>
122             {
123             maxlevel => $self -> maxlevel,
124             message_layout => '%m',
125             minlevel => $self -> minlevel,
126             }
127             );
128             }
129              
130             $self -> cooked_tree
131             (
132             Tree::DAG_Node -> new
133             ({
134             name => 'statements',
135             })
136             );
137              
138             $self -> raw_tree
139             (
140             Tree::DAG_Node -> new
141             ({
142             attributes => {type => 'Marpa'},
143             name => 'statements',
144             })
145             );
146              
147             } # End of BUILD.
148              
149             # --------------------------------------------------
150              
151             sub clean_name
152             {
153             my($self, $name) = @_;
154             my($attributes) = {bracketed_name => 0, quantifier => '', real_name => $name};
155              
156             # Expected cases:
157             # o {bare_name => $name}.
158             # o {bracketed_name => $name}.
159             # o $name.
160             #
161             # Quantified names are handled in sub compress_branch.
162              
163             if (ref $name eq 'HASH')
164             {
165             if (defined $$name{bare_name})
166             {
167             $$attributes{real_name} = $name = $$name{bare_name};
168             }
169             else
170             {
171             $$attributes{real_name} = $name = $$name{bracketed_name};
172             $$attributes{bracketed_name} = 1;
173             $name =~ s/^
174             $name =~ s/>$//;
175             }
176             }
177              
178             return ($name, $attributes);
179              
180             } # End of clean_name.
181              
182             # --------------------------------------------------
183              
184             sub compress_branch
185             {
186             my($self, $index, $a_node) = @_;
187              
188             my($name);
189             my($token);
190              
191             $a_node -> walk_down
192             ({
193             callback => sub
194             {
195             my($node, $option) = @_;
196             $name = $node -> name;
197              
198             if ($name eq 'default_rule')
199             {
200             $token = $self -> _process_default_rule($index, $node);
201             }
202             elsif ($name eq 'discard_rule')
203             {
204             $token = $self -> _process_discard_rule($index, $node);
205             }
206             elsif ($name eq 'empty_rule')
207             {
208             $token = $self -> _process_empty_rule($index, $node);
209             }
210             elsif ($name =~ /(.+)_event_declaration$/)
211             {
212             $token = $self -> _process_event_declaration($index, $node, $1);
213             }
214             elsif ($name eq 'lexeme_default_statement')
215             {
216             $token = $self -> _process_lexeme_default($index, $node);
217             }
218             elsif ($name eq 'lexeme_rule')
219             {
220             $token = $self -> _process_lexeme_rule($index, $node);
221             }
222             elsif ($name eq 'priority_rule')
223             {
224             $token = $self -> _process_priority_rule($index, $node);
225             }
226             elsif ($name eq 'quantified_rule')
227             {
228             $token = $self -> _process_quantified_rule($index, $node);
229             }
230             elsif ($name eq 'start_rule')
231             {
232             $token = $self -> _process_start_rule($index, $node);
233             }
234              
235             return 1; # Keep walking.
236             },
237             _depth => 0,
238             });
239              
240             my($attributes);
241              
242             ($name, $attributes) = $self -> clean_name(shift @$token);
243             my($node) = Tree::DAG_Node -> new
244             ({
245             attributes => $attributes,
246             name => $name,
247             });
248              
249             $self -> cooked_tree -> add_daughter($node);
250              
251             for (my $i = 0; $i <= $#$token; $i++)
252             {
253             $name = $$token[$i];
254             ($name, $attributes) = $self -> clean_name($name);
255              
256             # Special case handling: Quantitied rules.
257              
258             if ( ($i < $#$token) && (ref $$token[$i + 1] eq 'HASH') && ($$token[$i + 1]{quantifier}) )
259             {
260             $i++;
261              
262             $$attributes{quantifier} = $$token[$i]{quantifier};
263             }
264              
265             $node -> add_daughter
266             (
267             Tree::DAG_Node -> new
268             ({
269             attributes => $attributes,
270             name => $name,
271             })
272             );
273             }
274              
275             } # End of compress_branch.
276              
277             # --------------------------------------------------
278              
279             sub compress_tree
280             {
281             my($self) = @_;
282              
283             # Phase 1: Process the children of the root:
284             # o First daughter is offset of start within input stream.
285             # o Second daughter is offset of end within input stream.
286             # o Remainder are statements.
287              
288             my(@daughters) = $self -> raw_tree -> daughters;
289             my($start) = (shift @daughters) -> name;
290             my($end) = (shift @daughters) -> name;
291              
292             # Phase 2: Process each statement.
293              
294             for my $index (0 .. $#daughters)
295             {
296             $self -> compress_branch($index + 1, $daughters[$index]);
297             }
298              
299             } # End of compress_tree.
300              
301             # --------------------------------------------------
302              
303             sub log
304             {
305             my($self, $level, $s) = @_;
306              
307             $self -> logger -> log($level => $s) if ($self -> logger);
308              
309             } # End of log.
310              
311             # --------------------------------------------------
312              
313             sub _process_default_rule
314             {
315             my($self, $index, $a_node) = @_;
316             my(%map) =
317             (
318             action => 'action',
319             blessing => 'bless',
320             );
321              
322             my($name);
323             my(@token);
324              
325             $a_node -> walk_down
326             ({
327             callback => sub
328             {
329             my($node, $option) = @_;
330             $name = $node -> name;
331              
332             # Skip the first 2 daughters, which hold offsets for the
333             # start and end of the token within the input stream.
334              
335             return 1 if ($node -> my_daughter_index < 2);
336              
337             if ($node -> mother -> name =~ /op_declare_+/)
338             {
339             push @token, ':default', $name;
340             }
341             elsif ($node -> mother -> mother -> name =~ /(action|blessing)_name/)
342             {
343             push @token, $map{$1}, '=>', $name;
344             }
345              
346             return 1; # Keep walking.
347             },
348             _depth => 0,
349             });
350              
351             return [@token];
352              
353             } # End of _process_default_rule.
354              
355             # --------------------------------------------------
356              
357             sub _process_discard_rule
358             {
359             my($self, $index, $a_node) = @_;
360              
361             my($name);
362             my(@token);
363              
364             $a_node -> walk_down
365             ({
366             callback => sub
367             {
368             my($node, $option) = @_;
369             $name = $node -> name;
370              
371             # Skip the first 2 daughters, which hold offsets for the
372             # start and end of the token within the input stream.
373              
374             return 1 if ($node -> my_daughter_index < 2);
375              
376             if ($node -> mother -> mother -> name eq 'symbol_name')
377             {
378             push @token, ':discard', '=>', {$node -> mother -> name => $name};
379             }
380              
381             return 1; # Keep walking.
382             },
383             _depth => 0,
384             });
385              
386             return [@token];
387              
388             } # End of _process_discard_rule.
389              
390             # --------------------------------------------------
391              
392             sub _process_empty_rule
393             {
394             my($self, $index, $a_node) = @_;
395              
396             my($name);
397             my(@token);
398              
399             $a_node -> walk_down
400             ({
401             callback => sub
402             {
403             my($node, $option) = @_;
404             $name = $node -> name;
405              
406             # Skip the first 2 daughters, which hold offsets for the
407             # start and end of the token within the input stream.
408              
409             return 1 if ($node -> my_daughter_index < 2);
410              
411             if ($node -> mother -> name =~ /op_declare_+/)
412             {
413             push @token, $name;
414             }
415             elsif ($node -> mother -> mother -> name eq 'symbol_name')
416             {
417             push @token, {$node -> mother -> name => $name};
418             }
419              
420             return 1; # Keep walking.
421             },
422             _depth => 0,
423             });
424              
425             return [@token];
426              
427             } # End of _process_empty_rule.
428              
429             # --------------------------------------------------
430              
431             sub _process_event_declaration
432             {
433             my($self, $index, $a_node, $type) = @_;
434             my(%type) =
435             (
436             completion => 'completed',
437             nulled => 'nulled',
438             prediction => 'predicted',
439             );
440              
441             my($name);
442             my(@token);
443              
444             $a_node -> walk_down
445             ({
446             callback => sub
447             {
448             my($node, $option) = @_;
449             $name = $node -> name;
450              
451             # Skip the first 2 daughters, which hold offsets for the
452             # start and end of the token within the input stream.
453              
454             return 1 if ($node -> my_daughter_index < 2);
455              
456             if ($node -> mother -> mother -> name eq 'event_name')
457             {
458             push @token, 'event', $name, '=', $type{$type};
459             }
460             elsif ($node -> mother -> mother -> name eq 'symbol_name')
461             {
462             push @token, {$node -> mother -> name => $name};
463             }
464              
465             return 1; # Keep walking.
466             },
467             _depth => 0,
468             });
469              
470             return [@token];
471              
472             } # End of _process_event_declaration.
473              
474             # --------------------------------------------------
475              
476             sub _process_lexeme_default
477             {
478             my($self, $index, $a_node) = @_;
479             my(%map) =
480             (
481             action => 'action',
482             blessing => 'bless',
483             );
484             my(@token) = ('lexeme default', '=');
485              
486             my($name);
487              
488             $a_node -> walk_down
489             ({
490             callback => sub
491             {
492             my($node, $option) = @_;
493             $name = $node -> name;
494              
495             # Skip the first 2 daughters, which hold offsets for the
496             # start and end of the token within the input stream.
497              
498             return 1 if ($node -> my_daughter_index < 2);
499              
500             if ($node -> mother -> mother -> name =~ /(action|blessing)_name/)
501             {
502             push @token, $map{$1}, '=>', $name;
503             }
504              
505             return 1; # Keep walking.
506             },
507             _depth => 0,
508             });
509              
510             return [@token];
511              
512             } # End of _process_lexeme_default.
513              
514             # --------------------------------------------------
515              
516             sub _process_lexeme_rule
517             {
518             my($self, $index, $a_node) = @_;
519             my(@token) = (':lexeme', '~');
520              
521             my($name);
522              
523             $a_node -> walk_down
524             ({
525             callback => sub
526             {
527             my($node, $option) = @_;
528             $name = $node -> name;
529              
530             # Skip the first 2 daughters, which hold offsets for the
531             # start and end of the token within the input stream.
532              
533             return 1 if ($node -> my_daughter_index < 2);
534              
535             if ($node -> mother -> mother -> name eq 'event_name')
536             {
537             push @token, 'event', '=>', $name;
538             }
539             elsif ($node -> mother -> mother -> name eq 'pause_specification')
540             {
541             push @token, 'pause', '=>', $name;
542             }
543             elsif ($node -> mother -> mother -> name eq 'priority_specification')
544             {
545             push @token, 'priority', '=>', $name;
546             }
547             elsif ($node -> mother -> mother -> name eq 'symbol_name')
548             {
549             push @token, {$node -> mother -> name => $name};
550             }
551              
552             return 1; # Keep walking.
553             },
554             _depth => 0,
555             });
556              
557             return [@token];
558              
559             } # End of _process_lexeme_rule.
560              
561             # --------------------------------------------------
562              
563             sub _process_parenthesized_list
564             {
565             my($self, $index, $a_node, $depth_under) = @_;
566              
567             my($name);
568             my(@rhs);
569              
570             $a_node -> walk_down
571             ({
572             callback => sub
573             {
574             my($node, $option) = @_;
575             $name = $node -> name;
576              
577             # Skip the first 2 daughters, which hold offsets for the
578             # start and end of the token within the input stream.
579              
580             return 1 if ($node -> my_daughter_index < 2);
581              
582             if ($$option{_depth} == $depth_under)
583             {
584             push @rhs, $name;
585             }
586              
587             return 1; # Keep walking.
588             },
589             _depth => 0,
590             });
591              
592             $rhs[0] = "($rhs[0]";
593             $rhs[$#rhs] = "$rhs[$#rhs])";
594              
595             return [@rhs];
596              
597             } # End of _process_parenthesized_list.
598              
599             # --------------------------------------------------
600              
601             sub _process_priority_rule
602             {
603             my($self, $index, $a_node) = @_;
604              
605             my($alternative_count) = 0;
606              
607             my($continue);
608             my($depth_under);
609             my($name);
610             my(@token);
611              
612             $a_node -> walk_down
613             ({
614             callback => sub
615             {
616             my($node, $option) = @_;
617             $name = $node -> name;
618             $continue = 1;
619              
620             # Skip the first 2 daughters, which hold offsets for the
621             # start and end of the token within the input stream.
622              
623             return 1 if ($node -> my_daughter_index < 2);
624              
625             if ($node -> mother -> mother -> name eq 'action_name')
626             {
627             push @token, 'action', '=>', $name;
628             }
629             elsif ($name eq 'alternative')
630             {
631             $alternative_count++;
632              
633             push @token, '|' if ($alternative_count > 1);
634             }
635             elsif ($node -> mother -> mother -> name eq 'blessing_name')
636             {
637             push @token, 'bless', '=>', $name;
638             }
639             elsif ($node -> mother -> name eq 'character_class')
640             {
641             push @token, $name;
642             }
643             elsif ($node -> mother -> name =~ /op_declare_+/)
644             {
645             push @token, $name;
646             }
647             elsif ($name eq 'parenthesized_rhs_primary_list')
648             {
649             $continue = 0;
650             $depth_under = $node -> depth_under;
651              
652             push @token, @{$self -> _process_parenthesized_list($index, $node, $depth_under)};
653             }
654             elsif ($node -> mother -> mother -> name eq 'rank_specification')
655             {
656             push @token, 'rank', '=>', $name;
657             }
658             elsif ($node -> mother -> mother -> name eq 'separator_specification')
659             {
660             push @token, 'separator', '=>';
661             }
662             elsif ($node -> mother -> name eq 'single_quoted_string')
663             {
664             push @token, $name;
665             }
666             elsif ($node -> mother -> mother -> name eq 'symbol_name')
667             {
668             push @token, {$node -> mother -> name => $name};
669             }
670              
671             return $continue;
672             },
673             _depth => 0,
674             });
675              
676             return [@token];
677              
678             } # End of _process_priority_rule.
679              
680             # --------------------------------------------------
681              
682             sub _process_quantified_rule
683             {
684             my($self, $index, $a_node) = @_;
685              
686             my($name);
687             my(@token);
688              
689             $a_node -> walk_down
690             ({
691             callback => sub
692             {
693             my($node, $option) = @_;
694             $name = $node -> name;
695              
696             # Skip the first 2 daughters, which hold offsets for the
697             # start and end of the token within the input stream.
698              
699             return 1 if ($node -> my_daughter_index < 2);
700              
701             if ($node -> mother -> mother -> name eq 'action_name')
702             {
703             push @token, 'action', '=>', $name;
704             }
705             elsif ($node -> mother -> name eq 'character_class')
706             {
707             push @token, $name;
708             }
709             elsif ($node -> mother -> name =~ /op_declare_+/)
710             {
711             push @token, $name;
712             }
713             elsif ($node -> mother -> name eq 'quantifier')
714             {
715             push @token, {quantifier => $name};
716             }
717             elsif ($node -> mother -> mother -> name eq 'separator_specification')
718             {
719             push @token, 'separator', '=>';
720             }
721             elsif ($node -> mother -> mother -> name eq 'symbol_name')
722             {
723             push @token, {$node -> mother -> name => $name};
724             }
725              
726             return 1; # Keep walking.
727             },
728             _depth => 0,
729             });
730              
731             return [@token];
732              
733             } # End of _process_quantified_rule.
734              
735             # --------------------------------------------------
736              
737             sub _process_start_rule
738             {
739             my($self, $index, $a_node) = @_;
740              
741             my($name);
742             my(@token);
743              
744             $a_node -> walk_down
745             ({
746             callback => sub
747             {
748             my($node, $option) = @_;
749             $name = $node -> name;
750              
751             # Skip the first 2 daughters, which hold offsets for the
752             # start and end of the token within the input stream.
753              
754             return 1 if ($node -> my_daughter_index < 2);
755              
756             if ($node -> mother -> mother -> name eq 'symbol_name')
757             {
758             push @token, ':start', '::=', {$node -> mother -> name => $name};
759             }
760              
761             return 1; # Keep walking.
762             },
763             _depth => 0,
764             });
765              
766             return [@token];
767              
768             } # End of _process_start_rule.
769              
770             # ------------------------------------------------
771              
772             sub run
773             {
774             my($self) = @_;
775             my($package) = 'MarpaX::Grammar::Parser::Dummy'; # This is actually included below.
776             my $marpa_bnf = read_file($self -> marpa_bnf_file, binmode => ':utf8');
777             my($marpa_grammar) = Marpa::R2::Scanless::G -> new({bless_package => $package, source => \$marpa_bnf});
778             my $user_bnf = read_file($self -> user_bnf_file, binmode =>':utf8');
779             my($recce) = Marpa::R2::Scanless::R -> new({grammar => $marpa_grammar});
780              
781             $recce -> read(\$user_bnf);
782              
783             my($value) = $recce -> value;
784              
785             die "Parse failed\n" if (! defined $value);
786              
787             $value = $$value;
788              
789             die "Parse failed\n" if (! defined $value);
790              
791             Data::TreeDumper::DumpTree
792             (
793             $value,
794             '', # No title since Data::TreeDumper::Renderer::Marpa prints nothing.
795             DISPLAY_ROOT_ADDRESS => 1,
796             NO_WRAP => 1,
797             RENDERER =>
798             {
799             NAME => 'Marpa', # I.e.: Data::TreeDumper::Renderer::Marpa.
800             package => $package, # I.e.: MarpaX::Grammar::Parser::Dummy.
801             root => $self -> raw_tree,
802             }
803             );
804              
805             my($raw_tree_file) = $self -> raw_tree_file;
806              
807             if ($raw_tree_file)
808             {
809             open(my $fh, '>', $raw_tree_file) || die "Can't open(> $raw_tree_file): $!\n";
810             print $fh map{"$_\n"} @{$self -> raw_tree -> tree2string({no_attributes => 1 - $self -> bind_attributes})};
811             close $fh;
812             }
813              
814             $self -> compress_tree;
815              
816             my($cooked_tree_file) = $self -> cooked_tree_file;
817              
818             if ($cooked_tree_file)
819             {
820             open(my $fh, '>', $cooked_tree_file) || die "Can't open(> $cooked_tree_file): $!\n";
821             print $fh map{"$_\n"} @{$self -> cooked_tree -> tree2string({no_attributes => 1 - $self -> bind_attributes})};
822             close $fh;
823             }
824              
825             # Return 0 for success and 1 for failure.
826              
827             return 0;
828              
829             } # End of run.
830              
831             # ------------------------------------------------
832              
833             package MarpaX::Grammar::Parser::Dummy;
834              
835             our $VERSION = '1.03';
836              
837             sub new{return {};}
838              
839             #-------------------------------------------------
840              
841             1;
842              
843             =pod
844              
845             =head1 NAME
846              
847             C - Converts a Marpa grammar into a forest using Tree::DAG_Node
848              
849             =head1 Synopsis
850              
851             use MarpaX::Grammar::Parser;
852              
853             my(%option) =
854             ( # Inputs:
855             marpa_bnf_file => 'share/metag.bnf',
856             user_bnf_file => 'share/stringparser.bnf',
857             # Outputs:
858             cooked_tree_file => 'share/stringparser.cooked.tree',
859             raw_tree_file => 'share/stringparser.raw.tree',
860             );
861              
862             MarpaX::Grammar::Parser -> new(%option) -> run;
863              
864             See share/*.bnf for input files and share/*.tree for output files.
865              
866             For more help, run:
867              
868             shell> perl -Ilib scripts/bnf2tree.pl -h
869              
870             See share/*.bnf for input files and share/*.tree for output files.
871              
872             Note: Installation includes copying all files from the share/ directory, into a dir chosen by L.
873             Run scripts/find.grammars.pl to display the name of the latter dir.
874              
875             Note: The cooked tree can be graphed with L. That module has its own
876             L.
877              
878             =head1 Description
879              
880             C uses L to convert a user's SLIF-DSL into a tree of Marpa-style attributes,
881             (see L), and then post-processes that (see L) to create another tree, this time
882             containing just the original grammar (see L).
883              
884             So, currently, the forest contains just 2 trees, acessible via the methods L and L.
885             The nature of these trees is discussed in the L.
886              
887             These trees are managed by L.
888              
889             Lastly, the major purpose of the cooked tree is to serve as input to L.
890              
891             =head1 Installation
892              
893             Install C as you would for any C module:
894              
895             Run:
896              
897             cpanm MarpaX::Grammar::Parser
898              
899             or run:
900              
901             sudo cpan MarpaX::Grammar::Parser
902              
903             or unpack the distro, and then either:
904              
905             perl Build.PL
906             ./Build
907             ./Build test
908             sudo ./Build install
909              
910             or:
911              
912             perl Makefile.PL
913             make (or dmake or nmake)
914             make test
915             make install
916              
917             Note: Installation includes copying all files from the share/ directory, into a dir chosen by L.
918             Run scripts/find.grammars.pl to display the name of the latter dir.
919              
920             =head1 Constructor and Initialization
921              
922             C is called as C<< my($parser) = MarpaX::Grammar::Parser -> new(k1 => v1, k2 => v2, ...) >>.
923              
924             It returns a new object of type C.
925              
926             Key-value pairs accepted in the parameter list (see also the corresponding methods
927             [e.g. L]):
928              
929             =over 4
930              
931             =item o bind_attributes Boolean
932              
933             Include (1) or exclude (0) attributes in the tree file(s) output.
934              
935             Default: 0.
936              
937             =item o cooked_tree_file aTextFileName
938              
939             The name of the text file to write containing the grammar as a cooked tree.
940              
941             If '', the file is not written.
942              
943             Default: ''.
944              
945             Note: The bind_attributes option/method affects the output.
946              
947             =item o logger aLog::HandlerObject
948              
949             By default, an object of type L is created which prints to STDOUT, but in this version nothing
950             is actually printed.
951              
952             See C and C below.
953              
954             Set C to '' (the empty string) to stop a logger being created.
955              
956             Default: undef.
957              
958             =item o marpa_bnf_file aMarpaSLIF-DSLFileName
959              
960             Specify the name of Marpa's own SLIF-DSL file. This file ships with L. It's name is metag.bnf.
961              
962             A copy, as of Marpa::R2 V 2.068000, ships with C. See share/metag.bnf.
963              
964             This option is mandatory.
965              
966             Default: ''.
967              
968             =item o maxlevel logOption1
969              
970             This option affects L objects.
971              
972             See the L docs.
973              
974             Default: 'info'.
975              
976             =item o minlevel logOption2
977              
978             This option affects L object.
979              
980             See the L docs.
981              
982             Default: 'error'.
983              
984             No lower levels are used.
985              
986             =item o raw_tree_file aTextFileName
987              
988             The name of the text file to write containing the grammar as a raw tree.
989              
990             If '', the file is not written.
991              
992             Default: ''.
993              
994             Note: The bind_attributes option/method affects the output.
995              
996             =item o user_bnf_file aUserSLIF-DSLFileName
997              
998             Specify the name of the file containing your Marpa::R2-style grammar.
999              
1000             See share/stringparser.bnf for a sample.
1001              
1002             This option is mandatory.
1003              
1004             Default: ''.
1005              
1006             =back
1007              
1008             =head1 Methods
1009              
1010             =head2 bind_attributes([$Boolean])
1011              
1012             Here, the [] indicate an optional parameter.
1013              
1014             Get or set the option which includes (1) or excludes (0) node attributes from the output C
1015             and C.
1016              
1017             Note: C is a parameter to new().
1018              
1019             =head2 clean_name($name)
1020              
1021             Returns a list of 2 elements: ($name, $attributes).
1022              
1023             $name is just the name of the token.
1024              
1025             $attributes is a hashref with these keys:
1026              
1027             =over 4
1028              
1029             =item o bracketed_name => $Boolean
1030              
1031             Indicates the token's name is (1) or is not (0) of the form '<...>'.
1032              
1033             =item o quantifier => $char
1034              
1035             Indicates the token is quantified. $char is one of '', '*' or '+'.
1036              
1037             If $char is '' (the empty string), the token is not quantified.
1038              
1039             =item o real_name => $string
1040              
1041             The user-specified version of the name of the token, including leading '<' and trailing '>' if any.
1042              
1043             =back
1044              
1045             =head2 compress_branch($index, $node)
1046              
1047             Called by L.
1048              
1049             Converts 1 sub-tree of the raw tree into one sub-tree of the cooked tree.
1050              
1051             =head2 compress_tree()
1052              
1053             Called automatically by L.
1054              
1055             Converts the raw tree into the cooked tree, calling L once for each
1056             daughter of the raw tree.
1057              
1058             Output is the tree returned by L.
1059              
1060             =head2 cooked_tree()
1061              
1062             Returns the root node, of type L, of the cooked tree of items in the user's grammar.
1063              
1064             By cooked tree, I mean as post-processed from the raw tree so as to include just the original user's SLIF-DSL tokens.
1065              
1066             The cooked tree is optionally written to the file name given by L.
1067              
1068             The nature of this tree is discussed in the L.
1069              
1070             See also L.
1071              
1072             =head2 cooked_tree_file([$output_file_name])
1073              
1074             Here, the [] indicate an optional parameter.
1075              
1076             Get or set the name of the file to which the cooked tree form of the user's grammar will be written.
1077              
1078             If no output file is supplied, nothing is written.
1079              
1080             See share/stringparser.cooked.tree for the output of post-processing Marpa's analysis of share/stringparser.bnf.
1081              
1082             This latter file is the grammar used in L.
1083              
1084             Note: C is a parameter to new().
1085              
1086             Note: The bind_attributes option/method affects the output.
1087              
1088             =head2 log($level, $s)
1089              
1090             Calls $self -> logger -> log($level => $s) if ($self -> logger).
1091              
1092             =head2 logger([$logger_object])
1093              
1094             Here, the [] indicate an optional parameter.
1095              
1096             Get or set the logger object.
1097              
1098             To disable logging, just set logger to the empty string.
1099              
1100             Note: C is a parameter to new().
1101              
1102             =head2 marpa_bnf_file([$bnf_file_name])
1103              
1104             Here, the [] indicate an optional parameter.
1105              
1106             Get or set the name of the file to read Marpa's grammar from.
1107              
1108             Note: C is a parameter to new().
1109              
1110             =head2 maxlevel([$string])
1111              
1112             Here, the [] indicate an optional parameter.
1113              
1114             Get or set the value used by the logger object.
1115              
1116             This option is only used if an object of type L is created. See L.
1117              
1118             Note: C is a parameter to new().
1119              
1120             =head2 minlevel([$string])
1121              
1122             Here, the [] indicate an optional parameter.
1123              
1124             Get or set the value used by the logger object.
1125              
1126             This option is only used if an object of type L is created. See L.
1127              
1128             Note: C is a parameter to new().
1129              
1130             =head2 new()
1131              
1132             The constructor. See L.
1133              
1134             =head2 raw_tree()
1135              
1136             Returns the root node, of type L, of the raw tree of items in the user's grammar.
1137              
1138             By raw tree, I mean as derived directly from Marpa.
1139              
1140             The raw tree is optionally written to the file name given by L.
1141              
1142             The nature of this tree is discussed in the L.
1143              
1144             See also L.
1145              
1146             =head2 raw_tree_file([$output_file_name])
1147              
1148             Here, the [] indicate an optional parameter.
1149              
1150             Get or set the name of the file to which the raw tree form of the user's grammar will be written.
1151              
1152             If no output file is supplied, nothing is written.
1153              
1154             See share/stringparser.raw.tree for the output of Marpa's analysis of share/stringparser.bnf.
1155              
1156             This latter file is the grammar used in L.
1157              
1158             Note: C is a parameter to new().
1159              
1160             Note: The bind_attributes option/method affects the output.
1161              
1162             =head2 run()
1163              
1164             The method which does all the work.
1165              
1166             See L and scripts/bnf2tree.pl for sample code.
1167              
1168             =head2 user_bnf_file([$bnf_file_name])
1169              
1170             Here, the [] indicate an optional parameter.
1171              
1172             Get or set the name of the file to read the user's grammar's SLIF-DSL from. The whole file is slurped in as
1173             a single string.
1174              
1175             See share/stringparser.bnf for a sample. It is the grammar used in L.
1176              
1177             Note: C is a parameter to new().
1178              
1179             =head1 Files Shipped with this Module
1180              
1181             =head2 Data Files
1182              
1183             =over 4
1184              
1185             =item o share/c.ast.bnf
1186              
1187             This is part of L, by Jean-Damien Durand. It's 1,565 lines long.
1188              
1189             The outputs are share/c.ast.cooked.tree and share/c.ast.raw.tree.
1190              
1191             =item o share/c.ast.cooked.tree
1192              
1193             This is the output from post-processing Marpa's analysis of share/c.ast.bnf.
1194              
1195             The command to generate this file is:
1196              
1197             shell> scripts/bnf2tree.sh c.ast
1198              
1199             =item o share/c.ast.raw.tree
1200              
1201             This is the output from processing Marpa's analysis of share/c.ast.bnf. It's 56,723 lines long, which indicates
1202             the complexity of Jean-Damien's grammar for C.
1203              
1204             The command to generate this file is:
1205              
1206             shell> scripts/bnf2tree.sh c.ast
1207              
1208             =item o share/json.1.bnf
1209              
1210             It is part of L, written as a gist by Peter Stuifzand.
1211              
1212             See L.
1213              
1214             The outputs are share/json.1.cooked.tree and share/json.1.raw.tree.
1215              
1216             =item o share/json.1.cooked.tree
1217              
1218             This is the output from post-processing Marpa's analysis of share/json.1.bnf.
1219              
1220             The command to generate this file is:
1221              
1222             shell> scripts/bnf2tree.sh json.1
1223              
1224             =item o share/json.1.raw.tree
1225              
1226             This is the output from processing Marpa's analysis of share/json.1.bnf.
1227              
1228             The command to generate this file is:
1229              
1230             shell> scripts/bnf2tree.sh json.1
1231              
1232             =item o share/json.2.bnf
1233              
1234             It also is part of L, written by Jeffrey Kegler as a reply to the gist above from Peter.
1235              
1236             The outputs are share/json.2.cooked.tree and share/json.2.raw.tree.
1237              
1238             =item o share/json.2.cooked.tree
1239              
1240             This is the output from post-processing Marpa's analysis of share/json.2.bnf.
1241              
1242             The command to generate this file is:
1243              
1244             shell> scripts/bnf2tree.sh json.2
1245              
1246             =item o share/json.2.raw.tree
1247              
1248             This is the output from processing Marpa's analysis of share/json.2.bnf.
1249              
1250             The command to generate this file is:
1251              
1252             shell> scripts/bnf2tree.sh json.2
1253              
1254             =item o share/metag.bnf.
1255              
1256             This is a copy of L's SLIF-DSL, as of Marpa::R2 V 2.068000.
1257              
1258             See L above.
1259              
1260             =item o share/stringparser.bnf.
1261              
1262             This is a copy of L's SLIF-DSL.
1263              
1264             The outputs are share/stringparser.cooked.tree and share/stringparser.raw.tree.
1265              
1266             See L above.
1267              
1268             =item o share/stringparser.cooked.tree
1269              
1270             This is the output from post-processing Marpa's analysis of share/stringparser.bnf.
1271              
1272             The command to generate this file is:
1273              
1274             shell> scripts/bnf2tree.sh stringparser
1275              
1276             =item o share/stringparser.raw.tree
1277              
1278             This is the output from processing Marpa's analysis of share/stringparser.bnf.
1279              
1280             The command to generate this file is:
1281              
1282             shell> scripts/bnf2tree.sh stringparser
1283              
1284             See also the next item.
1285              
1286             =item o share/stringparser.treedumper
1287              
1288             This is the output of running:
1289              
1290             shell> perl scripts/metag.pl share/metag.bnf share/stringparser.bnf > share/stringparser.treedumper
1291              
1292             That script, metag.pl, is discussed just below, and in the L.
1293              
1294             =item o share/termcap.info.bnf
1295              
1296             It is part of L, written by Jean-Damien Durand.
1297              
1298             The outputs are share/termcap.cooked.tree and share/termcap.info.raw.tree.
1299              
1300             =item o share/termcap.info.cooked.tree
1301              
1302             This is the output from post-processing Marpa's analysis of share/termcap.info.bnf.
1303              
1304             The command to generate this file is:
1305              
1306             shell> scripts/bnf2tree.sh termcap.info
1307              
1308             =item o share/termcap.info.raw.tree
1309              
1310             This is the output from processing Marpa's analysis of share/termcap.info.bnf.
1311              
1312             The command to generate this file is:
1313              
1314             shell> scripts/bnf2tree.sh termcap.info
1315              
1316             =back
1317              
1318             =head2 Scripts
1319              
1320             =over 4
1321              
1322             =item o scripts/bnf2tree.pl
1323              
1324             This is a neat way of using this module. For help, run:
1325              
1326             shell> perl -Ilib scripts/bnf2tree.pl -h
1327              
1328             Of course you are also encouraged to include the module directly in your own code.
1329              
1330             =item o scripts/bnf2tree.sh
1331              
1332             This is a quick way for me to run bnf2tree.pl.
1333              
1334             =item o scripts/find.grammars.pl
1335              
1336             This prints the path to a grammar file. After installation of the module, run it with:
1337              
1338             shell> perl scripts/find.grammars.pl (Defaults to json.1.bnf)
1339             shell> perl scripts/find.grammars.pl c.ast.bnf
1340             shell> perl scripts/find.grammars.pl json.1.bnf
1341             shell> perl scripts/find.grammars.pl json.2.bnf
1342             shell> perl scripts/find.grammars.pl stringparser.bnf
1343             shell> perl scripts/find.grammars.pl termcap.inf.bnf
1344              
1345             It will print the name of the path to given grammar file.
1346              
1347             =item o scripts/metag.pl
1348              
1349             This is Jeffrey Kegler's code. See the L for more.
1350              
1351             =item o scripts/pod2html.sh
1352              
1353             This lets me quickly proof-read edits to the docs.
1354              
1355             =back
1356              
1357             =head1 FAQ
1358              
1359             =head2 What is this SLIF-DSL thingy?
1360              
1361             Marpa's grammars are written in what we call a SLIF-DSL. Here, SLIF stands for Marpa's Scanless Interface, and DSL
1362             is L.
1363              
1364             Many programmers will have heard of L. Well, Marpa's
1365             SLIF-DSL is an extended BNF. That is, it includes special tokens which only make sense within the context of a Marpa
1366             grammar. Hence the 'Domain Specific' part of the name.
1367              
1368             In practice, this means you express your grammar in a string, and Marpa treats that as a set of rules as to how
1369             you want Marpa to process your input stream.
1370              
1371             Marpa's docs for its SLIF-DSL L.
1372              
1373             =head2 What is the difference between the cooked tree and the raw tree?
1374              
1375             The raw tree is generated by processing the output of Marpa's parse of the user's grammar file.
1376             It contains Marpa's view of that grammar.
1377              
1378             The cooked tree is generated by post-processing the raw tree, to extract just the user's grammar's tokens.
1379             It contains the user's view of their grammar.
1380              
1381             The cooked tree can be graphed with L. That module has its own
1382             L.
1383              
1384             The following items explain this in more detail.
1385              
1386             =head2 What are the details of the nodes in the cooked tree?
1387              
1388             Under the root, there are a set of nodes:
1389              
1390             =over 4
1391              
1392             =item o N nodes, 1 per statement in the grammar
1393              
1394             The node's names are the left-hand side of each statement in the grammar.
1395              
1396             Each node is the root of a subtree describing the statement.
1397              
1398             Under those nodes are a set of nodes:
1399              
1400             =over 4
1401              
1402             =item o 1 node for the separator between the left and right sides of the statement
1403              
1404             So, the node's name is one of: '=' '::=' or '~'.
1405              
1406             =item o 1 node per token from the right-hand side of each statement
1407              
1408             The node's name is the token itself.
1409              
1410             =back
1411              
1412             =back
1413              
1414             The attributes of each node are a hashref, with these keys:
1415              
1416             =over 4
1417              
1418             =item o bracketed_name => $Boolean
1419              
1420             Indicates the token's name is or is not of the form '<...>'.
1421              
1422             =item o quantifier => $char
1423              
1424             Indicates the token is quantified. $char is one of '', '*' or '+'.
1425              
1426             If $char is ' ' (the empty string), the token is not quantified.
1427              
1428             =item o real_name => $string
1429              
1430             The user-specified version of the name of the token, including leading '<' and trailing '>' if any.
1431              
1432             =back
1433              
1434             See share/stringparser.cooked.tree.
1435              
1436             =head2 What are the details of the nodes in the raw tree?
1437              
1438             Under the root, there are a set of nodes:
1439              
1440             =over 4
1441              
1442             =item o One node for the offset of the start of the grammar within the input stream.
1443              
1444             The node's name is the integer start offset.
1445              
1446             =item o One node for the offset of the end of the grammar within the input stream.
1447              
1448             The node's name is the integer end offset.
1449              
1450             =item o N nodes, 1 per statement in the grammar
1451              
1452             The node's names are either an item from the user's grammar (when the attribute 'type' is 'Grammar')
1453             or a Marpa-assigned token (when the attribute 'type' is 'Marpa').
1454              
1455             Each node is the root of a subtree describing the statement.
1456              
1457             See share/stringparser.raw.attributes.tree for a tree with attributes displayed (bind_attributes => 1), and
1458             share/stringparser.raw.tree for the same tree without attributes (bind_attributes => 0).
1459              
1460             =back
1461              
1462             The attributes of each node are a hashref, with these keys:
1463              
1464             =over 4
1465              
1466             =item o type
1467              
1468             This indicates what type of node it is. Values:
1469              
1470             =over 4
1471              
1472             =item o 'Grammar' => The node's name is an item from the user-specified grammar.
1473              
1474             =item o 'Marpa' => Marpa has assigned a class to the node (or to one of its parents)
1475              
1476             The class name is for the form: $class_name::$node_name.
1477              
1478             C<$class_name> is a constant provided by this module, and is 'MarpaX::Grammar::Parser::Dummy'.
1479              
1480             See share/stringparser.treedumper, which will make this much clearer.
1481              
1482             The technique used to generate this file is discussed above, under L.
1483              
1484             Note: The file share/stringparser.treedumper shows some class names, but they are currently I stored
1485             in the tree returned by the method L.
1486              
1487             =back
1488              
1489             =back
1490              
1491             See share/stringparser.raw.tree.
1492              
1493             =head2 Why are attributes used to identify bracketed names?
1494              
1495             Because L assigns a special meaning to labels which begin with '<' and '<<'.
1496              
1497             =head2 How do I sort the daughters of a node?
1498              
1499             Here's one way, using the node names as sort keys.
1500              
1501             As an example, choose $root as either $self -> cooked_tree or $self -> raw_tree, and then:
1502              
1503             @daughters = sort{$a -> name cmp $b -> name} $root -> daughters;
1504              
1505             $root -> set_daughters(@daughters);
1506              
1507             Note: Since the original order of the daughters, in both the cooked and raw trees, is significant, sorting is
1508             contra-indicated.
1509              
1510             =head2 Where did the basic code come from?
1511              
1512             Jeffrey Kegler wrote it, and posted it on the Google Group dedicated to Marpa, on 2013-07-22,
1513             in the thread 'Low-hanging fruit'. I modified it slightly for a module context.
1514              
1515             The original code is shipped as scripts/metag.pl.
1516              
1517             =head2 Why did you use Data::TreeDump?
1518              
1519             It offered the output which was most easily parsed of the modules I tested.
1520             The others were L, L, L and L.
1521              
1522             =head2 Where is Marpa's Homepage?
1523              
1524             L.
1525              
1526             =head2 Are there any articles discussing Marpa?
1527              
1528             Yes, many by its author, and several others. See Marpa's homepage, just above, and:
1529              
1530             L, (in progress, by Peter Stuifzand and Ron Savage).
1531              
1532             L, by Peter Stuifzand.
1533              
1534             L, by Peter Stuifzand.
1535              
1536             L, by Ron Savage.
1537              
1538             =head1 See Also
1539              
1540             L.
1541              
1542             L.
1543              
1544             L.
1545              
1546             L.
1547              
1548             L.
1549              
1550             L.
1551              
1552             =head1 Machine-Readable Change Log
1553              
1554             The file Changes was converted into Changelog.ini by L.
1555              
1556             =head1 Version Numbers
1557              
1558             Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.
1559              
1560             =head1 Repository
1561              
1562             L
1563              
1564             =head1 Support
1565              
1566             Email the author, or log a bug on RT:
1567              
1568             L.
1569              
1570             =head1 Author
1571              
1572             L was written by Ron Savage Iron@savage.net.auE> in 2013.
1573              
1574             Home page: L.
1575              
1576             =head1 Copyright
1577              
1578             Australian copyright (c) 2013, Ron Savage.
1579              
1580             All Programs of mine are 'OSI Certified Open Source Software';
1581             you can redistribute them and/or modify them under the terms of
1582             The Artistic License 2.0, a copy of which is available at:
1583             http://www.opensource.org/licenses/index.html
1584              
1585             =cut