Chris@13: 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@13: 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@13: /** Chris@13: * Recursively traverse a node. Chris@13: * Chris@13: * @param Node $node Node to traverse. Chris@13: * Chris@13: * @return Node Result of traversal (may be original node or new one) Chris@13: */ Chris@13: protected function traverseNode(Node $node) : Node { Chris@0: foreach ($node->getSubNodeNames() as $name) { Chris@0: $subNode =& $node->$name; Chris@0: Chris@13: 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@17: $breakVisitorIndex = null; Chris@17: Chris@17: foreach ($this->visitors as $visitorIndex => $visitor) { Chris@0: $return = $visitor->enterNode($subNode); Chris@13: if (null !== $return) { Chris@13: if ($return instanceof Node) { Chris@13: $this->ensureReplacementReasonable($subNode, $return); Chris@13: $subNode = $return; Chris@13: } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { Chris@13: $traverseChildren = false; Chris@17: } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { Chris@17: $traverseChildren = false; Chris@17: $breakVisitorIndex = $visitorIndex; Chris@17: break; Chris@13: } elseif (self::STOP_TRAVERSAL === $return) { Chris@13: $this->stopTraversal = true; Chris@13: break 2; Chris@13: } else { Chris@13: throw new \LogicException( Chris@13: 'enterNode() returned invalid value of type ' . gettype($return) Chris@13: ); Chris@13: } 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@17: foreach ($this->visitors as $visitorIndex => $visitor) { Chris@0: $return = $visitor->leaveNode($subNode); Chris@17: Chris@13: if (null !== $return) { Chris@13: if ($return instanceof Node) { Chris@13: $this->ensureReplacementReasonable($subNode, $return); Chris@13: $subNode = $return; Chris@13: } elseif (self::STOP_TRAVERSAL === $return) { Chris@13: $this->stopTraversal = true; Chris@13: break 2; Chris@13: } 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@13: } else { Chris@13: throw new \LogicException( Chris@13: 'leaveNode() returned invalid value of type ' . gettype($return) Chris@13: ); Chris@0: } Chris@0: } Chris@17: Chris@17: if ($breakVisitorIndex === $visitorIndex) { Chris@17: break; Chris@17: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return $node; Chris@0: } Chris@0: Chris@13: /** Chris@13: * Recursively traverse array (usually of nodes). Chris@13: * Chris@13: * @param array $nodes Array to traverse Chris@13: * Chris@13: * @return array Result of traversal (may be original array or changed one) Chris@13: */ Chris@13: protected function traverseArray(array $nodes) : array { Chris@13: $doNodes = []; Chris@0: Chris@0: foreach ($nodes as $i => &$node) { Chris@13: if ($node instanceof Node) { Chris@0: $traverseChildren = true; Chris@17: $breakVisitorIndex = null; Chris@17: Chris@17: foreach ($this->visitors as $visitorIndex => $visitor) { Chris@0: $return = $visitor->enterNode($node); Chris@13: if (null !== $return) { Chris@13: if ($return instanceof Node) { Chris@13: $this->ensureReplacementReasonable($node, $return); Chris@13: $node = $return; Chris@13: } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { Chris@13: $traverseChildren = false; Chris@17: } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { Chris@17: $traverseChildren = false; Chris@17: $breakVisitorIndex = $visitorIndex; Chris@17: break; Chris@13: } elseif (self::STOP_TRAVERSAL === $return) { Chris@13: $this->stopTraversal = true; Chris@13: break 2; Chris@13: } else { Chris@13: throw new \LogicException( Chris@13: 'enterNode() returned invalid value of type ' . gettype($return) Chris@13: ); Chris@13: } 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@17: foreach ($this->visitors as $visitorIndex => $visitor) { Chris@0: $return = $visitor->leaveNode($node); Chris@17: Chris@13: if (null !== $return) { Chris@13: if ($return instanceof Node) { Chris@13: $this->ensureReplacementReasonable($node, $return); Chris@13: $node = $return; Chris@13: } elseif (\is_array($return)) { Chris@13: $doNodes[] = [$i, $return]; Chris@13: break; Chris@13: } elseif (self::REMOVE_NODE === $return) { Chris@13: $doNodes[] = [$i, []]; Chris@13: break; Chris@13: } elseif (self::STOP_TRAVERSAL === $return) { Chris@13: $this->stopTraversal = true; Chris@13: break 2; Chris@13: } elseif (false === $return) { Chris@13: throw new \LogicException( Chris@13: 'bool(false) return from leaveNode() no longer supported. ' . Chris@13: 'Return NodeTraverser::REMOVE_NODE instead' Chris@13: ); Chris@13: } else { Chris@13: throw new \LogicException( Chris@13: 'leaveNode() returned invalid value of type ' . gettype($return) Chris@13: ); Chris@13: } Chris@0: } Chris@17: Chris@17: if ($breakVisitorIndex === $visitorIndex) { Chris@17: break; Chris@17: } Chris@0: } Chris@13: } elseif (\is_array($node)) { Chris@13: 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@13: Chris@13: private function ensureReplacementReasonable($old, $new) { Chris@13: if ($old instanceof Node\Stmt && $new instanceof Node\Expr) { Chris@13: throw new \LogicException( Chris@13: "Trying to replace statement ({$old->getType()}) " . Chris@13: "with expression ({$new->getType()}). Are you missing a " . Chris@13: "Stmt_Expression wrapper?" Chris@13: ); Chris@13: } Chris@13: Chris@13: if ($old instanceof Node\Expr && $new instanceof Node\Stmt) { Chris@13: throw new \LogicException( Chris@13: "Trying to replace expression ({$old->getType()}) " . Chris@13: "with statement ({$new->getType()})" Chris@13: ); Chris@13: } Chris@13: } Chris@0: }