1: <?php
2: namespace Pharborist;
3:
4: 5: 6: 7: 8: 9: 10: 11: 12: 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: 24:
25: abstract class Node implements NodeInterface {
26: 27: 28:
29: protected $parent = NULL;
30:
31: 32: 33:
34: protected $previous = NULL;
35:
36: 37: 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: 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:
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:
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:
345: $previous = $this->previous;
346: $next = $this;
347: $replacement_previous = $replacement;
348: $replacement_next = $replacement->next;
349: }
350: elseif ($replacement === $this->previous) {
351:
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: 445: 446: 447: 448: 449: 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: 471: 472: 473: 474: 475: 476: 477: 478: 479: 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: 495: 496: 497: 498: 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: 511: 512: 513: 514: 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: 527: 528: 529: 530: 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: 540: 541: 542: 543: 544: 545: 546: 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: 578: 579: 580: 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: 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: 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:
643: $this->parent = NULL;
644: $this->previous = NULL;
645: $this->next = NULL;
646: }
647: }
648: