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