File Coverage

blib/lib/Marpa/R2/SLG.pm
Criterion Covered Total %
statement 511 538 94.9
branch 148 196 75.5
condition 37 49 75.5
subroutine 32 33 96.9
pod n/a
total 728 816 89.2


line stmt bran cond sub pod time code
1             # Copyright 2022 Jeffrey Kegler
2             # This file is part of Marpa::R2. Marpa::R2 is free software: you can
3             # redistribute it and/or modify it under the terms of the GNU Lesser
4             # General Public License as published by the Free Software Foundation,
5             # either version 3 of the License, or (at your option) any later version.
6             #
7             # Marpa::R2 is distributed in the hope that it will be useful,
8             # but WITHOUT ANY WARRANTY; without even the implied warranty of
9             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10             # Lesser General Public License for more details.
11             #
12             # You should have received a copy of the GNU Lesser
13             # General Public License along with Marpa::R2. If not, see
14             # http://www.gnu.org/licenses/.
15              
16             package Marpa::R2::Scanless::G;
17              
18 132     132   2687 use 5.010001;
  132         556  
19 132     132   852 use strict;
  132         356  
  132         3019  
20 132     132   1124 use warnings;
  132         339  
  132         4517  
21              
22 132     132   817 use vars qw($VERSION $STRING_VERSION);
  132         416  
  132         13574  
23             $VERSION = '12.000000';
24             $STRING_VERSION = $VERSION;
25             ## no critic(BuiltinFunctions::ProhibitStringyEval)
26             $VERSION = eval $VERSION;
27             ## use critic
28              
29             package Marpa::R2::Internal::Scanless::G;
30              
31 132     132   1083 use Scalar::Util 'blessed';
  132         346  
  132         9800  
32 132     132   2313 use English qw( -no_match_vars );
  132         341  
  132         1045  
