File Coverage

blib/lib/Graph/Easy/Marpa/Parser.pm
Criterion Covered Total %
statement 52 253 20.5
branch 5 94 5.3
condition 0 36 0.0
subroutine 16 32 50.0
pod 8 17 47.0
total 81 432 18.7


line stmt bran cond sub pod time code
1             package Graph::Easy::Marpa::Parser;
2              
3 3     3   750512 use strict;
  3         7  
  3         113  
4 3     3   2294 use utf8;
  3         30  
  3         21  
5 3     3   97 use warnings;
  3         10  
  3         117  
6 3     3   16 use warnings qw(FATAL utf8); # Fatalize encoding glitches.
  3         7  
  3         226  
7 3     3   1941 use open qw(:std :utf8); # Undeclared streams in UTF-8.
  3         2673  
  3         17  
8 3     3   6391 use charnames qw(:full :short); # Unneeded in v5.16.
  3         84579  
  3         22  
9              
10             # The next line is mandatory, else
11             # the action names cannot be resolved.
12              
13 3     3   2780 use Graph::Easy::Marpa::Actions;
  3         10  
  3         109  
14              
15 3     3   4181 use Log::Handler;
  3         185630  
  3         32  
16              
17 3     3   3179 use Marpa::R2;
  3         463596  
  3         56  
18              
19 3     3   2369 use Moo;
  3         33698  
  3         21  
20              
21 3     3   4371 use Set::Array;
  3         6  
  3         36  
22              
23 3     3   7198 use Text::CSV;
  3         45525  
  3         25  
24              
25 3     3   134 use Try::Tiny;
  3         7  
  3         19233  
