1<?php 2 3abstract class AASTNode extends Phobject { 4 5 private $id; 6 protected $l; 7 protected $r; 8 private $typeID; 9 private $typeName; 10 protected $tree; 11 12 private $children = array(); 13 private $parentNode = null; 14 private $previousSibling = null; 15 private $nextSibling = null; 16 17 private $selectCache; 18 private $tokenCache; 19 20 abstract public function isStaticScalar(); 21 abstract public function getDocblockToken(); 22 abstract public function evalStatic(); 23 abstract public function getStringLiteralValue(); 24 25 public function __construct($id, array $data, AASTTree $tree) { 26 $this->id = $id; 27 $this->typeID = $data[0]; 28 if (isset($data[1])) { 29 $this->l = $data[1]; 30 } else { 31 $this->l = -1; 32 } 33 if (isset($data[2])) { 34 $this->r = $data[2]; 35 } else { 36 $this->r = -1; 37 } 38 $this->tree = $tree; 39 } 40 41 final public function getParentNode() { 42 return $this->parentNode; 43 } 44 45 final public function setParentNode(AASTNode $node = null) { 46 $this->parentNode = $node; 47 return $this; 48 } 49 50 final public function getPreviousSibling() { 51 return $this->previousSibling; 52 } 53 54 final public function setPreviousSibling(AASTNode $node = null) { 55 $this->previousSibling = $node; 56 return $this; 57 } 58 59 final public function getNextSibling() { 60 return $this->nextSibling; 61 } 62 63 final public function setNextSibling(AASTNode $node = null) { 64 $this->nextSibling = $node; 65 return $this; 66 } 67 68 final public function getID() { 69 return $this->id; 70 } 71 72 final public function getTypeID() { 73 return $this->typeID; 74 } 75 76 final public function getTree() { 77 return $this->tree; 78 } 79 80 final public function getTypeName() { 81 if (empty($this->typeName)) { 82 $this->typeName = 83 $this->tree->getNodeTypeNameFromTypeID($this->getTypeID()); 84 } 85 return $this->typeName; 86 } 87 88 final public function getChildren() { 89 return $this->children; 90 } 91 92 final public function setChildren(array $children) { 93 // We don't call `assert_instances_of($children, 'AASTNode')` because doing 94 // so would incur a significant performance penalty. 95 $this->children = $children; 96 return $this; 97 } 98 99 public function getChildrenOfType($type) { 100 $nodes = array(); 101 102 foreach ($this->children as $child) { 103 if ($child->getTypeName() == $type) { 104 $nodes[] = $child; 105 } 106 } 107 108 return $nodes; 109 } 110 111 public function getChildOfType($index, $type) { 112 $child = $this->getChildByIndex($index); 113 if ($child->getTypeName() != $type) { 114 throw new Exception( 115 pht( 116 "Child in position '%d' is not of type '%s': %s", 117 $index, 118 $type, 119 $this->getDescription())); 120 } 121 122 return $child; 123 } 124 125 public function getChildByIndex($index) { 126 // NOTE: Microoptimization to avoid calls like array_values() or idx(). 127 128 $idx = 0; 129 foreach ($this->children as $child) { 130 if ($idx == $index) { 131 return $child; 132 } 133 ++$idx; 134 } 135 136 throw new Exception(pht("No child with index '%d'.", $index)); 137 } 138 139 /** 140 * Build a cache to improve the performance of 141 * @{method:selectDescendantsOfType}. This cache makes a time/memory tradeoff 142 * by aggressively caching node descendants. It may improve the tree's query 143 * performance substantially if you make a large number of queries, but also 144 * requires a significant amount of memory. 145 * 146 * This builds a cache for the entire tree and improves performance of all 147 * @{method:selectDescendantsOfType} calls. 148 */ 149 public function buildSelectCache() { 150 $cache = array(); 151 foreach ($this->getChildren() as $id => $child) { 152 $type_id = $child->getTypeID(); 153 if (empty($cache[$type_id])) { 154 $cache[$type_id] = array(); 155 } 156 $cache[$type_id][$id] = $child; 157 foreach ($child->buildSelectCache() as $type_id => $nodes) { 158 if (empty($cache[$type_id])) { 159 $cache[$type_id] = array(); 160 } 161 $cache[$type_id] += $nodes; 162 } 163 } 164 $this->selectCache = $cache; 165 return $this->selectCache; 166 } 167 168 /** 169 * Build a cache to improve the performance of @{method:selectTokensOfType}. 170 * This cache makes a time/memory tradeoff by aggressively caching token 171 * types. It may improve the tree's query performance substantially if you 172 * make a large number of queries, but also requires a significant amount of 173 * memory. 174 * 175 * This builds a cache for this node only. 176 */ 177 public function buildTokenCache() { 178 $cache = array(); 179 foreach ($this->getTokens() as $id => $token) { 180 $cache[$token->getTypeName()][$id] = $token; 181 } 182 $this->tokenCache = $cache; 183 return $this->tokenCache; 184 } 185 186 public function selectTokensOfType($type_name) { 187 return $this->selectTokensOfTypes(array($type_name)); 188 } 189 190 /** 191 * Select all tokens of any given types. 192 */ 193 public function selectTokensOfTypes(array $type_names) { 194 $tokens = array(); 195 196 foreach ($type_names as $type_name) { 197 if (isset($this->tokenCache)) { 198 $cached_tokens = idx($this->tokenCache, $type_name, array()); 199 foreach ($cached_tokens as $id => $cached_token) { 200 $tokens[$id] = $cached_token; 201 } 202 } else { 203 foreach ($this->getTokens() as $id => $token) { 204 if ($token->getTypeName() == $type_name) { 205 $tokens[$id] = $token; 206 } 207 } 208 } 209 } 210 211 return $tokens; 212 } 213 214 final public function isDescendantOf(AASTNode $node) { 215 for ($it = $this; $it !== null; $it = $it->getParentNode()) { 216 if ($it === $node) { 217 return true; 218 } 219 } 220 221 return false; 222 } 223 224 public function selectDescendantsOfType($type_name) { 225 return $this->selectDescendantsOfTypes(array($type_name)); 226 } 227 228 public function selectDescendantsOfTypes(array $type_names) { 229 $nodes = array(); 230 foreach ($type_names as $type_name) { 231 $type = $this->getTypeIDFromTypeName($type_name); 232 233 if (isset($this->selectCache)) { 234 if (isset($this->selectCache[$type])) { 235 $nodes = $nodes + $this->selectCache[$type]; 236 } 237 } else { 238 $nodes = $nodes + $this->executeSelectDescendantsOfType($this, $type); 239 } 240 } 241 242 return AASTNodeList::newFromTreeAndNodes($this->tree, $nodes); 243 } 244 245 protected function executeSelectDescendantsOfType($node, $type) { 246 $results = array(); 247 foreach ($node->getChildren() as $id => $child) { 248 if ($child->getTypeID() == $type) { 249 $results[$id] = $child; 250 } 251 $results += $this->executeSelectDescendantsOfType($child, $type); 252 } 253 return $results; 254 } 255 256 public function getTokens() { 257 if ($this->l == -1 || $this->r == -1) { 258 return array(); 259 } 260 $tokens = $this->tree->getRawTokenStream(); 261 $result = array(); 262 foreach (range($this->l, $this->r) as $token_id) { 263 $result[$token_id] = $tokens[$token_id]; 264 } 265 return $result; 266 } 267 268 public function getConcreteString() { 269 $values = array(); 270 foreach ($this->getTokens() as $token) { 271 $values[] = $token->getValue(); 272 } 273 return implode('', $values); 274 } 275 276 public function getSemanticString() { 277 $tokens = $this->getTokens(); 278 foreach ($tokens as $id => $token) { 279 if ($token->isComment()) { 280 unset($tokens[$id]); 281 } 282 } 283 return implode('', mpull($tokens, 'getValue')); 284 } 285 286 public function getIndentation() { 287 $tokens = $this->getTokens(); 288 $left = head($tokens); 289 290 while ($left && 291 (!$left->isAnyWhitespace() || 292 strpos($left->getValue(), "\n") === false)) { 293 $left = $left->getPrevToken(); 294 } 295 296 if (!$left) { 297 return null; 298 } 299 300 return preg_replace("/^.*\n/s", '', $left->getValue()); 301 } 302 303 public function getDescription() { 304 $concrete = $this->getConcreteString(); 305 if (strlen($concrete) > 75) { 306 $concrete = substr($concrete, 0, 36).'...'.substr($concrete, -36); 307 } 308 309 $concrete = addcslashes($concrete, "\\\n\""); 310 311 return pht('a node of type %s: "%s"', $this->getTypeName(), $concrete); 312 } 313 314 final protected function getTypeIDFromTypeName($type_name) { 315 return $this->tree->getNodeTypeIDFromTypeName($type_name); 316 } 317 318 final public function getOffset() { 319 $stream = $this->tree->getRawTokenStream(); 320 if (empty($stream[$this->l])) { 321 return null; 322 } 323 return $stream[$this->l]->getOffset(); 324 } 325 326 final public function getLength() { 327 $stream = $this->tree->getRawTokenStream(); 328 if (empty($stream[$this->r])) { 329 return null; 330 } 331 return $stream[$this->r]->getOffset() - $this->getOffset(); 332 } 333 334 335 public function getSurroundingNonsemanticTokens() { 336 $before = array(); 337 $after = array(); 338 339 $tokens = $this->tree->getRawTokenStream(); 340 341 if ($this->l != -1) { 342 $before = $tokens[$this->l]->getNonsemanticTokensBefore(); 343 } 344 345 if ($this->r != -1) { 346 $after = $tokens[$this->r]->getNonsemanticTokensAfter(); 347 } 348 349 return array($before, $after); 350 } 351 352 final public function getLineNumber() { 353 return idx($this->tree->getOffsetToLineNumberMap(), $this->getOffset()); 354 } 355 356 final public function getEndLineNumber() { 357 return idx( 358 $this->tree->getOffsetToLineNumberMap(), 359 $this->getOffset() + $this->getLength()); 360 } 361 362 /** 363 * Determines whether the current node appears //after// a specified node in 364 * the tree. 365 * 366 * @param AASTNode 367 * @return bool 368 */ 369 final public function isAfter(AASTNode $node) { 370 return head($this->getTokens())->getOffset() > 371 last($node->getTokens())->getOffset(); 372 } 373 374 /** 375 * Determines whether the current node appears //before// a specified node in 376 * the tree. 377 * 378 * @param AASTNode 379 * @return bool 380 */ 381 final public function isBefore(AASTNode $node) { 382 return last($this->getTokens())->getOffset() < 383 head($node->getTokens())->getOffset(); 384 } 385 386 /** 387 * Determines whether a specified node is a descendant of the current node. 388 * 389 * @param AASTNode 390 * @return bool 391 */ 392 final public function containsDescendant(AASTNode $node) { 393 return !$this->isAfter($node) && !$this->isBefore($node); 394 } 395 396 public function dispose() { 397 foreach ($this->getChildren() as $child) { 398 $child->dispose(); 399 } 400 401 unset($this->selectCache); 402 } 403 404} 405