33              
34             # names of packages for strings
35             our $PACKAGE = 'Marpa::R2::Scanless::G';
36              
37             sub Marpa::R2::Internal::Scanless::meta_grammar {
38              
39 72     72   283 my $meta_slg = bless [], 'Marpa::R2::Scanless::G';
40 72         390 state $hashed_metag = Marpa::R2::Internal::MetaG::hashed_grammar();
41 72         387 $meta_slg->[Marpa::R2::Internal::Scanless::G::TRACE_TERMINALS] = 0;
42 72         684 Marpa::R2::Internal::Scanless::G::hash_to_runtime( $meta_slg,
43             $hashed_metag,
44             { bless_package => 'Marpa::R2::Internal::MetaAST_Nodes' } );
45              
46 72         482 my $thick_g1_grammar =
47             $meta_slg->[Marpa::R2::Internal::Scanless::G::THICK_G1_GRAMMAR];
48 72         231 my @mask_by_rule_id;
49             $mask_by_rule_id[$_] = $thick_g1_grammar->_rule_mask($_)
50 72         545 for $thick_g1_grammar->rule_ids();
51 72         474 $meta_slg->[Marpa::R2::Internal::Scanless::G::MASK_BY_RULE_ID] =
52             \@mask_by_rule_id;
53 72         243 $meta_slg->[Marpa::R2::Internal::Scanless::G::TRACE_TERMINALS] = 0;
54              
55 72         441 return $meta_slg;
56              
57             } ## end sub Marpa::R2::Internal::Scanless::meta_grammar
58              
59             sub Marpa::R2::Scanless::G::new {
60 199     199   74236 my ( $class, @hash_ref_args ) = @_;
61              
62 199         601 my $slg = [];
63 199         595 bless $slg, $class;
64              
65 199         898 my ($dsl, $g1_args) = Marpa::R2::Internal::Scanless::G::set ( $slg, 'new', @hash_ref_args );
66 199         1385 my $ast = Marpa::R2::Internal::MetaAST->new( $dsl );
67 196         1102 my $hashed_ast = $ast->ast_to_hash();
68 196         1193 Marpa::R2::Internal::Scanless::G::hash_to_runtime($slg, $hashed_ast, $g1_args);
69 180         36276 return $slg;
70             } ## end sub Marpa::R2::Scanless::G::new
71              
72             sub Marpa::R2::Scanless::G::set {
73 1     1   1367 my ( $slg, @hash_ref_args ) = @_;
74 1         6 Marpa::R2::Internal::Scanless::G::set ( $slg, 'set', @hash_ref_args );
75 1         3 return $slg;
76             }
77              
78             # The context flag indicates whether this ::set() is called directly by the user;
79             # is for the external constructor; or is for the internal ("meta") constructor.
80             # "Context" flags of this kind
81             # are much decried practice, and for good reason, but in this case
82             # I think it is justified.
83             # This logic really needs to be all in one place, and so a flag
84             # to trigger the minor differences needed by the various calling
85             # contexts is a small price to pay.
86             sub Marpa::R2::Internal::Scanless::G::set {
87 200     200   747 my ( $slg, $method, @hash_ref_args ) = @_;
88              
89             # Other possible grammar options:
90             # default_rank
91             # inaccessible_ok
92             # unproductive_ok
93             # warnings
94              
95             state $copy_to_g1_args =
96 200         574 { map { ( $_, 1 ); }
  272         840  
97             qw(trace_file_handle action_object default_action bless_package) };
98             state $set_method_args =
99 200         540 { map { ( $_, 1 ); } qw(trace_file_handle trace_terminals) };
  136         481  
100             state $new_method_args = {
101 200         530 map { ( $_, 1 ); } qw(source trace_terminals), keys %{$copy_to_g1_args}
  408         821  
  68         313  
102             };
103 200         825 for my $args (@hash_ref_args) {
104 200         644 my $ref_type = ref $args;
105 200 50       703 if ( not $ref_type ) {
106 0         0 Marpa::R2::exception( q{$slg->}
107             . $method
108             . qq{() expects args as ref to HASH; got non-reference instead}
109             );
110             } ## end if ( not $ref_type )
111 200 50       971 if ( $ref_type ne 'HASH' ) {
112 0         0 Marpa::R2::exception( q{$slg->}
113             . $method
114             . qq{() expects args as ref to HASH, got ref to $ref_type instead}
115             );
116             } ## end if ( $ref_type ne 'HASH' )
117             } ## end for my $args (@hash_ref_args)
118              
119 200         529 my %flat_args = ();
120 200         569 for my $hash_ref (@hash_ref_args) {
121 200         393 ARG: for my $arg_name ( keys %{$hash_ref} ) {
  200         778  
122 239         910 $flat_args{$arg_name} = $hash_ref->{$arg_name};
123             }
124             }
125              
126 200         481 my $ok_args = $set_method_args;
127 200 100       858 $ok_args = $new_method_args if $method eq 'new';
128 200         711 my @bad_args = grep { not $ok_args->{$_} } keys %flat_args;
  239         1028  
129 200 50       992 if ( scalar @bad_args ) {
130 0         0 Marpa::R2::exception(
131             q{Bad named argument(s) to $slg->}
132             . $method
133             . q{() method: }
134             . join q{ },
135             @bad_args
136             );
137             } ## end if ( scalar @bad_args )
138              
139 200         518 my $dsl;
140 200 100       728 if ( $method eq 'new' ) {
141 199         414 state $arg_name = 'source';
142 199         513 $dsl = $flat_args{$arg_name};
143 199 50       631 Marpa::R2::exception(
144             qq{Marpa::R2::Scanless::G::new() called without a "$arg_name" argument}
145             ) if not defined $dsl;
146 199         658 my $ref_type = ref $dsl;
147 199 50       625 if ( $ref_type ne 'SCALAR' ) {
148 0 0       0 my $desc = $ref_type ? "a ref to $ref_type" : 'not a ref';
149 0         0 Marpa::R2::exception(
150             qq{'$arg_name' name argument to Marpa::R2::Scanless::G->new() is $desc\n},
151             " It should be a ref to a string\n"
152             );
153             } ## end if ( $ref_type ne 'SCALAR' )
154 199 50       381 if ( not defined ${$dsl} ) {
  199         720  
155 0         0 Marpa::R2::exception(
156             qq{'$arg_name' name argument to Marpa::R2::Scanless::G->new() is a ref to a an undef\n},
157             " It should be a ref to a string\n"
158             );
159             } ## end if ( $ref_type ne 'SCALAR' )
160             } ## end if ( $method eq 'new' )
161              
162             # A bit hack-ish, but some named args will be copies straight to a member of
163             # the Scanless::G class, so this maps named args to the index of the array
164             # that holds the members.
165 200         553 state $copy_arg_to_index = {
166             trace_file_handle => Marpa::R2::Internal::Scanless::G::TRACE_FILE_HANDLE,
167             trace_terminals => Marpa::R2::Internal::Scanless::G::TRACE_TERMINALS
168             };
169              
170 200         641 ARG: for my $arg_name ( keys %flat_args ) {
171 239         565 my $index = $copy_arg_to_index->{$arg_name};
172 239 100       903 next ARG if not defined $index;
173 1         3 my $value = $flat_args{$arg_name};
174 1         3 $slg->[$index] = $value;
175             } ## end ARG: for my $arg_name ( keys %flat_args )
176              
177             # Normalize trace_terminals
178 200 100       2053 $slg->[Marpa::R2::Internal::Scanless::G::TRACE_TERMINALS] = 0
179             if not Scalar::Util::looks_like_number(
180             $slg->[Marpa::R2::Internal::Scanless::G::TRACE_TERMINALS] );
181              
182             # Trace file handle needs to be populated downwards
183 200 100       1002 if ( defined( my $trace_file_handle = $flat_args{trace_file_handle} ) ) {
184             GRAMMAR:
185 1         3 for my $naif_grammar (
186             $slg->[Marpa::R2::Internal::Scanless::G::THICK_G1_GRAMMAR],
187 1         5 @{ $slg->[Marpa::R2::Internal::Scanless::G::THICK_LEX_GRAMMARS] }
188             )
189             {
190 2 50       7 next GRAMMAR if not defined $naif_grammar;
191 2         9 $naif_grammar->set( { trace_file_handle => $trace_file_handle } );
192             } ## end GRAMMAR: for my $naif_grammar ( $slg->[...])
193             } ## end if ( defined( my $trace_file_handle = $flat_args{...}))
194              
195 200 100       742 if ( $method eq 'new' ) {
196              
197             # Prune flat args of all those named args which are NOT to be copied
198             # into the NAIF recce args
199 199         560 for my $arg_name ( keys %flat_args ) {
200             delete $flat_args{$arg_name}
201 238 100       1010 if not $copy_to_g1_args->{$arg_name};
202             }
203              
204             # trace file handle must always be defined
205 199   50     1443 $slg->[Marpa::R2::Internal::Scanless::G::TRACE_FILE_HANDLE] //= \*STDERR;
206              
207 199         888 return ($dsl, \%flat_args);
208             } ## end if ( $method eq 'new' )
209              
210 1         3 return;
211              
212             } ## end sub Marpa::R2::Internal::Scanless::G::set
213              
214             sub Marpa::R2::Internal::Scanless::G::hash_to_runtime {
215 268     268   1029 my ( $slg, $hashed_source, $g1_args ) = @_;
216              
217 268         767 my $trace_terminals =
218             $slg->[Marpa::R2::Internal::Scanless::G::TRACE_TERMINALS];
219              
220             # Pre-lexer G1 processing
221              
222             my $start_lhs = $hashed_source->{'start_lhs'}
223 268   66     1434 // $hashed_source->{'first_lhs'};
224 268 50       994 Marpa::R2::exception('No rules in SLIF grammar')
225             if not defined $start_lhs;
226 268         1506 Marpa::R2::Internal::MetaAST::start_rule_create( $hashed_source,
227             $start_lhs );
228              
229 268         908 $slg->[Marpa::R2::Internal::Scanless::G::CACHE_RULEIDS_BY_LHS_NAME] = {};
230             $slg->[Marpa::R2::Internal::Scanless::G::DEFAULT_G1_START_ACTION] =
231 268         788 $hashed_source->{'default_g1_start_action'};
232              
233             my $trace_fh =
234             $slg->[Marpa::R2::Internal::Scanless::G::TRACE_FILE_HANDLE] =
235 268   50     1863 $g1_args->{trace_file_handle} // \*STDERR;
236              
237             my $if_inaccessible_default =
238 268   100     1650 $hashed_source->{defaults}->{if_inaccessible} // 'warn';
239              
240             # Prepare the arguments for the G1 grammar
241 268         804 $g1_args->{rules} = $hashed_source->{rules}->{G1};
242 268         706 $g1_args->{symbols} = $hashed_source->{symbols}->{G1};
243 268         620 state $g1_target_symbol = '[:start]';
244 268         784 $g1_args->{start} = $g1_target_symbol;
245 268         1144 $g1_args->{'_internal_'} =
246             { 'if_inaccessible' => $if_inaccessible_default };
247              
248 268         1863 my $thick_g1_grammar = Marpa::R2::Grammar->new($g1_args);
249 264         1278 my $g1_tracer = $thick_g1_grammar->tracer();
250 264         1018 my $g1_thin = $g1_tracer->grammar();
251              
252 264         722 my $symbol_ids_by_event_name_and_type = {};
253 264         735 $slg->[
254             Marpa::R2::Internal::Scanless::G::SYMBOL_IDS_BY_EVENT_NAME_AND_TYPE]
255             = $symbol_ids_by_event_name_and_type;
256              
257 264         614 my $completion_events_by_name = $hashed_source->{completion_events};
258 264         771 my $completion_events_by_id =
259             $slg->[Marpa::R2::Internal::Scanless::G::COMPLETION_EVENT_BY_ID] = [];
260 264         586 for my $symbol_name ( keys %{$completion_events_by_name} ) {
  264         1155  
261             my ( $event_name, $is_active ) =
262 54         92 @{ $completion_events_by_name->{$symbol_name} };
  54         127  
263 54         148 my $symbol_id = $g1_tracer->symbol_by_name($symbol_name);
264 54 50       142 if ( not defined $symbol_id ) {
265 0         0 Marpa::R2::exception(
266             "Completion event defined for non-existent symbol: $symbol_name\n"
267             );
268             }
269              
270             # Must be done before precomputation
271 54         195 $g1_thin->symbol_is_completion_event_set( $symbol_id, 1 );
272 54 100       153 $g1_thin->completion_symbol_activate( $symbol_id, 0 )
273             if not $is_active;
274 54         118 $slg->[Marpa::R2::Internal::Scanless::G::COMPLETION_EVENT_BY_ID]
275             ->[$symbol_id] = $event_name;
276             push
277 54         94 @{ $symbol_ids_by_event_name_and_type->{$event_name}->{completion}
278 54         288 }, $symbol_id;
279             } ## end for my $symbol_name ( keys %{$completion_events_by_name...})
280              
281 264         725 my $nulled_events_by_name = $hashed_source->{nulled_events};
282 264         891 my $nulled_events_by_id =
283             $slg->[Marpa::R2::Internal::Scanless::G::NULLED_EVENT_BY_ID] = [];
284 264         545 for my $symbol_name ( keys %{$nulled_events_by_name} ) {
  264         935  
285             my ( $event_name, $is_active ) =
286 46         67 @{ $nulled_events_by_name->{$symbol_name} };
  46         95  
287 46         103 my $symbol_id = $g1_tracer->symbol_by_name($symbol_name);
288 46 50       94 if ( not defined $symbol_id ) {
289 0         0 Marpa::R2::exception(
290             "nulled event defined for non-existent symbol: $symbol_name\n"
291             );
292             }
293              
294             # Must be done before precomputation
295 46         124 $g1_thin->symbol_is_nulled_event_set( $symbol_id, 1 );
296 46 100       98 $g1_thin->nulled_symbol_activate( $symbol_id, 0 ) if not $is_active;
297 46         84 $slg->[Marpa::R2::Internal::Scanless::G::NULLED_EVENT_BY_ID]
298             ->[$symbol_id] = $event_name;
299 46         68 push @{ $symbol_ids_by_event_name_and_type->{$event_name}->{nulled} },
  46         199  
300             $symbol_id;
301             } ## end for my $symbol_name ( keys %{$nulled_events_by_name} )
302              
303 264         677 my $prediction_events_by_name = $hashed_source->{prediction_events};
304 264         807 my $prediction_events_by_id =
305             $slg->[Marpa::R2::Internal::Scanless::G::PREDICTION_EVENT_BY_ID] = [];
306 264         562 for my $symbol_name ( keys %{$prediction_events_by_name} ) {
  264         881  
307             my ( $event_name, $is_active ) =
308 52         86 @{ $prediction_events_by_name->{$symbol_name} };
  52         110  
309 52         146 my $symbol_id = $g1_tracer->symbol_by_name($symbol_name);
310 52 50       111 if ( not defined $symbol_id ) {
311 0         0 Marpa::R2::exception(
312             "prediction event defined for non-existent symbol: $symbol_name\n"
313             );
314             }
315              
316             # Must be done before precomputation
317 52         158 $g1_thin->symbol_is_prediction_event_set( $symbol_id, 1 );
318 52 100       102 $g1_thin->prediction_symbol_activate( $symbol_id, 0 )
319             if not $is_active;
320 52         129 $slg->[Marpa::R2::Internal::Scanless::G::PREDICTION_EVENT_BY_ID]
321             ->[$symbol_id] = $event_name;
322             push
323 52         68 @{ $symbol_ids_by_event_name_and_type->{$event_name}->{prediction}
324 52         240 }, $symbol_id;
325             } ## end for my $symbol_name ( keys %{$prediction_events_by_name...})
326              
327 264         834 my $lexeme_events_by_id =
328             $slg->[Marpa::R2::Internal::Scanless::G::LEXEME_EVENT_BY_ID] = [];
329              
330 264 100       1211 if (defined(
331             my $precompute_error =
332             Marpa::R2::Internal::Grammar::slif_precompute(
333             $thick_g1_grammar)
334             )
335             )
336             {
337 1 50       3 if ( $precompute_error == $Marpa::R2::Error::UNPRODUCTIVE_START ) {
338              
339             # Maybe someday improve this by finding the start rule and showing
340             # its RHS -- for now it is clear enough
341 1         6 Marpa::R2::exception(qq{Unproductive start symbol});
342             } ## end if ( $precompute_error == ...)
343             Marpa::R2::exception(
344 0         0 'Internal errror: unnkown precompute error code ',
345             $precompute_error );
346             } ## end if ( defined( my $precompute_error = ...))
347              
348             # Find out the list of lexemes according to G1
349 260         768 my %g1_id_by_lexeme_name = ();
350 260         1656 SYMBOL: for my $symbol_id ( 0 .. $g1_thin->highest_symbol_id() ) {
351              
352             # Not a lexeme, according to G1
353 12758 100       26560 next SYMBOL if not $g1_thin->symbol_is_terminal($symbol_id);
354              
355 7686         14180 my $symbol_name = $g1_tracer->symbol_name($symbol_id);
356 7686         14028 $g1_id_by_lexeme_name{$symbol_name} = $symbol_id;
357              
358             } ## end SYMBOL: for my $symbol_id ( 0 .. $g1_thin->highest_symbol_id(...))
359              
360             # A first phase of applying defaults
361 260         1087 my $discard_default_adverbs = $hashed_source->{discard_default_adverbs};
362 260         749 my $lexeme_declarations = $hashed_source->{lexeme_declarations};
363 260         657 my $lexeme_default_adverbs = $hashed_source->{lexeme_default_adverbs};
364 260   100     1179 my $latm_default_value = $lexeme_default_adverbs->{latm} // 0;
365              
366             # Current lexeme data is spread out in many places.
367             # Change so that it all resides in this hash, indexed by
368             # name
369 260         694 my %lexeme_data = ();
370              
371             # Determine "latm" status
372 260         1847 LEXEME: for my $lexeme_name ( keys %g1_id_by_lexeme_name ) {
373 7686         10182 my $declarations = $lexeme_declarations->{$lexeme_name};
374 7686   100     17395 my $latm_value = $declarations->{latm} // $latm_default_value;
375 7686         18581 $lexeme_data{$lexeme_name}{latm} = $latm_value;
376             }
377              
378             # Lexers
379              
380 260         1108 my $lexer_id = 0;
381 260         666 my $lexer_name = 'L0';
382              
383 260         587 my %lexer_id_by_name = ();
384 260         551 my %thick_grammar_by_lexer_name = ();
385 260         523 my @discard_event_by_lexer_rule_id = ();
386 260         517 my %lexer_and_rule_to_g1_lexeme = ();
387 260         509 my %character_class_table_by_lexer_name = ();
388 260         522 state $lex_start_symbol_name = '[:start_lex]';
389 260         506 state $discard_symbol_name = '[:discard]';
390              
391 260         666 my $lexer_rules = $hashed_source->{rules}->{$lexer_name};
392 260         571 my $character_class_hash = $hashed_source->{character_classes};
393 260         615 my $lexer_symbols = $hashed_source->{symbols}->{'L'};
394              
395             # If no lexer rules, fake a lexer
396             # Fake a lexer -- it discards symbols in character classes which
397             # never matches
398 260 100       931 if ( not $lexer_rules ) {
399 2         11 $character_class_hash = { '[[^\\d\\D]]' => [ '[^\\d\\D]', '' ] };
400 2         40 $lexer_rules = [
401             { 'rhs' => [ '[[^\\d\\D]]' ],
402             'lhs' => '[:discard]',
403             'symbol_as_event' => '[^\\d\\D]',
404             'description' => 'Discard rule for <[[^\\d\\D]]>'
405             },
406             ];
407 2         19 $lexer_symbols = {
408             '[:discard]' => {
409             'display_form' => ':discard',
410             'description' => 'Internal LHS for lexer "L0" discard'
411             },
412             '[[^\\d\\D]]' => {
413             'dsl_form' => '[^\\d\\D]',
414             'display_form' => '[^\\d\\D]',
415             'description' => 'Character class: [^\\d\\D]'
416             }
417             };
418             } ## end if ( not $lexer_rules )
419              
420 260         609 my %lex_lhs = ();
421 260         571 my %lex_rhs = ();
422 260         502 my %lex_separator = ();
423 260         516 my %lexer_rule_by_tag = ();
424              
425 260         638 my $rule_tag = 'rule0';
426 260         470 for my $lex_rule ( @{$lexer_rules} ) {
  260         685  
427 12004         23031 $lex_rule->{tag} = ++$rule_tag;
428 12004         14632 my %lex_rule_copy = %{$lex_rule};
  12004         54591  
429 12004         33559 $lexer_rule_by_tag{$rule_tag} = \%lex_rule_copy;
430 12004         16259 delete $lex_rule->{event};
431 12004         14599 delete $lex_rule->{symbol_as_event};
432 12004         21324 $lex_lhs{ $lex_rule->{lhs} } = 1;
433 12004         14638 $lex_rhs{$_} = 1 for @{ $lex_rule->{rhs} };
  12004         39078  
434 12004 100       28151 if ( defined( my $separator = $lex_rule->{separator} ) ) {
435 144         716 $lex_separator{$separator} = 1;
436             }
437             } ## end for my $lex_rule ( @{$lexer_rules} )
438              
439 260         900 my %this_lexer_symbols = ();
440             SYMBOL:
441 260         4285 for my $symbol_name ( ( keys %lex_lhs ), ( keys %lex_rhs ),
442             ( keys %lex_separator ) )
443             {
444 17910         23994 my $symbol_data = $lexer_symbols->{$symbol_name};
445 17910 100       34436 $this_lexer_symbols{$symbol_name} = $symbol_data
446             if defined $symbol_data;
447             } ## end SYMBOL: for my $symbol_name ( ( keys %lex_lhs ), ( keys %lex_rhs...))
448              
449 7870         13915 my %is_lexeme_in_this_lexer = map { $_ => 1 }
450 260   100     2762 grep { not $lex_rhs{$_} and not $lex_separator{$_} }
  10336         25223  
451             keys %lex_lhs;
452              
453 260         2738 my @lex_lexeme_names = keys %is_lexeme_in_this_lexer;
454              
455 260 50       1052 Marpa::R2::exception( "No lexemes in lexer: $lexer_name\n",
456             " An SLIF grammar must have at least one lexeme\n" )
457             if not scalar @lex_lexeme_names;
458              
459             # Do I need this?
460             my @unproductive =
461 0         0 map {"<$_>"}
462 260   66     1693 grep { not $lex_lhs{$_} and not $_ =~ /\A \[\[ /xms }
  7574         23668  
463             ( keys %lex_rhs, keys %lex_separator );
464 260 50       1390 if (@unproductive) {
465 0         0 Marpa::R2::exception( 'Unproductive lexical symbols: ',
466             join q{ }, @unproductive );
467             }
468              
469             $this_lexer_symbols{$lex_start_symbol_name}->{display_form} =
470 260         1127 ':start_lex';
471             $this_lexer_symbols{$lex_start_symbol_name}->{description} =
472 260         771 'Internal L0 (lexical) start symbol';
473 260         4134 push @{$lexer_rules}, map {
474 260         553 ;
475 7870         32377 { description => "Internal lexical start rule for <$_>",
476             lhs => $lex_start_symbol_name,
477             rhs => [$_]
478             }
479             } sort keys %is_lexeme_in_this_lexer;
480              
481             # Prepare the arguments for the lex grammar
482 260         1476 my %lex_args = ();
483 260         855 $lex_args{trace_file_handle} = $trace_fh;
484 260         759 $lex_args{start} = $lex_start_symbol_name;
485 260         976 $lex_args{'_internal_'} =
486             { 'if_inaccessible' => $if_inaccessible_default };
487 260         717 $lex_args{rules} = $lexer_rules;
488 260         701 $lex_args{symbols} = \%this_lexer_symbols;
489              
490             # Create the thick lex grammar
491 260         1371 my $lex_grammar = Marpa::R2::Grammar->new( \%lex_args );
492 260         1018 $thick_grammar_by_lexer_name{$lexer_name} = $lex_grammar;
493 260         1057 my $lex_tracer = $lex_grammar->tracer();
494 260         942 my $lex_thin = $lex_tracer->grammar();
495              
496 260   100     984 my $lex_discard_symbol_id =
497             $lex_tracer->symbol_by_name($discard_symbol_name) // -1;
498 260         649 my @lex_lexeme_to_g1_symbol;
499 260         4757 $lex_lexeme_to_g1_symbol[$_] = -1 for 0 .. $g1_thin->highest_symbol_id();
500              
501 260         1284 LEXEME_NAME: for my $lexeme_name (@lex_lexeme_names) {
502 7868 100       14379 next LEXEME_NAME if $lexeme_name eq $discard_symbol_name;
503 7684 50       12402 next LEXEME_NAME if $lexeme_name eq $lex_start_symbol_name;
504 7684         11466 my $g1_symbol_id = $g1_id_by_lexeme_name{$lexeme_name};
505 7684 100       12577 if ( not defined $g1_symbol_id ) {
506 4         22 Marpa::R2::exception(
507             qq{<$lexeme_name> is a lexeme but it is not a legal lexeme in G1:\n},
508             qq{ Lexemes must be G1 symbols that do not appear on a G1 LHS.\n}
509             );
510             }
511 7680 100       19104 if ( not $g1_thin->symbol_is_accessible($g1_symbol_id) ) {
512 2         11 my $message =
513             "A lexeme in lexer $lexer_name is not accessible from the G1 start symbol: $lexeme_name";
514 2 50       7 say {$trace_fh} $message
  0         0  
515             if $if_inaccessible_default eq 'warn';
516 2 50       6 Marpa::R2::exception($message)
517             if $if_inaccessible_default eq 'fatal';
518             } ## end if ( not $g1_thin->symbol_is_accessible($g1_symbol_id...))
519 7680         15100 my $lex_symbol_id = $lex_tracer->symbol_by_name($lexeme_name);
520 7680         26992 $lexeme_data{$lexeme_name}{lexers}{$lexer_name}{'id'} =
521             $lex_symbol_id;
522 7680         13441 $lex_lexeme_to_g1_symbol[$lex_symbol_id] = $g1_symbol_id;
523             } ## end LEXEME_NAME: for my $lexeme_name (@lex_lexeme_names)
524              
525 256         750 my @lex_rule_to_g1_lexeme;
526 256         1018 my $lex_start_symbol_id =
527             $lex_tracer->symbol_by_name($lex_start_symbol_name);
528 256         1482 RULE_ID: for my $rule_id ( 0 .. $lex_thin->highest_rule_id() ) {
529 19862         33089 my $lhs_id = $lex_thin->rule_lhs($rule_id);
530 19862 100       32568 if ( $lhs_id == $lex_discard_symbol_id ) {
531 295         773 $lex_rule_to_g1_lexeme[$rule_id] = -2;
532 295         670 next RULE_ID;
533             }
534 19567 100       31740 if ( $lhs_id != $lex_start_symbol_id ) {
535 11703         15441 $lex_rule_to_g1_lexeme[$rule_id] = -1;
536 11703         16474 next RULE_ID;
537             }
538 7864         13848 my $lexer_lexeme_id = $lex_thin->rule_rhs( $rule_id, 0 );
539 7864 100       13489 if ( $lexer_lexeme_id == $lex_discard_symbol_id ) {
540 184         583 $lex_rule_to_g1_lexeme[$rule_id] = -1;
541 184         428 next RULE_ID;
542             }
543 7680   50     13570 my $lexeme_id = $lex_lexeme_to_g1_symbol[$lexer_lexeme_id] // -1;
544 7680         10669 $lex_rule_to_g1_lexeme[$rule_id] = $lexeme_id;
545 7680 50       12405 next RULE_ID if $lexeme_id < 0;
546 7680         14908 my $lexeme_name = $g1_tracer->symbol_name($lexeme_id);
547              
548             # If 1 is the default, we don't need an assertion
549 7680 100       16065 next RULE_ID if not $lexeme_data{$lexeme_name}{latm};
550              
551             my $assertion_id =
552 7253         11278 $lexeme_data{$lexeme_name}{lexers}{$lexer_name}{'assertion'};
553 7253 50       11783 if ( not defined $assertion_id ) {
554 7253         13055 $assertion_id = $lex_thin->zwa_new(0);
555              
556 7253 50       12599 if ( $trace_terminals >= 2 ) {
557 0         0 say {$trace_fh} "Assertion $assertion_id defaults to 0";
  0         0  
558             }
559              
560 7253         12265 $lexeme_data{$lexeme_name}{lexers}{$lexer_name}{'assertion'} =
561             $assertion_id;
562             } ## end if ( not defined $assertion_id )
563 7253         18067 $lex_thin->zwa_place( $assertion_id, $rule_id, 0 );
564 7253 50       14502 if ( $trace_terminals >= 2 ) {
565 0         0 say {$trace_fh}
  0         0  
566             "Assertion $assertion_id applied to $lexer_name rule ",
567             slg_rule_show( $slg, $rule_id, $lex_grammar );
568             }
569             } ## end RULE_ID: for my $rule_id ( 0 .. $lex_thin->highest_rule_id() )
570              
571 256         1529 Marpa::R2::Internal::Grammar::slif_precompute($lex_grammar);
572              
573 256         903 my @class_table = ();
574              
575             CLASS_SYMBOL:
576 256         701 for my $class_symbol ( sort keys %{$character_class_hash} ) {
  256         4107  
577 5104         11370 my $symbol_id = $lex_tracer->symbol_by_name($class_symbol);
578 5104 50       9759 next CLASS_SYMBOL if not defined $symbol_id;
579 5104         8281 my $cc_components = $character_class_hash->{$class_symbol};
580 5104         9700 my ( $compiled_re, $error ) =
581             Marpa::R2::Internal::MetaAST::char_class_to_re($cc_components);
582 5104 50       9638 if ( not $compiled_re ) {
583 0         0 $error =~ s/^/ /gxms; #indent all lines
584 0         0 Marpa::R2::exception(
585             "Failed belatedly to evaluate character class\n", $error );
586             }
587 5104         12159 push @class_table, [ $symbol_id, $compiled_re ];
588             } ## end CLASS_SYMBOL: for my $class_symbol ( sort keys %{...})
589 256         1479 $character_class_table_by_lexer_name{$lexer_name} = \@class_table;
590              
591 256         776 $lexer_and_rule_to_g1_lexeme{$lexer_name} = \@lex_rule_to_g1_lexeme;
592              
593             # Apply defaults to determine the discard event for every
594             # rule id of the lexer.
595              
596 256         768 my $default_discard_event = $discard_default_adverbs->{event};
597 256         1518 RULE_ID: for my $rule_id ( 0 .. $lex_thin->highest_rule_id() ) {
598 19862         35133 my $tag = $lex_grammar->tag($rule_id);
599 19862 100       36205 next RULE_ID if not defined $tag;
600 11998         14469 my $event;
601             FIND_EVENT: {
602 11998         14190 $event = $lexer_rule_by_tag{$tag}->{event};
  11998         21529  
603 11998 100       19465 last FIND_EVENT if defined $event;
604 11947         20717 my $lhs_id = $lex_thin->rule_lhs($rule_id);
605 11947 100       22815 last FIND_EVENT if $lhs_id != $lex_discard_symbol_id;
606 244         592 $event = $default_discard_event;
607             } ## end FIND_EVENT:
608 11998 100       22348 next RULE_ID if not defined $event;
609              
610 60         120 my ( $event_name, $event_starts_active ) = @{$event};
  60         169  
611 60 100       160 if ( $event_name eq q{'symbol} ) {
612             my @event = (
613             $lexer_rule_by_tag{$tag}->{symbol_as_event},
614 15         36 $event_starts_active
615             );
616 15         26 $discard_event_by_lexer_rule_id[$rule_id] = \@event;
617 15         28 next RULE_ID;
618             } ## end if ( $event_name eq q{'symbol} )
619 45 50       150 if ( ( substr $event_name, 0, 1 ) ne q{'} ) {
620 45         86 $discard_event_by_lexer_rule_id[$rule_id] = $event;
621 45         93 next RULE_ID;
622             }
623             Marpa::R2::exception(
624 0         0 qq{Discard event has unknown name: "$event_name"}
625             );
626              
627             } ## end RULE_ID: for my $rule_id ( 0 .. $lex_thin->highest_rule_id() )
628              
629             # Post-lexer G1 processing
630              
631 256         1090 my $thick_L0 = $thick_grammar_by_lexer_name{'L0'};
632 256         944 my $thin_L0 = $thick_L0->[Marpa::R2::Internal::Grammar::C];
633 256         1280 my $thin_slg = $slg->[Marpa::R2::Internal::Scanless::G::C] =
634             Marpa::R2::Thin::SLG->new( $thin_L0, $g1_tracer->grammar() );
635              
636             # Relies on default lexer being given number zero
637 256         947 $lexer_id_by_name{'L0'} = 0;
638              
639 256         2107 LEXEME: for my $lexeme_name ( keys %g1_id_by_lexeme_name ) {
640             Marpa::R2::exception(
641             "A lexeme in G1 is not a lexeme in any of the lexers: $lexeme_name"
642 7681 100       14021 ) if not defined $lexeme_data{$lexeme_name}{'lexers'};
643             }
644              
645             # At this point we know which symbols are lexemes.
646             # So now let's check for inconsistencies
647              
648             # Check for lexeme declarations for things which are not lexemes
649 252         1047 for my $lexeme_name ( keys %{$lexeme_declarations} ) {
  252         1106  
650             Marpa::R2::exception(
651             "Symbol <$lexeme_name> is declared as a lexeme, but it is not used as one.\n"
652 59 50       203 ) if not defined $g1_id_by_lexeme_name{$lexeme_name};
653             }
654              
655             # Now that we know the lexemes, check attempts to defined a
656             # completion or a nulled event for one
657 252         566 for my $symbol_name ( keys %{$completion_events_by_name} ) {
  252         824  
658             Marpa::R2::exception(
659             "A completion event is declared for <$symbol_name>, but it is a lexeme.\n",
660             " Completion events are only valid for symbols on the LHS of G1 rules.\n"
661 54 50       163 ) if defined $g1_id_by_lexeme_name{$symbol_name};
662             } ## end for my $symbol_name ( keys %{$completion_events_by_name...})
663              
664 252         548 for my $symbol_name ( keys %{$nulled_events_by_name} ) {
  252         759  
665             Marpa::R2::exception(
666             "A nulled event is declared for <$symbol_name>, but it is a G1 lexeme.\n",
667             " nulled events are only valid for symbols on the LHS of G1 rules.\n"
668 46 50       105 ) if defined $g1_id_by_lexeme_name{$symbol_name};
669             } ## end for my $symbol_name ( keys %{$nulled_events_by_name} )
670              
671             # Mark the lexemes, and set their data
672             # Now that we have created the SLG, we can set the latm value,
673             # already determined above.
674 252         1496 LEXEME: for my $lexeme_name ( keys %g1_id_by_lexeme_name ) {
675 7676         10724 my $g1_lexeme_id = $g1_id_by_lexeme_name{$lexeme_name};
676 7676         9567 my $declarations = $lexeme_declarations->{$lexeme_name};
677 7676   100     17824 my $priority = $declarations->{priority} // 0;
678 7676         16133 $thin_slg->g1_lexeme_set( $g1_lexeme_id, $priority );
679 7676   50     13694 my $latm_value = $lexeme_data{$lexeme_name}{latm} // 0;
680 7676         16189 $thin_slg->g1_lexeme_latm_set( $g1_lexeme_id, $latm_value );
681 7676         9817 my $pause_value = $declarations->{pause};
682 7676 100       15392 if ( defined $pause_value ) {
683 54         258 $thin_slg->g1_lexeme_pause_set( $g1_lexeme_id, $pause_value );
684 54         97 my $is_active = 1;
685              
686 54 100       169 if ( defined( my $event_data = $declarations->{'event'} ) ) {
687 49         77 my $event_name;
688 49         88 ( $event_name, $is_active ) = @{$event_data};
  49         113  
689 49         125 $lexeme_events_by_id->[$g1_lexeme_id] = $event_name;
690 49         117 push @{ $symbol_ids_by_event_name_and_type->{$event_name}
691 49         240 ->{lexeme} }, $g1_lexeme_id;
692             } ## end if ( defined( my $event_data = $declarations->{'event'...}))
693              
694 54         211 $thin_slg->g1_lexeme_pause_activate( $g1_lexeme_id, $is_active );
695             } ## end if ( defined $pause_value )
696              
697             } ## end LEXEME: for my $lexeme_name ( keys %g1_id_by_lexeme_name )
698              
699             # Second phase of lexer processing
700 252         1277 my $lexer_rule_to_g1_lexeme = $lexer_and_rule_to_g1_lexeme{$lexer_name};
701              
702 252         616 RULE_ID: for my $lexer_rule_id ( 0 .. $#{$lexer_rule_to_g1_lexeme} ) {
  252         889  
703 19852         25268 my $g1_lexeme_id = $lexer_rule_to_g1_lexeme->[$lexer_rule_id];
704 19852         34231 my $lexeme_name = $g1_tracer->symbol_name($g1_lexeme_id);
705             my $assertion_id =
706 19852   100     41766 $lexeme_data{$lexeme_name}{lexers}{$lexer_name}{'assertion'}
707             // -1;
708 19852         39995 $thin_slg->lexer_rule_to_g1_lexeme_set( $lexer_rule_id,
709             $g1_lexeme_id, $assertion_id );
710 19852         25020 my $discard_event = $discard_event_by_lexer_rule_id[$lexer_rule_id];
711 19852 100       36623 if ( defined $discard_event ) {
712 60         110 my ( $event_name, $is_active ) = @{$discard_event};
  60         123  
713 60         167 $slg->[
714             Marpa::R2::Internal::Scanless::G::DISCARD_EVENT_BY_LEXER_RULE
715             ]->[$lexer_rule_id] = $event_name;
716 60         94 push @{ $symbol_ids_by_event_name_and_type->{$event_name}
717 60         246 ->{discard} }, $lexer_rule_id;
718 60         195 $thin_slg->discard_event_set( $lexer_rule_id, 1 );
719 60 100       240 $thin_slg->discard_event_activate( $lexer_rule_id, 1 )
720             if $is_active;
721             } ## end if ( defined $discard_event )
722             } ## end RULE_ID: for my $lexer_rule_id ( 0 .. $#{$lexer_rule_to_g1_lexeme...})
723              
724             # Second phase of G1 processing
725              
726 252         1681 $thin_slg->precompute();
727 252         661 $slg->[Marpa::R2::Internal::Scanless::G::THICK_G1_GRAMMAR] =
728             $thick_g1_grammar;
729              
730             # More lexer processing
731             # Determine events by lexer rule, applying the defaults
732              
733             {
734 252         442 my $character_class_table =
735 252         599 $character_class_table_by_lexer_name{$lexer_name};
736 252         686 $slg->[Marpa::R2::Internal::Scanless::G::CHARACTER_CLASS_TABLES]
737             ->[$lexer_id] = $character_class_table;
738             $slg->[Marpa::R2::Internal::Scanless::G::THICK_LEX_GRAMMARS]
739 252         766 ->[$lexer_id] = $thick_grammar_by_lexer_name{$lexer_name};
740             }
741              
742             # This section violates the NAIF interface, directly changing some
743             # of its internal structures.
744             #
745             # Some lexeme default adverbs are applied in earlier phases.
746             #
747             APPLY_DEFAULT_LEXEME_ADVERBS: {
748 252 50       542 last APPLY_DEFAULT_LEXEME_ADVERBS if not $lexeme_default_adverbs;
  252         849  
749              
750 252         704 my $action = $lexeme_default_adverbs->{action};
751 252         572 my $g1_symbols =
752             $thick_g1_grammar->[Marpa::R2::Internal::Grammar::SYMBOLS];
753             LEXEME:
754 252         2188 for my $lexeme_name ( keys %g1_id_by_lexeme_name ) {
755 7676         11102 my $g1_lexeme_id = $g1_id_by_lexeme_name{$lexeme_name};
756 7676         11368 my $g1_symbol = $g1_symbols->[$g1_lexeme_id];
757 7676 100       18230 next LEXEME if $lexeme_name =~ m/ \] \z/xms;
758 1575   66     5481 $g1_symbol->[Marpa::R2::Internal::Symbol::LEXEME_SEMANTICS] //=
759             $action;
760             } ## end LEXEME: for my $lexeme_name ( keys %g1_id_by_lexeme_name )
761              
762 252         1339 my $blessing = $lexeme_default_adverbs->{bless};
763 252 100       1118 last APPLY_DEFAULT_LEXEME_ADVERBS if not $blessing;
764 76 50       412 last APPLY_DEFAULT_LEXEME_ADVERBS if $blessing eq '::undef';
765              
766             LEXEME:
767 76         1037 for my $lexeme_name ( keys %g1_id_by_lexeme_name ) {
768 7158         9896 my $g1_lexeme_id = $g1_id_by_lexeme_name{$lexeme_name};
769 7158         8905 my $g1_symbol = $g1_symbols->[$g1_lexeme_id];
770 7158 100       16199 next LEXEME if $lexeme_name =~ m/ \] \z/xms;
771 1312 50       2665 if ( $blessing eq '::name' ) {
772 1312 50       3012 if ( $lexeme_name =~ / [^ [:alnum:]] /xms ) {
773 0         0 Marpa::R2::exception(
774             qq{Lexeme blessing by '::name' only allowed if lexeme name is whitespace and alphanumerics\n},
775             qq{ Problematic lexeme was <$lexeme_name>\n}
776             );
777             } ## end if ( $lexeme_name =~ / [^ [:alnum:]] /xms )
778 1312         1769 my $blessing_by_name = $lexeme_name;
779 1312         3824 $blessing_by_name =~ s/[ ]/_/gxms;
780 1312   33     4971 $g1_symbol->[Marpa::R2::Internal::Symbol::BLESSING] //=
781             $blessing_by_name;
782 1312         2267 next LEXEME;
783             } ## end if ( $blessing eq '::name' )
784 0 0       0 if ( $blessing =~ / [\W] /xms ) {
785 0         0 Marpa::R2::exception(
786             qq{Blessing lexeme as '$blessing' is not allowed\n},
787             qq{ Problematic lexeme was <$lexeme_name>\n}
788             );
789             } ## end if ( $blessing =~ / [\W] /xms )
790 0   0     0 $g1_symbol->[Marpa::R2::Internal::Symbol::BLESSING] //= $blessing;
791             } ## end LEXEME: for my $lexeme_name ( keys %g1_id_by_lexeme_name )
792              
793             } ## end APPLY_DEFAULT_LEXEME_ADVERBS:
794              
795 252         20042 return $slg;
796              
797             } ## end sub Marpa::R2::Internal::Scanless::G::hash_to_runtime
798              
799             sub thick_subgrammar_by_name {
800 665     665   998 my ( $slg, $subgrammar ) = @_;
801              
802             # Allow G0 as legacy synonym for L0
803 665         943 state $grammar_names = { 'G0' => 1, 'G1' => 1, 'L0' => 1 };
804 665   100     2042 $subgrammar //= 'G1';
805              
806             Marpa::R2::exception(qq{No lexer named "$subgrammar"})
807 665 50       1371 if not defined $grammar_names->{$subgrammar};
808              
809 665 100       1975 return $slg->[Marpa::R2::Internal::Scanless::G::THICK_G1_GRAMMAR]
810             if $subgrammar eq 'G1';
811              
812 203         453 return $slg->[Marpa::R2::Internal::Scanless::G::THICK_LEX_GRAMMARS]
813             ->[0];
814             } ## end sub thick_subgrammar_by_name
815              
816             sub Marpa::R2::Scanless::G::start_symbol_id {
817 1     1   10 my ( $slg, $rule_id, $subgrammar ) = @_;
818 1         4 return thick_subgrammar_by_name( $slg, $subgrammar )->start_symbol();
819             }
820              
821             sub Marpa::R2::Scanless::G::rule_name {
822 5     5   21 my ( $slg, $rule_id, $subgrammar ) = @_;
823 5         10 return thick_subgrammar_by_name( $slg, $subgrammar )->rule_name($rule_id);
824             }
825              
826             sub Marpa::R2::Scanless::G::rule_expand {
827 60     60   423 my ( $slg, $rule_id, $subgrammar ) = @_;
828 60         103 return thick_subgrammar_by_name( $slg, $subgrammar )->tracer()
829             ->rule_expand($rule_id);
830             }
831              
832             sub Marpa::R2::Scanless::G::symbol_name {
833 171     171   836 my ( $slg, $symbol_id, $subgrammar ) = @_;
834 171         313 return thick_subgrammar_by_name($slg, $subgrammar)->tracer()
835             ->symbol_name($symbol_id);
836             }
837              
838             sub Marpa::R2::Scanless::G::symbol_display_form {
839 139     139   462 my ( $slg, $symbol_id, $subgrammar ) = @_;
840 139         285 return thick_subgrammar_by_name( $slg, $subgrammar )
841             ->symbol_in_display_form($symbol_id);
842             }
843              
844             sub Marpa::R2::Scanless::G::symbol_dsl_form {
845 59     59   296 my ( $slg, $symbol_id, $subgrammar ) = @_;
846 59         89 return thick_subgrammar_by_name( $slg, $subgrammar )
847             ->symbol_dsl_form($symbol_id);
848             }
849              
850             sub Marpa::R2::Scanless::G::symbol_description {
851 59     59   299 my ( $slg, $symbol_id, $subgrammar ) = @_;
852 59         86 return thick_subgrammar_by_name($slg, $subgrammar)
853             ->symbol_description($symbol_id);
854             }
855              
856             sub Marpa::R2::Scanless::G::rule_show
857             {
858 136     136   497 my ( $slg, $rule_id, $subgrammar) = @_;
859 136         261 return slg_rule_show($slg, $rule_id, thick_subgrammar_by_name($slg, $subgrammar));
860             }
861              
862             sub slg_rule_show {
863 136     136   232 my ( $slg, $rule_id, $subgrammar ) = @_;
864 136         335 my $tracer = $subgrammar->tracer();
865 136         249 my $subgrammar_c = $subgrammar->[Marpa::R2::Internal::Grammar::C];
866 136         351 my @symbol_ids = $tracer->rule_expand($rule_id);
867 136 50       314 return if not scalar @symbol_ids;
868             my ( $lhs, @rhs ) =
869 136         221 map { $subgrammar->symbol_in_display_form($_) } @symbol_ids;
  329         1047  
870 136         376 my $minimum = $subgrammar_c->sequence_min($rule_id);
871 136         199 my @quantifier = ();
872              
873 136 100       269 if ( defined $minimum ) {
874 8 100       23 @quantifier = ( $minimum <= 0 ? q{*} : q{+} );
875             }
876 136         708 return join q{ }, $lhs, q{::=}, @rhs, @quantifier;
877             } ## end sub slg_rule_show
878              
879             sub Marpa::R2::Scanless::G::show_rules {
880 10     10   4049 my ( $slg, $verbose, $subgrammar ) = @_;
881 10         43 my $text = q{};
882 10   100     90 $verbose //= 0;
883 10   100     51 $subgrammar //= 'G1';
884              
885 10         47 my $thick_grammar = thick_subgrammar_by_name($slg, $subgrammar);
886              
887 10         25 my $rules = $thick_grammar->[Marpa::R2::Internal::Grammar::RULES];
888 10         21 my $grammar_c = $thick_grammar->[Marpa::R2::Internal::Grammar::C];
889              
890 10         19 for my $rule ( @{$rules} ) {
  10         28  
891 183         283 my $rule_id = $rule->[Marpa::R2::Internal::Rule::ID];
892              
893 183         426 my $minimum = $grammar_c->sequence_min($rule_id);
894 183 100       365 my @quantifier =
    100          
895             defined $minimum ? $minimum <= 0 ? (q{*}) : (q{+}) : ();
896 183         343 my $lhs_id = $grammar_c->rule_lhs($rule_id);
897 183         340 my $rule_length = $grammar_c->rule_length($rule_id);
898             my @rhs_ids =
899 183         317 map { $grammar_c->rule_rhs( $rule_id, $_ ) }
  266         590  
900             ( 0 .. $rule_length - 1 );
901             $text .= join q{ }, $subgrammar, "R$rule_id",
902             $thick_grammar->symbol_in_display_form($lhs_id),
903             '::=',
904 183         514 ( map { $thick_grammar->symbol_in_display_form($_) } @rhs_ids ),
  266         490  
905             @quantifier;
906 183         326 $text .= "\n";
907              
908 183 100       396 if ( $verbose >= 2 ) {
909              
910 48         74 my $description = $rule->[Marpa::R2::Internal::Rule::DESCRIPTION];
911 48 100       103 $text .= " $description\n" if $description;
912 48         75 my @comment = ();
913 48 50       127 $grammar_c->rule_length($rule_id) == 0
914             and push @comment, 'empty';
915 48 50       114 $thick_grammar->rule_is_used($rule_id)
916             or push @comment, '!used';
917 48 50       127 $grammar_c->rule_is_productive($rule_id)
918             or push @comment, 'unproductive';
919 48 50       114 $grammar_c->rule_is_accessible($rule_id)
920             or push @comment, 'inaccessible';
921 48 50       82 $rule->[Marpa::R2::Internal::Rule::DISCARD_SEPARATION]
922             and push @comment, 'discard_sep';
923              
924 48 50       88 if (@comment) {
925 0         0 $text .= q{ } . ( join q{ }, q{/*}, @comment, q{*/} ) . "\n";
926             }
927              
928             $text .= " Symbol IDs: <$lhs_id> ::= "
929 48         102 . ( join q{ }, map {"<$_>"} @rhs_ids ) . "\n";
  72         220  
930              
931             } ## end if ( $verbose >= 2 )
932              
933 183 100       398 if ( $verbose >= 3 ) {
934              
935 48         104 my $tracer = $thick_grammar->tracer();
936              
937             $text
938             .= " Internal symbols: <"
939             . $tracer->symbol_name($lhs_id)
940             . q{> ::= }
941             . (
942             join q{ },
943 48         100 map { '<' . $tracer->symbol_name($_) . '>' } @rhs_ids
  72         138  
944             ) . "\n";
945              
946             } ## end if ( $verbose >= 3 )
947              
948             } ## end for my $rule ( @{$rules} )
949              
950 10         85 return $text;
951             } ## end sub Marpa::R2::Scanless::G::show_rules
952              
953             sub Marpa::R2::Scanless::G::show_symbols {
954 4     4   718 my ( $slg, $verbose, $subgrammar ) = @_;
955 4         13 my $text = q{};
956 4   100     19 $verbose //= 0;
957 4   100     23 $subgrammar //= 'G1';
958              
959 4         15 my $thick_grammar = thick_subgrammar_by_name($slg, $subgrammar);
960              
961 4         13 my $symbols = $thick_grammar->[Marpa::R2::Internal::Grammar::SYMBOLS];
962 4         9 my $grammar_c = $thick_grammar->[Marpa::R2::Internal::Grammar::C];
963              
964 4         7 for my $symbol ( @{$symbols} ) {
  4         13  
965 68         96 my $symbol_id = $symbol->[Marpa::R2::Internal::Symbol::ID];
966              
967 68         152 $text .= join q{ }, $subgrammar, "S$symbol_id",
968             $thick_grammar->symbol_in_display_form($symbol_id);
969              
970 68         128 my $description = $symbol->[Marpa::R2::Internal::Symbol::DESCRIPTION];
971 68 100       115 if ($description) {
972 49         80 $text .= " -- $description";
973             }
974 68         92 $text .= "\n";
975              
976 68 100       118 if ( $verbose >= 2 ) {
977              
978 59         107 my @tag_list = ();
979 59 50       141 $grammar_c->symbol_is_productive($symbol_id)
980             or push @tag_list, 'unproductive';
981 59 50       136 $grammar_c->symbol_is_accessible($symbol_id)
982             or push @tag_list, 'inaccessible';
983 59 50       127 $grammar_c->symbol_is_nulling($symbol_id)
984             and push @tag_list, 'nulling';
985 59 100       139 $grammar_c->symbol_is_terminal($symbol_id)
986             and push @tag_list, 'terminal';
987              
988 59 100       106 if (@tag_list) {
989 30         62 $text
990             .= q{ } . ( join q{ }, q{/*}, @tag_list, q{*/} ) . "\n";
991             }
992              
993 59         113 my $tracer = $thick_grammar->tracer();
994 59         116 $text .= " Internal name: <"
995             . $tracer->symbol_name($symbol_id) . qq{>\n};
996              
997             } ## end if ( $verbose >= 2 )
998              
999 68 100       131 if ( $verbose >= 3 ) {
1000              
1001 59         87 my $dsl_form = $symbol->[Marpa::R2::Internal::Symbol::DSL_FORM];
1002 59 100       103 if ($dsl_form) { $text .= qq{ SLIF name: $dsl_form\n}; }
  42         86  
1003              
1004             } ## end if ( $verbose >= 3 )
1005              
1006             } ## end for my $symbol ( @{$symbols} )
1007              
1008 4         35 return $text;
1009             } ## end sub Marpa::R2::Scanless::G::show_symbols
1010              
1011             sub Marpa::R2::Scanless::G::show_dotted_rule {
1012 428     428   4230 my ( $slg, $rule_id, $dot_position ) = @_;
1013 428         652 my $grammar = $slg->[Marpa::R2::Internal::Scanless::G::THICK_G1_GRAMMAR];
1014 428         903 my $tracer = $grammar->tracer();
1015 428         612 my $grammar_c = $grammar->[Marpa::R2::Internal::Grammar::C];
1016             my ( $lhs, @rhs ) =
1017 428         901 map { $grammar->symbol_in_display_form($_) } $tracer->rule_expand($rule_id);
  2870         5278  
1018 428         852 my $rhs_length = scalar @rhs;
1019              
1020 428         914 my $minimum = $grammar_c->sequence_min($rule_id);
1021 428         643 my @quantifier = ();
1022 428 100       793 if (defined $minimum) {
1023 4 50       16 @quantifier = ($minimum <= 0 ? q{*} : q{+} );
1024             }
1025 428 100       761 $dot_position += ($rhs_length + 1) if $dot_position < 0;
1026 428 50       718 $dot_position = 0 if $dot_position < 0;
1027 428 100       735 if ($dot_position < $rhs_length) {
1028 324         650 splice @rhs, $dot_position, 0, q{.};
1029 324         1448 return join q{ }, $lhs, q{->}, @rhs, @quantifier;
1030             } else {
1031 104         465 return join q{ }, $lhs, q{->}, @rhs, @quantifier, q{.};
1032             }
1033             } ## end sub Marpa::R2::Grammar::show_dotted_rule
1034              
1035             sub Marpa::R2::Scanless::G::rule {
1036 6     6   61 my ( $slg, @args ) = @_;
1037 6         14 return $slg->[Marpa::R2::Internal::Scanless::G::THICK_G1_GRAMMAR]
1038             ->rule(@args);
1039             }
1040              
1041             sub Marpa::R2::Scanless::G::rule_ids {
1042 11     11   4635 my ($slg, $subgrammar) = @_;
1043 11         39 return thick_subgrammar_by_name($slg, $subgrammar)->rule_ids();
1044             }
1045              
1046             sub Marpa::R2::Scanless::G::symbol_ids {
1047 6     6   2208 my ($slg, $subgrammar) = @_;
1048 6         22 return thick_subgrammar_by_name($slg, $subgrammar)->symbol_ids();
1049             }
1050              
1051             sub Marpa::R2::Scanless::G::g1_rule_ids {
1052 1     1   1002 my ($slg) = @_;
1053 1         3 return $slg->rule_ids();
1054             }
1055              
1056             sub Marpa::R2::Scanless::G::g0_rule_ids {
1057 1     1   18 my ($slg) = @_;
1058 1         7 return $slg->rule_ids('L0');
1059             }
1060              
1061             sub Marpa::R2::Scanless::G::g0_rule {
1062 17     17   168 my ( $slg, @args ) = @_;
1063 17         38 return $slg->[Marpa::R2::Internal::Scanless::G::THICK_LEX_GRAMMARS]->[0]
1064             ->rule(@args);
1065             }
1066              
1067             # Internal methods, not to be documented
1068              
1069             sub Marpa::R2::Scanless::G::thick_g1_grammar {
1070 0     0   0 my ($slg) = @_;
1071 0         0 return $slg->[Marpa::R2::Internal::Scanless::G::THICK_G1_GRAMMAR];
1072             }
1073              
1074             sub Marpa::R2::Scanless::G::show_irls {
1075 2     2   10 my ($slg, $subgrammar) = @_;
1076 2         9 return thick_subgrammar_by_name($slg, $subgrammar)->show_irls();
1077             }
1078              
1079             sub Marpa::R2::Scanless::G::show_isys {
1080 2     2   1311 my ($slg, $subgrammar) = @_;
1081 2         8 return thick_subgrammar_by_name($slg, $subgrammar)->show_isys();
1082             }
1083              
1084             1;
1085              
1086             # vim: expandtab shiftwidth=4: