File Coverage

blib/lib/MarpaX/Grammar/GraphViz2.pm
Criterion Covered Total %
statement 25 27 92.5
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 34 36 94.4


line stmt bran cond sub pod time code
1             package MarpaX::Grammar::GraphViz2;
2              
3 1     1   523 use strict;
  1         2  
  1         40  
4 1     1   574 use utf8;
  1         9  
  1         4  
5 1     1   28 use warnings;
  1         4  
  1         26  
6 1     1   4 use warnings qw(FATAL utf8); # Fatalize encoding glitches.
  1         0  
  1         37  
7 1     1   455 use open qw(:std :utf8); # Undeclared streams in UTF-8.
  1         1009  
  1         4  
8 1     1   581 use charnames qw(:full :short); # Unneeded in v5.16.
  1         24833  
  1         6  
9              
10 1     1   177 use File::Basename; # For basename().
  1         1  
  1         78  
11 1     1   443 use File::Which; # For which().
  1         745  
  1         46  
12              
13 1     1   890 use GraphViz2;
  0            
  0            
14              
15             use List::AllUtils qw/first_index indexes/;
16              
17             use Log::Handler;
18              
19             use MarpaX::Grammar::Parser;
20              
21             use Moo;
22              
23             has default_count =>
24             (
25             default => sub{return 0},
26             is => 'rw',
27             #isa => 'Int',
28             required => 0,
29             );
30              
31             has discard_count =>
32             (
33             default => sub{return 0},
34             is => 'rw',
35             #isa => 'Int',
36             required => 0,
37             );
38              
39             has driver =>
40             (
41             default => sub{return ''},
42             is => 'rw',
43             #isa => 'Str',
44             required => 0,
45             );
46              
47             has event_count =>
48             (
49             default => sub{return 0},
50             is => 'rw',
51             #isa => 'Int',
52             required => 0,
53             );
54              
55             has format =>
56             (
57             default => sub{return ''},
58             is => 'rw',
59             #isa => 'Str',
60             required => 0,
61             );
62              
63             has graph =>
64             (
65             default => sub{return ''},
66             is => 'rw',
67             #isa => 'GraphViz',
68             required => 0,
69             );
70              
71             has legend =>
72             (
73             default => sub{return 0},
74             is => 'rw',
75             #isa => 'Int',
76             required => 0,
77             );
78              
79             has lexeme_count =>
80             (
81             default => sub{return 0},
82             is => 'rw',
83             #isa => 'Int',
84             required => 0,
85             );
86              
87             has lexemes =>
88             (
89             default => sub{return {} },
90             is => 'rw',
91             #isa => 'HashRef',
92             required => 0,
93             );
94              
95             has logger =>
96             (
97             default => sub{return undef},
98             is => 'rw',
99             # isa => 'Str',
100             required => 0,
101             );
102              
103             has marpa_bnf_file =>
104             (
105             default => sub{return ''},
106             is => 'rw',
107             #isa => 'Str',
108             required => 1,
109             );
110              
111             has maxlevel =>
112             (
113             default => sub{return 'notice'},
114             is => 'rw',
115             # isa => 'Str',
116             required => 0,
117             );
118              
119             has minlevel =>
120             (
121             default => sub{return 'error'},
122             is => 'rw',
123             # isa => 'Str',
124             required => 0,
125             );
126              
127             has nodes_seen =>
128             (
129             default => sub{return {} },
130             is => 'rw',
131             #isa => 'HashRef',
132             required => 0,
133             );
134              
135             has output_file =>
136             (
137             default => sub{return ''},
138             is => 'rw',
139             #isa => 'Str',
140             required => 0,
141             );
142              
143             has parser =>
144             (
145             default => sub{return ''},
146             is => 'rw',
147             #isa => 'MarpaX::Grammar::Parser',
148             required => 0,
149             );
150              
151             has root_node =>
152             (
153             default => sub{return ''},
154             is => 'rw',
155             #isa => 'Tree::DAG_Node',
156             required => 0,
157             );
158              
159             has separators =>
160             (
161             default => sub{return {} },
162             is => 'rw',
163             #isa => 'HashRef',
164             required => 0,
165             );
166              
167             has user_bnf_file =>
168             (
169             default => sub{return ''},
170             is => 'rw',
171             #isa => 'Str',
172             required => 1,
173             );
174              
175             our $VERSION = '1.05';
176              
177             # ------------------------------------------------
178              
179             sub add_legend
180             {
181             my($self) = @_;
182              
183             $self -> graph -> push_subgraph
184             (
185             # No options...
186             # Legend: top. Border: no. Label: no.
187             #
188             # label => 'cluster_legend',
189             # Legend: top. Border: no. Label: no.
190             #
191             # name => 'cluster_legend',
192             # Legend: top. Border: yes. Label: *.bnf.
193             #
194             #graph => {label => 'cluster_legend'},
195             # Legend: top. Border: no. Label: no. Not using subgraph => {...}.
196             #graph => {label => 'cluster_Legend'},
197             # Legend: bottom. Border: no. Label: no. Using subgraph => {...}.
198             # Legend: top. Border: no. Label: no. Not using subgraph => {...}.
199             subgraph => {rank => 'max'},
200             # Legend: top. Border: no. Label: no. Using graph => {...}.
201             # Legend: bottom. Border: no. Label: no. Not using graph => {...}.
202             );
203              
204             $self -> graph -> add_node
205             (
206             label =>
207             q|
208             <
209            
210             The green node is the start node
211            
212            
213             Lightblue nodes are for lexeme attributes
214            
215            
216             Orchid nodes are for lexemes
217            
218            
219             Golden nodes are for actions
220            
221            
222             Red nodes are for events
223            
224            
>
225             |,
226             name => 'Legend',
227             shape => 'plaintext',
228             );
229              
230             $self -> graph -> pop_subgraph;
231              
232             } # End of add_legend.
233              
234             # ------------------------------------------------
235              
236             sub add_node
237             {
238             my($self, %attributes) = @_;
239             my($name) = delete $attributes{name};
240             my($seen) = $self -> nodes_seen;
241              
242             $self -> graph -> add_node(name => $name, %attributes) if (! $$seen{$name});
243              
244             $$seen{$name} = 1;
245              
246             $self -> nodes_seen($seen);
247              
248             } # End of add_node.
249              
250             # ------------------------------------------------
251              
252             sub BUILD
253             {
254             my($self) = @_;
255              
256             die "No Marpa BNF file found\n" if (! -e $self -> marpa_bnf_file);
257             die "No user BNF file found\n" if (! -e $self -> user_bnf_file);
258              
259             $self -> driver($self -> driver || which('dot') );
260             $self -> format($self -> format || 'svg');
261              
262             if (! defined $self -> logger)
263             {
264             $self -> logger(Log::Handler -> new);
265             $self -> logger -> add
266             (
267             screen =>
268             {
269             maxlevel => $self -> maxlevel,
270             message_layout => '%m',
271             minlevel => $self -> minlevel,
272             }
273             );
274             }
275              
276             my($graph) ||= GraphViz2 -> new
277             (
278             edge => {color => 'grey'},
279             global => {directed => 1, driver => $self -> driver, format => $self -> format},
280             graph => {label => basename($self -> user_bnf_file), rankdir => 'TB'},
281             logger => $self -> logger,
282             node => {shape => 'rectangle', style => 'filled'},
283             );
284              
285             $self -> graph($graph);
286              
287             $self -> parser
288             (
289             MarpaX::Grammar::Parser -> new
290             (
291             marpa_bnf_file => $self -> marpa_bnf_file,
292             logger => $self -> logger,
293             user_bnf_file => $self -> user_bnf_file,
294             )
295             );
296              
297             } # End of BUILD.
298              
299             # ------------------------------------------------
300              
301             sub clean_name
302             {
303             my($self, $name, $skip_symbols) = @_;
304              
305             # If $skip_symbols is defined (as in calls from rectify_name() ),
306             # then do not do this check.
307              
308             return $name if ( (! defined $skip_symbols) && ($name =~ /(?::=|=>|=)/) );
309              
310             $name =~ s/\\/\\\\/g; # Escape \.
311             $name =~ s/
312             $name =~ s/>/\\>/g; # Escape >.
313             $name =~ s/:/\x{a789}/g; # Escape :.
314             $name =~ s/\"/\x{a78c}\x{a78c}/g; # Convert " into Unicode ' x 2.
315              
316             return $name;
317              
318             } # End of clean_name.
319              
320             # ------------------------------------------------
321              
322             sub clean_tree
323             {
324             my($self) = @_;
325              
326             my($attributes);
327             my($name);
328              
329             $self -> parser -> cooked_tree -> walk_down
330             ({
331             callback => sub
332             {
333             # Elsewhere, the code uses these attributes (so we init them if necessary):
334             # o name.
335             # o quantifier.
336             # o real_name.
337              
338             my($node, $options) = @_;
339             $name = $node -> name;
340             $attributes = {name => '', quantifier => '', real_name => '', %{$node -> attributes} };
341              
342             $node -> attributes($attributes);
343             $node -> name($self -> clean_name($name) );
344              
345             return 1; # Keep walking.
346             },
347             _depth => 0,
348             });
349              
350             } # End of clean_tree.
351              
352             # ------------------------------------------------
353              
354             sub hashref2string
355             {
356             my($self, $hashref) = @_;
357             $hashref ||= {};
358              
359             return '{' . join(', ', map{qq|$_ => "$$hashref{$_}"|} sort keys %$hashref) . '}';
360              
361             } # End of hashref2string.
362              
363             # ------------------------------------------------
364              
365             sub log
366             {
367             my($self, $level, $s) = @_;
368              
369             $self -> logger -> log($level => $s) if ($self -> logger);
370              
371             } # End of log.
372              
373             # ------------------------------------------------
374              
375             sub _process_adverbs
376             {
377             my($self, $daughters) = @_;
378             my($end) = $#$daughters;
379             my($separators) = $self -> separators;
380              
381             # Chop adverbs off the end of the list.
382              
383             my($adverb, @adverbs);
384             my(@token);
385              
386             while ($end > 0)
387             {
388             if ($$daughters[$end - 1] -> name eq '=>')
389             {
390             $adverb = $$daughters[$end - 2] -> name;
391              
392             # rectify_name() returns a ($name => $label) pair.
393              
394             @token = $self -> rectify_name($$daughters[$end]);
395              
396             pop @$daughters for 1 .. 3;
397              
398             push @adverbs,
399             {
400             adverb => $adverb,
401             name => $token[0],
402             };
403              
404             $end = $#$daughters;
405             $$separators{$token[0]} = 1 if ($adverb eq 'separator');
406             }
407             else
408             {
409             $end = 0;
410             }
411             }
412              
413             # Construct the label as an array of hashrefs.
414              
415             if ($#adverbs >= 0)
416             {
417             @adverbs = map{"$$_{adverb} =\\> $$_{name}"} @adverbs;
418             $adverbs[0] = "\{$adverbs[0]";
419             $adverbs[$#adverbs] .= '}';
420             @adverbs = map{ {text => $_} } @adverbs;
421             }
422              
423             return ([@$daughters], [@adverbs]);
424              
425             } # End of _process_adverbs.
426              
427             # ------------------------------------------------
428             # This handles prioritized rules and quantized rules.
429              
430             sub _process_complex_adverbs
431             {
432             my($self, $index, $a_node) = @_;
433              
434             # Sample inputs:
435             # o 1 token, no adverbs:
436             # |--- number
437             # | |--- ~
438             # | |--- int
439             # o 2 tokens, no adverbs:
440             # |--- json
441             # | |--- ::=
442             # | |--- object
443             # | |--- |
444             # | |--- array
445             # o 5 tokens, various adverbs:
446             # |--- object
447             # | |--- ::=
448             # | |--- '{'
449             # | |--- '}'
450             # | |--- action
451             # | |--- =>
452             # | |--- do_empty_object
453             # | |--- |
454             # | |--- '{'
455             # | |--- members
456             # | |--- '}'
457             # | |--- action
458             # | |--- =>
459             # | |--- do_object
460              
461             my($daughters) = [$a_node -> daughters];
462             my($finished) = $#$daughters >= 0 ? 0 : 1;
463              
464             my($adverbs, @adverb_stack);
465             my(@daughter_stack);
466             my(@token_stack);
467              
468             while (! $finished)
469             {
470             # Chew adverbs, if any, off the end of the list of daughters.
471              
472             ($daughters, $adverbs) = $self -> _process_adverbs($daughters);
473              
474             # Stack the adverbs owned by the token(s) at the end of the daughters.
475              
476             unshift @adverb_stack, $adverbs;
477              
478             # Chew the tokens owning the adverbs off the end of the list of daughters.
479             # This backward processing stops with a '|' or $daughter[0].
480              
481             @token_stack = ();
482              
483             my($i) = $#$daughters;
484              
485             while ( ($i > 0) && ($$daughters[$i] -> name ne '|') )
486             {
487             unshift @token_stack, pop @$daughters;
488              
489             $i--;
490             }
491              
492             unshift @daughter_stack, [@token_stack];
493              
494             # Discard the '|' separating alternatives in the BND.
495              
496             pop @$daughters if ( ($i >= 0) && ($$daughters[$i] -> name eq '|') );
497              
498             $finished = 1 if ($#$daughters == 0);
499             }
500              
501             return ([@daughter_stack], [@adverb_stack]);
502              
503             } # End of _process_complex_adverbs.
504              
505             # ------------------------------------------------
506              
507             sub _process_default_rule
508             {
509             my($self, $index, $a_node) = @_;
510              
511             $self -> default_count($self -> default_count + 1);
512              
513             my($default_count) = $self -> default_count;
514             my($default_name) = "\x{a789}default";
515             my($attributes) =
516             {
517             fillcolor => 'lightblue',
518             label => $default_name,
519             };
520              
521             if ($default_count == 1)
522             {
523             $self -> add_node(name => $default_name, %$attributes);
524             $self -> graph -> add_edge(from => $self -> root_node -> name, to => $default_name);
525             }
526              
527             my($daughters, $adverbs) = $self -> _process_adverbs([$a_node -> daughters]);
528              
529             if ($#$adverbs >= 0)
530             {
531             $$attributes{fillcolor} = 'goldenrod';
532             $$attributes{label} = $adverbs;
533             my($adverb_name) = "${default_name}_$default_count";
534              
535             $self -> add_node(name => $adverb_name, %$attributes);
536             $self -> graph -> add_edge(from => $default_name, to => $adverb_name);
537             }
538              
539             } # End of _process_default_rule.
540              
541             # ------------------------------------------------
542              
543             sub _process_discard_rule
544             {
545             my($self, $index, $a_node) = @_;
546              
547             $self -> discard_count($self -> discard_count + 1);
548              
549             my($discard_count) = $self -> discard_count;
550             my($discard_name) = "\x{a789}discard";
551             my($attributes) =
552             {
553             fillcolor => 'lightblue',
554             label => $discard_name,
555             };
556              
557             if ($discard_count == 1)
558             {
559             $self -> add_node(name => $discard_name, %$attributes);
560             $self -> graph -> add_edge(from => $self -> root_node -> name, to => $discard_name);
561             }
562              
563             # Ignore the first daughter, which is '=>'.
564              
565             my(@daughters) = $a_node -> daughters;
566              
567             # rectify_name() returns a ($name => $label) pair.
568              
569             my(@name) = $self -> rectify_name($daughters[1]);
570             $$attributes{label} = $name[1];
571              
572             $self -> add_node(name => $name[0], %$attributes);
573             $self -> graph -> add_edge(from => $discard_name, to => $name[0]);
574              
575             } # End of _process_discard_rule.
576              
577             # ------------------------------------------------
578              
579             sub _process_event_rule
580             {
581             my($self, $index, $a_node) = @_;
582              
583             $self -> event_count($self -> event_count + 1);
584              
585             my($event_count) = $self -> event_count;
586             my($event_name) = 'event';
587             my($attributes) =
588             {
589             fillcolor => 'firebrick1',
590             label => $event_name,
591             };
592              
593             if ($event_count == 1)
594             {
595             $self -> add_node(name => $event_name, %$attributes);
596             $self -> graph -> add_edge(from => $self -> root_node -> name, to => $event_name);
597             }
598              
599             my($item_name) = "${event_name}_$event_count";
600             my(@daughters) = $a_node -> daughters;
601              
602             # rectify_name() returns a ($name => $label) pair.
603              
604             my(@lhs) = $self -> rectify_name($daughters[3]);
605             $$attributes{label} =
606             [
607             {text => '{' . $daughters[0] -> name},
608             {text => $daughters[2] -> name},
609             {text => "$lhs[0]}"},
610             ];
611              
612             $self -> add_node(name => $item_name, %$attributes);
613             $self -> graph -> add_edge(from => $event_name, to => $item_name);
614             $self -> graph -> add_edge(from => $item_name, to => $lhs[0]);
615              
616             } # End of _process_event_rule.
617              
618             # ------------------------------------------------
619              
620             sub _process_lexeme_default_rule
621             {
622             my($self, $index, $a_node) = @_;
623             my($lexeme_name) = 'lexeme default';
624             my($attributes) =
625             {
626             fillcolor => 'lightblue',
627             label => $lexeme_name,
628             };
629              
630             $self -> add_node(name => $lexeme_name, %$attributes);
631             $self -> graph -> add_edge(from => $self -> root_node -> name, to => $lexeme_name);
632              
633             my($daughters, $adverbs) = $self -> _process_adverbs([$a_node -> daughters]);
634              
635             if ($#$adverbs >= 0)
636             {
637             $$attributes{label} = $adverbs;
638             my($adverb_name) = "${lexeme_name}_1";
639              
640             $self -> add_node(name => $adverb_name, %$attributes);
641             $self -> graph -> add_edge(from => $lexeme_name, to => $adverb_name);
642             }
643              
644             } # End of _process_lexeme_default_rule.
645              
646             # ------------------------------------------------
647              
648             sub _process_lexeme_rule
649             {
650             my($self, $index, $a_node) = @_;
651             my($daughters, $adverbs) = $self -> _process_adverbs([$a_node -> daughters]);
652              
653             # rectify_name() returns a ($name => $label) pair.
654              
655             my(@name) = $self -> rectify_name($$daughters[1]);
656             my($lexeme) = $self -> lexemes;
657             $$lexeme{$name[0]} = $#$adverbs >= 0 ? $adverbs : '';
658              
659             $self -> lexemes($lexeme);
660              
661             } # End of _process_lexeme_rule.
662              
663             # ------------------------------------------------
664              
665             sub _process_lexeme_token
666             {
667             my($self, $index, $lexemes, $name, $label) = @_;
668              
669             my($attributes);
670              
671             if ($$lexemes{$name})
672             {
673             $attributes =
674             {
675             fillcolor => 'orchid',
676             label => [{text => "\{\x{a789}lexeme"}, {text => "$label}"}],
677             };
678             }
679             else
680             {
681             $attributes =
682             {
683             fillcolor => 'white',
684             label => $label,
685             };
686             }
687              
688             return $attributes;
689              
690             } # End of _process_lexeme_token.
691              
692             # ------------------------------------------------
693              
694             sub _process_normal_rule
695             {
696             my($self, $index, $a_node, $lexemes) = @_;
697             my($daughters, $adverbs) = $self -> _process_complex_adverbs($index, $a_node);
698              
699             for my $i (0 .. $#$daughters)
700             {
701             if ($#{$$daughters[$i]} >= 0)
702             {
703             $self -> _process_normal_tokens($index, $a_node, $lexemes, $$daughters[$i], $$adverbs[$i]);
704             }
705             }
706              
707             } # End of _process_normal_rule.
708              
709             # ------------------------------------------------
710              
711             sub _process_normal_tokens
712             {
713             my($self, $index, $a_node, $lexemes, $daughters, $adverbs) = @_;
714              
715             # rectify_name() returns a ($name => $label) pair.
716              
717             my(@name_map) = map{$self -> rectify_name($_)} @$daughters;
718             my(@name) = map{$name_map[$_]} indexes{$_ % 2 == 0} 0 .. $#name_map;
719             my(@label) = map{$name_map[$_]} indexes{$_ % 2 != 0} 0 .. $#name_map;
720             my($rule_name) = join(' ', @name);
721             my($rule_label) = join(' ', @label);
722             my($attributes) = $self -> _process_lexeme_token($index, $lexemes, $rule_name, $rule_label);
723             my(@parent) = $self -> rectify_name($a_node);
724              
725             $self -> add_node(name => $rule_name, %$attributes);
726             $self -> graph -> add_edge(from => $parent[0], to => $rule_name);
727              
728             my($attr_name);
729             my($name);
730              
731             for my $i (0 .. $#name)
732             {
733             $name = $name[$i];
734              
735             $attributes = $self -> _process_lexeme_token($index, $lexemes, $name, $label[$i]);
736              
737             # Don't re-add the node added just above.
738             # This happens in cases where there is just 1 daughter,
739             # which mean the join() above only had 1 name to 'join'.
740             # Nevertheless, after this 'if', we still add its attributes (outside the loop).
741              
742             if ($name ne $rule_name)
743             {
744             $self -> add_node(name => $name, %$attributes);
745             $self -> graph -> add_edge(from => $rule_name, to => $name);
746             }
747              
748             if ($$lexemes{$name})
749             {
750             $$attributes{fillcolor} = 'lightblue';
751             $$attributes{label} = $$lexemes{$name};
752             $attr_name = "${name}_$i";
753              
754             $self -> add_node(name => $attr_name, %$attributes);
755             $self -> graph -> add_edge(from => $name, to => $attr_name);
756             }
757             }
758              
759             if ($#$adverbs >= 0)
760             {
761             $$attributes{fillcolor} = 'goldenrod';
762             $$attributes{label} = $adverbs;
763             $attr_name = "${rule_name}_attributes";
764              
765             $self -> add_node(name => $attr_name, %$attributes);
766             $self -> graph -> add_edge(from => $rule_name, to => $attr_name);
767             }
768              
769             } # End of _process_normal_tokens.
770              
771             # ------------------------------------------------
772              
773             sub _process_start_rule
774             {
775             my($self, $index, $a_node) = @_;
776             my(@daughters) = $a_node -> daughters;
777             my(@name) = $self -> rectify_name($daughters[1]);
778              
779             $self -> root_node($daughters[1]);
780              
781             my($attributes) =
782             {
783             fillcolor => 'lightgreen',
784             label => [{text => '{:start'}, {text => "$name[1]}"}],
785             };
786              
787             $self -> add_node(name => $name[0], %$attributes);
788              
789             } # End of _process_start_rule.
790              
791             # ------------------------------------------------
792              
793             sub rectify_name
794             {
795             my($self, $node) = @_;
796             my($attributes) = $node -> attributes;
797             my($name) = $self -> clean_name($$attributes{real_name}, 1);
798             my($label) = $name . $$attributes{quantifier};
799              
800             return ($name, $label);
801              
802             } # End of rectify_name.
803              
804             # ----------------------------------------------
805              
806             sub run
807             {
808             my($self) = @_;
809             my($result) = $self -> parser -> run;
810              
811             # Return 0 for success and 1 for failure.
812              
813             return $result if ($result == 1);
814              
815             $self -> clean_tree;
816              
817             #$self -> log(debug => $_) for @{$self -> parser -> cooked_tree -> tree2string({no_attributes => 0})};
818              
819             my(@rule) = $self -> parser -> cooked_tree -> daughters;
820             my($start_index) = first_index{$_ -> name eq "\x{a789}start"} @rule;
821              
822             # Warning: This must be first because it sets $self -> root_node().
823              
824             $self -> _process_start_rule($start_index + 1, $rule[$start_index]);
825              
826             for my $index (indexes {$_ -> name eq "\x{a789}default"} @rule)
827             {
828             $self -> _process_default_rule($index + 1, $rule[$index]);
829             }
830              
831             for my $index (indexes {$_ -> name eq "\x{a789}discard"} @rule)
832             {
833             $self -> _process_discard_rule($index + 1, $rule[$index]);
834             }
835              
836             my($lexeme_default_index) = first_index{$_ -> name eq 'lexeme default'} @rule;
837              
838             $self -> _process_lexeme_default_rule($lexeme_default_index + 1, $rule[$lexeme_default_index]) if ($lexeme_default_index >= 0);
839              
840             for my $index (indexes {$_ -> name eq "\x{a789}lexeme"} @rule)
841             {
842             $self -> _process_lexeme_rule($index + 1, $rule[$index]);
843             }
844              
845             my(%seen) =
846             (
847             "\x{a789}default" => 1,
848             "\x{a789}discard" => 1,
849             'event' => 1, # Not yet, but we want process_normal_rule() to skip events.
850             "\x{a789}lexeme" => 1,
851             'lexeme default' => 1,
852             "\x{a789}start" => 1,
853             );
854              
855             my($lexemes) = $self -> lexemes;
856              
857             for my $index (0 .. $#rule)
858             {
859             next if ($seen{$rule[$index] -> name});
860              
861             $self -> _process_normal_rule($index + 1, $rule[$index], $lexemes);
862             }
863              
864             for my $index (indexes {$_ -> name eq 'event'} @rule)
865             {
866             $self -> _process_event_rule($index + 1, $rule[$index]);
867             }
868              
869             $self -> add_legend if ($self -> legend);
870              
871             my($output_file) = $self -> output_file;
872              
873             if ($output_file)
874             {
875             $self -> graph -> run(output_file => $output_file);
876             }
877              
878             # Return 0 for success and 1 for failure.
879              
880             return $result;
881              
882             } # End of run.
883              
884             # ------------------------------------------------
885              
886             1;
887              
888             =pod
889              
890             =encoding utf8
891              
892             =head1 NAME
893              
894             L - Convert a Marpa grammar into an image
895              
896             =head1 Synopsis
897              
898             use MarpaX::Grammar::GraphViz2;
899              
900             my(%option) =
901             ( # Inputs:
902             legend => 1,
903             marpa_bnf_file => 'share/metag.bnf',
904             user_bnf_file => 'share/stringparser.bnf',
905             # Outputs:
906             output_file => 'html/stringparser.svg',
907             );
908              
909             MarpaX::Grammar::GraphViz2 -> new(%option) -> run;
910              
911             See share/*.bnf for input files and html/*.svg for output files.
912              
913             For more help, run:
914              
915             shell> perl -Ilib scripts/bnf2graph.pl -h
916              
917             Note: Installation includes copying all files from the share/ directory, into a dir chosen by
918             L. Run scripts/find.grammars.pl to display the name of the latter dir.
919              
920             See also
921             L.
922              
923             =head1 Description
924              
925             For a given BNF, process the cooked tree output by L, and turn it into an
926             image.
927              
928             The tree holds a representation of the user's BNF (SLIF-DSL), and is managed by L.
929              
930             This modules uses L internally. It does not read that module's output file.
931              
932             =head1 Installation
933              
934             Install L as you would for any C module:
935              
936             Run:
937              
938             cpanm MarpaX::Grammar::GraphViz2
939              
940             or run:
941              
942             sudo cpan MarpaX::Grammar::GraphViz2
943              
944             or unpack the distro, and then either:
945              
946             perl Build.PL
947             ./Build
948             ./Build test
949             sudo ./Build install
950              
951             or:
952              
953             perl Makefile.PL
954             make (or dmake or nmake)
955             make test
956             make install
957              
958             Note: Installation includes copying all files from the share/ directory, into a dir chosen by
959             L. Run scripts/find.grammars.pl to display the name of the latter dir.
960              
961             =head1 Constructor and Initialization
962              
963             Call C as C<< my($parser) = MarpaX::Grammar::GraphViz2 -> new(k1 => v1, k2 => v2, ...) >>.
964              
965             It returns a new object of type C.
966              
967             Key-value pairs accepted in the parameter list (see also the corresponding methods
968             [e.g. L]):
969              
970             =over 4
971              
972             =item o driver aGraphvizDriverName
973              
974             The name of the Graphviz program to provide to L.
975              
976             Default: 'dot'.
977              
978             =item o format => $format_name
979              
980             This is the format of the output file, to be passed to L.
981              
982             Default: 'svg'.
983              
984             =item o graph => $graphviz2_object
985              
986             Provides an object of type L, to do the rendering.
987              
988             Default:
989              
990             GraphViz2 -> new
991             (
992             edge => {color => 'grey'},
993             global => {directed => 1, driver => $self -> driver, format => $self -> format},
994             graph => {label => basename($self -> user_bnf_file), rankdir => 'TB'},
995             logger => $self -> logger,
996             node => {shape => 'rectangle', style => 'filled'},
997             );
998              
999             =item o legend => $Boolean
1000              
1001             Add a legend (1) to the graph, or omit it (0).
1002              
1003             Default: 0.
1004              
1005             =item o logger => $logger_object
1006              
1007             Specify a logger object.
1008              
1009             The default value triggers creation of an object of type L which outputs to the
1010             screen.
1011              
1012             To disable logging, just set I to the empty string.
1013              
1014             The value for I is passed to L.
1015              
1016             Default: undef.
1017              
1018             =item o marpa_bnf_file aMarpaBNFFileName
1019              
1020             Specify the name of Marpa's own BNF file. This file ships with L. It's name is
1021             metag.bnf.
1022              
1023             A copy, as of Marpa::R2 V 2.096000, ships with C. See share/metag.bnf.
1024              
1025             This option is mandatory.
1026              
1027             Default: ''.
1028              
1029             =item o maxlevel => $level
1030              
1031             This option is only used if an object of type L is created. See I above.
1032              
1033             See also L.
1034              
1035             Default: 'notice'. A typical value is 'debug'.
1036              
1037             =item o minlevel => $level
1038              
1039             This option is only used if an object of type L is created. See I above.
1040              
1041             See also L.
1042              
1043             Default: 'error'.
1044              
1045             No lower levels are used.
1046              
1047             =item o output_file => $output_file_name
1048              
1049             Write the image to this file.
1050              
1051             Use the C option to specify the type of image desired.
1052              
1053             If '', the file is not written.
1054              
1055             Default: ''.
1056              
1057             =item o user_bnf_file aUserBNFFileName
1058              
1059             Specify the name of the file containing your Marpa::R2-style grammar.
1060              
1061             See share/stringparser.bnf for a sample.
1062              
1063             This option is mandatory.
1064              
1065             Default: ''.
1066              
1067             =back
1068              
1069             =head1 Methods
1070              
1071             =head2 add_legend()
1072              
1073             Adds a legend to the graph if new() was called as C<< new(legend => 1) >>.
1074              
1075             =head2 add_node(%attributes)
1076              
1077             Adds (once only) a node to the graph. The node's name is C<$attributes{name}>.
1078              
1079             Also, adds that name to the hashref of node names seen, which is returned by L.
1080              
1081             =head2 clean_name($name, $skip_symbols)
1082              
1083             Cleans the given name to escape or replace characters special to L.
1084              
1085             Note: L also escapes some characters.
1086              
1087             $skip_symbols is used by the caller in 1 case to stop a regexp being activated.
1088              
1089             See the L for details.
1090              
1091             Returns the cleaned-up name.
1092              
1093             =head2 clean_tree()
1094              
1095             Calls L for each node in the tree.
1096              
1097             =head2 default_count()
1098              
1099             Returns the number of C<:default>' rules in the user's input.
1100              
1101             =head2 discard_count()
1102              
1103             Returns the number of C<:discard> rules in the user's input.
1104              
1105             =head2 driver([$executable_name])
1106              
1107             Here, the [] indicate an optional parameter.
1108              
1109             Get or set the name of the Graphviz program to provide to L.
1110              
1111             Note: C is a parameter to new().
1112              
1113             =head2 event_count()
1114              
1115             Returns the number of C rules in the user's input.
1116              
1117             =head2 format([$format])
1118              
1119             Here, the [] indicate an optional parameter.
1120              
1121             Get or set the format of the output file, to be created by the renderer.
1122              
1123             Note: C is a parameter to new().
1124              
1125             =head2 graph([$graph])
1126              
1127             Get of set the L object which will do the graphing.
1128              
1129             See also L.
1130              
1131             Note: C is a parameter to new().
1132              
1133             =head2 legend([$Boolean])
1134              
1135             Here, the [] indicate an optional parameter.
1136              
1137             Get or set the option to include (1) or exclude (0) a legend from the image.
1138              
1139             Note: C is a parameter to new().
1140              
1141             =head2 lexeme_count()
1142              
1143             Returns the number of C<:lexeme> rules in the user's input.
1144              
1145             =head2 lexemes()
1146              
1147             Returns a hashref keyed by the clean name, of lexemes seen in the user's input.
1148              
1149             The value for each key is an arrayref of hashrefs suitable for forcing L to plot the node
1150             as a record structure. See L for the gory
1151             details.
1152              
1153             =head2 log($level, $s)
1154              
1155             Calls $self -> logger -> log($level => $s) if ($self -> logger).
1156              
1157             =head2 logger([$logger_object])
1158              
1159             Here, the [] indicate an optional parameter.
1160              
1161             Get or set the logger object.
1162              
1163             To disable logging, just set logger to the empty string.
1164              
1165             This logger is passed to L.
1166              
1167             Note: C is a parameter to new().
1168              
1169             =head2 marpa_bnf_file([$bnf_file_name])
1170              
1171             Here, the [] indicate an optional parameter.
1172              
1173             Get or set the name of the file to read Marpa's grammar from.
1174              
1175             Note: C is a parameter to new().
1176              
1177             =head2 maxlevel([$string])
1178              
1179             Here, the [] indicate an optional parameter.
1180              
1181             Get or set the value used by the logger object.
1182              
1183             This option is only used if an object of type L is created. See
1184             L.
1185              
1186             Note: C is a parameter to new().
1187              
1188             =head2 minlevel([$string])
1189              
1190             Here, the [] indicate an optional parameter.
1191              
1192             Get or set the value used by the logger object.
1193              
1194             This option is only used if an object of type L is created. See
1195             L.
1196              
1197             Note: C is a parameter to new().
1198              
1199             =head2 new()
1200              
1201             The constructor. See L.
1202              
1203             =head2 nodes_seen()
1204              
1205             Returns a hashref keyed by the node name, of nodes passed to L.
1206              
1207             This is simply used to stop nodes being plotted twice.
1208              
1209             =head2 output_file([$output_file_name])
1210              
1211             Here, the [] indicate an optional parameter.
1212              
1213             Get or set the name of the file to which the renderer will write the resultant graph.
1214              
1215             If no output file is supplied, nothing is written.
1216              
1217             See also L.
1218              
1219             Note: C is a parameter to new().
1220              
1221             =head2 parser()
1222              
1223             Returns the L object which will do the analysis of the user's grammar.
1224              
1225             This object is created automatically during the call to L.
1226              
1227             =head2 rectify_node($node)
1228              
1229             For the given $node, which is an object of type L, clean it's real name.
1230              
1231             Then it adds the node's quantifier ('', '*' or '+') to that name, to act as the label (visible name)
1232             of the node, when the node is finally passed to L.
1233              
1234             Returns a 2-element list of ($name, $label).
1235              
1236             =head2 root_node()
1237              
1238             Returns an object of type L, representing the C<:start> token in the user's grammar.
1239              
1240             =head2 run()
1241              
1242             The method which does all the work.
1243              
1244             See L and scripts/bnf2graph.pl for sample code.
1245              
1246             =head2 separators()
1247              
1248             Returns a hashref keyed by token name, of tokens used in the grammar construc
1249             C<< separator => $token >>.
1250              
1251             This hashref is currently not used.
1252              
1253             =head2 user_bnf_file([$bnf_file_name])
1254              
1255             Here, the [] indicate an optional parameter.
1256              
1257             Get or set the name of the file to read the user's grammar from.
1258              
1259             Note: C is a parameter to new().
1260              
1261             =head1 Files Shipped with this Module
1262              
1263             =head2 Data Files
1264              
1265             =over 4
1266              
1267             =item o share/c.ast.bnf
1268              
1269             This is part of L, by Jean-Damien Durand. It's 1,565 lines long.
1270              
1271             =item o html/c.ast.svg
1272              
1273             This is the image from c.ast.bnf.
1274              
1275             See the next point for how this file is created.
1276              
1277             =item o share/c.ast.log
1278              
1279             This is the log produced by running the code at log level C:
1280              
1281             shell> scripts/bnf2graph.sh c.ast -max debug > share/c.ast.log
1282              
1283             =item o share/json.1.bnf
1284              
1285             It is part of L, written as a gist by Peter Stuifzand.
1286              
1287             See L.
1288              
1289             See the next point for how this file is created.
1290              
1291             =item o share/json.1.log
1292              
1293             This is the log produced by running the code at log level C:
1294              
1295             shell> scripts/bnf2graph.sh json.1 -max debug > share/json.1.log
1296              
1297             =item o html/json.1.svg
1298              
1299             This is the image from json.1.bnf.
1300              
1301             =item o share/json.2.bnf
1302              
1303             It also is part of L, written by Jeffrey Kegler as a reply to the gist
1304             above from Peter.
1305              
1306             =item o share/json.2.log
1307              
1308             This is the log produced by running the code at log level C:
1309              
1310             shell> scripts/bnf2graph.sh json.2 -max debug > share/json.2.log
1311              
1312             =item o html/json.2.svg
1313              
1314             This is the image from json.2.bnf.
1315              
1316             See the previous point for how this file is created.
1317              
1318             =item o share/json.3.bnf
1319              
1320             It also is part of L, and is written by Jeffrey Kegler.
1321              
1322             =item o share/json.3.log
1323              
1324             This is the log produced by running the code at log level C:
1325              
1326             shell> scripts/bnf2graph.sh json.3 -max debug > share/json.3.log
1327              
1328             =item o html/json.3.svg
1329              
1330             This is the image from json.3.bnf.
1331              
1332             =item o share/metag.bnf.
1333              
1334             This is a copy of L's BNF, as of Marpa::R2 V 2.096000.
1335              
1336             See L above.
1337              
1338             =item o share/stringparser.bnf.
1339              
1340             This is a copy of L's BNF.
1341              
1342             =item o html/stringparser.svg
1343              
1344             This is the image from stringparser.bnf.
1345              
1346             See the next point for how this file is created.
1347              
1348             =item o share/stringparser.log
1349              
1350             This is the log produced by running the code at log level C:
1351              
1352             shell> scripts/bnf2graph.sh stringparser -max debug > share/stringparser.log
1353              
1354             See L above.
1355              
1356             =item o share/termcap.info.bnf
1357              
1358             It also is part of L, written by Jean-Damien Durand.
1359              
1360             =item o html/termcap.info.svg
1361              
1362             This is the image from termcap.info.bnf.
1363              
1364             See the next point for how this file is created.
1365              
1366             =item o share/termcap.info.log
1367              
1368             This is the log produced by running the code at log level C:
1369              
1370             shell> scripts/bnf2graph.sh termcap.info -max debug > share/termcap.info.log
1371              
1372             =back
1373              
1374             =head2 Scripts
1375              
1376             =over 4
1377              
1378             =item o scripts/bnf2graph.pl
1379              
1380             This is a neat way of using the module. For help, run:
1381              
1382             shell> perl -Ilib scripts/bnf2graph.pl -h
1383              
1384             Of course you are also encouraged to include this module directly in your own code.
1385              
1386             =item o scripts/bnf2graph.sh
1387              
1388             This is a quick way for me to run bnf2graph.pl.
1389              
1390             =item o scripts/find.grammars.pl
1391              
1392             This prints the path to a grammar file. After installation of the module, run it with:
1393              
1394             shell> perl scripts/find.grammars.pl (Defaults to json.1.bnf)
1395             shell> perl scripts/find.grammars.pl c.ast.bnf
1396             shell> perl scripts/find.grammars.pl json.1.bnf
1397             shell> perl scripts/find.grammars.pl json.2.bnf
1398             shell> perl scripts/find.grammars.pl stringparser.bnf
1399             shell> perl scripts/find.grammars.pl termcap.inf.bnf
1400              
1401             It will print the name of the path to given grammar file.
1402              
1403             =item o scripts/generate.demo.pl
1404              
1405             Generates html/index.html.
1406              
1407             =item o scripts/generate.demo.sh
1408              
1409             This calls generate.demo.pl for each grammar shipped with the module.
1410              
1411             Actually, it skips c.ast by default, since it takes 6 m 47 s to run that. But if you pass any
1412             command line parameter to the script, it includes c.ast.
1413              
1414             Then it copies html/* to my web server's doc root (which is in Debian's default RAM disk) at
1415             /dev/shm/html.
1416              
1417             =item o scripts/pod2html.sh
1418              
1419             This lets me quickly proof-read edits to the docs.
1420              
1421             =back
1422              
1423             =head1 FAQ
1424              
1425             =head2 Why are some characters in the images replaced by Unicode versions?
1426              
1427             Firstly, the Perl module L escapes some characters. Currently, these are:
1428              
1429             [ ] " (in various circumstances)
1430              
1431             We let L handle these.
1432              
1433             Secondly, L itself treats some characters specially. Currently, these
1434             are:
1435              
1436             < > : "
1437              
1438             We use this code to handle these:
1439              
1440             $name =~ s/\\/\\\\/g; # Escape \.
1441             $name =~ s/
1442             $name =~ s/>/\\>/g; # Escape >.
1443             $name =~ s/:/\x{a789}/g; # Replace : with a Unicode :
1444             $name =~ s/\"/\x{a78c}\x{a78c}/g; # Replace " with 2 copies of a Unicode ' ...
1445             # ... because I could not find a Unicode ".
1446              
1447             =head2 Why do some images have a tiny sub-graph, whose root is, e.g., ''?
1448              
1449             This is due to the author using both 'comma' and '' as tokens within the grammar.
1450              
1451             So far this module does not notice the two are the same.
1452              
1453             A similar thing can happen elsewhere, e.g. with named event statements, when the rhs name uses (say)
1454             ''
1455             and the rule referred to uses just 'xyz'.
1456              
1457             In all such cases, there will be 2 nodes, with 2 names differing in just the brackets.
1458              
1459             =head2 Why do some nodes have (or lack) a quantifier when I use it both with and without one?
1460              
1461             There is simply no way to plot a node both with and without the quantifier. The one which appears is
1462             chosen arbitrarily, depending on how the code scans the grammar. This means it is currently beyond
1463             control.
1464              
1465             =head2 Why do the nodes on the demo page lack rule numbers?
1466              
1467             I'm undecided as to whether or not they are a good idea. I documented it on the demo page to
1468             indicate it was easy (for some nodes), and await feedback.
1469              
1470             =head2 Can I control the format or placement of the legend?
1471              
1472             No, but you can turn it off with the C option to C<< new() >>.
1473              
1474             =head1 ToDo
1475              
1476             =over 4
1477              
1478             =item o Perhaps add rule # to each node
1479              
1480             This is the rule # within the input stream. Doing this is simple for some nodes, and difficult for
1481             others.
1482              
1483             =back
1484              
1485             =head1 Machine-Readable Change Log
1486              
1487             The file Changes was converted into Changelog.ini by L.
1488              
1489             =head1 Version Numbers
1490              
1491             Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.
1492              
1493             =head1 Repository
1494              
1495             L
1496              
1497             =head1 Support
1498              
1499             Email the author, or log a bug on RT:
1500              
1501             L.
1502              
1503             =head1 Author
1504              
1505             L was written by Ron Savage Iron@savage.net.auE> in 2013.
1506              
1507             Home page: L.
1508              
1509             =head1 Copyright
1510              
1511             Australian copyright (c) 2013, Ron Savage.
1512              
1513             All Programs of mine are 'OSI Certified Open Source Software';
1514             you can redistribute them and/or modify them under the terms of
1515             The Artistic License, a copy of which is available at:
1516             http://www.opensource.org/licenses/index.html
1517              
1518             =cut