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