File Coverage

blib/lib/Parse/YALALR/Explain.pl
Criterion Covered Total %
statement 12 470 2.5
branch 0 158 0.0
condition 0 7 0.0
subroutine 4 33 12.1
pod 0 29 0.0
total 16 697 2.3


line stmt bran cond sub pod time code
1             # This is really a -*- cperl -*- extension package to Parse::YALALR::Build
2              
3             package Parse::YALALR::Build;
4 1     1   5 use Parse::YALALR::Common;
  1         1  
  1         10  
5 1     1   6 use strict;
  1         1  
  1         4689  
6              
7             ######################## EXPLANATIONS ###########################
8              
9             sub effects_to_causes {
10 0     0 0   my Parse::YALALR::Build $self = shift;
11 0           my Parse::YALALR::Parser $parser = $self->parser;
12              
13 0           for my $cause (map { @{$_->{items}} } @{$parser->{states}}) {
  0            
  0            
  0            
14 0           for my $effect (values %{$cause->{EFFECTS}}) {
  0            
15 0           push(@{$effect->{CAUSES}}, $cause);
  0            
16             }
17             }
18             }
19              
20             # Explain why $symbol ->* B, where $item is B -> \alpha (with a dot somewhere)
21             sub explain_sym_chain {
22 0     0 0   my Parse::YALALR::Build $self = shift;
23 0           my ($symbol, $item, $asXML) = @_;
24 0           my Parse::YALALR::Parser $parser = $self->parser;
25 0           my $nil = $parser->{nil};
26              
27             # print "Called explain_sym_chain(".$parser->dump_sym($symbol).", ".$parser->dump_item($item)."\n";
28              
29 0           my @chain;
30              
31 0 0         return undef if ($parser->is_token($symbol));
32 0           my $target;
33 0           while (1) {
34 0           --$item while ($parser->{grammar}[$item] != $nil);
35 0           $item++;
36 0           my $target = $parser->{grammar}[$item];
37 0           push(@chain, $parser->dump_item($item+1, $asXML));
38 0 0         last if ($symbol == $target);
39 0           $item = $self->{chainreachable}{$symbol}{$target};
40 0 0         return undef if !defined $item;
41             }
42              
43 0           my $desc = '';
44 0           foreach (reverse @chain) {
45 0           $desc .= "generates $_\n";
46             }
47 0           chomp($desc);
48              
49 0           return $desc;
50             }
51              
52             # Explain how STATE came to have an item X -> . SYMBOL \alpha
53             sub explain_chain {
54 0     0 0   my Parse::YALALR::Build $self = shift;
55 0           my ($state, $symbol, $asXML) = @_;
56 0           my Parse::YALALR::Parser $parser = $self->parser;
57 0           my $desc;
58 0           foreach my $kitem (@{$state->{items}}) {
  0            
59 0           $desc = $self->explain_sym_chain($parser->{grammar}[$kitem->{GRAMIDX}],
60             $symbol, $asXML);
61 0 0         return $desc if defined $desc;
62             }
63 0           return undef;
64             }
65              
66             sub explain_FIRST {
67 0     0 0   my Parse::YALALR::Build $self = shift;
68 0           my ($token, $symbol, $asXML) = @_;
69 0           my Parse::YALALR::Parser $parser = $self->parser;
70              
71             # WHY_FIRST : { A => { t => } }
72 0           my ($rule, $reason, $parent) = @{$self->{WHY_FIRST}->{$symbol}->{$token}};
  0            
73 0 0         die unless defined $rule;
74            
75 0           my $str;
76 0 0         $str .= "" if $asXML;
77 0           $str .= "rule ".$parser->dump_rule($rule, undef, $asXML);
78 0 0         $str .= "" if $asXML;
79 0           $str .= "\n";
80              
81 0           my $idx = $rule+1;
82 0           while ((my $A = $parser->{grammar}->[$idx++]) != $parser->{nil}) {
83 0           print "A=".$parser->dump_sym($A)." reason=$reason";
84 0 0         if ($reason eq 'propagated') {
85 0           print " from ".$parser->dump_sym($parent);
86             }
87 0           print "\n";
88 0 0 0       if ($A == $token) {
    0          
89 0           chomp($str);
90 0           return (undef, $str);
91             } elsif ($reason eq 'propagated' && $A == $parent) {
92 0           $str .= "and ";
93 0           my (undef, $substr) =
94             $self->explain_FIRST($token, $parent, $asXML);
95 0           chomp($substr);
96 0           return (undef, $str.$substr);
97             } else {
98 0           $str .= "and ";
99 0 0         $str .= "" if $asXML;
100 0           $str .= $parser->dump_sym($A, $asXML)." is nullable";
101 0 0         $str .= "" if $asXML;
102 0           $str .= "\n";
103             }
104             }
105              
106 0           die "Can't get here! tok=$ID{$parser->dump_sym($token)} symbol=$ID{$parser->dump_sym($symbol)} str=$str";
107             }
108              
109             sub explain_nullable {
110 0     0 0   my Parse::YALALR::Build $self = shift;
111 0           my ($symbol, $asXML, $visited) = @_;
112 0           my Parse::YALALR::Parser $parser = $self->parser;
113 0           my $grammar = $parser->{grammar};
114              
115 0   0       $visited ||= {};
116 0           $visited->{$symbol} = 1;
117              
118 0           my $str;
119 0           my $rule = $self->{why_nullable}->{$symbol};
120 0           $str .= $parser->dump_rule($rule, undef, $asXML);
121              
122 0           my $idx = $rule;
123 0           while ((my $A = $grammar->[++$idx]) != $parser->{nil}) {
124 0 0         next if $visited->{$A};
125 0           my (undef, $substr) = $self->explain_nullable($A, $asXML);
126 0           $str .= "\n$substr";
127             }
128              
129 0           return (undef, $str);
130             }
131              
132             # When in state $state, why shift on token/nonterminal $symbol?
133             sub explain_shift {
134 0     0 0   my Parse::YALALR::Build $self = shift;
135 0           my ($state, $symbol, $action, $actions, $asXML) = @_;
136 0           my Parse::YALALR::Parser $parser = $self->parser;
137              
138 0 0         if (!ref $actions->[$symbol]) {
139             # Usual explanation of a shift: state n has A -> \a1 . t \a2
140             # in it. This might actually be because the kernel has
141             # X -> \a3 . A \a4, though.
142              
143 0           my ($item, $reason, $chainfrom) = @{$state->{SHIFT_WHY}->{$symbol}};
  0            
144 0 0         my $where = ($reason eq 'kernel' ? 'kernel ' : 'chained ');
145 0           my $desc;
146 0 0         if ($reason eq 'chained') {
147             # No need to dump out the generation list if ...?
148 0 0         if ($item->{GRAMIDX} != $chainfrom + 1) {
149 0           $desc .= "\n".$self->explain_chain($state, $chainfrom, $asXML);
150             }
151 0           $desc .= "\ngenerates ";
152 0           $desc .= $parser->dump_item($item->{GRAMIDX}-1, $asXML);
153             } else {
154 0 0         if (@{ $parser->{states}->[$state] } > 1) {
  0            
155 0           $desc .= "in particular, item ";
156 0           $desc .= $parser->dump_item($item->{GRAMIDX}-1, $asXML);
157             }
158             }
159 0           return ($state->{SHIFT_WHY}->{$symbol}, $desc);
160             } else {
161             # Hm. Some strange reason.
162 0           return (undef, 'dunno(shift)(internal error)');
163             }
164             }
165              
166             sub create_item2state_map {
167 0     0 0   my Parse::YALALR::Build $self = shift;
168 0           my Parse::YALALR::Parser $parser = $self->parser;
169 0           for my $state (@{$parser->{states}}) {
  0            
170 0           for my $item (@{$state->{items}}) {
  0            
171 0           $self->{item2state}->{$item} = $state;
172 0           $self->{itemmap}->{"$state->{id}_$item->{GRAMIDX}"} = $item;
173             }
174             }
175             }
176              
177             sub lookup_lookahead_why ($$) {
178 0     0 0   my ($why, $token) = @_;
179 0           while (my ($vec, $reason) = each %$why) {
180 0 0         return $reason if vec($vec, $token, 1);
181             }
182 0           warn("Failed to figure out why token is in lookahead of item");
183 0           return undef;
184             }
185              
186             # Figure out which (possibly generated) item propagated lookahead TOKEN
187             # to the item EFFECT. Favor items which were themselves generated rather
188             # than propagated (to avoid propagation cycles).
189             #
190             # ->
191             #
192             sub search_for_cause {
193 0     0 0   my Parse::YALALR::Build $self = shift;
194 0           my ($effect, $token) = @_;
195 0           my Parse::YALALR::Parser $parser = $self->parser;
196 0           my $effect_idx = $effect->{GRAMIDX};
197              
198 0           my $cand_state;
199             my $cand_item;
200 0           for my $cause (@{ $effect->{SOURCES} }) {
  0            
201             # $DB::single = 1 if $effect->{GRAMIDX} == 245;
202 0           my $state = $self->{item2state}->{$cause};
203 0           my $xstate = $self->expand_state($state);
204 0           my $cause_xitem = $xstate->{$effect_idx - 1};
205 0 0         next if !exists $cause_xitem->{$token};
206 0           $cand_item = $cause_xitem->{item};
207 0 0         return ($state, $cand_item)
208             if $cause_xitem->{$token}->[0] eq 'generated';
209 0 0         return ($state, $cand_item)
210             if $cause_xitem->{$token}->[0] eq 'kernel';
211 0           $cand_state = $state;
212             }
213              
214 0 0         $DB::single = 1 if !defined $cand_state;
215 0 0         die "No cause found!" if !defined $cand_state;
216 0           return ($cand_state, $cand_item);
217             }
218              
219             sub find_ultimate {
220 0     0 0   my Parse::YALALR::Build $self = shift;
221 0           my ($state, $symbol) = @_;
222 0           my Parse::YALALR::Parser $parser = $self->parser;
223 0           my $reasons;
224 0           while (my ($la, $r) = each %{$state->{REDUCE_WHY}}) {
  0            
225 0 0         $reasons = $r, last if vec($la, $symbol, 1);
226             }
227 0           return $reasons->[1];
228             }
229              
230             sub explain_reduce {
231 0     0 0   my Parse::YALALR::Build $self = shift;
232 0           my ($state, $symbol, $action, $actions, $asXML) = @_;
233 0           my Parse::YALALR::Parser $parser = $self->parser;
234 0           my $str = '';
235              
236 0           my $reasons;
237 0           while (my ($la, $r) = each %{$state->{REDUCE_WHY}}) {
  0            
238 0 0         $reasons = $r, last if vec($la, $symbol, 1);
239             }
240              
241 0           my $index = $reasons->[0];
242 0           ++$index while $parser->{grammar}->[$index] != $parser->{nil};
243              
244 0           my ($xml, $reason);
245 0           $xml .= "has item ".$parser->dump_item($index, $asXML);
246 0           $xml .= "\nwith lookahead "
247             ."{id} item=$index token=$symbol ultimate=$reasons->[1]->{GRAMIDX}>"
248             .$parser->dump_sym($symbol, $asXML)
249             ."";
250 0           $reason = bless [ $state, $index, $symbol, $reasons->[1] ], 'reduce_reason';
251 0           print "WOULD HAVE CALLED exp_la($state=$state->{id}, $index, ".$parser->dump_sym($symbol).", $reasons->[1]=$reasons->[1]->{GRAMIDX}, $asXML\n";
252 0           return ($reason, $xml);
253             }
254              
255             # Why was conflict resolved to $action?
256 0     0 0   sub explain_conflict {
257             }
258              
259             sub explain {
260 0     0 0   my Parse::YALALR::Build $self = shift;
261 0           my ($state0, $cause, $action) = @_;
262 0           my Parse::YALALR::Parser $parser = $self->parser;
263 0           $state0 = $parser->{states}->[$state0];
264 0           my $actions = $state0->{actions};
265 0           my $desc = "state $state0->{id} is ".$parser->dump_kernel($state0)."\nand in particular ";
266 0           my ($exp, $reason);
267 0 0         if ($action eq 'shift') {
    0          
268 0           ($exp, $reason) = $self->explain_shift($state0, $cause, $action, $actions);
269             } elsif ($action eq 'reduce') {
270 0           ($exp, $reason) = $self->explain_reduce($state0, $cause, $action, $actions);
271             }
272              
273 0           return ($exp, $desc.$reason);
274             }
275              
276             # DESTRUCTION TRACKING (no practical purpose yet)
277             # sub xitem::DESTROY { print "xitem::DESTROY\n"; }
278             # sub item::DESTROY { print "item::DESTROY\n"; }
279             # sub kernel::DESTROY { print "kernel::DESTROY\n"; }
280              
281             # Create a graph of xitem : { 'item' => grammar index of item B -> . \beta,
282             # token => }
283             # where whylookahead is 'generated' | 'propagated' | 'kernel'
284             # and the xitem ref is the xitem containing item A -> \a1 . B \a2, f1
285             # if 'generated', then token is in FIRST(\a2)
286             # if 'propagated', then token is in f1
287             # if 'kernel', then item is a kernel item and token is in the lookahead
288             #
289             # Note that this routine is very slow and produces a huge amount of data.
290             # Should probably destroy the whole thing afterwards. (This routine is NOT
291             # used to build a parser, only to explain why a lookahead is in an item if
292             # the user asks.)
293             #
294             sub expand_state {
295 0     0 0   my Parse::YALALR::Build $self = shift;
296 0           my Parse::YALALR::Parser $parser = $self->parser;
297 0           my ($state) = @_;
298 0           return $self->expand_items(@{ $state->{items} });
  0            
299             }
300              
301             sub expand_items {
302 0     0 0   my Parse::YALALR::Build $self = shift;
303 0           my Parse::YALALR::Parser $parser = $self->parser;
304 0           my (@kitems) = @_;
305              
306 0           my @xitems;
307             my %visited; # { grammar index => xitem }
308 0           my @Q;
309              
310 0           for my $kitem (@kitems) {
311 0           my $xitem = bless { item => $kitem->{GRAMIDX},
312 0           map { ($_ => [ 'kernel' ]) }
313             ($parser->{symmap}->get_indices($kitem->{LA})) },
314             'xitem';
315 0           push @xitems, $xitem;
316 0           $visited{$kitem->{GRAMIDX}} = $xitem;
317 0           push(@Q, $xitem);
318             }
319              
320 0           while (@Q) {
321 0           my $node = shift(@Q);
322             # $node : { 'item' => B -> \gamma . \beta1, t1=>..., t2=>... }
323             # (t1,t2 are the lookahead)
324             # (\gamma is empty unless it's a kernel item)
325             #
326             # If we make it past the upcoming 'next's, we'll know that
327             # the item is actually -> \gamma . C \beta2
328             # (i.e., \beta1 = C \beta2)
329              
330 0           my $C = $parser->{grammar}->[$node->{item}];
331 0 0         next if $C == $parser->{nil};
332 0 0         next if $parser->is_token($C);
333              
334             # $F_beta2 := FIRST(\beta2)
335 0           my @beta2 = $parser->get_dotalpha($node->{item} + 1);
336 0           my $F_beta2 = $self->FIRST_nonvec(@beta2);
337             # Gather up everything that will be passed to the children, either
338             # by being generated by FIRST(\beta2) or propagated from the lookahead
339             # of $node.
340 0           my %generations;
341             my %propagations;
342 0           for my $t ($parser->{symmap}->get_indices($F_beta2)) {
343 0 0         if ($t == $parser->{nil}) {
344 0           foreach (keys %$node) {
345 0 0         next if $_ eq 'item';
346 0 0         next if $_ eq 'parent0';
347 0 0         next if $_ == $parser->{nil};
348 0           $propagations{$_} = [ 'propagated', $node ];
349             }
350             } else {
351 0           $generations{$t} = [ 'generated', $node ];
352             }
353             }
354              
355 0           for my $rule ($parser->get_rules($C)) {
356             # $rule : grammar index of . C -> \alpha
357 0           my $child = $rule + 1; # C -> . \alpha
358              
359 0           my $newXitem;
360 0 0         if ($visited{$child}) {
361 0           $newXitem = $visited{$child};
362 0           my $old_number_of_lookaheads = keys %$newXitem;
363 0           %$newXitem = (%propagations,
364             %generations,
365 0           %{$visited{$child}});
366 0 0         next if keys %$newXitem == $old_number_of_lookaheads;
367             } else {
368 0           $newXitem = bless { item => $child,
369             parent0 => $node,
370             %generations,
371             %propagations }, 'xitem';
372             }
373              
374 0           $visited{$child} = $newXitem;
375 0           push(@Q, $newXitem);
376             }
377             }
378              
379 0           return \%visited;
380             }
381              
382             sub dump_xreason {
383 0     0 0   my Parse::YALALR::Build $self = shift;
384 0           my Parse::YALALR::Parser $parser = $self->parser;
385 0           my $reason = shift;
386 0           my $str = $parser->dump_item($reason->[0]);
387 0 0         if ($reason->[1] eq 'kernel') {
388 0           return $str." (kernel item)";
389             } else {
390 0           return $str." <-$reason->[1]-- ".$parser->dump_item($reason->[2]->{item});
391             }
392             }
393              
394             # 1. LA_WHY trace to an item in the correct state
395             sub LA_WHY_chain_explstr {
396 0     0 0   my Parse::YALALR::Build $self = shift;
397 0           my Parse::YALALR::Parser $parser = $self->parser;
398              
399 0           my $asXML;
400 0 0         if (!ref $_[-1]) {
401 0           $asXML = pop(@_);
402             }
403              
404 0           my $str = '';
405 0           foreach (reverse @_) {
406 0           my ($reason, $cause_item, $f1, $f2) = @$_;
407 0           my $itemdesc = $parser->dump_item($cause_item, $asXML);
408             # if ($reason eq 'generated') {
409             # $str .= "generated by $itemdesc\n";
410             # } elsif ($reason eq 'propagated') {
411             # $str .= "propagated from $itemdesc\n";
412             # } elsif ($reason eq 'chain-generated') {
413             # $str .= "chain-generated from $itemdesc\n";
414             # } elsif ($reason eq 'epsilon-generated') {
415             # $str .= "epsilon-generated from $itemdesc\n";
416             # }
417 0           $str .= "propagates the lookahead to $itemdesc\n";
418             }
419              
420 0           chomp($str);
421 0           return $str;
422             }
423              
424             sub xreason_chain_explstr {
425 0     0 0   my Parse::YALALR::Build $self = shift;
426 0           my Parse::YALALR::Parser $parser = $self->parser;
427 0           my ($token, $chain, $ultimate_state, $asXML) = @_;
428 0 0         return '' if @$chain == 0;
429              
430 0           my $str;
431              
432 0 0         print "asXML: ".((defined $asXML) ? $asXML : "(undef)")."\n";
433 0           print "ITEMS: ", join(";; ", map { $parser->dump_item($_->[0]) } @$chain), "\n";
  0            
434 0           print "REASONS: ", join(" , ", map { $_->[1] } @$chain), "\n";
  0            
435 0 0         print "CAUSES: ", join(" , ", map { (defined $_->[2]) ? $parser->dump_item($_->[2]{item}) : "(undef)" } @$chain), "\n";
  0            
436              
437 0           @$chain = reverse @$chain;
438              
439 0           my $lastitem;
440              
441 0 0         if ($chain->[0]->[1] eq 'kernel') {
442 0           $lastitem = $chain->[0]->[0];
443 0 0         if ($lastitem == 1) {
444 0           $str .= ":automatically generated item ";
445             # $str .= $parser->dump_item($lastitem, $asXML)."\n";
446             } else {
447 0           my (undef, $tmp) =
448             $self->lookahead_inherit_explanation($ultimate_state, $lastitem,
449             $token, $asXML);
450 0           $tmp =~ s/\n/\n:/g;
451 0           $str .= ":$tmp\n";
452             }
453             } else {
454 0           $lastitem = $chain->[0]->[2]->{item};
455 0           $str .= "generates ".$parser->dump_item($lastitem, $asXML)."\n";
456             }
457              
458 0           foreach (@$chain) {
459 0           my ($item, $reason, $cause) = @$_;
460              
461             # if ($reason eq 'generated') {
462             # $str .= "with la ".$parser->dump_sym($token, $asXML);
463             # $str .= "generates ".$parser->dump_item($cause->{item}, $asXML)."\n";
464             # } elsif ($reason eq 'propagated') {
465             # $str .= "with la ".$parser->dump_sym($token, $asXML);
466             # $str .= "generates ".$parser->dump_item($cause->{item}, $asXML)."\n";
467             # } else {
468             # $str .= "which is a kernel item\n";
469             # }
470              
471 0 0         if ($reason eq 'generated') {
    0          
472             # That means $lastitem generated the lookahead. Examine why.
473 0           $str .= $self->lookahead_generation_explanation($lastitem, $token, $asXML, " ")."\n";
474             } elsif ($reason eq 'kernel') {
475 0           $str .= $self->lookahead_generation_explanation($item, $token, $asXML, "")."\n";
476             }
477              
478 0           $str .= "generates ".$parser->dump_item($item, $asXML)."\n";
479              
480 0           print "LASTITEM turning over from ".$parser->dump_item($lastitem)." TO ".$parser->dump_item($item)."\n";
481              
482 0           $lastitem = $item;
483             }
484              
485 0           $str =~ s/\n+$//;
486 0           return $str;
487             }
488              
489             sub lookahead_generation_explanation {
490 0     0 0   my Parse::YALALR::Build $self = shift;
491 0           my Parse::YALALR::Parser $parser = $self->parser;
492 0           my ($item, $lookahead, $asXML, $tab) = @_;
493 0           my @alpha = $parser->get_dotalpha($item+1);
494 0           print "ALPHA=".join(" ", $parser->dump_sym(@alpha))."\n";
495 0           my $firstalpha = $self->FIRST_nonvec(@alpha);
496 0           my $str = '';
497 0 0         if (vec($firstalpha, $lookahead, 1)) {
498 0           my $expl = $self->explain_first_alpha($lookahead, \@alpha, $asXML);
499 0           $str .= "generates the lookahead ";
500 0           $str .= $parser->dump_sym($lookahead, $asXML)."\n";
501 0           $str .= "because $expl";
502             } else {
503             # $str = "inherits the lookahead ".$parser->dump_sym($lookahead, $asXML);
504             }
505              
506 0           chomp($str);
507 0           $str =~ s/\n/\n$tab/g;
508 0           return $tab.$str;
509             }
510              
511             sub lookahead_inherit_explanation {
512 0     0 0   my Parse::YALALR::Build $self = shift;
513 0           my Parse::YALALR::Parser $parser = $self->parser;
514 0           my ($ultimate_state, $lastitem, $token, $asXML) = @_;
515 0           return $self->explain_lookahead($ultimate_state, $lastitem, $token,
516             undef, $asXML);
517             }
518              
519             sub explain_first_alpha {
520 0     0 0   my Parse::YALALR::Build $self = shift;
521 0           my Parse::YALALR::Parser $parser = $self->parser;
522 0           my ($la, $alpha, $asXML) = @_;
523              
524 0           my $str;
525 0           foreach (@$alpha) {
526 0 0         if ($self->FIRST_nonvec($_)) {
    0          
527 0 0         if ($parser->is_nonterminal($_)) {
528 0           $str .= $parser->dump_sym($la, $asXML)." is in ";
529 0 0         $str .= "" if $asXML;
530 0           $str .= "FIRST(".$parser->dump_sym($_).")";
531 0 0         $str .= "" if $asXML;
532             } else {
533 0           $str .= $parser->dump_sym($la, $asXML)." immediately follows the expanded nonterminal";
534             }
535 0           return $str;
536             } elsif ($parser->is_nonterminal($_)) {
537 0 0         $str .= "" if $asXML;
538 0           $str .= $parser->dump_sym($_, $asXML)." derives the empty string";
539 0 0         $str .= "" if $asXML;
540 0           $str .= ", and\n";
541             }
542             }
543              
544 0           die "Hey! Never found lookahead in alpha!";
545             }
546              
547             sub xitem_chain_explstr {
548 0     0 0   my Parse::YALALR::Build $self = shift;
549 0           my Parse::YALALR::Parser $parser = $self->parser;
550              
551 0           my $asXML;
552 0 0         if (!ref $_[-1]) {
553 0           $asXML = pop(@_);
554             }
555              
556 0 0         return '' if @_ == 0;
557              
558 0 0         my $xformat = ($asXML ? "briefxml" : "brief");
559 0           my $str = '';
560 0           my @xitems = reverse @_;
561 0           my $kernel = shift(@xitems);
562 0           $str .= "kernel item ".$parser->dump_item($kernel->{item}, $xformat)."\n";
563              
564 0           for my $xitem (@xitems) {
565 0           $str .= "generates ".$parser->dump_item($xitem->{item}, $xformat)."\n";
566             }
567              
568 0           chomp($str);
569 0           return $str;
570             }
571              
572             # Tie the chains together
573             # $ultimate_chain
574             # $xreason_chain
575             # $lawhy_chain
576             #
577             # $lawhy_chain is in reverse order
578             # $xreason_chain is in reverse order
579             # $ultimate_chain is in reverse order
580             #
581             sub explain_lookahead {
582 0     0 0   my Parse::YALALR::Build $self = shift;
583 0           my Parse::YALALR::Parser $parser = $self->parser;
584 0           my ($state, $idx, $token, $ultimate_kitem, $asXML) = @_;
585              
586 0           $DB::single = 1;
587              
588 0           my ($lawhy_chain, $xreason_chain, $ultimate_chain, $ultimate_state) =
589             $self->lookahead_explanation($state, $idx, $token, $ultimate_kitem);
590              
591 0           my $str;
592              
593             # $str .= "--Kernel to just before cause--\n";
594 0           $str .= $self->xitem_chain_explstr(@$ultimate_chain, $asXML);
595 0           $str =~ s/^\n//;
596             # $str .= "\n--Cause to A : . b x y--";
597 0           $str .= "\n".$self->xreason_chain_explstr($token, $xreason_chain, $ultimate_state, $asXML);
598 0           $str =~ s/^\n//;
599             # $str .= "\n--Propagation chain--";
600 0           pop(@$lawhy_chain); # Get rid of kernel item (printed above)
601 0           $str .= "\n".$self->LA_WHY_chain_explstr(@$lawhy_chain, $asXML);
602             # $str .= "\n--done--";
603              
604 0           return [ $lawhy_chain, $xreason_chain, $ultimate_chain ], $str;
605             }
606              
607             # lookahead_explanation
608             #
609             # Explaining lookaheads is a 3-step process:
610             #
611             # 1. Use item->{LA_WHY} to trace to the ultimately generating state
612             # and kernel item.
613             #
614             # 2. Look back at the path found in #1 and find the second-to-last
615             # item (it will be the one the was propagated from an item generated
616             # by the ultimate kernel item.) Expand the ultimate state and use the
617             # xitem lookahead links to get to the generating xitem for that 2nd to
618             # last item respecting that particular lookahead.
619             #
620             # 3. To get to the kernel, expand just the ultimately generating
621             # kernel item to find the chain of GRAMIDXes that lead from the xitem
622             # found in the previous step to the kernel item.
623             #
624             # Example:
625             # A -> X D C y
626             # D -> B d
627             # B -> a b
628             # C ->
629             # X ->
630             #
631             # Consider X -> a b ., d
632             #
633             # Step 1 finds
634             # D -> X . D C y (kernel)
635             # B -> a . b, d (propagated)
636             # B -> a b ., d (propagated)
637             # Step 2 finds the generating xitem
638             # D -> . B d, ...
639             # B -> . a b, d (generated)
640             # Step 3 finds the path from the kernel
641             # A -> X . D C y (kernel)
642             # D -> . B d, ... (generated)
643             #
644             # Sewing those together in the correct order results in:
645             # A -> X . D C y (kernel)
646             # D -> . B d, ... (generated) *** source of the lookahead
647             # B -> . a b, d (generated)
648             # B -> a . b, d (propagated)
649             # B -> a b ., d (propagated)
650             #
651             sub lookahead_explanation {
652 0     0 0   my Parse::YALALR::Build $self = shift;
653 0           my Parse::YALALR::Parser $parser = $self->{parser};
654 0           my ($state, $idx, $token, $ultimate_kitem) = @_;
655              
656             # Output
657 0           my (@lawhy_chain, @xreason_chain, @ultimate_chain);
658              
659             # First, find the item we're talking about
660 0           my ($item) = grep { $_->{GRAMIDX} == $idx } @{ $state->{items} };
  0            
  0            
661 0 0         undef $ultimate_kitem if defined $item;
662              
663             # Step 1 is unnecessary if the caller gave us the ultimate kernel item
664 0           my $lastidx;
665 0 0         if (!defined $ultimate_kitem) {
666 1     1   9 use Carp;
  1         2  
  1         739  
667 0 0         confess "No ultimate kernel item given and item not found"
668             if !defined $item;
669              
670             # Step 1: item->{LA_WHY} chain
671 0           @lawhy_chain = $self->get_LA_WHY_chain($token, $item);
672 0           $ultimate_kitem = $lawhy_chain[-1]->[1];
673 0 0         if ($lawhy_chain[-1]->[0] eq 'init') {
674 0           print "Ran afoul of autogenerated item\n";
675 0           $DB::single = 1;
676 0           $ultimate_kitem = $parser->{states}->[0]->{items}->[0];
677 0           $lastidx = 1;
678             } else {
679 0           $state = $self->{item2state}->{$ultimate_kitem};
680 0           $lastidx = $lawhy_chain[-2]->[1]->{GRAMIDX}-1;
681             }
682             } else {
683 0           $lastidx = $idx;
684             }
685              
686             # Step 2: expand the state and find the generating xitem
687 0           my $xstate = $self->expand_state($state);
688 0           { local $^W = 0; print $parser->dump_xstate($xstate); }
  0            
  0            
689 0           my $xitem = $xstate->{$lastidx};
690 0           $DB::single = 1;
691 0           @xreason_chain = $self->get_xreason_chain($xitem, $token);
692 0           my $generating_reason = $xreason_chain[-1];
693              
694             # Check whether step 3 makes sense
695 0 0         if ($generating_reason->[1] ne 'kernel') {
696 0           @ultimate_chain =
697             $self->get_any_chain($generating_reason->[2]->{item},
698             $ultimate_kitem);
699             } else {
700 0 0         warn "Hm. Found an alternate reason?"
701             if $generating_reason->[0] != $ultimate_kitem->{GRAMIDX};
702             }
703              
704 0           return \@lawhy_chain, \@xreason_chain, \@ultimate_chain, $state;
705             }
706              
707             # Given an item A -> x y z . \alpha, t|other0 and a token t
708             # return the sequence
709             # <'myself', A -> x y z . \alpha, t|other0>
710             # <'propagated', A -> x y . z \alpha, t|other1>
711             # <'propagated', A -> x . y z \alpha, t|other2>
712             # <'generated', Q -> Z t P . A t>
713             #
714             # Could this be changed to produce shorter chains by favoring generated
715             # links? (I don't think this is necessary to avoid cycles)
716             sub get_LA_WHY_chain {
717 0     0 0   my Parse::YALALR::Build $self = shift;
718 0           my Parse::YALALR::Parser $parser = $self->parser;
719 0           my ($token, $item) = @_;
720              
721 0           my @chain = ([ 'myself', $item ]);
722            
723 0 0         if ($item->{GRAMIDX} == 1) {
724 0           return ([ 'init', $item ]);
725             }
726              
727 0           my $lawhy;
728 0           do {
729 0           my @la = grep { vec($_, $token, 1) } (keys %{ $item->{LA_WHY} });
  0            
  0            
730 0 0         die "Unable to find LA_WHY for ".$parser->dump_sym($token)
731             if @la == 0;
732 0           $lawhy = $item->{LA_WHY}->{$la[0]};
733 0           push(@chain, $lawhy);
734 0           $item = $lawhy->[1];
735             } while ($lawhy->[0] !~ /generated/);
736              
737 0           return @chain;
738             }
739              
740             # Given an xitem C -> . \alpha and token a token d, produce a chain
741             # C -> . \alpha, d
742             # B -> . C D
743             # A -> X . B d
744             #
745             # saying that d is generated by A -> . B d (because D is nullable)
746             #
747             sub get_xreason_chain {
748 0     0 0   my Parse::YALALR::Build $self = shift;
749 0           my Parse::YALALR::Parser $parser = $self->parser;
750 0           my ($xitem, $token) = @_;
751              
752 0           my %visited; # { grammar index }
753              
754 1     1   7 use Carp;
  1         2  
  1         7060  
755 0 0 0       confess ($token || "false") if !defined $xitem->{$token};
756 0           my @chain = (bless [ $xitem->{item}, @{$xitem->{$token}} ], 'xreason');
  0            
757              
758             # Traverse upwards from the given item until either a kernel item
759             # is reached or the requested token is generated.
760 0           while (1) {
761 0           my ($last_item, $last_reason, $last_cause) = @{$chain[-1]};
  0            
762 0 0         return @chain if $last_reason ne 'propagated';
763 0 0         die "Infinite loop!" if $visited{$last_cause->{item}}; # DBG
764 0           $visited{$last_cause->{item}} = 1;
765 0           push(@chain,
766 0           bless [ $last_cause->{item}, @{$last_cause->{$token}} ],
767             'xreason');
768 0 0         return @chain if $chain[-1]->[1] eq 'kernel';
769             }
770             }
771              
772             # from_xitem
773             # .
774             # .
775             # .
776             # to_xitem
777             sub get_any_chain {
778 0     0 0   my Parse::YALALR::Build $self = shift;
779 0           my Parse::YALALR::Parser $parser = $self->parser;
780 0           my ($from_idx, $to_kitem) = @_;
781              
782 0           my $xwad = $self->expand_items($to_kitem);
783 0           my @chain;
784 0           my $xitem = $xwad->{$from_idx};
785 0           while (1) {
786 0           push(@chain, $xitem);
787 0 0         last if $xitem->{item} == $to_kitem->{GRAMIDX};
788 0           $xitem = $xitem->{parent0};
789             }
790              
791 0           shift(@chain); # Really just want the explanation starting after from_idx
792 0           return @chain;
793             }
794              
795             sub get_lookahead_chain {
796 0     0 0   my Parse::YALALR::Build $self = shift;
797 0           my Parse::YALALR::Parser $parser = $self->parser;
798 0           my ($state, $token, $item) = @_;
799              
800 0           my $lawhy;
801             # Foreach lavec in LA_WHY = { la => lawhy } that contains TOKEN
802 0           for my $lavec (grep { vec($_, $token, 1) } (keys %{$item->{LA_WHY}})) {
  0            
  0            
803 0           $lawhy = $item->{LA_WHY}->{$lavec};
804 0 0         last if $lawhy->[0] eq 'generated';
805             }
806              
807 0 0         $DB::single = 1 if !defined $lawhy;
808 0 0         die "Unable to find lookahead chain for token ".$parser->dump_sym($token)." in item ".$parser->dump_item($item) if !defined $lawhy;
809              
810 0 0         return ($lawhy) if $lawhy->[0] eq 'generated';
811 0           my $K0 = $self->{item2state}->{$lawhy->[1]};
812 0           my @chain = $self->get_lookahead_chain($K0, $token, $lawhy->[1]->{GRAMIDX});
813 0           return ($lawhy, @chain);
814             }
815              
816             sub get_lookahead_chain3 {
817 0     0 0   my Parse::YALALR::Build $self = shift;
818 0           my Parse::YALALR::Parser $parser = $self->parser;
819 0           my ($state, $token, $item) = @_;
820              
821 0           my $expansion = $self->expand_state($state);
822 0           my $xitem = $expansion->{$item};
823              
824             # @chain : ( )
825             # item ITEMIDX was created because REASON by xitem CAUSE
826 0           my @chain = (bless [ $item, @{$xitem->{$token}} ], 'xreason');
  0            
827 0           my %visited;
828 0           $visited{$item} = 1;
829              
830             # Traverse upwards from the given item until either a kernel item
831             # is reached or the requested token is generated.
832 0           while (1) {
833 0           my ($last_item, $last_reason, $last_cause) = @{$chain[-1]};
  0            
834 0           $visited{$last_cause->{item}} = 1;
835 0 0         last if $last_reason ne 'propagated';
836 0           push(@chain,
837 0           bless [ $last_cause->{item}, @{$last_cause->{$token}} ], 'xreason');
838 0 0         return \@chain if $chain[-1]->[1] eq 'kernel';
839             }
840              
841             # Then keep traversing upward along randomly chosen unvisited
842             # links until a kernel item is reached.
843 0           LINK: while (1) {
844 0           my ($last_item, $last_reason, $last_cause) = @{$chain[-1]};
  0            
845 0           $visited{$last_cause->{item}} = 1;
846              
847             # Pick randomly (grab the first one reached)
848 0           while (my ($t, $r) = each %$last_cause) {
849 0 0         next if $t eq 'item';
850 0 0         next if $t eq 'parent0';
851 0 0         last LINK if $r->[0] eq 'kernel';
852 0 0         next if $visited{$r->[1]->{item}};
853 0           push(@chain, bless [ $last_cause->{item}, @$r ], 'xreason');
854 0           next LINK;
855             }
856              
857 0           print STDERR "Failed to find path to kernel item\n";
858 0           print STDERR "Visited items:\n";
859 0           foreach (keys %visited) {
860 0           print STDERR $parser->dump_item($_)."\n";
861             }
862 0           die "Bye bye\n";
863             }
864              
865 0           return \@chain;
866             }
867              
868             1; # I am not a module!