Overview

Namespaces

  • Pharborist
    • Constants
    • ControlStructures
    • Exceptions
    • Functions
    • Generators
    • Namespaces
    • Objects
    • Operators
    • Types
    • Variables

Classes

  • Pharborist\Variables\CompoundVariableNode
  • Pharborist\Variables\GlobalStatementNode
  • Pharborist\Variables\ReferenceVariableNode
  • Pharborist\Variables\StaticVariableNode
  • Pharborist\Variables\StaticVariableStatementNode
  • Pharborist\Variables\VariableNode
  • Pharborist\Variables\VariableVariableNode

Interfaces

  • Pharborist\Variables\VariableExpressionNode
  • Overview
  • Namespace
  • Class
  1: <?php
  2: namespace Pharborist;
  3: 
  4: /**
  5:  * @mainpage
  6:  *
  7:  * @section Nodes
  8:  * A node is the basic building block of a PHP syntax tree. It represents a
  9:  * single cohesive piece of code. Nodes are made up one or more tokens, which
 10:  * are individual characters or strings which mean something to the PHP
 11:  * interpreter. Pharborist provides a node class for just about every kind of
 12:  * statement or expression you can write in PHP.
 13:  */
 14: use Pharborist\Types\ArrayNode;
 15: use Pharborist\Types\ArrayPairNode;
 16: use Pharborist\Types\BooleanNode;
 17: use Pharborist\Types\FloatNode;
 18: use Pharborist\Types\IntegerNode;
 19: use Pharborist\Types\NullNode;
 20: use Pharborist\Types\StringNode;
 21: 
 22: /**
 23:  * A node in the PHP syntax tree.
 24:  */
 25: abstract class Node implements NodeInterface {
 26:   /**
 27:    * @var ParentNode
 28:    */
 29:   protected $parent = NULL;
 30: 
 31:   /**
 32:    * @var Node
 33:    */
 34:   protected $previous = NULL;
 35: 
 36:   /**
 37:    * @var Node
 38:    */
 39:   protected $next = NULL;
 40: 
 41:   public function parent(callable $callback = NULL) {
 42:     if ($callback) {
 43:       return $callback($this->parent) ? $this->parent : NULL;
 44:     }
 45:     else {
 46:       return $this->parent;
 47:     }
 48:   }
 49: 
 50:   public function parents(callable $callback = NULL) {
 51:     $parents = [];
 52:     $parent = $this->parent;
 53:     while ($parent) {
 54:       if ($callback === NULL || $callback($parent)) {
 55:         $parents[] = $parent;
 56:       }
 57:       $parent = $parent->parent;
 58:     }
 59:     return new NodeCollection(array_reverse($parents), FALSE);
 60:   }
 61: 
 62:   public function parentsUntil(callable $callback, $inclusive = FALSE) {
 63:     $parents = [];
 64:     $parent = $this->parent;
 65:     while ($parent) {
 66:       if ($callback($parent)) {
 67:         if ($inclusive) {
 68:           $parents[] = $parent;
 69:         }
 70:         break;
 71:       }
 72:       $parents[] = $parent;
 73:       $parent = $parent->parent;
 74:     }
 75:     return new NodeCollection(array_reverse($parents), FALSE);
 76:   }
 77: 
 78:   public function closest(callable $callback) {
 79:     if ($callback($this)) {
 80:       return $this;
 81:     }
 82:     $parent = $this->parent;
 83:     while ($parent) {
 84:       if ($callback($parent)) {
 85:         return $parent;
 86:       }
 87:       $parent = $parent->parent;
 88:     }
 89:     return NULL;
 90:   }
 91: 
 92:   public function furthest(callable $callback) {
 93:     $match = NULL;
 94:     if ($callback($this)) {
 95:       $match = $this;
 96:     }
 97:     $parent = $this->parent;
 98:     while ($parent) {
 99:       if ($callback($parent)) {
100:         $match = $parent;
101:       }
102:       $parent = $parent->parent;
103:     }
104:     return $match;
105:   }
106: 
107:   public function index() {
108:     $index = 0;
109:     $child = $this->parent->head;
110:     while ($child) {
111:       if ($child === $this) {
112:         return $index;
113:       }
114:       $child = $child->next;
115:       $index++;
116:     }
117:     return -1;
118:   }
119: 
120:   public function previous(callable $callback = NULL) {
121:     if ($callback) {
122:       return $callback($this->previous) ? $this->previous : NULL;
123:     }
124:     else {
125:       return $this->previous;
126:     }
127:   }
128: 
129:   public function previousAll(callable $callback = NULL) {
130:     $matches = [];
131:     $previous = $this->previous;
132:     while ($previous) {
133:       if ($callback === NULL || $callback($previous)) {
134:         $matches[] = $previous;
135:       }
136:       $previous = $previous->previous;
137:     }
138:     return new NodeCollection(array_reverse($matches), FALSE);
139:   }
140: 
141:   public function previousUntil(callable $callback, $inclusive = FALSE) {
142:     $matches = [];
143:     $previous = $this->previous;
144:     while ($previous) {
145:       if ($callback($previous)) {
146:         if ($inclusive) {
147:           $matches[] = $previous;
148:         }
149:         break;
150:       }
151:       $matches[] = $previous;
152:       $previous = $previous->previous;
153:     }
154:     return new NodeCollection(array_reverse($matches), FALSE);
155:   }
156: 
157:   public function next(callable $callback = NULL) {
158:     if ($callback) {
159:       return $callback($this->next) ? $this->next : NULL;
160:     }
161:     else {
162:       return $this->next;
163:     }
164:   }
165: 
166:   public function nextAll(callable $callback = NULL) {
167:     $matches = [];
168:     $next = $this->next;
169:     while ($next) {
170:       if ($callback === NULL || $callback($next)) {
171:         $matches[] = $next;
172:       }
173:       $next = $next->next;
174:     }
175:     return new NodeCollection($matches);
176:   }
177: 
178:   public function nextUntil(callable $callback, $inclusive = FALSE) {
179:     $matches = [];
180:     $next = $this->next;
181:     while ($next) {
182:       if ($callback($next)) {
183:         if ($inclusive) {
184:           $matches[] = $next;
185:         }
186:         break;
187:       }
188:       $matches[] = $next;
189:       $next = $next->next;
190:     }
191:     return new NodeCollection($matches);
192:   }
193: 
194:   /**
195:    * {@inheritdoc}
196:    */
197:   public function siblings(callable $callback = NULL) {
198:     return $this->previousAll($callback)->add($this->nextAll($callback));
199:   }
200: 
201:   public function insertBefore($targets) {
202:     $this->remove();
203:     if ($targets instanceof Node) {
204:       $targets->parent->insertBeforeChild($targets, $this);
205:     }
206:     elseif ($targets instanceof NodeCollection || is_array($targets)) {
207:       $first = TRUE;
208:       foreach ($targets as $target) {
209:         $target->parent->insertBeforeChild($target, $first ? $this : clone $this);
210:         $first = FALSE;
211:       }
212:     }
213:     else {
214:       throw new \InvalidArgumentException();
215:     }
216:     return $this;
217:   }
218: 
219:   public function before($nodes) {
220:     if ($nodes instanceof Node) {
221:       $nodes->remove();
222:       $this->parent->insertBeforeChild($this, $nodes);
223:     }
224:     elseif ($nodes instanceof NodeCollection || is_array($nodes)) {
225:       /** @var Node $node */
226:       foreach ($nodes as $node) {
227:         $node->remove();
228:         $this->parent->insertBeforeChild($this, $node);
229:       }
230:     }
231:     else {
232:       throw new \InvalidArgumentException();
233:     }
234:     return $this;
235:   }
236: 
237:   public function insertAfter($targets) {
238:     $this->remove();
239:     if ($targets instanceof Node) {
240:       $targets->parent->insertAfterChild($targets, $this);
241:     }
242:     elseif ($targets instanceof NodeCollection || is_array($targets)) {
243:       $first = TRUE;
244:       foreach ($targets as $target) {
245:         $target->parent->insertAfterChild($target, $first ? $this : clone $this);
246:         $first = FALSE;
247:       }
248:     }
249:     else {
250:       throw new \InvalidArgumentException();
251:     }
252:     return $this;
253:   }
254: 
255:   public function after($nodes) {
256:     if ($nodes instanceof Node) {
257:       $this->parent->insertAfterChild($this, $nodes);
258:     }
259:     elseif ($nodes instanceof NodeCollection || is_array($nodes)) {
260:       $insert_after = $this;
261:       foreach ($nodes as $node) {
262:         $insert_after->parent->insertAfterChild($insert_after, $node);
263:         $insert_after = $node;
264:       }
265:     }
266:     else {
267:       throw new \InvalidArgumentException();
268:     }
269:     return $this;
270:   }
271: 
272:   public function remove() {
273:     if ($this->parent) {
274:       $this->parent->removeChild($this);
275:     }
276:     return $this;
277:   }
278: 
279:   public function replaceWith($nodes) {
280:     if (!$this->parent) {
281:       return $this;
282:     }
283:     if ($nodes instanceof Node) {
284:       if ($nodes === $this) {
285:         return $this;
286:       }
287:       $nodes->remove();
288:       $this->parent->replaceChild($this, $nodes);
289:     }
290:     elseif ($nodes instanceof NodeCollection || is_array($nodes)) {
291:       $first = TRUE;
292:       $insert_after = NULL;
293:       /** @var Node $node */
294:       foreach ($nodes as $node) {
295:         if ($first) {
296:           if ($node !== $this) {
297:             $node->remove();
298:             $this->parent->replaceChild($this, $node);
299:           }
300:           $insert_after = $node;
301:           $first = FALSE;
302:         }
303:         else {
304:           $node->remove();
305:           $insert_after->parent->insertAfterChild($insert_after, $node);
306:           $insert_after = $node;
307:         }
308:       }
309:     }
310:     elseif (is_callable($nodes)) {
311:       $this->replaceWith($nodes($this));
312:     }
313:     else {
314:       throw new \InvalidArgumentException();
315:     }
316:     return $this;
317:   }
318: 
319:   public function replaceAll($targets) {
320:     $this->remove();
321:     if ($targets instanceof Node) {
322:       if ($targets->parent) {
323:         $targets->parent->replaceChild($targets, $this);
324:       }
325:     }
326:     elseif ($targets instanceof NodeCollection || is_array($targets)) {
327:       $first = TRUE;
328:       foreach ($targets as $target) {
329:         if ($target->parent) {
330:           $target->parent->replaceChild($target, $first ? $this : clone $this);
331:         }
332:         $first = FALSE;
333:       }
334:     }
335:     else {
336:       throw new \InvalidArgumentException();
337:     }
338:     return $this;
339:   }
340: 
341:   public function swapWith(Node $replacement) {
342:     $parent = $this->parent;
343:     if ($this->next === $replacement) {
344:       // Nodes are adjacent
345:       $previous = $this->previous;
346:       $next = $this;
347:       $replacement_previous = $replacement;
348:       $replacement_next = $replacement->next;
349:     }
350:     elseif ($replacement === $this->previous) {
351:       // Nodes are adjacent
352:       $previous = $this;
353:       $next = $this->next;
354:       $replacement_previous = $replacement->previous;
355:       $replacement_next = $replacement;
356:     }
357:     else {
358:       $previous = $this->previous;
359:       $next = $this->next;
360:       $replacement_previous = $replacement->previous;
361:       $replacement_next = $replacement->next;
362:     }
363:     $replacement_head = $replacement_tail = FALSE;
364:     if ($replacement->parent) {
365:       $replacement_head = $replacement->parent->head === $replacement;
366:       $replacement_tail = $replacement->parent->tail === $replacement;
367:     }
368:     if ($this->parent) {
369:       if ($this->parent->head === $this) {
370:         $this->parent->head = $replacement;
371:       }
372:       if ($this->parent->tail === $this) {
373:         $this->parent->tail = $replacement;
374:       }
375:     }
376:     $this->parent = $replacement->parent;
377:     $this->previous = $replacement_previous;
378:     if ($this->previous) {
379:       $this->previous->next = $this;
380:     }
381:     $this->next = $replacement_next;
382:     if ($this->next) {
383:       $this->next->previous = $this;
384:     }
385:     if ($replacement_head) {
386:       $replacement->parent->head = $this;
387:     }
388:     if ($replacement_tail) {
389:       $replacement->parent->tail = $this;
390:     }
391:     $replacement->parent = $parent;
392:     $replacement->previous = $previous;
393:     if ($replacement->previous) {
394:       $replacement->previous->next = $replacement;
395:     }
396:     $replacement->next = $next;
397:     if ($replacement->next) {
398:       $replacement->next->previous = $replacement;
399:     }
400:     return $this;
401:   }
402: 
403:   public function prependTo($targets) {
404:     $this->remove();
405:     if ($targets instanceof ParentNode) {
406:       $targets->prependChild($this);
407:     }
408:     elseif ($targets instanceof NodeCollection || is_array($targets)) {
409:       $first = TRUE;
410:       foreach ($targets as $target) {
411:         if ($target instanceof ParentNode) {
412:           $target->prependChild($first ? $this : clone $this);
413:           $first = FALSE;
414:         }
415:       }
416:     }
417:     else {
418:       throw new \InvalidArgumentException();
419:     }
420:     return $this;
421:   }
422: 
423:   public function appendTo($targets) {
424:     $this->remove();
425:     if ($targets instanceof ParentNode) {
426:       $targets->appendChild($this);
427:     }
428:     elseif ($targets instanceof NodeCollection || is_array($targets)) {
429:       $first = TRUE;
430:       foreach ($targets as $target) {
431:         if ($target instanceof ParentNode) {
432:           $target->appendChild($first ? $this : clone $this);
433:           $first = FALSE;
434:         }
435:       }
436:     }
437:     else {
438:       throw new \InvalidArgumentException();
439:     }
440:     return $this;
441:   }
442: 
443:   /**
444:    * Get a unique key for sorting this node.
445:    *
446:    * Used to sort nodes into tree order. That is top to bottom and then left
447:    * to right.
448:    *
449:    * @return string
450:    */
451:   public function sortKey() {
452:     if ($this instanceof RootNode) {
453:       return spl_object_hash($this);
454:     }
455:     if (!$this->parent) {
456:       return '~/' . spl_object_hash($this);
457:     }
458:     $path = $this->parent->sortKey() . '/';
459:     $position = 0;
460:     $previous = $this->previous;
461:     while ($previous) {
462:       $position++;
463:       $previous = $previous->previous;
464:     }
465:     $path .= $position;
466:     return $path;
467:   }
468: 
469:   /**
470:    * Tests this node against a condition.
471:    *
472:    * @param callable|string $test
473:    *  Either a callback function to test the node against, or a class name
474:    *  (wrapper around instanceof). Eventually this will accept a callable
475:    *  or a filter query (issue #61).
476:    *
477:    * @return boolean
478:    *
479:    * @throws \InvalidArgumentException
480:    */
481:   public function is($test) {
482:     if (is_callable($test)) {
483:       return (boolean) $test($this);
484:     }
485:     elseif (is_string($test)) {
486:       return $this->is(Filter::isInstanceOf($test));
487:     }
488:     else {
489:       throw new \InvalidArgumentException();
490:     }
491:   }
492: 
493:   /**
494:    * Tests if this node matches any the tests in the array.
495:    *
496:    * @param string|callable[] $tests
497:    *
498:    * @return boolean
499:    */
500:   public function isAnyOf(array $tests) {
501:     foreach ($tests as $test) {
502:       if ($this->is($test)) {
503:         return TRUE;
504:       }
505:     }
506:     return FALSE;
507:   }
508: 
509:   /**
510:    * Tests if this node matches all of the tests in the array.
511:    *
512:    * @param string|callable[] $tests
513:    *
514:    * @return boolean
515:    */
516:   public function isAllOf(array $tests) {
517:     foreach ($tests as $test) {
518:       if (! $this->is($test)) {
519:         return FALSE;
520:       }
521:     }
522:     return TRUE;
523:   }
524: 
525:   /**
526:    * Returns the code block containing this node. The code block could be a
527:    * control structure (if statement, for loop, case statement, etc.), a
528:    * function, a class definition, or the whole syntax tree.
529:    *
530:    * @return StatementBlockNode|RootNode|NULL
531:    */
532:   public function getLogicalBlock() {
533:     return $this->closest(function(Node $node) {
534:       return $node instanceof StatementBlockNode || $node instanceof RootNode;
535:     });
536:   }
537: 
538:   /**
539:    * Creates a Node from a php value.
540:    *
541:    * @param string|integer|float|boolean|array|null $value
542:    *  The value to create a node for.
543:    *
544:    * @return FloatNode|IntegerNode|StringNode|BooleanNode|NullNode|ArrayNode
545:    *
546:    * @throws \InvalidArgumentException if $value is not a scalar.
547:    */
548:   public static function fromValue($value) {
549:     if (is_array($value)) {
550:       $elements = [];
551:       foreach ($value as $k => $v) {
552:         $elements[] = ArrayPairNode::create(static::fromValue($k), static::fromValue($v));
553:       }
554:       return ArrayNode::create($elements);
555:     }
556:     elseif (is_string($value)) {
557:       return StringNode::create(var_export($value, TRUE));
558:     }
559:     elseif (is_integer($value)) {
560:       return new IntegerNode(T_LNUMBER, $value);
561:     }
562:     elseif (is_float($value)) {
563:       return new FloatNode(T_DNUMBER, $value);
564:     }
565:     elseif (is_bool($value)) {
566:       return BooleanNode::create($value);
567:     }
568:     elseif (is_null($value)) {
569:       return NullNode::create('NULL');
570:     }
571:     else {
572:       throw new \InvalidArgumentException();
573:     }
574:   }
575: 
576:   /**
577:    * Returns the statement (or statement block) which contains this node, if
578:    * it's part of a statement.
579:    *
580:    * @return StatementNode|NULL
581:    */
582:   public function getStatement() {
583:     return $this->closest(Filter::isInstanceOf('\Pharborist\StatementNode'));
584:   }
585: 
586:   public function getRoot() {
587:     return $this->closest(Filter::isInstanceOf('\Pharborist\RootNode'));
588:   }
589: 
590:   public function hasRoot() {
591:     return $this->getRoot() !== NULL;
592: 
593:   }
594: 
595:   /**
596:    * @return TokenNode
597:    */
598:   public function previousToken() {
599:     $prev_node = $this->previous;
600:     if ($prev_node === NULL) {
601:       $parent = $this->parent;
602:       while ($parent !== NULL && $parent->previous === NULL) {
603:         $parent = $parent->parent;
604:       }
605:       if ($parent === NULL) {
606:         return NULL;
607:       }
608:       $prev_node = $parent->previous;
609:     }
610:     if ($prev_node instanceof ParentNode) {
611:       return $prev_node->isEmpty() ? $prev_node->previousToken() : $prev_node->lastToken();
612:     }
613:     else {
614:       return $prev_node;
615:     }
616:   }
617: 
618:   /**
619:    * @return TokenNode
620:    */
621:   public function nextToken() {
622:     $next_node = $this->next;
623:     if ($next_node === NULL) {
624:       $parent = $this->parent;
625:       while ($parent !== NULL && $parent->next === NULL) {
626:         $parent = $parent->parent;
627:       }
628:       if ($parent === NULL) {
629:         return NULL;
630:       }
631:       $next_node = $parent->next;
632:     }
633:     if ($next_node instanceof ParentNode) {
634:       return $next_node->isEmpty() ? $next_node->nextToken() : $next_node->firstToken();
635:     }
636:     else {
637:       return $next_node;
638:     }
639:   }
640: 
641:   public function __clone() {
642:     // Clone does not belong to any parent.
643:     $this->parent = NULL;
644:     $this->previous = NULL;
645:     $this->next = NULL;
646:   }
647: }
648: 
Pharborist API documentation generated by ApiGen