Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace PhpParser;
|
Chris@0
|
4
|
Chris@0
|
5 class NodeTraverser implements NodeTraverserInterface
|
Chris@0
|
6 {
|
Chris@0
|
7 /**
|
Chris@0
|
8 * If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
|
Chris@0
|
9 * of the current node will not be traversed for any visitors.
|
Chris@0
|
10 *
|
Chris@0
|
11 * For subsequent visitors enterNode() will still be called on the current
|
Chris@0
|
12 * node and leaveNode() will also be invoked for the current node.
|
Chris@0
|
13 */
|
Chris@0
|
14 const DONT_TRAVERSE_CHILDREN = 1;
|
Chris@0
|
15
|
Chris@0
|
16 /**
|
Chris@0
|
17 * If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns
|
Chris@0
|
18 * STOP_TRAVERSAL, traversal is aborted.
|
Chris@0
|
19 *
|
Chris@0
|
20 * The afterTraverse() method will still be invoked.
|
Chris@0
|
21 */
|
Chris@0
|
22 const STOP_TRAVERSAL = 2;
|
Chris@0
|
23
|
Chris@0
|
24 /**
|
Chris@0
|
25 * If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
|
Chris@0
|
26 * in an array, it will be removed from the array.
|
Chris@0
|
27 *
|
Chris@0
|
28 * For subsequent visitors leaveNode() will still be invoked for the
|
Chris@0
|
29 * removed node.
|
Chris@0
|
30 */
|
Chris@0
|
31 const REMOVE_NODE = false;
|
Chris@0
|
32
|
Chris@0
|
33 /** @var NodeVisitor[] Visitors */
|
Chris@0
|
34 protected $visitors;
|
Chris@0
|
35
|
Chris@0
|
36 /** @var bool Whether traversal should be stopped */
|
Chris@0
|
37 protected $stopTraversal;
|
Chris@0
|
38
|
Chris@0
|
39 /**
|
Chris@0
|
40 * Constructs a node traverser.
|
Chris@0
|
41 */
|
Chris@0
|
42 public function __construct() {
|
Chris@0
|
43 $this->visitors = array();
|
Chris@0
|
44 }
|
Chris@0
|
45
|
Chris@0
|
46 /**
|
Chris@0
|
47 * Adds a visitor.
|
Chris@0
|
48 *
|
Chris@0
|
49 * @param NodeVisitor $visitor Visitor to add
|
Chris@0
|
50 */
|
Chris@0
|
51 public function addVisitor(NodeVisitor $visitor) {
|
Chris@0
|
52 $this->visitors[] = $visitor;
|
Chris@0
|
53 }
|
Chris@0
|
54
|
Chris@0
|
55 /**
|
Chris@0
|
56 * Removes an added visitor.
|
Chris@0
|
57 *
|
Chris@0
|
58 * @param NodeVisitor $visitor
|
Chris@0
|
59 */
|
Chris@0
|
60 public function removeVisitor(NodeVisitor $visitor) {
|
Chris@0
|
61 foreach ($this->visitors as $index => $storedVisitor) {
|
Chris@0
|
62 if ($storedVisitor === $visitor) {
|
Chris@0
|
63 unset($this->visitors[$index]);
|
Chris@0
|
64 break;
|
Chris@0
|
65 }
|
Chris@0
|
66 }
|
Chris@0
|
67 }
|
Chris@0
|
68
|
Chris@0
|
69 /**
|
Chris@0
|
70 * Traverses an array of nodes using the registered visitors.
|
Chris@0
|
71 *
|
Chris@0
|
72 * @param Node[] $nodes Array of nodes
|
Chris@0
|
73 *
|
Chris@0
|
74 * @return Node[] Traversed array of nodes
|
Chris@0
|
75 */
|
Chris@0
|
76 public function traverse(array $nodes) {
|
Chris@0
|
77 $this->stopTraversal = false;
|
Chris@0
|
78
|
Chris@0
|
79 foreach ($this->visitors as $visitor) {
|
Chris@0
|
80 if (null !== $return = $visitor->beforeTraverse($nodes)) {
|
Chris@0
|
81 $nodes = $return;
|
Chris@0
|
82 }
|
Chris@0
|
83 }
|
Chris@0
|
84
|
Chris@0
|
85 $nodes = $this->traverseArray($nodes);
|
Chris@0
|
86
|
Chris@0
|
87 foreach ($this->visitors as $visitor) {
|
Chris@0
|
88 if (null !== $return = $visitor->afterTraverse($nodes)) {
|
Chris@0
|
89 $nodes = $return;
|
Chris@0
|
90 }
|
Chris@0
|
91 }
|
Chris@0
|
92
|
Chris@0
|
93 return $nodes;
|
Chris@0
|
94 }
|
Chris@0
|
95
|
Chris@0
|
96 protected function traverseNode(Node $node) {
|
Chris@0
|
97 foreach ($node->getSubNodeNames() as $name) {
|
Chris@0
|
98 $subNode =& $node->$name;
|
Chris@0
|
99
|
Chris@0
|
100 if (is_array($subNode)) {
|
Chris@0
|
101 $subNode = $this->traverseArray($subNode);
|
Chris@0
|
102 if ($this->stopTraversal) {
|
Chris@0
|
103 break;
|
Chris@0
|
104 }
|
Chris@0
|
105 } elseif ($subNode instanceof Node) {
|
Chris@0
|
106 $traverseChildren = true;
|
Chris@0
|
107 foreach ($this->visitors as $visitor) {
|
Chris@0
|
108 $return = $visitor->enterNode($subNode);
|
Chris@0
|
109 if (self::DONT_TRAVERSE_CHILDREN === $return) {
|
Chris@0
|
110 $traverseChildren = false;
|
Chris@0
|
111 } else if (self::STOP_TRAVERSAL === $return) {
|
Chris@0
|
112 $this->stopTraversal = true;
|
Chris@0
|
113 break 2;
|
Chris@0
|
114 } else if (null !== $return) {
|
Chris@0
|
115 $subNode = $return;
|
Chris@0
|
116 }
|
Chris@0
|
117 }
|
Chris@0
|
118
|
Chris@0
|
119 if ($traverseChildren) {
|
Chris@0
|
120 $subNode = $this->traverseNode($subNode);
|
Chris@0
|
121 if ($this->stopTraversal) {
|
Chris@0
|
122 break;
|
Chris@0
|
123 }
|
Chris@0
|
124 }
|
Chris@0
|
125
|
Chris@0
|
126 foreach ($this->visitors as $visitor) {
|
Chris@0
|
127 $return = $visitor->leaveNode($subNode);
|
Chris@0
|
128 if (self::STOP_TRAVERSAL === $return) {
|
Chris@0
|
129 $this->stopTraversal = true;
|
Chris@0
|
130 break 2;
|
Chris@0
|
131 } else if (null !== $return) {
|
Chris@0
|
132 if (is_array($return)) {
|
Chris@0
|
133 throw new \LogicException(
|
Chris@0
|
134 'leaveNode() may only return an array ' .
|
Chris@0
|
135 'if the parent structure is an array'
|
Chris@0
|
136 );
|
Chris@0
|
137 }
|
Chris@0
|
138 $subNode = $return;
|
Chris@0
|
139 }
|
Chris@0
|
140 }
|
Chris@0
|
141 }
|
Chris@0
|
142 }
|
Chris@0
|
143
|
Chris@0
|
144 return $node;
|
Chris@0
|
145 }
|
Chris@0
|
146
|
Chris@0
|
147 protected function traverseArray(array $nodes) {
|
Chris@0
|
148 $doNodes = array();
|
Chris@0
|
149
|
Chris@0
|
150 foreach ($nodes as $i => &$node) {
|
Chris@0
|
151 if (is_array($node)) {
|
Chris@0
|
152 $node = $this->traverseArray($node);
|
Chris@0
|
153 if ($this->stopTraversal) {
|
Chris@0
|
154 break;
|
Chris@0
|
155 }
|
Chris@0
|
156 } elseif ($node instanceof Node) {
|
Chris@0
|
157 $traverseChildren = true;
|
Chris@0
|
158 foreach ($this->visitors as $visitor) {
|
Chris@0
|
159 $return = $visitor->enterNode($node);
|
Chris@0
|
160 if (self::DONT_TRAVERSE_CHILDREN === $return) {
|
Chris@0
|
161 $traverseChildren = false;
|
Chris@0
|
162 } else if (self::STOP_TRAVERSAL === $return) {
|
Chris@0
|
163 $this->stopTraversal = true;
|
Chris@0
|
164 break 2;
|
Chris@0
|
165 } else if (null !== $return) {
|
Chris@0
|
166 $node = $return;
|
Chris@0
|
167 }
|
Chris@0
|
168 }
|
Chris@0
|
169
|
Chris@0
|
170 if ($traverseChildren) {
|
Chris@0
|
171 $node = $this->traverseNode($node);
|
Chris@0
|
172 if ($this->stopTraversal) {
|
Chris@0
|
173 break;
|
Chris@0
|
174 }
|
Chris@0
|
175 }
|
Chris@0
|
176
|
Chris@0
|
177 foreach ($this->visitors as $visitor) {
|
Chris@0
|
178 $return = $visitor->leaveNode($node);
|
Chris@0
|
179
|
Chris@0
|
180 if (self::REMOVE_NODE === $return) {
|
Chris@0
|
181 $doNodes[] = array($i, array());
|
Chris@0
|
182 break;
|
Chris@0
|
183 } else if (self::STOP_TRAVERSAL === $return) {
|
Chris@0
|
184 $this->stopTraversal = true;
|
Chris@0
|
185 break 2;
|
Chris@0
|
186 } elseif (is_array($return)) {
|
Chris@0
|
187 $doNodes[] = array($i, $return);
|
Chris@0
|
188 break;
|
Chris@0
|
189 } elseif (null !== $return) {
|
Chris@0
|
190 $node = $return;
|
Chris@0
|
191 }
|
Chris@0
|
192 }
|
Chris@0
|
193 }
|
Chris@0
|
194 }
|
Chris@0
|
195
|
Chris@0
|
196 if (!empty($doNodes)) {
|
Chris@0
|
197 while (list($i, $replace) = array_pop($doNodes)) {
|
Chris@0
|
198 array_splice($nodes, $i, 1, $replace);
|
Chris@0
|
199 }
|
Chris@0
|
200 }
|
Chris@0
|
201
|
Chris@0
|
202 return $nodes;
|
Chris@0
|
203 }
|
Chris@0
|
204 }
|