基础代码
This commit is contained in:
+215
@@ -0,0 +1,215 @@
|
||||
<?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 boolena 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user