1<?php
2
3declare(strict_types=1);
4
5namespace Antlr\Antlr4\Runtime\Tree;
6
7use Antlr\Antlr4\Runtime\ParserRuleContext;
8
9class ParseTreeWalker
10{
11    public static function default() : self
12    {
13        static $instance;
14
15        return $instance ?? ($instance = new self());
16    }
17
18    public function walk(ParseTreeListener $listener, ParseTree $tree) : void
19    {
20        if ($tree instanceof ErrorNode) {
21            $listener->visitErrorNode($tree);
22
23            return;
24        }
25
26        if ($tree instanceof TerminalNode) {
27            $listener->visitTerminal($tree);
28
29            return;
30        }
31
32        if (!$tree instanceof RuleNode) {
33            throw new \RuntimeException('Unexpected tree type.');
34        }
35
36        $this->enterRule($listener, $tree);
37
38        $count = $tree->getChildCount();
39
40        for ($i = 0; $i < $count; $i++) {
41            $child = $tree->getChild($i);
42
43            if ($child !== null) {
44                $this->walk($listener, $child);
45            }
46        }
47
48        $this->exitRule($listener, $tree);
49    }
50
51    /**
52     * The discovery of a rule node, involves sending two events: the generic
53     * {@see ParseTreeListener::enterEveryRule()} and a
54     * {@see RuleContext}-specific event. First we trigger the generic and then
55     * the rule specific. We to them in reverse order upon finishing the node.
56     */
57    protected function enterRule(ParseTreeListener $listener, RuleNode $ruleNode) : void
58    {
59        /** @var ParserRuleContext $ctx */
60        $ctx = $ruleNode->getRuleContext();
61
62        $listener->enterEveryRule($ctx);
63
64        $ctx->enterRule($listener);
65    }
66
67    protected function exitRule(ParseTreeListener $listener, RuleNode $ruleNode) : void
68    {
69        /** @var ParserRuleContext $ctx */
70        $ctx = $ruleNode->getRuleContext();
71
72        $ctx->exitRule($listener);
73
74        $listener->exitEveryRule($ctx);
75    }
76}
77