Chris@0: visitors[] = $visitor; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Removes an added visitor. Chris@0: * Chris@0: * @param NodeVisitor $visitor Chris@0: */ Chris@0: public function removeVisitor(NodeVisitor $visitor) { Chris@0: foreach ($this->visitors as $index => $storedVisitor) { Chris@0: if ($storedVisitor === $visitor) { Chris@0: unset($this->visitors[$index]); Chris@0: break; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Traverses an array of nodes using the registered visitors. Chris@0: * Chris@0: * @param Node[] $nodes Array of nodes Chris@0: * Chris@0: * @return Node[] Traversed array of nodes Chris@0: */ Chris@0: public function traverse(array $nodes) : array { Chris@0: $this->stopTraversal = false; Chris@0: Chris@0: foreach ($this->visitors as $visitor) { Chris@0: if (null !== $return = $visitor->beforeTraverse($nodes)) { Chris@0: $nodes = $return; Chris@0: } Chris@0: } Chris@0: Chris@0: $nodes = $this->traverseArray($nodes); Chris@0: Chris@0: foreach ($this->visitors as $visitor) { Chris@0: if (null !== $return = $visitor->afterTraverse($nodes)) { Chris@0: $nodes = $return; Chris@0: } Chris@0: } Chris@0: Chris@0: return $nodes; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Recursively traverse a node. Chris@0: * Chris@0: * @param Node $node Node to traverse. Chris@0: * Chris@0: * @return Node Result of traversal (may be original node or new one) Chris@0: */ Chris@0: protected function traverseNode(Node $node) : Node { Chris@0: foreach ($node->getSubNodeNames() as $name) { Chris@0: $subNode =& $node->$name; Chris@0: Chris@0: if (\is_array($subNode)) { Chris@0: $subNode = $this->traverseArray($subNode); Chris@0: if ($this->stopTraversal) { Chris@0: break; Chris@0: } Chris@0: } elseif ($subNode instanceof Node) { Chris@0: $traverseChildren = true; Chris@4: $breakVisitorIndex = null; Chris@4: Chris@4: foreach ($this->visitors as $visitorIndex => $visitor) { Chris@0: $return = $visitor->enterNode($subNode); Chris@0: if (null !== $return) { Chris@0: if ($return instanceof Node) { Chris@0: $this->ensureReplacementReasonable($subNode, $return); Chris@0: $subNode = $return; Chris@0: } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { Chris@0: $traverseChildren = false; Chris@4: } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { Chris@4: $traverseChildren = false; Chris@4: $breakVisitorIndex = $visitorIndex; Chris@4: break; Chris@0: } elseif (self::STOP_TRAVERSAL === $return) { Chris@0: $this->stopTraversal = true; Chris@0: break 2; Chris@0: } else { Chris@0: throw new \LogicException( Chris@0: 'enterNode() returned invalid value of type ' . gettype($return) Chris@0: ); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if ($traverseChildren) { Chris@0: $subNode = $this->traverseNode($subNode); Chris@0: if ($this->stopTraversal) { Chris@0: break; Chris@0: } Chris@0: } Chris@0: Chris@4: foreach ($this->visitors as $visitorIndex => $visitor) { Chris@0: $return = $visitor->leaveNode($subNode); Chris@4: Chris@0: if (null !== $return) { Chris@0: if ($return instanceof Node) { Chris@0: $this->ensureReplacementReasonable($subNode, $return); Chris@0: $subNode = $return; Chris@0: } elseif (self::STOP_TRAVERSAL === $return) { Chris@0: $this->stopTraversal = true; Chris@0: break 2; Chris@0: } elseif (\is_array($return)) { Chris@0: throw new \LogicException( Chris@0: 'leaveNode() may only return an array ' . Chris@0: 'if the parent structure is an array' Chris@0: ); Chris@0: } else { Chris@0: throw new \LogicException( Chris@0: 'leaveNode() returned invalid value of type ' . gettype($return) Chris@0: ); Chris@0: } Chris@0: } Chris@4: Chris@4: if ($breakVisitorIndex === $visitorIndex) { Chris@4: break; Chris@4: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return $node; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Recursively traverse array (usually of nodes). Chris@0: * Chris@0: * @param array $nodes Array to traverse Chris@0: * Chris@0: * @return array Result of traversal (may be original array or changed one) Chris@0: */ Chris@0: protected function traverseArray(array $nodes) : array { Chris@0: $doNodes = []; Chris@0: Chris@0: foreach ($nodes as $i => &$node) { Chris@0: if ($node instanceof Node) { Chris@0: $traverseChildren = true; Chris@4: $breakVisitorIndex = null; Chris@4: Chris@4: foreach ($this->visitors as $visitorIndex => $visitor) { Chris@0: $return = $visitor->enterNode($node); Chris@0: if (null !== $return) { Chris@0: if ($return instanceof Node) { Chris@0: $this->ensureReplacementReasonable($node, $return); Chris@0: $node = $return; Chris@0: } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { Chris@0: $traverseChildren = false; Chris@4: } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { Chris@4: $traverseChildren = false; Chris@4: $breakVisitorIndex = $visitorIndex; Chris@4: break; Chris@0: } elseif (self::STOP_TRAVERSAL === $return) { Chris@0: $this->stopTraversal = true; Chris@0: break 2; Chris@0: } else { Chris@0: throw new \LogicException( Chris@0: 'enterNode() returned invalid value of type ' . gettype($return) Chris@0: ); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if ($traverseChildren) { Chris@0: $node = $this->traverseNode($node); Chris@0: if ($this->stopTraversal) { Chris@0: break; Chris@0: } Chris@0: } Chris@0: Chris@4: foreach ($this->visitors as $visitorIndex => $visitor) { Chris@0: $return = $visitor->leaveNode($node); Chris@4: Chris@0: if (null !== $return) { Chris@0: if ($return instanceof Node) { Chris@0: $this->ensureReplacementReasonable($node, $return); Chris@0: $node = $return; Chris@0: } elseif (\is_array($return)) { Chris@0: $doNodes[] = [$i, $return]; Chris@0: break; Chris@0: } elseif (self::REMOVE_NODE === $return) { Chris@0: $doNodes[] = [$i, []]; Chris@0: break; Chris@0: } elseif (self::STOP_TRAVERSAL === $return) { Chris@0: $this->stopTraversal = true; Chris@0: break 2; Chris@0: } elseif (false === $return) { Chris@0: throw new \LogicException( Chris@0: 'bool(false) return from leaveNode() no longer supported. ' . Chris@0: 'Return NodeTraverser::REMOVE_NODE instead' Chris@0: ); Chris@0: } else { Chris@0: throw new \LogicException( Chris@0: 'leaveNode() returned invalid value of type ' . gettype($return) Chris@0: ); Chris@0: } Chris@0: } Chris@4: Chris@4: if ($breakVisitorIndex === $visitorIndex) { Chris@4: break; Chris@4: } Chris@0: } Chris@0: } elseif (\is_array($node)) { Chris@0: throw new \LogicException('Invalid node structure: Contains nested arrays'); Chris@0: } Chris@0: } Chris@0: Chris@0: if (!empty($doNodes)) { Chris@0: while (list($i, $replace) = array_pop($doNodes)) { Chris@0: array_splice($nodes, $i, 1, $replace); Chris@0: } Chris@0: } Chris@0: Chris@0: return $nodes; Chris@0: } Chris@0: Chris@0: private function ensureReplacementReasonable($old, $new) { Chris@0: if ($old instanceof Node\Stmt && $new instanceof Node\Expr) { Chris@0: throw new \LogicException( Chris@0: "Trying to replace statement ({$old->getType()}) " . Chris@0: "with expression ({$new->getType()}). Are you missing a " . Chris@0: "Stmt_Expression wrapper?" Chris@0: ); Chris@0: } Chris@0: Chris@0: if ($old instanceof Node\Expr && $new instanceof Node\Stmt) { Chris@0: throw new \LogicException( Chris@0: "Trying to replace expression ({$old->getType()}) " . Chris@0: "with statement ({$new->getType()})" Chris@0: ); Chris@0: } Chris@0: } Chris@0: }