Skip to content

Latest commit

 

History

History
120 lines (104 loc) · 4.46 KB

tree-traversing.md

File metadata and controls

120 lines (104 loc) · 4.46 KB

Tree Traversing

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.

Options

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.

Tree manipulation

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 node
  • Traverser::DONT_TRAVERSE_CHILD_NODES: skips the node's children
  • Traverser::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()));
    }
});

Shortcut method

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);