26              
27             has description =>
28             (
29             default => sub{return ''},
30             is => 'rw',
31             # isa => 'Str',
32             required => 0,
33             );
34              
35             has grammar =>
36             (
37             default => sub{return ''},
38             is => 'rw',
39             # isa => 'Marpa::R2::Scanless::G',
40             required => 0,
41             );
42              
43             has graph_text =>
44             (
45             default => sub{return ''},
46             is => 'rw',
47             # isa => 'Str',
48             required => 0,
49             );
50              
51             has input_file =>
52             (
53             default => sub{return ''},
54             is => 'rw',
55             # isa => 'Str',
56             required => 0,
57             );
58              
59             has items =>
60             (
61             default => sub{return ''},
62             is => 'rw',
63             # isa => 'Set::Array',
64             required => 0,
65             );
66              
67             has logger =>
68             (
69             default => sub{return undef},
70             is => 'rw',
71             # isa => 'Str',
72             required => 0,
73             );
74              
75             has maxlevel =>
76             (
77             default => sub{return 'info'},
78             is => 'rw',
79             # isa => 'Str',
80             required => 0,
81             );
82              
83             has minlevel =>
84             (
85             default => sub{return 'error'},
86             is => 'rw',
87             # isa => 'Str',
88             required => 0,
89             );
90              
91             has recce =>
92             (
93             default => sub{return ''},
94             is => 'rw',
95             # isa => 'Marpa::R2::Scanless::R',
96             required => 0,
97             );
98              
99             has report_tokens =>
100             (
101             default => sub{return 0},
102             is => 'rw',
103             # isa => 'Int',
104             required => 0,
105             );
106              
107             has subgraph_name =>
108             (
109             default => sub{return {} },
110             is => 'rw',
111             # isa => 'String',
112             required => 0,
113             );
114              
115             has token_file =>
116             (
117             default => sub{return ''},
118             is => 'rw',
119             # isa => 'Str',
120             required => 0,
121             );
122              
123             our $VERSION = '2.01';
124              
125             # ------------------------------------------------
126              
127             sub attribute_list
128             {
129 0     0 0 0 my($self, $attribute_list) = @_;
130 0         0 my(@char) = split(//, $attribute_list);
131 0         0 my($inside_name) = 1;
132 0         0 my($inside_value) = 0;
133 0         0 my($quote) = '';
134 0         0 my($name) = '';
135 0         0 my($previous_char) = '';
136              
137 0         0 my($char);
138             my(%attribute);
139 0         0 my($key);
140 0         0 my($value);
141              
142 0         0 for my $i (0 .. $#char)
143             {
144 0         0 $char = $char[$i];
145              
146             # Name matches /^[a-zA-Z_]+$/.
147              
148 0 0       0 if ($inside_name)
    0          
149             {
150 0 0       0 next if ($char =~ /\s/);
151              
152 0 0       0 if ($char eq ':')
    0          
153             {
154 0         0 $self -> log(debug => "Attribute name: $name");
155              
156 0         0 $inside_name = 0;
157 0         0 $key = $name;
158 0         0 $name = '';
159             }
160             elsif ($char =~ /[a-zA-Z_]/)
161             {
162 0         0 $name .= $char;
163             }
164             else
165             {
166 0         0 die "The char '$char' is not allowed in the names of attributes\n";
167             }
168             }
169             elsif ($inside_value)
170             {
171 0 0 0     0 if ($char eq $quote)
    0          
172             {
173             # Get out of quotes if matching one found.
174             # But, ignore an escaped quote.
175             # The first 2 backslashes are just to fix syntax highlighting in UltraEdit.
176              
177 0 0       0 if ($char =~ /[\"\']/)
178             {
179 0 0       0 if ($previous_char ne '\\')
180             {
181 0         0 $quote = '';
182             }
183             }
184             else
185             {
186 0 0 0     0 if ( (substr($value, 0, 2) eq '<<') && ($i > 0) && ($char[$i - 1]) eq '>')
    0 0        
      0        
      0        
187             {
188 0         0 $quote = '';
189             }
190             elsif ( (substr($value, 0, 1) eq '<') && (substr($value, 1, 1) ne '<') && ($previous_char ne '\\') )
191             {
192 0         0 $quote = '';
193             }
194             }
195              
196 0         0 $value .= $char;
197             }
198             elsif ( ($char eq ';') && ($quote eq '') )
199             {
200 0 0       0 if ($previous_char eq '\\')
201             {
202 0         0 $value .= $char;
203             }
204             else
205             {
206 0         0 $attribute{$key} = $value;
207              
208 0         0 $self -> log(debug => "Attribute value: $value");
209              
210 0         0 $inside_name = 1;
211 0         0 $inside_value = 0;
212 0         0 $quote = '';
213 0         0 $key = '';
214 0         0 $value = '';
215             }
216             }
217             else
218             {
219 0         0 $value .= $char;
220             }
221             }
222             else # After name and ':' but before label.
223             {
224 0 0       0 next if ($char =~ /\s/);
225              
226 0         0 $inside_value = 1;
227 0         0 $value = $char;
228              
229             # Look out for quotes, amd make '<' match '>'.
230             # The backslashes are just to fix syntax highlighting in UltraEdit.
231             # Also, this being the 1st char in the value, there can't be a '\' before it.
232              
233 0 0       0 if ($char =~ /[\"\'<]/)
234             {
235 0 0       0 $quote = $char eq '<' ? '>' : $char;
236             }
237             }
238              
239 0         0 $previous_char = $char;
240             }
241              
242             # Beware {a:b;}. In this case, the ';' leaves $key eq ''.
243              
244 0 0       0 if (length $key)
245             {
246 0         0 $attribute{$key} = $value;
247              
248 0         0 $self -> log(debug => "Attribute value: $value");
249             }
250              
251 0         0 for $key(sort keys %attribute)
252             {
253 0         0 $value = $attribute{$key};
254 0         0 $value =~ s/\s+$//;
255              
256             # The first 2 backslashes are just to fix syntax highlighting in UltraEdit.
257              
258 0         0 $value =~ s/^([\"\'])(.*)\1$/$2/;
259              
260 0         0 $self -> log(debug => "Attribute: $key => $value");
261              
262 0         0 $self -> items -> push
263             ({
264             name => $key,
265             type => 'attribute',
266             value => $value,
267             });
268             }
269              
270             } # End of attribute_list.
271              
272             # --------------------------------------------------
273             # References from an email from Jeffrey to the Marpa Google Groups list:
274             # Check out the SLIF grammar:
275             # .
276             # It's full of stuff you can steal, including rules for quoted strings.
277             # The basic idea is that strings must be G0 lexemes, not assembled in G1 as your (Paul Bennett) gist has it.
278             # Jean-Damien's C language BNF:
279             #
280             # is also full of stuff to do all the C syntax, including strings and C-style comments. -- jeffrey
281              
282             sub BUILD
283             {
284 85     85 0 714 my($self) = @_;
285              
286 85 50       582 if (! defined $self -> logger)
287             {
288 85         696 $self -> logger(Log::Handler -> new);
289 85         7344 $self -> logger -> add
290             (
291             screen =>
292             {
293             maxlevel => $self -> maxlevel,
294             message_layout => '%m',
295             minlevel => $self -> minlevel,
296             }
297             );
298             }
299              
300 85         65399 $self -> items(Set::Array -> new);
301              
302 85         1966 $self -> grammar
303             (
304             Marpa::R2::Scanless::G -> new
305             ({
306             action_object => 'Graph::Easy::Marpa::Actions',
307             source => \(<<'END_OF_SOURCE'),
308              
309             :default ::= action => [values]
310              
311             # Overall stuff.
312              
313             :start ::= graph_grammar
314              
315             graph_grammar ::= class_and_graph action => graph
316              
317             class_and_graph ::= class_definition graph_definition
318              
319             # Class stuff.
320              
321             class_definition ::= class_statement*
322              
323             # This uses attribute_statement and not attribute_definition
324             # because attributes are mandatory after class names.
325              
326             class_statement ::= class_lexeme attribute_statement
327              
328             :lexeme ~ class_lexeme pause => before event => class
329             class_lexeme ~ [a-z.]+
330              
331             # Graph stuff.
332              
333             graph_definition ::= node_definition
334             | edge_definition
335             | subgraph_definition
336             # Node stuff
337              
338             node_definition ::= node_statement
339             | node_statement graph_definition
340              
341             node_statement ::= node_name
342             | node_name attribute_definition
343             | node_statement (',') node_statement
344              
345             node_name ::= start_node end_node
346              
347             :lexeme ~ start_node pause => before event => start_node
348             start_node ~ '['
349              
350             :lexeme ~ end_node
351             end_node ~ ']'
352              
353             # Edge stuff
354              
355             edge_definition ::= edge_statement
356             | edge_statement graph_definition
357              
358             edge_statement ::= edge_name
359             | edge_name attribute_definition
360             | edge_statement (',') edge_statement
361              
362             edge_name ::= directed_edge
363             | undirected_edge
364              
365             :lexeme ~ directed_edge pause => before event => directed_edge
366             directed_edge ~ '->'
367              
368             :lexeme ~ undirected_edge pause => before event => undirected_edge
369             undirected_edge ~ '--'
370              
371             # Attribute stuff.
372              
373             attribute_definition ::= attribute_statement*
374              
375             attribute_statement ::= start_attributes end_attributes
376              
377             :lexeme ~ start_attributes pause => before event => start_attributes
378             start_attributes ~ '{'
379              
380             :lexeme ~ end_attributes
381             end_attributes ~ '}'
382              
383             # subgraph stuff.
384              
385             subgraph_definition ::= subgraph_sequence
386             | subgraph_sequence graph_definition
387              
388             subgraph_sequence ::= subgraph_statement
389             | subgraph_statement attribute_definition
390              
391             subgraph_statement ::= subgraph_prefix subgraph_name (':') graph_definition subgraph_suffix
392              
393             subgraph_prefix ::= '('
394             subgraph_name ::= subgraph_name_lexeme
395             subgraph_suffix ::= subgraph_suffix_lexeme
396              
397             :lexeme ~ subgraph_name_lexeme pause => before event => push_subgraph
398             subgraph_name_lexeme ~ [a-zA-Z_.0-9]+
399              
400             :lexeme ~ subgraph_suffix_lexeme pause => before event => pop_subgraph
401             subgraph_suffix_lexeme ~ ')'
402              
403             # Boilerplate.
404              
405             :discard ~ whitespace
406             whitespace ~ [\s]+
407              
408             END_OF_SOURCE
409             })
410             );
411              
412 85         8642900 $self -> recce
413             (
414             Marpa::R2::Scanless::R -> new
415             ({
416             grammar => $self -> grammar
417             })
418             );
419              
420             } # End of BUILD.
421              
422             # ------------------------------------------------
423              
424             sub class
425             {
426 0     0 1 0 my($self, $class_name) = @_;
427              
428 0         0 $self -> log(debug => "Class: $class_name");
429              
430 0         0 my($reserved_class_name) = 'edge|global|graph|group|node';
431              
432 0 0       0 if ($class_name !~ /^(?:$reserved_class_name)(?:\.[a-zA-Z_]+)?$/)
433             {
434 0         0 die "Class name '$class_name' must be one of '$reserved_class_name'\n";
435             }
436              
437             $self -> items -> push
438             ({
439 0         0 name => $class_name,
440             type => 'class',
441             value => '',
442             });
443              
444             } # End of class.
445              
446             # ------------------------------------------------
447              
448             sub edge
449             {
450 0     0 0 0 my($self, $edge_name) = @_;
451              
452 0         0 $self -> log(debug => "Edge: $edge_name");
453              
454 0         0 $self -> items -> push
455             ({
456             name => $edge_name,
457             type => 'edge',
458             value => '',
459             });
460              
461             } # End of edge.
462              
463             # -----------------------------------------------
464             # $target is either qr/]/ or qr/}/, and allows us to handle
465             # both node names and either edge or node attributes.
466             # The special case is <<...>>, as used in attributes.
467              
468             sub find_terminator
469             {
470 0     0 0 0 my($self, $string, $target, $start) = @_;
471 0         0 my(@char) = split(//, substr($$string, $start) );
472 0         0 my($offset) = 0;
473 0         0 my($quote) = '';
474 0         0 my($angle) = 0; # Set to 1 if inside <<...>>.
475              
476 0         0 my($char);
477              
478 0         0 for my $i (0 .. $#char)
479             {
480 0         0 $char = $char[$i];
481 0         0 $offset = $i;
482              
483 0 0       0 if ($quote)
484             {
485             # Ignore an escaped quote.
486             # The first 2 backslashes are just to fix syntax highlighting in UltraEdit.
487              
488 0 0 0     0 next if ( ($char =~ /[\]\"\'>]/) && ($i > 0) && ($char[$i - 1] eq '\\') );
      0        
489              
490             # Get out of quotes if matching one found.
491              
492 0 0       0 if ($char eq $quote)
493             {
494 0 0       0 if ($quote eq '>')
495             {
496 0 0 0     0 $quote = '' if (! $angle || ($char[$i - 1] eq '>') );
497              
498 0         0 next;
499             }
500              
501 0         0 $quote = '';
502              
503 0         0 next;
504             }
505             }
506             else
507             {
508             # Look for quotes.
509             # 1: Skip escaped chars.
510              
511 0 0 0     0 next if ( ($i > 0) && ($char[$i - 1] eq '\\') );
512              
513             # 2: " and '.
514             # The backslashes are just to fix syntax highlighting in UltraEdit.
515              
516 0 0       0 if ($char =~ /[\"\']/)
517             {
518 0         0 $quote = $char;
519              
520 0         0 next;
521             }
522              
523             # 3: <.
524             # In the case of attributes ($target eq '}') but not nodes names,
525             # quotes can be <...> or <<...>>.
526              
527 0 0 0     0 if ( ($target =~ '}') && ($char =~ '<') )
528             {
529 0         0 $quote = '>';
530 0 0 0     0 $angle = 1 if ( ($i < $#char) && ($char[$i + 1] eq '<') );
531              
532 0         0 next;
533             }
534              
535 0 0       0 last if ($char =~ $target);
536             }
537             }
538              
539 0         0 return $start + $offset;
540              
541             } # End of find_terminator.
542              
543             # -----------------------------------------------
544              
545             sub format_token
546             {
547 0     0 0 0 my($self, $item) = @_;
548 0         0 my($format) = '%4s %-13s %-s';
549 0         0 my($value) = $$item{name};
550 0 0       0 $value = "$value => $$item{value}" if (length($$item{value}) > 0);
551              
552 0         0 return sprintf($format, $$item{count}, $$item{type}, $value);
553              
554             } # End of format_token.
555              
556             # --------------------------------------------------
557              
558             sub generate_token_file
559             {
560 0     0 1 0 my($self, $file_name) = @_;
561 0         0 my($csv) = Text::CSV -> new
562             ({
563             always_quote => 1,
564             binary => 1,
565             });
566              
567 0 0       0 open(OUT, '>', $file_name) || die "Can't open(> $file_name): $!";
568              
569             # Don't call binmode here, because we're already using it.
570              
571 0         0 $csv -> print(\*OUT, ['key', 'name', 'value']);
572 0         0 print OUT "\n";
573              
574 0         0 for my $item ($self -> items -> print)
575             {
576 0         0 $csv -> print(\*OUT, [$$item{type}, $$item{name}, $$item{value}]);
577 0         0 print OUT "\n";
578             }
579              
580 0         0 close OUT;
581              
582             } # End of generate_token_file.
583              
584             # --------------------------------------------------
585              
586             sub get_graph_from_command_line
587             {
588 0     0 1 0 my($self) = @_;
589              
590 0         0 $self -> graph_text($self -> description);
591              
592             } # End of get_graph_from_command_line.
593              
594             # --------------------------------------------------
595              
596             sub get_graph_from_file
597             {
598 75     75 1 166 my($self) = @_;
599              
600             # This code accepts utf8 data, due to the standard preamble above.
601              
602 75 50       7355 open(INX, $self -> input_file) || die "Can't open input file(" . $self -> input_file . "): $!\n";
603 0         0 my(@line) = ;
604 0         0 close INX;
605 0         0 chomp @line;
606              
607 0   0     0 shift(@line) while ( ($#line >= 0) && ($line[0] =~ /^\s*#/) );
608              
609 0         0 $self -> graph_text(join(' ', @line) );
610              
611             } # End of get_graph_from_file.
612              
613             # --------------------------------------------------
614              
615             sub log
616             {
617 0     0 1 0 my($self, $level, $s) = @_;
618              
619 0 0       0 $self -> logger -> log($level => $s) if ($self -> logger);
620              
621             } # End of log.
622              
623             # ------------------------------------------------
624              
625             sub node
626             {
627 0     0 0 0 my($self, $node_name) = @_;
628 0         0 $node_name =~ s/^\s+//;
629 0         0 $node_name =~ s/\s+$//;
630              
631             # The first 2 backslashes are just to fix syntax highlighting in UltraEdit.
632              
633 0         0 $node_name =~ s/^([\"\'])(.*)\1$/$2/;
634              
635 0         0 $self -> log(debug => "Node: $node_name");
636              
637 0         0 $self -> items -> push
638             ({
639             name => $node_name,
640             type => 'node',
641             value => '',
642             });
643              
644 0 0       0 if ($node_name eq '')
645             {
646 0         0 $self -> items -> push
647             ({
648             name => 'color',
649             type => 'attribute',
650             value => 'invis',
651             });
652             }
653              
654             } # End of node.
655              
656             # --------------------------------------------------
657              
658             sub process
659             {
660 0     0 0 0 my($self) = @_;
661 0         0 my($string) = $self -> graph_text;
662 0         0 my($length) = length $string;
663              
664             # We use read()/lexeme_read()/resume() because we pause at each lexeme.
665              
666 0         0 my($attribute_list);
667             my($do_lexeme_read);
668 0         0 my(@event, $event_name);
669 0         0 my($lexeme_name, $lexeme);
670 0         0 my($node_name);
671 0         0 my($span, $start);
672              
673 0         0 for
674             (
675             my $pos = $self -> recce -> read(\$string);
676             $pos < $length;
677             $pos = $self -> recce -> resume($pos)
678             )
679             {
680 0         0 $self -> log(debug => "read() => pos: $pos");
681              
682 0         0 $do_lexeme_read = 1;
683 0         0 @event = @{$self -> recce -> events};
  0         0  
684 0         0 $event_name = ${$event[0]}[0];
  0         0  
685 0         0 ($start, $span) = $self -> recce -> pause_span;
686 0         0 $lexeme_name = $self -> recce -> pause_lexeme;
687 0         0 $lexeme = $self -> recce -> literal($start, $span);
688              
689 0         0 $self -> log(debug => "pause_span($lexeme_name) => start: $start. " .
690             "span: $span. lexeme: $lexeme. event: $event_name");
691              
692 0 0       0 if ($event_name eq 'start_attributes')
    0          
    0          
    0          
    0          
    0          
    0          
693             {
694             # Read the attribute_start lexeme, but don't do lexeme_read()
695             # at the bottom of the for loop, because we're just about
696             # to fiddle $pos to skip the attributes.
697              
698 0         0 $pos = $self -> recce -> lexeme_read($lexeme_name);
699 0         0 $pos = $self -> find_terminator(\$string, qr/}/, $start);
700 0         0 $attribute_list = substr($string, $start + 1, $pos - $start - 1);
701 0         0 $do_lexeme_read = 0;
702              
703 0         0 $self -> log(debug => "index() => attribute list: $attribute_list");
704              
705 0         0 $self -> attribute_list($attribute_list);
706             }
707             elsif ($event_name eq 'start_node')
708             {
709             # Read the node_start lexeme, but don't do lexeme_read()
710             # at the bottom of the for loop, because we're just about
711             # to fiddle $pos to skip the node's name.
712              
713 0         0 $pos = $self -> recce -> lexeme_read($lexeme_name);
714 0         0 $pos = $self -> find_terminator(\$string, qr/]/, $start);
715 0         0 $node_name = substr($string, $start + 1, $pos - $start - 1);
716 0         0 $do_lexeme_read = 0;
717              
718 0         0 $self -> log(debug => "index() => node name: $node_name");
719              
720 0         0 $self -> node($node_name);
721             }
722             elsif ($event_name eq 'directed_edge')
723             {
724 0         0 $self -> edge($lexeme);
725             }
726             elsif ($event_name eq 'undirected_edge')
727             {
728 0         0 $self -> edge($lexeme);
729             }
730             elsif ($event_name eq 'class_lexeme')
731             {
732 0         0 $self -> class($lexeme);
733             }
734             elsif ($event_name eq 'push_subgraph')
735             {
736 0         0 $self -> push_subgraph($lexeme);
737             }
738             elsif ($event_name eq 'pop_subgraph')
739             {
740 0         0 $self -> pop_subgraph($lexeme);
741             }
742             else
743             {
744 0         0 die "Unexpected lexeme '$lexeme_name' with a pause\n";
745             }
746              
747 0 0       0 $pos = $self -> recce -> lexeme_read($lexeme_name) if ($do_lexeme_read);
748              
749 0         0 $self -> log(debug => "lexeme_read($lexeme_name) => $pos");
750             }
751              
752             # Return a defined value for success and undef for failure.
753              
754 0         0 return $self -> recce -> value;
755              
756             } # End of process.
757              
758             # ------------------------------------------------
759              
760             sub pop_subgraph
761             {
762 0     0 0 0 my($self, $subgraph_suffix) = @_;
763 0         0 my($subgraph_name) = $self -> subgraph_name;
764              
765 0         0 $self -> log(debug => "Pop subgraph: $subgraph_name");
766              
767 0         0 $self -> items -> push
768             ({
769             name => $subgraph_name,
770             type => 'pop_subgraph',
771             value => '',
772             });
773              
774 0         0 $self -> subgraph_name('');
775              
776             } # End of pop_subgraph.
777              
778             # ------------------------------------------------
779              
780             sub push_subgraph
781             {
782 0     0 0 0 my($self, $subgraph_name) = @_;
783              
784 0         0 $self -> log(debug => "Push subgraph: $subgraph_name");
785              
786 0         0 my($subgraph_name_regexp) = '^(?:[a-zA-Z_.][a-zA-Z_.0-9]*)$';
787              
788 0 0       0 if ($subgraph_name !~ /^$subgraph_name_regexp/)
789             {
790 0         0 die "Subgraph name '$subgraph_name' must match '$subgraph_name_regexp'\n";
791             }
792              
793             $self -> items -> push
794             ({
795 0         0 name => $subgraph_name,
796             type => 'push_subgraph',
797             value => '',
798             });
799              
800 0         0 $self -> subgraph_name($subgraph_name);
801              
802             } # End of push_subgraph.
803              
804             # -----------------------------------------------
805              
806             sub renumber_items
807             {
808 0     0 1 0 my($self) = @_;
809 0         0 my(@item) = @{$self -> items};
  0         0  
810 0         0 my($count) = 0;
811              
812 0         0 my(@new);
813              
814 0         0 for my $item (@item)
815             {
816 0         0 $$item{count} = ++$count;
817              
818 0         0 push @new, $item;
819             }
820              
821 0         0 $self -> items(Set::Array -> new(@new) );
822              
823             } # End of renumber_items.
824              
825             # -----------------------------------------------
826              
827             sub report
828             {
829 0     0 1 0 my($self) = @_;
830              
831 0         0 $self -> log(info => $self -> format_token
832             ({
833             count => 'Item',
834             name => 'Name',
835             type => 'Type',
836             value => '',
837             }) );
838              
839 0         0 for my $item ($self -> items -> print)
840             {
841 0         0 $self -> log(info => $self -> format_token($item) );
842             }
843              
844             } # End of report.
845              
846             # --------------------------------------------------
847              
848             sub run
849             {
850 85     85 1 34765 my($self) = @_;
851              
852 85 50       781 if ($self -> description)
    100          
853             {
854 0         0 $self -> get_graph_from_command_line;
855             }
856             elsif ($self -> input_file)
857             {
858 75         333 $self -> get_graph_from_file;
859             }
860             else
861             {
862 10         131 die "Error: You must provide a graph using one of -input_file or -description\n";
863             }
864              
865             # Return 0 for success and 1 for failure.
866              
867 0           my($result) = 0;
868              
869             try
870             {
871 0 0   0     if (defined $self -> process)
872             {
873 0           $self -> renumber_items;
874 0 0         $self -> report if ($self -> report_tokens);
875              
876 0           my($file_name) = $self -> token_file;
877              
878 0 0         $self -> generate_token_file($file_name) if ($file_name);
879             }
880             else
881             {
882 0           $result = 1;
883              
884 0           $self -> log(error => 'Parse failed');
885             }
886             }
887             catch
888             {
889 0     0     $result = 1;
890              
891 0           $self -> log(error => "Parse failed: $_");
892 0           };
893              
894             # Return 0 for success and 1 for failure.
895              
896 0           $self -> log(info => "Parse result: $result (0 is success)");
897              
898 0           return $result;
899              
900             } # End of run.
901              
902             # --------------------------------------------------
903              
904             1;
905              
906             =pod
907              
908             =head1 NAME
909              
910             C - A Marpa-based parser for Graph::Easy::Marpa files
911              
912             =head1 Synopsis
913              
914             See L.
915              
916             =head1 Description
917              
918             C provides a Marpa-based parser for L-style graph definitions.
919              
920             =head1 Installation
921              
922             Install L as you would for any C module:
923              
924             Run:
925              
926             cpanm Graph::Easy::Marpa
927              
928             or run:
929              
930             sudo cpan Graph::Easy::Marpa
931              
932             or unpack the distro, and then either:
933              
934             perl Build.PL
935             ./Build
936             ./Build test
937             sudo ./Build install
938              
939             or:
940              
941             perl Makefile.PL
942             make (or dmake or nmake)
943             make test
944             make install
945              
946             =head1 Constructor and Initialization
947              
948             C is called as C<< my($parser) = Graph::Easy::Marpa::Parser -> new(k1 => v1, k2 => v2, ...) >>.
949              
950             It returns a new object of type C.
951              
952             Key-value pairs accepted in the parameter list (see corresponding methods for details
953             [e.g. graph()]):
954              
955             =over 4
956              
957             =item o description => '[node.1]<->[node.2]'
958              
959             Specify a string for the graph definition.
960              
961             You are strongly encouraged to surround this string with '...' to protect it from your shell.
962              
963             See also the 'input_file' key to read the graph from a file.
964              
965             The 'description' key takes precedence over the 'input_file' key.
966              
967             =item o input_file => $graph_file_name
968              
969             Read the graph definition from this file.
970              
971             See also the 'graph' key to read the graph from the command line.
972              
973             The whole file is slurped in as 1 graph.
974              
975             The first lines of the input file can start with /^\s*#/, and will be discarded as comments.
976              
977             The 'description' key takes precedence over the 'input_file' key.
978              
979             =item o logger => $logger_object
980              
981             Specify a logger object.
982              
983             To disable logging, just set logger to the empty string.
984              
985             The default value is an object of type L.
986              
987             =item o maxlevel => $level
988              
989             This option is only used if an object of type L is created. See I above.
990              
991             See also L.
992              
993             Default: 'info'. A typical value is 'debug'.
994              
995             =item o minlevel => $level
996              
997             This option is only used if an object of type L is created. See I above.
998              
999             See also L.
1000              
1001             Default: 'error'.
1002              
1003             No lower levels are used.
1004              
1005             =item o report_items => $Boolean
1006              
1007             Calls L to report, via the log, the items recognized by the state machine.
1008              
1009             =back
1010              
1011             See L.
1012              
1013             =head1 Methods
1014              
1015             =head2 file([$file_name])
1016              
1017             The [] indicate an optional parameter.
1018              
1019             Get or set the name of the file the graph will be read from.
1020              
1021             See L.
1022              
1023             =head2 generate_token_file($file_name)
1024              
1025             Returns nothing.
1026              
1027             Writes a CSV file of tokens output by the parse if new() was called with the C option.
1028              
1029             =head2 get_graph_from_command_line()
1030              
1031             If the caller has requested a graph be parsed from the command line, with the graph option to new(), get it now.
1032              
1033             Called as appropriate by run().
1034              
1035             =head2 get_graph_from_file()
1036              
1037             If the caller has requested a graph be parsed from a file, with the file option to new(), get it now.
1038              
1039             Called as appropriate by run().
1040              
1041             =head2 grammar()
1042              
1043             Returns an object of type L.
1044              
1045             =head2 input_file([$graph_file_name])
1046              
1047             Here, the [] indicate an optional parameter.
1048              
1049             Get or set the name of the file to read the graph definition from.
1050              
1051             See also the description() method.
1052              
1053             The whole file is slurped in as 1 graph.
1054              
1055             The first lines of the input file can start with /^\s*#/, and will be discarded as comments.
1056              
1057             The value supplied to the description() method takes precedence over the value read from the input file.
1058              
1059             =head2 items()
1060              
1061             Returns a object of type L, which is an arrayref of items output by the state machine.
1062              
1063             See the L for details.
1064              
1065             =head2 log($level, $s)
1066              
1067             Calls $self -> logger -> $level($s).
1068              
1069             =head2 logger([$logger_object])
1070              
1071             Here, the [] indicate an optional parameter.
1072              
1073             Get or set the logger object.
1074              
1075             To disable logging, just set logger to the empty string.
1076              
1077             =head2 maxlevel([$string])
1078              
1079             Here, the [] indicate an optional parameter.
1080              
1081             Get or set the value used by the logger object.
1082              
1083             This option is only used if an object of type L is created. See L.
1084              
1085             =head2 minlevel([$string])
1086              
1087             Here, the [] indicate an optional parameter.
1088              
1089             Get or set the value used by the logger object.
1090              
1091             This option is only used if an object of type L is created. See L.
1092              
1093             =head2 recce()
1094              
1095             Returns an object of type L.
1096              
1097             =head2 renumber_items()
1098              
1099             Ensures each item in the stack as a sequential number 1 .. N.
1100              
1101             =head2 report()
1102              
1103             Report, via the log, the list of items recognized by the state machine.
1104              
1105             =head2 report_items([0 or 1])
1106              
1107             The [] indicate an optional parameter.
1108              
1109             Get or set the value which determines whether or not to report the items recognised by the state machine.
1110              
1111             =head2 run()
1112              
1113             This is the only method the caller needs to call. All parameters are supplied to new().
1114              
1115             Returns 0 for success and 1 for failure.
1116              
1117             =head2 token_file([$csv_file_name])
1118              
1119             The [] indicate an optional parameter.
1120              
1121             Get or set the name of the file to write containing the tokens (items) output from the parser.
1122              
1123             =head2 tokens()
1124              
1125             Returns an arrayref of tokens. Each element of this arrayref is an arrayref of 2 elements:
1126              
1127             =over 4
1128              
1129             =item o The type of the token
1130              
1131             =item o The value of the token
1132              
1133             =back
1134              
1135             If you look at the source code for the run() method in L, you'll see this arrayref can be
1136             passed directly as the value of the items key in the call to L's run()
1137             method.
1138              
1139             =head1 FAQ
1140              
1141             =head2 What is the Graph::Easy::Marpa language?
1142              
1143             Basically, it is derived from, and very similar to, the L language, with a few irregularities
1144             cleaned up. It exists to server as a wrapper around L.
1145              
1146             The re-write took place because, instead of L's home-grown parser, Graph::Easy::Marpa::Parser uses
1147             L, which requires a formally-spelled-out grammar for the language being parsed.
1148              
1149             That grammar is in the source code of Graph::Easy::Marpa::Parser, in C, and is explained next.
1150              
1151             Firstly, a summary:
1152              
1153             Element Syntax
1154             ---------------------
1155             Edge names Either '->' or '--'
1156             ---------------------
1157             Node names 1: Delimited by '[' and ']'.
1158             2: May be quoted with " or '.
1159             3: Escaped characters, using '\', are allowed.
1160             4: Internal spaces in node names are preserved even if not quoted.
1161             ---------------------
1162             Attributes 1: Delimited by '{' and '}'.
1163             2: Within that, any number of "key : value" pairs separated by ';'.
1164             3: Values may be quoted with " or ' or '<...>' or '<...
>'.
1165             4: Escaped characters, using '\', are allowed.
1166             5: Internal spaces in attribute values are preserved even if not quoted.
1167             ---------------------
1168              
1169             Note: Both edges and nodes can have attributes.
1170              
1171             Note: HTML-like labels trigger special-case processing in Graphviz.
1172             See L below.
1173              
1174             Demo pages:
1175              
1176             L
1177             L
1178              
1179             The latter page utilizes a cut-down version of the Graph::Easy::Marpa language, as documented in
1180             L.
1181              
1182             And now the details:
1183              
1184             =over 4
1185              
1186             =item o Attributes
1187              
1188             Both nodes and edges can have any number of attributes.
1189              
1190             Attributes are delimited by '{' and '}'.
1191              
1192             These attributes are listed immdiately after their owing node or edge.
1193              
1194             Each attribute consists of a key:value pair, where ':' must appear literally.
1195              
1196             These key:value pairs must be separated by the ';' character. A trailing ';' is optional.
1197              
1198             The values for 'key' are reserved words used by Graphviz's L.
1199             These keys match the regexp /^[a-zA-Z_]+$/.
1200              
1201             For the 'value', any printable character can be used.
1202              
1203             Some escape sequences are a special meaning within L.
1204              
1205             E.g. if you use [node name] {label: \N}, then if that graph is input to Graphviz's I, \N will be replaced
1206             by the name of the node.
1207              
1208             Some literals - ';', '}', '<', '>', '"', "'" - can be used in the attribute's value, but they must satisfy one
1209             of these conditions. They must be:
1210              
1211             =over 4
1212              
1213             =item o Escaped using '\'.
1214              
1215             Eg: \;, \}, etc.
1216              
1217             =item o Placed inside " ... "
1218              
1219             =item o Placed inside ' ... '
1220              
1221             =item o Placed inside <...>
1222              
1223             This does I mean you can use <>. See the next point.
1224              
1225             =item o Placed inside < ...
>
1226              
1227             Using this construct allows you to use HTML entities such as &, <, > and ".
1228              
1229             =back
1230              
1231             Internal spaces are preserved within an attribute's value, but leading and trailing spaces are not (unless quoted).
1232              
1233             Samples:
1234              
1235             [node.1] {color: red; label: Green node}
1236             -> {penwidth: 5; label: From Here to There}
1237             [node.2]
1238             -> {label: "A literal semicolon '\;' in a label"}
1239              
1240             Note: That '\;' does not actually need those single-quote characters, since it is within a set of double-quotes.
1241              
1242             Note: Attribute values quoted with a balanced pair or single- or double-quotes will have those quotes stripped.
1243              
1244             =item o Classes
1245              
1246             Class and subclass names must match /^(edge|global|graph|group|node)(\.[a-z]+)?$/.
1247              
1248             The name before the '.' is the class name.
1249              
1250             'global' is used to specify whether you want a directed or undirected graph. The default is directed.
1251              
1252             global {directed: 1} [node.1] -> [node.2]
1253              
1254             'graph' is used to specify the direction of the graph as a whole, and must be one of: LR or RL or TB or BT.
1255             The default is TB.
1256              
1257             graph {rankdir: LR} [node.1] -> [node.2]
1258              
1259             The name after the '.' is the subclass name. And if '.' is present, the subclass name must be present.
1260             This means things like 'edge.' etc are syntax errors.
1261              
1262             node {shape: rect} node.forest {color: green}
1263             [node.1] -> [node.2] {class: forest} -> [node.3] {shape: circle; color: blue}
1264              
1265             Here, node.1 gets the default shape, rect, and node.2 gets both shape rect and color green. node.3
1266             gets shape circle and color blue.
1267              
1268             As always, specific attributes override class attributes.
1269              
1270             You use the subclass name in the attributes of an edge, a group or a node, whereas 'global' and 'graph'
1271             appear only once, at the start of the input stream. That is, tt does not make sense for a class of I
1272             or I to have any subclasses.
1273              
1274             =item o Comments
1275              
1276             The first few lines of the input file can start with /^\s*#/, and will be discarded as comments.
1277              
1278             =item o Daisy-chains
1279              
1280             See L for the origin of this term.
1281              
1282             =over 4
1283              
1284             =item o Edges
1285              
1286             Edges can be daisy-chained by juxtaposition, or by using a comma (','), newline, space, or attributes ('{...}')
1287             to separate them.
1288              
1289             Hence both of these are valid: '->,->{color:green}' and '->{color:red}->{color:green}'.
1290              
1291             See data/edge.03.ge and data/edge.09.ge.
1292              
1293             =item o Groups
1294              
1295             Groups can be daisy chained by juxtaposition, or by using a newline or space to separate them.
1296              
1297             =item o Nodes
1298              
1299             Nodes can be daisy-chained by juxtaposition, or by using a comma (','), newline, space, or attributes ('{...}')
1300             to separate them.
1301              
1302             Hence all of these are valid: '[node.1][node.2]' and '[node.1],[node.2]' and '[node.1]{color:red}[node.2]'.
1303              
1304             =back
1305              
1306             =item o Edges
1307              
1308             Edge names are either '->' or '--'.
1309              
1310             No other edge names are accepted.
1311              
1312             Note: The syntax for edges is just a visual clue for the user. The I 'v' I nature of the
1313             graph depends on the value of the 'directed' attribute present (explicitly or implicitly) in the input stream.
1314             Nevertheless, usage of '->' or '--' must match the nature of the graph, or Graphviz will issue a syntax error.
1315              
1316             The default is {directed: 1}. See data/class.global.01.ge for a case where we use {directed: 0} attached to
1317             class 'global'.
1318              
1319             Edges can have attributes such as arrowhead, arrowtail, etc. See L
1320              
1321             Samples:
1322              
1323             ->
1324             -- {penwidth: 9}
1325              
1326             =item o Graphs
1327              
1328             Graphs are sequences of nodes and edges, in any order.
1329              
1330             The sample given just above for attributes is in fact a single graph.
1331              
1332             A sample:
1333              
1334             [node]
1335             [node] ->
1336             -> {label: Start} -> {color: red} [node.1] {color: green] -> [node.2]
1337             [node.1] [node.2] [node.3]
1338              
1339             For more samples, see the data/*.ge files shipped with the distro.
1340              
1341             =item o Line-breaks
1342              
1343             These are converted into a single space.
1344              
1345             =item o Nodes
1346              
1347             Nodes are delimited by '[' and ']'.
1348              
1349             Within those, any printable character can be used for a node's name.
1350              
1351             Some literals - ']', '"', "'" - can be used in the node's value, but they must satisfy one of these
1352             conditions. They must be:
1353              
1354             =over 4
1355              
1356             =item o Escaped using '\'
1357              
1358             Eg: \].
1359              
1360             =item o Placed inside " ... "
1361              
1362             =item o Placed inside ' ... '
1363              
1364             =back
1365              
1366             Internal spaces are preserved within a node's name, but leading and trailing spaces are not (unless quoted).
1367              
1368             Lastly, the node's name can be empty. I.e.: You use '[]' in the input stream to create an anonymous node.
1369              
1370             Samples:
1371              
1372             []
1373             [node.1]
1374             [node 1]
1375             [[node\]]
1376             ["[node]"]
1377             [ From here ] -> [ To there ]
1378              
1379             Note: Node names quoted with a balanced pair or single- or double-quotes will have those quotes stripped.
1380              
1381             =item o Subgraphs aka Groups
1382              
1383             Subgraph names must match /^[a-zA-Z_.][a-zA-Z_0-9. ]*$/.
1384              
1385             Subgraph names beginning with 'cluster' trigger special-case processing within Graphviz.
1386              
1387             See 'Subgraphs and Clusters' on L.
1388              
1389             Samples:
1390              
1391             Here, the subgraph name is 'cluster.1':
1392             (cluster.1: [node.1] -> [node.2])
1393             group {bgcolor: red} (cluster.1: [node.1] -> [node.2]) {class: group}
1394              
1395             =back
1396              
1397             =head2 Does this module handle utf8?
1398              
1399             Yes. See the last sample on L.
1400              
1401             =head2 How is the parsed graph stored in RAM?
1402              
1403             Items are stored in an arrayref. This arrayref is available via the L method.
1404              
1405             Each element in the array is a hashref, listed here in alphabetical order by type.
1406              
1407             Note: Items are numbered from 1 up.
1408              
1409             =over 4
1410              
1411             =item o Attributes
1412              
1413             An attribute can belong to a graph, node or an edge. An attribute definition of
1414             '{color: red;}' would produce a hashref of:
1415              
1416             {
1417             count => $n,
1418             name => 'color',
1419             type => 'attribute',
1420             value => 'red',
1421             }
1422              
1423             An attribute definition of '{color: red; shape: circle;}' will produce 2 hashrefs,
1424             i.e. 2 sequential elements in the arrayref:
1425              
1426             {
1427             count => $n,
1428             name => 'color',
1429             type => 'attribute',
1430             value => 'red',
1431             }
1432              
1433             {
1434             count => $n + 1,
1435             name => 'shape',
1436             type => 'attribute',
1437             value => 'circle',
1438             }
1439              
1440             Attribute hashrefs appear in the arrayref immediately after the item (edge, group, node) to which they belong.
1441             For subgraphs, this means they appear straight after the hashref whose type is 'pop_subgraph'.
1442              
1443             The following has been extracted manually from the Graphviz documentation, and is listed here in case I need it.
1444             Classes are written as [x] rather than [x]+, etc, so it uses various abbreviations.
1445              
1446             Attribute Regexp+ Interpretation
1447             --------- ------ --------------
1448             addDouble +?[0-9.] A double preceeded by an optional '+'
1449             arrowType [a-z] A word
1450             aspectType [0-9.,] A double or a double + ',' + an integer
1451             bool [a-zA-Z0-0] Case-insensitive 'true', 'false', 0 or N (true)
1452             color [#0-9a-f] '#' followed by 3 or 4 hex numbers
1453             [0-9. ] 3 numbers 0 .. 1 separated by '.' or \s
1454             [/a-z] A word or /word or /word1/word2
1455             clusterMode [a-z] A word
1456             colorList color(;[0-9.])? N tokens separated by ':'.
1457             dirType [a-z] A word
1458             doubleList [0-9.:] Various doubles separated by ':'
1459             escString \[NGETHLnlr] A list of escaped letters
1460             HTML label <<[.]>> A quoted list of stuff
1461             layerRange
1462             lblString escString or HTML label
1463             outputMode [a-z] A word
1464             pagedir [A-Z] A word of 2 caps (TB etc)
1465             point [0-9.,]!? 2 doubles followed by an optional '!'
1466             pointList [0-9., ]!? A list of points separated by spaces
1467             quadType [a-z] A word
1468             rankdir [A-Z] A word of 2 caps (TB etc)
1469             rankType [a-z] A word
1470             rect [0-9.,] Four doubles seperated by ','s
1471             shape [a-z] A word, or
1472             [<>{}] Bracketed strings, or
1473             ? User-defined
1474             smoothType [a-z] A word
1475             splineType [0-9.,;es] Various doubles, with ',' and ';', and optional 'e', 's'
1476             startType [a-z][0-9] A word optionally followed by a number
1477             style [a-z(),] A list of words separated by ',' each with optional '(...)'
1478             viewPort [0-9.,] A list of 5 doubles or
1479             [0-9.,] A list of 4 doubles followed by a node name
1480              
1481             =item o Classes and class attributes
1482              
1483             These notes apply to all classes and subclasses.
1484              
1485             A class definition of 'edge {color: white}' would produce 2 hashrefs:
1486              
1487             {
1488             count => $n,
1489             name => 'edge',
1490             type => 'class_name',
1491             value => '',
1492             }
1493              
1494             {
1495             count => $n + 1,
1496             name => 'color',
1497             type => 'attribute',
1498             value => 'white',
1499             }
1500              
1501             A class definition of 'node.green {color: green; shape: rect}' would produce 3 hashrefs:
1502              
1503             {
1504             count => $n,
1505             name => 'node.green',
1506             type => 'class_name',
1507             value => '',
1508             }
1509              
1510             {
1511             count => $n + 1,
1512             name => 'color',
1513             type => 'attribute',
1514             value => 'green',
1515             }
1516              
1517             {
1518             count => $n + 2,
1519             name => 'shape',
1520             type => 'attribute',
1521             value => 'rect',
1522             }
1523              
1524             Class and class attribute hashrefs always appear at the start of the arrayref of items.
1525              
1526             =item o Edges
1527              
1528             An edge definition of '->' would produce a hashref of:
1529              
1530             {
1531             count => $n,
1532             name => '->',
1533             type => 'edge',
1534             value => '',
1535             }
1536              
1537             =item o Nodes
1538              
1539             A node definition of '[Name]' would produce a hashref of:
1540              
1541             {
1542             count => $n,
1543             name => 'Name',
1544             type => 'node',
1545             value => '',
1546             }
1547              
1548             A node can have a definition of '[]', which means it has no name. Such nodes are called anonymous (or
1549             invisible) because while they take up space in the output stream, they have no printable or visible
1550             characters in the output stream.
1551              
1552             Each anonymous node will have at least these 2 attributes:
1553              
1554             {
1555             count => $n,
1556             name => '',
1557             type => 'node',
1558             value => '',
1559             }
1560              
1561             {
1562             count => $n + 1,
1563             name => 'color',
1564             type => 'attribute',
1565             value => 'invis',
1566             }
1567              
1568             You can of course give your anonymous nodes any attributes, but they will be forced to have
1569             these attributes.
1570              
1571             E.g. If you give it a color, that would become element $n + 2 in the arrayref, and hence that color would override
1572             the default color 'invis'. See the output for data/node.04.ge on
1573             L.
1574              
1575             Node names are case-sensitive in C.
1576              
1577             =item o Subgraphs
1578              
1579             Subgraph names must match /^(?:[a-zA-Z_.][a-zA-Z_.0-9]*)^/.
1580              
1581             A subgraph produces 2 hashrefs, one at the start of the subgraph, and one at the end.
1582              
1583             A group defnition of '(Solar system: [Mercury] -> [Neptune])' would produce a hashref like this at the start,
1584             i.e. when the '(' - just before 'Solar' - is detected in the input stream:
1585              
1586             {
1587             count => $n,
1588             name => 'Solar system',
1589             type => 'push_subgraph',
1590             value => '',
1591             }
1592              
1593             and a hashref like this at the end, i.e. when the ')' - just after '[Neptune]' - is detected:
1594              
1595             {
1596             count => $n + N,
1597             name => 'Solar system',
1598             type => 'pop_subgraph',
1599             value => '',
1600             }
1601              
1602             =back
1603              
1604             =head2 Why doesn't the parser handle my HTML-style labels?
1605              
1606             Traps for young players:
1607              
1608             =over 4
1609              
1610             =item o The
component must include the '/'
1611              
1612             =item o If any tag's attributes use double-quotes, they will be doubled in the CSV output file
1613              
1614             That is, just like double-quotes everywhere else.
1615              
1616             =back
1617              
1618             See L for details of Graphviz's HTML-like syntax.
1619              
1620             See data/node.16.ge and data/node.17.ge for a couple of examples.
1621              
1622             =head2 Why do I get error messages like the following?
1623              
1624             Error: :1: syntax error near line 1
1625             context: digraph >>> Graph <<< {
1626              
1627             Graphviz reserves some words as keywords, meaning they can't be used as an ID, e.g. for the name of the graph.
1628             So, don't do this:
1629              
1630             strict graph graph{...}
1631             strict graph Graph{...}
1632             strict graph strict{...}
1633             etc...
1634              
1635             Likewise for non-strict graphs, and digraphs. You can however add double-quotes around such reserved words:
1636              
1637             strict graph "graph"{...}
1638              
1639             Even better, use a more meaningful name for your graph...
1640              
1641             The keywords are: node, edge, graph, digraph, subgraph and strict. Compass points are not keywords.
1642              
1643             See L in the discussion of the syntax of DOT
1644             for details.
1645              
1646             =head2 Where are the action subs named in the grammar?
1647              
1648             In L.
1649              
1650             =head2 Has any graph syntax changed moving from V 1.* to V 2.*?
1651              
1652             Yes. Under V 1.*, to specify an empty label, this was possible:
1653              
1654             [node] { label: ;}
1655              
1656             Any attribute, here C
1657              
1658             [node] { label: ''; }
1659              
1660             Of cource, the same applies to attributes for edges.
1661              
1662             =head1 Machine-Readable Change Log
1663              
1664             The file Changes was converted into Changelog.ini by L.
1665              
1666             =head1 Version Numbers
1667              
1668             Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.
1669              
1670             =head1 Support
1671              
1672             Email the author, or log a bug on RT:
1673              
1674             L.
1675              
1676             =head1 Author
1677              
1678             L was written by Ron Savage Iron@savage.net.auE> in 2011.
1679              
1680             Home page: L.
1681              
1682             =head1 Copyright
1683              
1684             Australian copyright (c) 2011, Ron Savage.
1685              
1686             All Programs of mine are 'OSI Certified Open Source Software';
1687             you can redistribute them and/or modify them under the terms of
1688             The Artistic License, a copy of which is available at:
1689             http://www.opensource.org/licenses/index.html
1690              
1691             =cut