File Coverage

lib/Graph/Regexp.pm
Criterion Covered Total %
statement 188 224 83.9
branch 58 98 59.1
condition 18 38 47.3
subroutine 15 19 78.9
pod 7 7 100.0
total 286 386 74.0


line stmt bran cond sub pod time code
1             ############################################################################
2             # Generate flowcharts from Regexp debug dumpes
3             #
4              
5             package Graph::Regexp;
6              
7             require 5.008001;
8 6     6   597783 use Graph::Easy;
  6         1009889  
  6         262  
9 6     6   73 use Graph::Easy::Base;
  6         12  
  6         248  
10              
11             $VERSION = 0.05;
12             @ISA = qw/Graph::Easy::Base/;
13              
14 6     6   31 use strict;
  6         13  
  6         227  
15              
16             # Perl 5.8.8, might be different for 5.10?
17 6     6   33 use constant MAX_MATCHES => 32767;
  6         9  
  6         1616  
18              
19             #############################################################################
20             #############################################################################
21              
22             sub _init
23             {
24 20     20   2265 my ($self, $args) = @_;
25              
26 20         78 $self->{options} = {};
27 20   50     158 $self->{debug} = $args->{debug} || 0;
28 20         84 $self->reset();
29 20         108 $self;
30             }
31              
32             sub option
33             {
34 0     0 1 0 my $self = shift;
35 0         0 $self->{options}->{$_[0]};
36             }
37              
38             sub graph
39             {
40             # decompose regexp dump and return as Graph::Easy object
41              
42             # allow Graph::Regexp->graph() calling style
43 19     19 1 178605 my $class = 'Graph::Regexp';
44 19 50       90 $class = shift if @_ == 2; $class = ref($class) if ref($class);
  19 100       66  
45 19         37 my $code = shift;
46              
47 19         146 my $self = $class->new();
48 19         53 $self->reset();
49 19         71 $self->parse($code);
50              
51 19         269 $self->{graph}; # return the Graph::Easy object
52             }
53              
54             sub as_graph
55             {
56             # return the internal Graph::Easy object
57 0     0 1 0 my $self = shift;
58              
59 0         0 $self->{graph};
60             }
61              
62             sub as_ascii
63             {
64             # return the graph as ASCII
65 0     0 1 0 my $self = shift;
66              
67 0         0 $self->{graph}->as_ascii();
68             }
69              
70             BEGIN
71             {
72             # make an alias for decompose
73 6     6   24103 *decompose = \&parse;
74             }
75              
76             sub parse
77             {
78 20     20 1 2417 my ($self, $doc) = @_;
79              
80 20         53 $self->reset(); # clear data
81              
82 20 50 33     143 $self->_croak("Expected SCALAR ref, but got " . ref($doc))
83             if ref($doc) && ref($doc) ne 'SCALAR';
84              
85 20 0 33     61 $self->_croak("Got filename '$doc', but can't read it: $!")
86             if !ref($doc) && !-f $doc;
87              
88             # XXX TODO: filenames
89              
90 20         115 $self->_parse($$doc);
91              
92 20         40 $self;
93             }
94              
95             sub reset
96             {
97             # reset the internal structure
98 59     59 1 79 my $self = shift;
99              
100 59         91 delete $self->{fail};
101 59         82 delete $self->{success};
102 59         201 $self->{graph} = Graph::Easy->new();
103              
104 59         4290 $self->{stack} = [];
105 59         1135 $self->{entries} = {};
106              
107 59         86 $self;
108             }
109              
110             sub graph_label
111             {
112             # get/set the label of the graph
113 0     0 1 0 my ($self) = shift;
114              
115 0         0 my $g = $self->{graph};
116 0 0       0 if (@_ > 0)
117             {
118 0         0 $g->set_attribute('label',$_[0]);
119             }
120 0         0 $g->label();
121             }
122              
123             #############################################################################
124             #############################################################################
125             # main parse routine, recursive
126              
127             sub _setup_nodeclass
128             {
129             # add the attributes for one node class
130 220     220   374 my ($self, $class, $title, $label) = @_;
131              
132 220         320 my $g = $self->{graph};
133              
134 220         760 $g->set_attribute("node.$class", 'title', $title);
135 220         17140 $g->set_attribute("node.$class", 'label', $label);
136             }
137              
138             sub _parse
139             {
140             # take the regexp string and decompose it into a tree, then turn this into
141             # a graph.
142 20     20   41 my ($self, $text) = @_;
143              
144 20         40 my $g = $self->{graph};
145              
146             # add the start node
147 20         88 my $root = $g->add_node('0');
148 20         1312 $g->set_attribute('root','0'); # the first node is the root
149 20         2549 $root->set_attribute('label','START');
150 20         1775 $root->set_attribute('class','start');
151              
152             # add the final fail and success nodes
153 20         2050 $self->{fail} = $g->add_node('FAIL');
154 20         887 $self->{success} = $g->add_node('SUCCESS');
155 20         946 $self->{fail}->set_attribute('class','fail');
156 20         1638 $self->{success}->set_attribute('class','success');
157              
158             # this is a hack to workaround that Graph::Easy has not yet "end => '0'" for edges
159 20         1769 $self->{fail}->set_attribute('origin','SUCCESS');
160 20         2280 $self->{fail}->set_attribute('offset','0,2');
161              
162 20         1863 $g->set_attribute('node.nothing', 'label', "\\''");
163 20         1620 $g->set_attribute('node.nothing', 'title', "Nothing (always matches)");
164              
165             # Special nodes:
166             # ^ (BOL)
167             # $ (EOL)
168             # \z (EOS)
169             # \Z (SEOL)
170             # \A (SBOL)
171             # \b \B (BOUND, NBOUND)
172             # \d \D (DIGIT, NDIGIT)
173             # \w \W (ALNUM, NALNUM)
174              
175 20         1485 $self->_setup_nodeclass('bol', 'BOL (Begin Of Line)', '^');
176 20         1410 $self->_setup_nodeclass('eol', 'EOL (End Of Line)', '$');
177 20         1436 $self->_setup_nodeclass('eos', 'EOS (End Of String)', '\\z');
178 20         1518 $self->_setup_nodeclass('seol', 'SEOL (String end or End Of Line)', '\\Z');
179 20         9910 $self->_setup_nodeclass('sbol', 'SBOL (String begin or Begin Of Line)', '\\A');
180 20         1512 $self->_setup_nodeclass('bound', 'BOUND (Boundary)', '\\b');
181 20         1497 $self->_setup_nodeclass('nbound', 'NBOUND (Non-boundary)', '\\B');
182 20         1462 $self->_setup_nodeclass('digit', 'DIGIT (Digit)', '\\d');
183 20         1463 $self->_setup_nodeclass('ndigit', 'NDIGIT (Non-digit)', '\\D');
184 20         1496 $self->_setup_nodeclass('alnum', 'ALNUM (Alphanumeric)', '\\w');
185 20         1471 $self->_setup_nodeclass('nalnum', 'NALNUM (Non-alphanumeric)', '\\W');
186              
187 20         1557 $g->set_attributes('node.fail', { fill => 'darkred', color => 'white' } );
188 20         4404 $g->set_attributes('node.success', { fill => 'darkgreen', color => 'white' } );
189              
190 20         3886 $g->set_attributes('edge.match', {
191             'label' => 'match',
192             'color' => 'darkgreen'
193             } );
194 20         3786 $g->set_attributes('edge.always', {
195             'label' => 'always',
196             } );
197 20         1955 $g->set_attributes('edge.fail', {
198             'label' => 'fail',
199             'color' => 'darkred'
200             } );
201              
202             # The general family of this object. These are any of:
203             # alnum, anchor, anyof, anyof_char, anyof_class, anyof_range,
204             # assertion, bol, branch, close, clump, digit, exact, flags, group, groupp,
205             # minmod, open, prop, sol, eol, seol, sbol, quant, ref, reg_any,
206             # star, plus ...
207              
208             # first we parse the following text:
209              
210             # 1: OPEN1(3)
211             # 3: BRANCH(6)
212             # 4: EXACT (9)
213             # 6: BRANCH(9)
214             # 7: EXACT (9)
215             # 9: CLOSE1(11)
216             # 11: EXACT (13)
217             # 13: PLUS(16)
218             # 14: EXACT (0)
219             # 16: EXACT <1>(18)
220              
221             # into entries like:
222              
223             # { id => 1, level => 0, type => "open", next => 3, id => 1, }
224              
225             # to preserve the entries in their original order
226 20         3735 my $stack = $self->{stack};
227             # to quickly find entries by their id
228 20         41 my $entries = $self->{entries};
229              
230 20         85 $text =~ s/[\r\n]\z//;
231              
232 20 50       78 print STDERR "# Input: \n# '$text'\n" if $self->{debug};
233              
234 20         88 my @lines = split /\n/, $text; my $index = 0;
  20         34  
235 20         41 for my $line (@lines)
236             {
237             # ignore all other lines
238 65 100       283 next unless $line =~ /^\s+(\d+):(\s+)[A-Z]/;
239              
240 63 50       142 print STDERR "# Parsing line: '$line'\n" if $self->{debug} > 1;
241              
242             # level: ' ' => 0, ' ' => 1 etc
243 63         329 my $entry = { level => (length($2)-1) / 2, id => $1 };
244              
245             # "7: EXACT (9)" => "EXACT (9)"
246 63         236 $line =~ s/^\s+\d+:\s+//;
247            
248             # OPEN1(3) or OPEN1 (3)
249 63 100       404 if ($line =~ /^([A-Z][A-Z0-9]+)\s*\((\d+)\)/)
    100          
    100          
    50          
    0          
    0          
    0          
250             {
251 44         124 $entry->{class} = lc($1);
252 44         113 $entry->{next} = $2;
253 44         82 $entry->{exact} = '';
254             }
255             # EXACT (16) or EXACT (16)
256             elsif ($line =~ /^([A-Z][A-Z0-9-]+)(\s*<(.+)>)?\s*\((\d+)\)/)
257             {
258 16         57 $entry->{class} = lc($1);
259 16         33 my $t = $3;
260 16         49 $entry->{next} = $4;
261 16         107 $t =~ s/(\$|\@|\\)/\\$1/g; # quote $, @ and \
262 16         59 $entry->{exact} = "\\\"$t\\\"";
263 16         46 $entry->{title} = "EXACT <$t>";
264             }
265             # TRIE-EXACT [bo](9)
266             elsif ($line =~ /^TRIE-EXACT\s*(\[([^\]]+)\])\s*?\((\d+)\)/)
267             {
268 1         3 $entry->{class} = 'trie';
269 1         6 $entry->{title} = "TRIE-EXACT <$1>";
270 1         3 $entry->{exact} = "$1";
271 1         3 $entry->{next} = $2;
272             }
273             # ANYOF[ab](8)
274             elsif ($line =~ /^([A-Z][A-Z0-9-]+)\s*(\[([^\]]+)\])\s*?\((\d+)\)/)
275             {
276 2         8 $entry->{class} = lc($1);
277 2 50       8 if ($entry->{class} eq 'anyof')
    0          
278             {
279 2         11 $entry->{exact} = "[$3]";
280             }
281             elsif ($entry->{class} eq 'nothing')
282             {
283 0         0 $entry->{exact} = "[$3]";
284             }
285             else
286             {
287 0         0 $entry->{exact} = "\"$3\"";
288             }
289 2         8 $entry->{title} = "EXACT <$3>";
290 2         20 $entry->{next} = $4;
291             }
292             # CURLY {0,1}(22) or CURLY {0,1} (22)
293             elsif ($line =~ /^([A-Z][A-Z0-9]+)\s*\{(\d+),(\d+)\}\s*\((\d+)\)/)
294             {
295 0         0 $entry->{class} = lc($1);
296 0         0 $entry->{next} = $4;
297 0         0 $entry->{min} = $2;
298 0         0 $entry->{max} = $3;
299 0         0 $entry->{exact} = "\{$entry->{min},$entry->{max}\}";
300             }
301             # CURLYM[1] {0,1}(22) or CURLY {0,1} (22) or CURLYX[1] {1,2}(22)
302             elsif ($line =~ /^([A-Z][A-Z0-9]+)\[[^]]\]\s*\{(\d+),(\d+)\}\s*\((\d+)\)/)
303             {
304 0         0 $entry->{class} = lc($1);
305 0         0 $entry->{next} = $4;
306 0         0 $entry->{min} = $2;
307 0         0 $entry->{max} = $3;
308 0         0 $entry->{exact} = "\{$entry->{min},$entry->{max}\}";
309             # make curlym, curly and curlyx all "curly"
310 0 0       0 $entry->{class} = 'curly' if $entry->{class} =~ /^curly/;
311             }
312             # PLUS (22)
313             elsif ($line =~ /^PLUS\s*\((\d+)\)/)
314             {
315 0         0 $entry->{class} = 'plus';
316 0         0 $entry->{next} = $1;
317 0         0 $entry->{min} = 1;
318 0         0 $entry->{max} = MAX_MATCHES;
319 0         0 $entry->{exact} = "\{$entry->{min},$entry->{max}\}";
320             }
321 63         163 $entry->{class} =~ s/[0-9]//g; # OPEN1 => open
322 63         126 $entry->{index} = $index++;
323              
324 63         100 push @$stack, $entry;
325 63         163 $entries->{ $entry->{id} } = $entry;
326              
327 63 100       350 next if $entry->{class} =~ /(open|close|branch|end|succeed|curly|minmod|plus|star|whilem)/;
328              
329             # add the nodes right away
330             # print STDERR "# adding node for $line\n";
331              
332 35         186 my $n = $g->add_node($entry->{id});
333 35 100       1804 $n->set_attribute('label', $entry->{exact}) if $entry->{exact} ne '';
334 35         1635 $n->set_attribute('class', $entry->{class});
335 35 100       2831 $n->set_attribute('title', $entry->{title}) if $entry->{title};
336              
337 35         1346 $entry->{node} = $n;
338             }
339              
340             # empty text => matches always
341 20 50       80 if (keys %$entries == 0)
342             {
343 0         0 my $edge = $g->add_edge( $root, $self->{success});
344 0         0 $edge->set_attribute('class','always');
345 0         0 return $self;
346             }
347              
348             # Now we take the stack of entries and transform it into a graph by
349             # connecting all the nodes with "match" and "fail" edges.
350              
351             # Notes:
352              
353             # Each tried (sub)expression in the regexp has exactly two outcomes:
354             # 'match' or 'fail'.
355             # If a expression consists of more than on part than it is handled
356             # like an "and" (first and second part must match).
357             # F.i. in "[ab]foo", if [ab] matches, it goes to try "foo", If it
358             # it fails, it goes one level up. Likewise for "foo", match goes
359             # on to the next part and fail goes up.
360             # If we are already at level 0, the entire expression fails.
361              
362             # Branches try each subexpression in order, that is if one subexpression
363             # fails, it goes to the next branch. If any of them matches, it goes
364             # on to the next part, and if all of them fail, it goes up.
365              
366             # /just(another|perl)hacker/ will result in:
367              
368             # 1: EXACT (3)
369             # 3: OPEN1(5)
370             # 5: BRANCH(9)
371             # 6: EXACT (12)
372             # 9: BRANCH(12)
373             # 10: EXACT (12)
374             # 12: CLOSE1(14)
375             # 14: EXACT (17)
376             # 17: END(0)
377              
378             # [ just ] - match -> [ another ] - match -> [ hacker ] - match -> [ success ]
379             # | | ^ |
380             # | fail | fail | |
381             # | | | | fail
382             # | [ perl ] - match ------| |
383             # | | |
384             # | | fail |
385             # -------------------------------------------------------------> [ fail ]
386              
387             # XXX TODO: each OPEN/CLOSE pair should result in a subgroup. This is not
388             # yet possible since Graph::Easy doesn't allow nesting yet.
389              
390             # connect the root node to the first part
391 20         98 my $next = $self->_find_node($stack->[0]);
392 20         161 my $edge = $g->add_edge( $root, $next);
393              
394             # The "NOTHING" node has no predecessor and needs to be weeded out:
395             #
396             # 1: CURLYM[1] {0,32767}(15)
397             # 5: BRANCH(8)
398             # 6: EXACT (13)
399             # 8: BRANCH(11)
400             # 9: EXACT (13)
401             # 13: SUCCEED(0)
402             # 14: NOTHING(15)
403             # 15: END(0)
404              
405             ###########################################################################
406             ###########################################################################
407             # main conversion loop
408              
409             # the entry/part we are trying
410 20         1917 my $i = 0;
411 20         75 while ($i < @$stack)
412             {
413 63         82 my $entry = $stack->[$i];
414              
415 63 100       148 next unless exists $entry->{node};
416              
417 35 50 66     156 if ($entry->{class} eq 'nothing' && $entry->{node}->predecessors() == 0)
418             {
419             # a nothing node with no incoming connection, filter it out
420 0         0 $g->del_node($entry->{node});
421 0         0 next;
422             }
423              
424             # the "match" egde goes to the next part
425 35         222 my $next = $self->_find_next($entry);
426              
427 35 100       55 my $n = $next; $n = $self->{success} unless defined $n;
  35         83  
428              
429 35         122 my $edge = $g->add_edge( $entry->{node}, $n);
430 35         2775 $edge->set_attribute('class','match');
431              
432 35 100       3152 if ($n == $self->{success})
433             {
434 22         90 $edge->set_attribute('end','back,0');
435             }
436              
437             # nothing nodes do not have a fail edge, they match always
438 35 100 33     2131 if ( ($entry->{class} eq 'nothing') ||
      66        
439             (defined $entry->{min} && $entry->{min} == 0) )
440             {
441 3         9 $edge->set_attribute('class','always');
442 3         257 next;
443             }
444            
445             # generate the fail edge:
446              
447             # if the next node is $self->{success}, then fail must be $self->{fail}
448 32         60 my $fail = $self->{fail};
449             # walked over end?
450 32 100       112 if (!defined $next)
    50          
451             {
452 19         51 $fail = $self->_find_next_branching($entry);
453             }
454             # otherwise, find the next branching part
455             elsif ($next != $self->{success})
456             {
457 13         45 $fail = $self->_find_next_branching($entry);
458             }
459              
460 32         107 $edge = $g->add_edge( $entry->{node}, $fail);
461 32         2102 $edge->set_attribute('class','fail');
462 32         2711 $edge->set_attribute('end','back,0');
463              
464 63         2530 } continue { $i++; }
465              
466             # if there are no incoming edges to fail, the regexp always matches (like //):
467 20 100       116 $g->del_node($self->{fail}) if scalar $self->{fail}->incoming() == 0;
468              
469 20         716 $self;
470             }
471              
472             sub _find_next_branching
473             {
474             # Given an entry on the stack, go backwards to find the
475             # last branch, then skip to the next part in that branch.
476             # If there is no next part, try one level higher, until
477             # we are at the upper-most level.
478 32     32   50 my ($self, $entry) = @_;
479              
480             # Example:
481              
482             # starting with 14: EXACT (19)
483              
484             # 1: EXACT <0>(3)
485             # 3: OPEN1(5)
486             # 5: BRANCH(8)
487             # 6: EXACT (35)
488             # 8: BRANCH(32)
489             # 9: EXACT (11)
490             # 11: OPEN2(13)
491             # 13: BRANCH(16) 1 # look at next(16) is it a branch?
492             # yes it is, so go forward to it
493             # 14: EXACT (19) 0 # find 13: BRANCH(16)
494             # 16: BRANCH(19) 2 # skip forward
495             # 17: EXACT (19) 3 # return this
496             # 19: CLOSE2(21)
497             # 21: ANYOF[i](35)
498             # 32: BRANCH(35)
499             # 33: EXACT (35)
500             # 35: CLOSE1(37)
501             # 37: EXACT (39)
502             # 39: END(0)
503              
504             # starting with 17: EXACT (19)
505              
506             # 1: EXACT <0>(3)
507             # 3: OPEN1(5)
508             # 5: BRANCH(8)
509             # 6: EXACT (35)
510             # 8: BRANCH(32) 2 # look at next(32) is it a branch?
511             # yes it is, so go forward to it
512             # 9: EXACT (11)
513             # 11: OPEN2(13)
514             # 13: BRANCH(16)
515             # 14: EXACT (19)
516             # 16: BRANCH(19) 1 # look at next(16) is it a branch?
517             # no, 19 is not, so find 8: BRANCH(32)
518             # 17: EXACT (19) 0 # find 16: BRANCH(19)
519             # 19: CLOSE2(21)
520             # 21: ANYOF[i](35)
521             # 32: BRANCH(35)
522             # 33: EXACT (35) 3 # return this:
523             # 35: CLOSE1(37)
524             # 37: EXACT (39)
525             # 39: END(0)
526              
527 32 50       75 print STDERR "# find next branch for $entry->{id}\n" if $self->{debug};
528              
529 32         43 my $entries = $self->{entries};
530 32         47 do {
531             # find branch one level up
532 33         75 my $branch = $self->_find_previous_branch($entry);
533              
534 33 0 33     109 print STDERR "# prev branch for $entry->{id} should be at $branch->{id}\n"
      33        
535             if $self->{debug} && $branch && defined $branch->{id};
536              
537             # no branch above us, fail completely
538 33 100       116 return $self->{fail} unless defined $branch;
539              
540             # skip to next part
541 4         6 $entry = $entries->{ $branch->{next} };
542              
543 4 50       9 print STDERR "# next branch should be at $entry->{id} ($entry->{class})\n"
544             if $self->{debug};
545              
546 4 100 66     27 return $self->{fail} if $entry && $entry->{class} eq 'end';
547              
548             # loop ends if there is a next part in the current branch
549             } while ($entry->{class} ne 'branch');
550              
551             # skip over the branch, open etc to the first real part
552 2         6 $entry = $self->_find_node($entry);
553              
554 2 50       6 print STDERR "# next branch is at $entry->{id}\n"
555             if $self->{debug};
556              
557 2         5 $entry;
558             }
559              
560             sub _find_previous_branch
561             {
562             # Given an entry on the stack, go backwards to find the
563             # last branch.
564 33     33   41 my ($self, $entry) = @_;
565              
566 33         47 my $entries = $self->{entries};
567 33         45 my $stack = $self->{stack};
568              
569 33         45 my $index = $entry->{index};
570              
571 33 50       67 print STDERR "# Finding prev branch for entry $entry->{id}\n"
572             if $self->{debug};
573              
574             # the branch must be this level or lower
575 33         44 my $level = $entry->{level};
576              
577             # go backwards until we find a BRANCH
578 33         76 while ($index > 0)
579             {
580 28         29 $index--;
581 28         35 my $e = $stack->[$index];
582              
583 28 0 33     68 print STDERR "# Found $entry->{id} ($level vs $e->{level}\n"
      33        
584             if $self->{debug} && $entry && $entry->{class} eq 'branch';
585              
586 28 100 100     115 return $e if $e->{class} eq 'branch' && $e->{level} <= $level;
587             }
588             # the part we looked at is in the upper-most level, so there is
589             # no next branch part we can skip to, meaning we fail completely.
590 29         270 return;
591             }
592              
593             sub _find_node
594             {
595             # Given an entry on the stack, skip to next entry if the current
596             # isnt a node itself.
597 22     22   42 my ($self, $entry) = @_;
598              
599             # Example:
600              
601             # 3: OPEN1(5) # open => skip, go to next
602             # 5: BRANCH(9) # branch => skip, go to next
603             # 6: EXACT (12) # return this
604              
605             # 1: EXACT (3) # return this
606             # 3: OPEN1(5)
607             # 5: BRANCH(9)
608             # 6: EXACT (12)
609            
610 22 50       88 print STDERR "# find node for entry $entry->{id}\n"
611             if $self->{debug};
612              
613 22         40 my $entries = $self->{entries};
614 22         50 my $stack = $self->{stack};
615 22         74 while (!exists $entry->{node})
616             {
617 6 50       17 print STDERR "# at entry $entry->{id}\n"
618             if $self->{debug};
619              
620 6 50       28 if ($entry->{class} =~ /^(open|branch|plus|star|curly)/)
621             {
622 6         17 $entry = $stack->[ $entry->{index} + 1 ];
623             }
624             else
625             {
626 0         0 $entry = $entries->{ $entry->{next} };
627             }
628 6 50       24 return $self->{success} unless ref $entry; # walked over end
629             }
630              
631 22         59 $entry->{node};
632             }
633              
634             sub _find_next
635             {
636             # Given an entry on the stack, find the next entry.
637 35     35   49 my ($self, $entry) = @_;
638              
639             # Example:
640              
641             # 1: EXACT (3) # go to 3
642             # 3: OPEN1(5) # open => skip, go to next
643             # 5: BRANCH(9) # branch => skip, go to next
644             # 6: EXACT (12) # return this
645              
646 35 50       86 print STDERR "# Skipping ahead for $entry->{id}:\n"
647             if $self->{debug};
648 35         55 my $entries = $self->{entries};
649 35         70 my $stack = $self->{stack};
650             do
651 35         40 {
652 58 50       159 print STDERR "# at entry $entry->{id}\n"
653             if $self->{debug};
654              
655 58 50       143 if ($entry->{class} =~ /^(open|branch|plus|star|curly)/)
656             {
657 0         0 $entry = $stack->[ $entry->{index} + 1 ];
658             }
659             else
660             {
661 58         103 $entry = $entries->{ $entry->{next} };
662             }
663 58 100       167 return unless ref $entry; # walked over end
664              
665 36 50 33     170 print STDERR "# next $entry->{id}\n"
666             if $self->{debug} && ref($entry);
667             } while (!exists $entry->{node});
668              
669 13 50       27 print STDERR "# return $entry->{id}\n"
670             if $self->{debug};
671              
672 13         31 $entry->{node};
673             }
674              
675             1;
676             __END__