File Coverage

blib/lib/MarpaX/G4/Parser.pm
Criterion Covered Total %
statement 185 321 57.6
branch 65 156 41.6
condition 28 75 37.3
subroutine 31 49 63.2
pod 0 4 0.0
total 309 605 51.0


line stmt bran cond sub pod time code
1             # ----------------------------------------------------------------------------------------------------- #
2             # MarpaX::G4 #
3             # #
4             # a grammar for parsing antlr4 grammars and translating them to Marpa::R2 grammars. #
5             # #
6             # ----------------------------------------------------------------------------------------------------- #
7              
8             package MarpaX::G4::Parser;
9 2     2   12 use strict;
  2         2  
  2         57  
10 2     2   7 use warnings FATAL => 'all';
  2         3  
  2         69  
11              
12 2     2   10 use strict;
  2         2  
  2         34  
13 2     2   716 use Marpa::R2 2.039_000;
  2         210151  
  2         484  
14              
15             sub new
16             {
17 1     1 0 2 my ($class) = @_;
18              
19 1         2 my $self = bless {}, $class;
20              
21 1         16 $self->{grammarstate} = undef;
22              
23 1         12 $self->{grammar} = Marpa::R2::Scanless::G->new(
24             {
25             action_object => 'MarpaX::G4::Actions',
26             default_action => 'default_action',
27             source => \(<<'END_OF_SOURCE'),
28             lexeme default = latm => 1
29              
30             :start ::= g4grammar
31              
32             :discard ~ whitespace
33             whitespace ~ [\s]+
34              
35             g4grammar ::= grammarentry* action => do_grammar
36              
37             grammarentry ::= symbolrule
38             | fragmentrule
39             | grammarspec
40             | optionspec
41             | namedspec
42             | linecomment
43             | blockcomment
44              
45             symbolrule ::= name opt_return opt_comment (COLON) right_side action => do_single_rule
46              
47             right_side ::= rhs opt_hashcomment opt_comment end_rhs action => do_right_side
48             | opt_redir (SEMICOLON) action => do_empty_rule
49              
50             end_rhs ::= (BAR) right_side action => do_endrhs
51             | opt_redir (SEMICOLON) action => do_empty_rule
52              
53             rhs ::= nonterminal+ action => do_rhs
54              
55             nonterminal ::= opt_assoc rulecomponent opt_card action => do_nonterminal
56              
57             rulecomponent ::= token | group action => do_rulecomponent
58              
59             group ::= (LPAREN) opt_colon rhs grouplist opt_bar (RPAREN) action => do_group
60              
61             grouplist ::= groupelement* action => do_grouplist
62             groupelement ::= (BAR) rhs action => do_groupelement_alternative
63             | rhs action => do_groupelement_concat
64              
65             fragmentrule ::= (fragmentkeywd) opt_comment name opt_comment
66             (COLON) fragment_right_side action => do_fragment
67             fragmentkeywd ~ 'fragment'
68              
69             fragment_right_side ::= fragment_rhs opt_hashcomment opt_comment
70             fragment_end_rhs action => do_right_side
71             | (SEMICOLON)
72              
73             fragment_rhs ::= tokenlist action => do_rhs
74              
75             fragment_end_rhs ::= (BAR) fragment_right_side action => do_endrhs
76             | (SEMICOLON) action => do_empty_rule
77              
78             token ::= opt_neg literal action => do_token
79             | opt_neg tokengroup action => do_token
80             | name ALIASOP rulecomponent action => do_assignalias
81             | opt_neg name action => do_name
82             | range
83             | regex
84             | valueclause
85             |
86             | linecomment
87             | blockcomment
88              
89             tokengroup ::= (LPAREN) opt_colon tokenlist (RPAREN) action => do_token_group
90             tokenlist ::= tokenelement+ action => do_token_list
91             tokenelement ::= (BAR) token opt_card action => do_tokenelement_alternative
92             | token opt_card action => do_tokenelement_concat
93              
94             range ::= literal (RANGEOP) literal action => do_range
95              
96             name ~ [a-zA-Z0-9_]+
97              
98             valueclause ::= (LANGLE) name (RANGLE) action => do_valueclause
99              
100             literal ::= lstring action => do_literal
101             | characterclass action => do_characterclass
102              
103             lstring ~ quote in_string quote
104             quote ~ [']
105             in_string ~ in_string_char*
106              
107             in_string_char ~ [^'\\]
108             | '\' [']
109             | '\' 'b'
110             | '\' 'f'
111             | '\' 'n'
112             | '\' 'r'
113             | '\' 't'
114             | '\' '/'
115             | '\' '*'
116             | '\' '#'
117             | '\\'
118             | '\' 'u' four_hex_digits
119             | '\' 'u' '{' hex_digits '}'
120              
121             # a dash ('-') character immediately following or preceding the opening/closing bracket counts as a dash not a range
122             characterclass ~ '[-' in_class ']'
123             | '[' in_class '-]'
124             | '[' in_class ']'
125             in_class ~ in_class_element+
126             in_class_element ~ in_class_char | in_class_range
127             in_class_range ~ in_class_char '-' in_class_char
128             in_class_char ~ [^-\]\\]
129             | '\' [^u]
130             | '\' 'u' four_hex_digits
131             | '\' 'u' '{' hex_digits '}'
132              
133             hex_digits ~ hex_digit+
134             four_hex_digits ~ hex_digit hex_digit hex_digit hex_digit
135             hex_digit ~ [0-9a-fA-F]
136              
137             regex ::= '.' regex_cardinality action => do_regex
138             regex_cardinality ::= cardinality*
139              
140             opt_assoc ::=
141             opt_assoc ::= assoc_clause
142             assoc_clause ~ ''
143             assoc_type ~ 'left' | 'right' | 'group'
144              
145             opt_redir ::=
146             opt_redir ::= (redirect) redir_target redir_list action => do_redirect
147             redir_list ::= redir_suffix*
148             redir_suffix ::= COMMA redir_target
149             redir_target ::= name
150             | name (LPAREN) name (RPAREN)
151             redirect ~ '->'
152              
153             opt_bar ::=
154             opt_bar ::= BAR
155              
156             opt_colon ::=
157             opt_colon ::= COLON
158              
159             opt_neg ::=
160             opt_neg ::= negation
161             negation ~ '~'
162              
163             opt_card ::=
164             opt_card ::= cardinality
165             cardinality ~ [?*+]
166             | [*+] [?]
167              
168             grammarspec ::= opt_grammarprefix ('grammar') name (SEMICOLON) action => do_grammarspec
169             opt_grammarprefix ::=
170             opt_grammarprefix ::= 'lexer'
171             | 'parser'
172              
173             opt_return ::=
174             opt_return ::= 'returns' (LBRACKET) namelist (RBRACKET) action => do_return_clause
175             namelist ::= name+
176              
177             optionspec ::= action => do_option_spec
178             ~ 'options'
179             | 'channels'
180             | 'tokens'
181             | '@'
182             ~ [a-zA-Z0-9_:]+
183              
184             namedspec ::= namedoptionkeywords name (SEMICOLON) action => do_named_spec
185             namedoptionkeywords ~ 'mode'
186             | 'import'
187              
188             ::= ('{') ('}') action => do_inline_action
189             ~ [^}]*
190              
191             opt_comment ::=
192             opt_comment ::= multi_comment
193             multi_comment ::= single_comment+
194             single_comment ::= linecomment
195             | blockcomment
196              
197             opt_hashcomment ::=
198             opt_hashcomment ::= hashcomment
199             hashcomment ::= hashcommenttext
200             hashcommenttext ~ '#' linecommenttext
201              
202             linecomment ::= linecommentmatch action => do_comment
203             linecommentmatch ~ linecommentprefix linecommenttext
204             linecommentprefix ~ '//'
205             linecommenttext ~ [^\n]*
206              
207             blockcomment ::= action => do_comment
208             ~ '/*' '*/'
209             ~
210             ~ [^*]*
211             ~ *
212             ~ [^/*]
213             ~ [*]+
214             ~ [^*]*
215             ~ [*]*
216              
217             LPAREN ~ '('
218             RPAREN ~ ')'
219             LBRACKET ~ '['
220             RBRACKET ~ ']'
221             LANGLE ~ '<'
222             RANGLE ~ '>'
223             COLON ~ ':'
224             SEMICOLON ~ ';'
225             COMMA ~ ','
226             RANGEOP ~ '..'
227             BAR ~ [|]
228             ALIASOP ~ '='
229             | '+='
230              
231             END_OF_SOURCE
232             }
233             );
234 1         263441 return $self;
235             }
236              
237 0     0 0 0 sub enabletrace { my ($self) = @_; $MarpaX::G4::Actions::trace = 1; }
  0         0  
238 0     0 0 0 sub ignoreredirect { my ($self) = @_; $MarpaX::G4::Actions::ignoreredirect = 1; }
  0         0  
239              
240             sub parse
241             {
242 1     1 0 4 my ($self, $string) = @_;
243 1         9 my $parserinstance = Marpa::R2::Scanless::R->new({ grammar => $self->{grammar} });
244 1         350 $parserinstance->read(\$string);
245 1         18680 my $value_ref = $parserinstance->value();
246 1         65 return ${$value_ref};
  1         109  
247             }
248              
249             # ----------------------------------------------------------------------------------------------------- #
250             # MarpaX::G4::Actions #
251             # #
252             # actions for processing grammar rules. #
253             # ----------------------------------------------------------------------------------------------------- #
254              
255             package MarpaX::G4::Actions;
256 2     2   15 use strict;
  2         5  
  2         31  
257              
258 2     2   10 use Data::Dumper;
  2         3  
  2         6516  
259              
260             my $trace = 0;
261             my $ignoreredirect = 0;
262              
263             sub new
264             {
265 1     1   15829 my ($class) = @_;
266 1         11 return bless {}, $class;
267             }
268              
269             sub isNotNull
270             {
271 236     236   253 my ($value) = @_;
272 236 100       299 return 0 if !defined $value;
273 230 100 100     639 return 0 if ref $value eq "ARRAY" && scalar @$value == 0;
274 13 50 33     20 return 0 if ref $value eq "HASH" && scalar keys %$value == 0;
275 13         30 return 1;
276             }
277              
278             sub dumpStructure
279             {
280 0     0   0 my ( $self, $title, $structure ) = @_;
281              
282 0         0 printf "=== %s\n", $title;
283 0         0 $Data::Dumper::Indent = 1;
284 0         0 print Dumper($structure);
285             }
286              
287             sub abortWithError
288             {
289 0     0   0 my ( $self, $msg, $structure ) = @_;
290              
291 0 0       0 $self->dumpStructure($msg, $structure) if defined $structure;
292 0         0 die $msg;
293             }
294              
295             sub do_trace
296             {
297 519     519   563 my ($self, $subroutinename, $structure ) = @_;
298 519 50       732 return if !$MarpaX::G4::Actions::trace;
299 0         0 $self->dumpStructure($subroutinename,$structure);
300             }
301              
302             sub flattenArray
303             {
304 11     11   35 my ($self, $value) = @_;
305 11         15 my $result = $value;
306 11 50       15 if (ref $value eq "ARRAY")
307             {
308 0 0       0 $self->abortWithError("\$value must hold exactly 1 value", $value) if scalar @$value != 1;
309 0         0 $result = @{$value}[0];
  0         0  
310             }
311 11         22 return $result;
312             }
313              
314             sub default_action
315             {
316 278     278   9047 my ($self, @items ) = @_;
317 278         429 $self->do_trace("default_action", \@items);
318 278         291 my $result = \@items;
319 278 100       358 $result = $items[0] if scalar @items == 1;
320 278         401 return $result;
321             }
322              
323             sub do_grammar
324             {
325 1     1   33 my ($self, @rules ) = @_;
326 1         3 return \@rules;
327             }
328              
329             sub do_option_spec
330             {
331 0     0   0 my ($self, @items) = @_;
332 0         0 $self->do_trace("do_option_spec", \@items);
333 0         0 my $value = $items[1]->{action};
334 0 0       0 $value = join("\n", @$value) if ref($value) eq "ARRAY";
335 0         0 my $result = { comment => sprintf("%s : <%s>", $items[0], $value) };
336 0         0 return $result;
337             }
338              
339             sub do_named_spec
340             {
341 0     0   0 my ($self, @items) = @_;
342 0         0 $self->do_trace("do_named_spec", \@items);
343 0         0 my $result = { comment => sprintf("%s : <%s>", $items[0], $items[1]) };
344 0         0 return $result;
345             }
346              
347             sub do_return_clause
348             {
349 0     0   0 my ($self, @items) = @_;
350 0         0 $self->do_trace("do_return_clause", \@items);
351 0         0 my $result = { comment => sprintf("returns : [%s]", join( " ", @{$items[1]})) };
  0         0  
352 0         0 return $result;
353             }
354              
355             sub do_comment
356             {
357 0     0   0 my ($self, @comment ) = @_;
358 0         0 $self->do_trace("do_comment", \@comment);
359 0         0 my $result = {};
360              
361 0 0       0 if ( ref($comment[0]) eq "HASH")
362             {
363 0         0 my $multicomment = [];
364 0         0 for my $entry (@comment)
365             {
366 0 0       0 $self->abortWithError( "'comment' entry not found", $entry) if !exists $entry->{comment};
367 0         0 push @$multicomment, $entry->{comment};
368             }
369 0         0 $result = { comment => \@$multicomment };
370             }
371             else
372             {
373 0         0 $result = { comment => $comment[0] };
374 0 0       0 if (scalar @comment > 1)
375             {
376 0         0 my $multicomment = [];
377 0         0 for my $entry (@comment)
378             {
379 0         0 push @$multicomment, $entry;
380             }
381 0         0 $result = { comment => \@$multicomment };
382             }
383             }
384              
385 0         0 return $result;
386             }
387              
388             sub do_grammarspec
389             {
390 2     2   82 my ($self, @items) = @_;
391 2         6 $self->do_trace("do_grammarspec", \@items);
392 2         6 my $result = { grammarspec => $items[1] };
393              
394 2 100       6 if (isNotNull($items[0]))
395             {
396 1         3 $self->{grammarstate} = $items[0];
397 1         3 $result->{type} = $items[0];
398             }
399              
400 2         4 return $result;
401             }
402              
403             sub do_inline_action
404             {
405 0     0   0 my ($self, @items) = @_;
406 0         0 $self->do_trace("do_inline_action", \@items);
407 0         0 my $result = { action => $items[0] };
408 0         0 return $result;
409             }
410              
411             sub do_redirect
412             {
413 1     1   29 my ($self, @items ) = @_;
414 1         4 $self->do_trace("do_redirect", \@items);
415              
416             ##
417             # only process skip/hidden redirects when 'ignoreredirect' is active
418             ##
419 1 50 33     5 return {} if $MarpaX::G4::Actions::ignoreredirect && $items[0] !~ /skip|hidden/i;
420              
421 1         2 my ($redir, @redirlist) = @items;
422              
423 1         2 my $result = $redir;
424 1 50       4 if ( scalar @redirlist > 0)
425             {
426 1         3 $result = [$redir];
427 1 50       3 map { push @$result, $_ if scalar @$_ > 0; } @redirlist;
  1         5  
428             }
429              
430 1         3 return $result;
431             }
432              
433             sub do_fragment
434             {
435 6     6   175 my ($self, $comment1, $name, $comment2, $tokenlist ) = @_;
436 6         17 $self->do_trace("do_fragment", \[$name, $tokenlist]);
437              
438 6 50 33     24 $tokenlist = $tokenlist->{rightsides} if ref $tokenlist eq "HASH" && exists $tokenlist->{rightsides};
439              
440 6 50       14 $self->abortWithError( "'tokenlist' must be an array in fragment $name", $tokenlist) if ref $tokenlist ne "ARRAY";
441              
442             ##
443             # save the fragment's tokenlist in the same format as a regular rule's right sides
444             ##
445 6         7 my $result = {};
446              
447 6 100       11 if ( scalar @$tokenlist <= 1 )
448             {
449 2         8 $result = {
450             type => 'fragment',
451             name => $name,
452             rightsides => [ { rhs => $tokenlist->[0] } ]
453             };
454             }
455             else
456             {
457 4         19 $result = {
458             type => 'fragment',
459             name => $name,
460             rightsides => [ { rhs => { type => 'tokengroup', definition => $tokenlist } } ]
461             };
462             }
463              
464 6 50 33     11 if (isNotNull($comment1) || isNotNull($comment2))
465             {
466 0         0 my $commentlines = [];
467 0 0       0 push @$commentlines, $comment1 if ref $comment1 eq '';
468 0 0       0 push @$commentlines, $comment1->{comment} if ref $comment1 eq "HASH";
469 0 0       0 if (ref $comment2 eq "ARRAY")
470             {
471 0         0 for my $cl (@$comment2)
472             {
473 0 0       0 push @$commentlines, $cl->{comment} if ref $cl eq "HASH";
474             }
475             }
476 0 0       0 $result->{comment} = $commentlines if scalar @$commentlines > 0;
477             }
478              
479 6 50       15 $result->{grammarstate} = $self->{grammarstate} if exists $self->{grammarstate};
480              
481 6         10 return $result;
482             }
483              
484             sub do_single_rule
485             {
486 8     8   238 my ( $self, @items ) = @_;
487 8         15 $self->do_trace("do_single_rule", \@items);
488              
489 8         12 my ( $name, $retclause, $comment, $rightsides ) = @items;
490              
491 8         14 my $result = { name => $name };
492              
493 8 100 66     28 $result->{redirect} = $rightsides->{redirect} if ref $rightsides eq "HASH" && exists $rightsides->{redirect};
494              
495             # CAVEAT: extract 'redirect' before reassigning '$rightsides' !
496 8 50 33     25 $rightsides = $rightsides->{rightsides} if ref $rightsides eq "HASH" && exists $rightsides->{rightsides};
497              
498 8         10 $result->{rightsides} = $rightsides;
499              
500 8 50       11 if (isNotNull($comment))
501             {
502 0 0 0     0 if (ref $comment eq "HASH" && exists $comment->{comment})
    0          
503             {
504 0         0 $result->{comment} = $comment->{comment};
505             }
506             elsif (ref $comment eq "ARRAY")
507             {
508 0         0 my $commentlines = [];
509 0         0 for my $cl (@$comment)
510             {
511 0 0 0     0 if (ref $cl eq "HASH" && exists $cl->{comment})
512             {
513 0         0 push @$commentlines, $cl->{comment};
514             }
515             else
516             {
517 0         0 push @$commentlines, $cl;
518             }
519             }
520 0         0 $result->{comment} = $commentlines;
521             }
522             else
523             {
524 0         0 $result->{comment} = $comment;
525             }
526             }
527              
528 8 50       12 $result->{returns} = $retclause if isNotNull($retclause);
529 8 100       35 $result->{grammarstate} = $self->{grammarstate} if exists $self->{grammarstate};
530              
531 8         16 return $result;
532             }
533              
534             sub do_right_side
535             {
536 22     22   621 my ($self, @items ) = @_;
537 22         36 $self->do_trace("do_right_side", \@items);
538 22         46 my ( $rhs, $comment1, $comment2, $endrhs ) = @items;
539              
540 22         26 my $result = {};
541              
542             ##
543             # create 'rightsides' from rhs
544             ##
545 22 100 66     53 if (ref $rhs eq "ARRAY" && scalar @$rhs > 0)
546             {
547 13         24 $rhs = { rightsides => $rhs };
548             }
549             else
550             {
551 9         18 $rhs = { rightsides => [$rhs] };
552             }
553              
554 22         28 $result = $rhs;
555              
556 22 50 33     29 if (isNotNull($comment1) || isNotNull($comment2))
557             {
558 0         0 my $commentlines = [];
559 0 0       0 push @$commentlines, $comment1 if ref $comment1 eq '';
560 0 0       0 push @$commentlines, $comment1->{comment} if ref $comment1 eq "HASH";
561 0 0       0 if (ref $comment2 eq "ARRAY")
562             {
563 0         0 for my $cl (@$comment2)
564             {
565 0 0       0 push @$commentlines, $cl->{comment} if ref $cl eq "HASH";
566             }
567             }
568 0 0       0 $result->{comment} = $commentlines if scalar @$commentlines > 0;
569             }
570              
571 22 100 66     57 my $redirect = $endrhs->{redirect} if ref $endrhs eq "HASH" && exists $endrhs->{redirect};
572              
573 22 50 33     40 if (ref $endrhs eq "ARRAY" && isNotNull($endrhs))
574             {
575 0         0 $self->abortWithError("unexpected : endrhs is an ARRAY\n");
576             }
577              
578             ##
579             # merge the rightsides of endrhs to those of rhs
580             ##
581 22 100 66     54 if (ref $endrhs eq "HASH" && exists $endrhs->{rightsides})
582             {
583 8         9 my $rightsides = $endrhs->{rightsides};
584              
585 8 50       14 $self->abortWithError("INTERNAL: endrhs rightsides must be an array", $rightsides ) if ref $rightsides ne "ARRAY";
586 8 50 33     23 $self->abortWithError("INTERNAL: rhs in do_endrhs must be a hash and contain 'rightsides'", $rhs ) if ref $rhs ne "HASH" || !exists $rhs->{rightsides};
587 8 50       14 $self->abortWithError("INTERNAL: rhs/rightsides in do_endrhs must be a an array" , $rhs ) if ref $rhs->{rightsides} ne "ARRAY";
588              
589             # merge the rightsides of rhs and endrhs
590 8         7 push (@{$result->{rightsides}}, @$rightsides);
  8         17  
591             }
592              
593 22 100       31 $result->{redirect} = $redirect if defined $redirect;
594              
595 22         37 return $result;
596             }
597              
598             sub do_empty_rule
599             {
600 14     14   394 my ($self, $opt_redir ) = @_;
601 14         30 $self->do_trace("do_empty_rule", \$opt_redir);
602 14         18 my $result = {};
603 14 100       18 $result->{redirect} = $opt_redir if isNotNull($opt_redir);
604 14         20 return $result;
605             }
606              
607             sub do_endrhs
608             {
609 8     8   229 my ($self, @items ) = @_;
610 8         18 $self->do_trace("do_endrhs", \@items);
611 8         9 my $rhs = $items[0];
612              
613             ##
614             # return an empty array if we run into an empty rule/alternative.
615             ##
616 8 50 33     17 if (ref $rhs eq "ARRAY" && scalar @$rhs == 0)
617             {
618 0         0 printf "WARNING: encountered empty right side of rule in 'endrhs'\n";
619 0         0 return [];
620             }
621              
622 8 50       16 $self->abortWithError("INTERNAL: 'rhs' must be a hash", $rhs) if ref $rhs ne "HASH";
623              
624 8 50 33     17 return { redirect => $rhs->{redirect} } if exists $rhs->{redirect} && !exists $rhs->{rightsides};
625              
626 8 50       11 if (!exists $rhs->{rightsides})
627             {
628 0         0 printf "WARNING: 'rhs' does not contain 'rightsides' in 'endrhs'";
629 0         0 return {};
630             }
631              
632 8         10 my $rightsides = $rhs->{rightsides};
633 8 50       15 $self->abortWithError( "INTERNAL: rightsides must be an array", $rightsides) if ref $rightsides ne "ARRAY";
634              
635 8         29 my $firstrhs = $rightsides->[0];
636 8 50       15 $self->abortWithError( "INTERNAL: firstrhs must be a hash", $firstrhs) if ref $firstrhs ne "HASH";
637 8 50       13 $self->abortWithError( "INTERNAL: firstrhs must contain 'rhs'", $firstrhs) if !exists $firstrhs->{rhs};
638              
639 8         11 $firstrhs->{rhs}{alternative} = 'true';
640              
641 8         14 return $rhs;
642             }
643              
644             sub do_rhs
645             {
646 22     22   612 my ($self, @items) = @_;
647 22         39 $self->do_trace("do_rhs", \@items);
648 22         25 my $result = $items[0];
649 22 100       33 $result = \@items if scalar @items > 1;
650 22         32 return $result;
651             }
652              
653             sub do_nonterminal
654             {
655 31     31   850 my ( $self, @items ) = @_;
656 31         49 $self->do_trace("do_nonterminal", \@items);
657 31         37 my ($opt_assoc, $rulecomponent, $opt_card) = @items;
658              
659 31 50 33     54 $rulecomponent = $rulecomponent->[0] if ref $rulecomponent eq "ARRAY" && scalar @$rulecomponent == 1;
660              
661 31 100       38 if (isNotNull($opt_card))
662             {
663 7 50       12 if (ref $rulecomponent ne "HASH")
664             {
665 0 0       0 if (ref $rulecomponent eq "")
666             {
667 0         0 $rulecomponent = { token => $rulecomponent };
668             }
669             else
670             {
671 0         0 $self->abortWithError( "'rulecomponent' must be a scalar or a hash", $rulecomponent );
672             }
673             }
674 7         13 $rulecomponent->{cardinality} = $self->flattenArray($opt_card);
675             }
676              
677 31 50       35 if (isNotNull($opt_assoc))
678             {
679 0 0       0 if (ref $rulecomponent ne "HASH")
680             {
681 0 0       0 if (ref $rulecomponent eq "")
682             {
683 0         0 $rulecomponent = { token => $rulecomponent };
684             }
685             else
686             {
687 0         0 $self->abortWithError( "'rulecomponent' must be a scalar or a hash", $rulecomponent );
688             }
689             }
690 0         0 $rulecomponent->{associativity} = $self->flattenArray($opt_assoc);
691             }
692              
693 31         52 my $result = { rhs => $rulecomponent };
694              
695 31         51 return $result;
696             }
697              
698             sub do_rulecomponent
699             {
700 0     0   0 my ($self, @items ) = @_;
701 0         0 $self->do_trace("do_rulecomponent", \@items);
702 0         0 return \@items;
703             }
704              
705             sub do_group
706             {
707 0     0   0 my ($self, @items ) = @_;
708 0         0 $self->do_trace("do_group", \@items);
709 0         0 my ( $opt_colon, $rhs, $grouplist, $opt_bar ) = @items;
710              
711 0         0 my $resultlist = [];
712              
713 0 0 0     0 $rhs = $rhs->[1] if ref $rhs eq "ARRAY" && scalar @$rhs == 1;
714 0         0 push @$resultlist, $rhs;
715              
716 0 0       0 push @$resultlist, $grouplist if ref $grouplist eq "HASH";
717 0 0       0 map { push @$resultlist, $_ } @$grouplist if ref $grouplist eq "ARRAY";
  0         0  
718              
719 0         0 my $result = { type => 'rulegroup', definition => $resultlist };
720 0 0       0 $result->{option} = '(:' if isNotNull($opt_colon);
721 0 0       0 $result->{option} = '|)' if isNotNull($opt_bar);
722              
723 0         0 return $result;
724             }
725              
726             sub do_grouplist
727             {
728 0     0   0 my ($self, @items ) = @_;
729 0         0 $self->do_trace("do_grouplist", \@items);
730 0         0 my $result = $items[0];
731 0         0 return $result;
732             }
733              
734             sub do_groupelement_concat
735             {
736 0     0   0 my ($self, @items ) = @_;
737 0         0 $self->do_trace("do_groupelement_concat", \@items);
738 0         0 my $element = $items[0];
739 0         0 my $result = { groupelement => $element };
740 0 0 0     0 $result = $element if ref $element eq "HASH" && scalar keys %$element == 1;
741 0         0 return $result;
742             }
743              
744             sub do_groupelement_alternative
745             {
746 0     0   0 my ($self, @items ) = @_;
747 0         0 $self->do_trace("do_groupelement_alternative", \@items);
748 0         0 my $element = $items[0];
749              
750 0         0 my $result = { alternative => 'true', groupelement => $element };
751              
752 0 0 0     0 if ( ref $element eq "HASH" && scalar keys %$element == 1)
753             {
754 0         0 $result = $element;
755 0         0 $result->{alternative} = 'true';
756             }
757              
758 0         0 return $result;
759             }
760              
761             sub do_token_group
762             {
763 5     5   144 my ($self, @items ) = @_;
764 5         11 $self->do_trace("do_token_group", \@items);
765 5         11 my $result = { type => 'tokengroup', definition => $items[1] };
766 5 50       7 $result->{option} = '(:' if isNotNull($items[0]);
767 5         8 return $result;
768             }
769              
770             sub do_token_list
771             {
772 11     11   318 my ($self, @items ) = @_;
773 11         21 $self->do_trace("do_token_list", \@items);
774 11         18 return \@items;
775             }
776              
777             sub do_tokenelement_concat
778             {
779 22     22   670 my ($self, $token, $opt_card ) = @_;
780 22         50 $self->do_trace("do_tokenelement_concat", \[$token, $opt_card]);
781 22 50 66     99 $token = $token->{token} if ref $token eq "HASH" && scalar keys %$token == 1 && exists $token->{token};
      66        
782 22         40 my $result = { token => $token };
783 22 100       30 $result->{cardinality} = $self->flattenArray($opt_card) if isNotNull($opt_card);
784 22         31 return $result;
785             }
786              
787             sub do_tokenelement_alternative
788             {
789 3     3   84 my ( $self, $token, $opt_card ) = @_;
790 3         8 $self->do_trace("do_tokenelement_alternative", \[$token, $opt_card]);
791 3 50 33     20 $token = $token->{token} if ref $token eq "HASH" && scalar keys %$token == 1 && exists $token->{token};
      33        
792 3         9 my $result = { alternative => 'true', token => $token };
793 3 50       4 $result->{cardinality} = $self->flattenArray($opt_card) if isNotNull($opt_card);
794 3         6 return $result;
795             }
796              
797             sub do_token
798             {
799 35     35   1002 my ($self, $opt_neg, $token ) = @_;
800 35         73 $self->do_trace("do_token", \[$opt_neg, $token]);
801 35         53 my $result = { token => $token };
802 35 100       51 $result->{negation} = $self->flattenArray($opt_neg) if isNotNull($opt_neg);
803 35         55 return $result;
804             }
805              
806             sub do_assignalias
807             {
808 0     0   0 my ($self, @items ) = @_;
809 0         0 $self->do_trace("do_assignalias", \@items);
810             # alias op can be '=' or '+='
811 0         0 my $result = { token => $items[2], alias => { name => $items[0], op => $items[1] } };
812 0         0 return $result;
813             }
814              
815             sub do_name
816             {
817 21     21   611 my ($self, @items ) = @_;
818 21         41 $self->do_trace("do_name", \@items);
819 21         38 my $result = { token => $items[1] };
820 21 50       32 $result->{negation} = $items[0] if isNotNull($items[0]);
821 21         35 return $result;
822             }
823              
824             sub do_range
825             {
826 0     0   0 my ($self, @items ) = @_;
827 0         0 $self->do_trace("do_range", \@items);
828 0         0 my $result = { type => 'range', begr => $items[0], endr => $items[1] };
829 0         0 return $result;
830             }
831              
832             sub do_regex
833             {
834 0     0   0 my ($self, @items ) = @_;
835 0         0 $self->do_trace("do_regex", \@items);
836 0         0 my $result = { type => 'regex', anchor => $items[0], cardinality => $items[1] };
837 0         0 return $result;
838             }
839              
840             sub do_valueclause
841             {
842 0     0   0 my ($self, @items ) = @_;
843 0         0 $self->do_trace("do_valueclause", \@items);
844 0         0 my $result = { type => 'value', name => $items[0] };
845 0         0 return $result;
846             }
847              
848             sub do_literal
849             {
850 21     21   625 my ($self, @items ) = @_;
851 21         37 $self->do_trace("do_literal", \@items);
852 21         25 my $value = $items[0];
853 21         27 my $delimiter = substr($value, 0, 1);
854 21         145 $value =~ s/^${delimiter}(.*)${delimiter}$/$1/;
855 21         62 my $result = { type => 'literal', value => $value };
856 21         38 return $result;
857             }
858              
859             sub do_characterclass
860             {
861 9     9   261 my ($self, @items ) = @_;
862 9         20 $self->do_trace("do_characterclass", \@items);
863 9         10 my $classtext = $items[0];
864 9         42 $classtext =~ s/\[(.*)\]/$1/;
865 9         21 my $result = { type => 'class', value => $classtext };
866 9         13 return $result;
867             }
868              
869             1;
870              
871             # ABSTRACT: G4 parser using Marpa
872              
873             =head1 SYNOPSIS
874             =for MarpaX::G4
875             name: Landing page synopsis
876             normalize-whitespace: 1
877             use MarpaX::G4;
878             my $infile = shift @ARGV;
879             my $grammartext = readFile($infile);
880             my $data = MarpaX::G4::parse_rules($grammartext);
881             =head1 DESCRIPTION
882             Parse an antlr4 grammar from the grammar text and return a parse tree.
883             =cut