215 lines
6.7 KiB
PHP
215 lines
6.7 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* This file is part of the Peast package
|
||
|
*
|
||
|
* (c) Marco Marchiò <marco.mm89@gmail.com>
|
||
|
*
|
||
|
* For the full copyright and license information refer to the LICENSE file
|
||
|
* distributed with this source code
|
||
|
*/
|
||
|
namespace Peast;
|
||
|
|
||
|
/**
|
||
|
* Nodes traverser class
|
||
|
*
|
||
|
* @author Marco Marchiò <marco.mm89@gmail.com>
|
||
|
*/
|
||
|
class Traverser
|
||
|
{
|
||
|
/**
|
||
|
* If a function return this value, the current node will be removed
|
||
|
*/
|
||
|
const REMOVE_NODE = 1;
|
||
|
|
||
|
/**
|
||
|
* If a function return this value, the current node's children won't be
|
||
|
* traversed
|
||
|
*/
|
||
|
const DONT_TRAVERSE_CHILD_NODES = 2;
|
||
|
|
||
|
/**
|
||
|
* If a function return this value, the traverser will stop running
|
||
|
*/
|
||
|
const STOP_TRAVERSING = 4;
|
||
|
|
||
|
/**
|
||
|
* Array of functions to call on each node
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $functions = array();
|
||
|
|
||
|
/**
|
||
|
* Pass parent node flag
|
||
|
*
|
||
|
* @var bool
|
||
|
*/
|
||
|
protected $passParentNode = false;
|
||
|
|
||
|
/**
|
||
|
* Skip starting node flag
|
||
|
*
|
||
|
* @var bool
|
||
|
*/
|
||
|
protected $skipStartingNode = false;
|
||
|
|
||
|
/**
|
||
|
* Class constructor. Available options are:
|
||
|
* - skipStartingNode: if true the starting node will be skipped
|
||
|
* - passParentNode: if true the parent node of each node will be
|
||
|
* passed as second argument when the functions are called. Note
|
||
|
* that the parent node is calculated during traversing, so for
|
||
|
* the starting node it will always be null.
|
||
|
*
|
||
|
* @param array $options Options array
|
||
|
*/
|
||
|
public function __construct($options = array())
|
||
|
{
|
||
|
if (isset($options["passParentNode"])) {
|
||
|
$this->passParentNode = (bool) $options["passParentNode"];
|
||
|
}
|
||
|
if (isset($options["skipStartingNode"])) {
|
||
|
$this->skipStartingNode = (bool) $options["skipStartingNode"];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adds a function that will be called for each node in the tree. The
|
||
|
* function will receive the current node as argument. The action that will
|
||
|
* be executed on the node by the traverser depends on the returned value
|
||
|
* of the function:
|
||
|
* - a node: it will replace the node with the returned one
|
||
|
* - a numeric value that is a combination of the constants defined in this
|
||
|
* class: it will execute the function related to each constant
|
||
|
* - an array where the first element is a node and the second element is a
|
||
|
* numeric value that is a combination of the constants defined in this
|
||
|
* class: it will replace the node with the returned one and it will
|
||
|
* execute the function related to each constant (REMOVE_NODE will be
|
||
|
* ignored since it does not make any sense in this case)
|
||
|
* - other: nothing
|
||
|
*
|
||
|
* @param callable $fn Function to add
|
||
|
*
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function addFunction(callable $fn)
|
||
|
{
|
||
|
$this->functions[] = $fn;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Starts the traversing
|
||
|
*
|
||
|
* @param Syntax\Node\Node $node Starting node
|
||
|
*
|
||
|
* @return Syntax\Node\Node
|
||
|
*/
|
||
|
public function traverse(Syntax\Node\Node $node)
|
||
|
{
|
||
|
if ($this->skipStartingNode) {
|
||
|
$this->traverseChildren($node);
|
||
|
} else {
|
||
|
$this->execFunctions($node);
|
||
|
}
|
||
|
return $node;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Executes all functions on the given node and, if required, starts
|
||
|
* traversing its children. The returned value is an array where the first
|
||
|
* value is the node or null if it has been removed and the second value is
|
||
|
* a boolean indicating if the traverser must continue the traversing or not
|
||
|
*
|
||
|
* @param Syntax\Node\Node $node Node
|
||
|
* @param Syntax\Node\Node|null $parent Parent node
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
protected function execFunctions($node, $parent = null)
|
||
|
{
|
||
|
$traverseChildren = true;
|
||
|
$continueTraversing = true;
|
||
|
|
||
|
foreach ($this->functions as $fn) {
|
||
|
$ret = $this->passParentNode ? $fn($node, $parent) : $fn($node);
|
||
|
if ($ret) {
|
||
|
if (is_array($ret) && $ret[0] instanceof Syntax\Node\Node) {
|
||
|
$node = $ret[0];
|
||
|
if (isset($ret[1]) && is_numeric($ret[1])) {
|
||
|
if ($ret[1] & self::DONT_TRAVERSE_CHILD_NODES) {
|
||
|
$traverseChildren = false;
|
||
|
}
|
||
|
if ($ret[1] & self::STOP_TRAVERSING) {
|
||
|
$continueTraversing = false;
|
||
|
}
|
||
|
}
|
||
|
} elseif ($ret instanceof Syntax\Node\Node) {
|
||
|
$node = $ret;
|
||
|
} elseif (is_numeric($ret)) {
|
||
|
if ($ret & self::DONT_TRAVERSE_CHILD_NODES) {
|
||
|
$traverseChildren = false;
|
||
|
}
|
||
|
if ($ret & self::STOP_TRAVERSING) {
|
||
|
$continueTraversing = false;
|
||
|
}
|
||
|
if ($ret & self::REMOVE_NODE) {
|
||
|
$node = null;
|
||
|
$traverseChildren = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($traverseChildren && $continueTraversing) {
|
||
|
$continueTraversing = $this->traverseChildren($node);
|
||
|
}
|
||
|
|
||
|
return array($node, $continueTraversing);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Traverses node children. It returns a boolean indicating if the
|
||
|
* traversing must continue or not
|
||
|
*
|
||
|
* @param Syntax\Node\Node $node Node
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
protected function traverseChildren(Syntax\Node\Node $node)
|
||
|
{
|
||
|
$continue = true;
|
||
|
|
||
|
foreach (Syntax\Utils::getNodeProperties($node, true) as $prop) {
|
||
|
$getter = $prop["getter"];
|
||
|
$setter = $prop["setter"];
|
||
|
$child = $node->$getter();
|
||
|
if (!$child) {
|
||
|
continue;
|
||
|
} elseif (is_array($child)) {
|
||
|
$newChildren = array();
|
||
|
foreach ($child as $c) {
|
||
|
if (!$c || !$continue) {
|
||
|
$newChildren[] = $c;
|
||
|
} else {
|
||
|
list($c, $continue) = $this->execFunctions($c, $node);
|
||
|
if ($c) {
|
||
|
$newChildren[] = $c;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$node->$setter($newChildren);
|
||
|
} else {
|
||
|
list($child, $continue) = $this->execFunctions($child, $node);
|
||
|
$node->$setter($child);
|
||
|
}
|
||
|
|
||
|
if (!$continue) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $continue;
|
||
|
}
|
||
|
}
|