From version 1.1
To traverse an AST generated by Peast you can use the Traverser class:
//Generate the AST
$ast = Peast\Peast::latest($source, $options)->parse();
//Set up the Traverser
$traverser = new Peast\Traverser;
$traverser->addFunction(function ($node) {
//Do something with the current node
});
//Start traversing
$traverser->traverse($ast);
You can add one or more functions to the Traverser using the addFunction
method. These functions receive the current traversed node while the Traverser runs on the tree.
The traverse
method runs the traversing starting from the given node.
From version 1.12 The Traverser class constructor takes an optional associative array of options. Available options are:
- "skipStartingNode": by default the traversing begins with the starting node passed to the
traverse
method. If this option is set to true the starting node will be ignored. - "passParentNode": by default the functions added to the Traverser receive the traversed node as the only argument. if this option is set to true the node's parent node will be passed as second argument to all the functions. Note that the parent node is calculated during traversing, so for the starting node it will always be null.
Functions added to the Traverser instance can alter the tree during the traversing by modifying, replacing or removing the node they receive and they can also control the behaviour of the Traverser to make it stop or skip the node's children.
Traverser class provides some constants to perform these operations:
Traverser::REMOVE_NODE
: removes the nodeTraverser::DONT_TRAVERSE_CHILD_NODES
: skips the node's childrenTraverser::STOP_TRAVERSING
: stops the traversing
The action that will be executed depends on the value returned by the functions.
If you want to remove a node the function must return the Traverser::REMOVE_NODE
constant:
$traverser->addFunction(function ($node) {
//Remove all literal nodes
if ($node->getType() === "Literal") {
return Peast\Traverser::REMOVE_NODE;
}
});
If you want to control the traversing you can return Traverser::DONT_TRAVERSE_CHILD_NODES
or Traverser::STOP_TRAVERSING
constants:
$traverser->addFunction(function ($node) {
//Skip nodes inside array expressions
if ($node->getType() === "ArrayExpression") {
return Peast\Traverser::DONT_TRAVERSE_CHILD_NODES;
}
//Stop the traversing when an identifier named "stop" is found
elseif ($node->getType() === "Identifier" && $node->getName() === "stop") {
return Peast\Traverser::STOP_TRAVERSING;
}
});
You can also return a combination of these constants:
$traverser->addFunction(function ($node) {
//Remove the string "test" and then stop the traversing
if ($node->getType() === "Literal" && $node->getValue() === "test") {
return Peast\Traverser::REMOVE_NODE | Peast\Traverser::STOP_TRAVERSING;
}
});
To replace a node you must return the replacement node:
$traverser->addFunction(function ($node) {
//Replace the number 2 with 1
if ($node->getType() === "Literal" && $node->getValue() === 2) {
$literal = new \Peast\Syntax\Node\NumericLiteral();
return $literal->setValue(1);
}
});
You can also control the traversing and replace a node in the same function:
$traverser->addFunction(function ($node) {
//Replace an array expression with an object expression without traversing its children
if ($node->getType() === "ArrayExpression") {
$obj = new \Peast\Syntax\Node\ObjectExpression();
return array($obj, Peast\Traverser::DONT_TRAVERSE_CHILD_NODES);
}
});
If the function returns any other value or nothing, no action will be executed and you can modify nodes without altering the tree structure:
use Peast\Syntax\Node\StringLiteral;
$traverser->addFunction(function ($node) {
//Make all the strings uppercase
if ($node->getType() === "Literal" && $node instanceof StringLiteral) {
$node->setValue(strtoupper($node->getValue()));
}
});
Every node implements a traverse
method as a shortcut to initialize a traverser on itself:
$traversingFn = function ($node) { /* ... */ };
//Traverse $ast node
$ast->traverse($traversingFn, $options);
//Equivalent to
$traverser = new Peast\Traverser;
$traverser->addFunction($traversingFn)->traverse($ast);