diff --git a/src/parser/aast/api/AASTNode.php b/src/parser/aast/api/AASTNode.php index 9cf1408..7411de2 100644 --- a/src/parser/aast/api/AASTNode.php +++ b/src/parser/aast/api/AASTNode.php @@ -1,339 +1,349 @@ id = $id; $this->typeID = $data[0]; if (isset($data[1])) { $this->l = $data[1]; } else { $this->l = -1; } if (isset($data[2])) { $this->r = $data[2]; } else { $this->r = -1; } $this->tree = $tree; } final public function getParentNode() { return $this->parentNode; } + final public function getPreviousSibling() { + return $this->previousSibling; + } + + final public function getNextSibling() { + return $this->nextSibling; + } + final public function getID() { return $this->id; } final public function getTypeID() { return $this->typeID; } final public function getTree() { return $this->tree; } final public function getTypeName() { if (empty($this->typeName)) { $this->typeName = $this->tree->getNodeTypeNameFromTypeID($this->getTypeID()); } return $this->typeName; } final public function getChildren() { return $this->children; } public function getChildrenOfType($type) { $nodes = array(); foreach ($this->children as $child) { if ($child->getTypeName() == $type) { $nodes[] = $child; } } return $nodes; } public function getChildOfType($index, $type) { $child = $this->getChildByIndex($index); if ($child->getTypeName() != $type) { throw new Exception( pht( "Child in position '%d' is not of type '%s': %s", $index, $type, $this->getDescription())); } return $child; } public function getChildByIndex($index) { // NOTE: Microoptimization to avoid calls like array_values() or idx(). $idx = 0; foreach ($this->children as $child) { if ($idx == $index) { return $child; } ++$idx; } throw new Exception(pht("No child with index '%d'.", $index)); } /** * Build a cache to improve the performance of * @{method:selectDescendantsOfType}. This cache makes a time/memory tradeoff * by aggressively caching node descendants. It may improve the tree's query * performance substantially if you make a large number of queries, but also * requires a significant amount of memory. * * This builds a cache for the entire tree and improves performance of all * @{method:selectDescendantsOfType} calls. */ public function buildSelectCache() { $cache = array(); foreach ($this->getChildren() as $id => $child) { $type_id = $child->getTypeID(); if (empty($cache[$type_id])) { $cache[$type_id] = array(); } $cache[$type_id][$id] = $child; foreach ($child->buildSelectCache() as $type_id => $nodes) { if (empty($cache[$type_id])) { $cache[$type_id] = array(); } $cache[$type_id] += $nodes; } } $this->selectCache = $cache; return $this->selectCache; } /** * Build a cache to improve the performance of @{method:selectTokensOfType}. * This cache makes a time/memory tradeoff by aggressively caching token * types. It may improve the tree's query performance substantially if you * make a large number of queries, but also requires a significant amount of * memory. * * This builds a cache for this node only. */ public function buildTokenCache() { $cache = array(); foreach ($this->getTokens() as $id => $token) { $cache[$token->getTypeName()][$id] = $token; } $this->tokenCache = $cache; return $this->tokenCache; } public function selectTokensOfType($type_name) { return $this->selectTokensOfTypes(array($type_name)); } /** * Select all tokens of any given types. */ public function selectTokensOfTypes(array $type_names) { $tokens = array(); foreach ($type_names as $type_name) { if (isset($this->tokenCache)) { $cached_tokens = idx($this->tokenCache, $type_name, array()); foreach ($cached_tokens as $id => $cached_token) { $tokens[$id] = $cached_token; } } else { foreach ($this->getTokens() as $id => $token) { if ($token->getTypeName() == $type_name) { $tokens[$id] = $token; } } } } return $tokens; } final public function isDescendantOf(AASTNode $node) { for ($it = $this; $it !== null; $it = $it->getParentNode()) { if ($it === $node) { return true; } } return false; } public function selectDescendantsOfType($type_name) { return $this->selectDescendantsOfTypes(array($type_name)); } public function selectDescendantsOfTypes(array $type_names) { $nodes = array(); foreach ($type_names as $type_name) { $type = $this->getTypeIDFromTypeName($type_name); if (isset($this->selectCache)) { if (isset($this->selectCache[$type])) { $nodes = $nodes + $this->selectCache[$type]; } } else { $nodes = $nodes + $this->executeSelectDescendantsOfType($this, $type); } } return AASTNodeList::newFromTreeAndNodes($this->tree, $nodes); } protected function executeSelectDescendantsOfType($node, $type) { $results = array(); foreach ($node->getChildren() as $id => $child) { if ($child->getTypeID() == $type) { $results[$id] = $child; } $results += $this->executeSelectDescendantsOfType($child, $type); } return $results; } public function getTokens() { if ($this->l == -1 || $this->r == -1) { return array(); } $tokens = $this->tree->getRawTokenStream(); $result = array(); foreach (range($this->l, $this->r) as $token_id) { $result[$token_id] = $tokens[$token_id]; } return $result; } public function getConcreteString() { $values = array(); foreach ($this->getTokens() as $token) { $values[] = $token->getValue(); } return implode('', $values); } public function getSemanticString() { $tokens = $this->getTokens(); foreach ($tokens as $id => $token) { if ($token->isComment()) { unset($tokens[$id]); } } return implode('', mpull($tokens, 'getValue')); } public function getIndentation() { $tokens = $this->getTokens(); $left = head($tokens); while ($left && (!$left->isAnyWhitespace() || strpos($left->getValue(), "\n") === false)) { $left = $left->getPrevToken(); } if (!$left) { return null; } return preg_replace("/^.*\n/s", '', $left->getValue()); } public function getDescription() { $concrete = $this->getConcreteString(); if (strlen($concrete) > 75) { $concrete = substr($concrete, 0, 36).'...'.substr($concrete, -36); } $concrete = addcslashes($concrete, "\\\n\""); return pht('a node of type %s: "%s"', $this->getTypeName(), $concrete); } final protected function getTypeIDFromTypeName($type_name) { return $this->tree->getNodeTypeIDFromTypeName($type_name); } final public function getOffset() { $stream = $this->tree->getRawTokenStream(); if (empty($stream[$this->l])) { return null; } return $stream[$this->l]->getOffset(); } final public function getLength() { $stream = $this->tree->getRawTokenStream(); if (empty($stream[$this->r])) { return null; } return $stream[$this->r]->getOffset() - $this->getOffset(); } public function getSurroundingNonsemanticTokens() { $before = array(); $after = array(); $tokens = $this->tree->getRawTokenStream(); if ($this->l != -1) { $before = $tokens[$this->l]->getNonsemanticTokensBefore(); } if ($this->r != -1) { $after = $tokens[$this->r]->getNonsemanticTokensAfter(); } return array($before, $after); } final public function getLineNumber() { return idx($this->tree->getOffsetToLineNumberMap(), $this->getOffset()); } final public function getEndLineNumber() { return idx( $this->tree->getOffsetToLineNumberMap(), $this->getOffset() + $this->getLength()); } public function dispose() { foreach ($this->getChildren() as $child) { $child->dispose(); } unset($this->selectCache); } } diff --git a/src/parser/aast/api/AASTTree.php b/src/parser/aast/api/AASTTree.php index 6720e74..3c46e2f 100644 --- a/src/parser/aast/api/AASTTree.php +++ b/src/parser/aast/api/AASTTree.php @@ -1,180 +1,194 @@ stream[$ii] = $this->newToken( $ii, $token[0], substr($source, $offset, $token[1]), $offset, $this); $offset += $token[1]; ++$ii; } $this->rawSource = $source; $this->buildTree(array($tree)); } final public function setTreeType($description) { $this->treeType = $description; return $this; } final public function getTreeType() { return $this->treeType; } final public function setTokenConstants(array $token_map) { $this->tokenConstants = $token_map; $this->tokenReverseMap = array_flip($token_map); return $this; } final public function setNodeConstants(array $node_map) { $this->nodeConstants = $node_map; $this->nodeReverseMap = array_flip($node_map); return $this; } final public function getNodeTypeNameFromTypeID($type_id) { if (empty($this->nodeConstants[$type_id])) { $tree_type = $this->getTreeType(); throw new Exception( pht( "No type name for node type ID '%s' in '%s' AAST.", $type_id, $tree_type)); } return $this->nodeConstants[$type_id]; } final public function getNodeTypeIDFromTypeName($type_name) { if (empty($this->nodeReverseMap[$type_name])) { $tree_type = $this->getTreeType(); throw new Exception( pht( "No type ID for node type name '%s' in '%s' AAST.", $type_name, $tree_type)); } return $this->nodeReverseMap[$type_name]; } final public function getTokenTypeNameFromTypeID($type_id) { if (empty($this->tokenConstants[$type_id])) { $tree_type = $this->getTreeType(); throw new Exception( pht( "No type name for token type ID '%s' in '%s' AAST.", $type_id, $tree_type)); } return $this->tokenConstants[$type_id]; } final public function getTokenTypeIDFromTypeName($type_name) { if (empty($this->tokenReverseMap[$type_name])) { $tree_type = $this->getTreeType(); throw new Exception( pht( "No type ID for token type name '%s' in '%s' AAST.", $type_name, $tree_type)); } return $this->tokenReverseMap[$type_name]; } /** * Unlink internal datastructures so that PHP will garbage collect the tree. * * This renders the object useless. * * @return void */ public function dispose() { $this->getRootNode()->dispose(); unset($this->tree); unset($this->stream); } final public function getRootNode() { return $this->tree[0]; } protected function buildTree(array $tree) { $ii = count($this->tree); $nodes = array(); foreach ($tree as $node) { $this->tree[$ii] = $this->newNode($ii, $node, $this); $nodes[$ii] = $node; ++$ii; } foreach ($nodes as $node_id => $node) { if (isset($node[3])) { $children = $this->buildTree($node[3]); - foreach ($children as $child) { + $previous_child = null; + + foreach ($children as $ii => $child) { $child->parentNode = $this->tree[$node_id]; + $child->previousSibling = $previous_child; + + if ($previous_child) { + $previous_child->nextSibling = $child; + } + + $previous_child = $child; } + + if ($previous_child) { + $previous_child->nextSibling = $child; + } + $this->tree[$node_id]->children = $children; } } $result = array(); foreach ($nodes as $key => $node) { $result[$key] = $this->tree[$key]; } return $result; } final public function getRawTokenStream() { return $this->stream; } public function getOffsetToLineNumberMap() { if ($this->lineMap === null) { $src = $this->rawSource; $len = strlen($src); $lno = 1; $map = array(); for ($ii = 0; $ii < $len; ++$ii) { $map[$ii] = $lno; if ($src[$ii] == "\n") { ++$lno; } } $this->lineMap = $map; } return $this->lineMap; } }