1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15:
16: namespace TokenReflection\Stream;
17:
18: use TokenReflection\Exception;
19: use SeekableIterator, Countable, ArrayAccess, Serializable;
20:
21:
22: if (!defined('NATIVE_TRAITS')) {
23: require_once __DIR__ . '/../Broker.php';
24: }
25:
26: 27: 28:
29: abstract class StreamBase implements SeekableIterator, Countable, ArrayAccess, Serializable
30: {
31: 32: 33: 34: 35:
36: protected $fileName = 'unknown';
37:
38: 39: 40: 41: 42:
43: private $tokens = array();
44:
45: 46: 47: 48: 49:
50: private $position = 0;
51:
52: 53: 54: 55: 56:
57: private $count = 0;
58:
59: 60: 61: 62: 63: 64: 65:
66: protected function __construct()
67: {
68: if (!extension_loaded('tokenizer')) {
69: throw new Exception\StreamException($this, 'The tokenizer PHP extension is not loaded.', Exception\StreamException::PHP_EXT_MISSING);
70: }
71: }
72:
73: 74: 75: 76: 77:
78: protected final function processSource($source)
79: {
80: $stream = @token_get_all(str_replace(array("\r\n", "\r"), "\n", $source));
81:
82: static $checkLines = array(T_COMMENT => true, T_WHITESPACE => true, T_DOC_COMMENT => true, T_INLINE_HTML => true, T_ENCAPSED_AND_WHITESPACE => true, T_CONSTANT_ENCAPSED_STRING => true);
83:
84: foreach ($stream as $position => $token) {
85: if (is_array($token)) {
86: if (!NATIVE_TRAITS && T_STRING === $token[0]) {
87: $lValue = strtolower($token[1]);
88: if ('trait' === $lValue) {
89: $token[0] = T_TRAIT;
90: } elseif ('insteadof' === $lValue) {
91: $token[0] = T_INSTEADOF;
92: } elseif ('__TRAIT__' === $token[1]) {
93: $token[0] = T_TRAIT_C;
94: } elseif ('callable' === $lValue) {
95: $token[0] = T_CALLABLE;
96: }
97: }
98:
99: $this->tokens[] = $token;
100: } else {
101: $previous = $this->tokens[$position - 1];
102: $line = $previous[2];
103: if (isset($checkLines[$previous[0]])) {
104: $line += substr_count($previous[1], "\n");
105: }
106:
107: $this->tokens[] = array($token, $token, $line);
108: }
109: }
110:
111: $this->count = count($this->tokens);
112: }
113:
114: 115: 116: 117: 118:
119: public function getFileName()
120: {
121: return $this->fileName;
122: }
123:
124: 125: 126: 127: 128:
129: public function getSource()
130: {
131: return $this->getSourcePart();
132: }
133:
134: 135: 136: 137: 138: 139: 140:
141: public function getSourcePart($start = null, $end = null)
142: {
143: $start = (int) $start;
144: $end = null === $end ? ($this->count - 1) : (int) $end;
145:
146: $source = '';
147: for ($i = $start; $i <= $end; $i++) {
148: $source .= $this->tokens[$i][1];
149: }
150: return $source;
151: }
152:
153: 154: 155: 156: 157: 158:
159: public function find($type)
160: {
161: $actual = $this->position;
162: while (isset($this->tokens[$this->position])) {
163: if ($type === $this->tokens[$this->position][0]) {
164: return $this;
165: }
166:
167: $this->position++;
168: }
169:
170: $this->position = $actual;
171: return false;
172: }
173:
174: 175: 176: 177: 178: 179: 180: 181:
182: public function findMatchingBracket()
183: {
184: static $brackets = array(
185: '(' => ')',
186: '{' => '}',
187: '[' => ']',
188: T_CURLY_OPEN => '}',
189: T_DOLLAR_OPEN_CURLY_BRACES => '}'
190: );
191:
192: if (!$this->valid()) {
193: throw new Exception\StreamException($this, 'Out of token stream.', Exception\StreamException::READ_BEYOND_EOS);
194: }
195:
196: $position = $this->position;
197:
198: $bracket = $this->tokens[$this->position][0];
199:
200: if (!isset($brackets[$bracket])) {
201: throw new Exception\StreamException($this, sprintf('There is no usable bracket at position "%d".', $position), Exception\StreamException::DOES_NOT_EXIST);
202: }
203:
204: $searching = $brackets[$bracket];
205:
206: $level = 0;
207: while (isset($this->tokens[$this->position])) {
208: $type = $this->tokens[$this->position][0];
209: if ($searching === $type) {
210: $level--;
211: } elseif ($bracket === $type || ($searching === '}' && ('{' === $type || T_CURLY_OPEN === $type || T_DOLLAR_OPEN_CURLY_BRACES === $type))) {
212: $level++;
213: }
214:
215: if (0 === $level) {
216: return $this;
217: }
218:
219: $this->position++;
220: }
221:
222: throw new Exception\StreamException($this, sprintf('Could not find the end bracket "%s" of the bracket at position "%d".', $searching, $position), Exception\StreamException::DOES_NOT_EXIST);
223: }
224:
225: 226: 227: 228: 229: 230:
231: public function skipWhitespaces($skipDocBlocks = false)
232: {
233: static $skipped = array(T_WHITESPACE => true, T_COMMENT => true, T_DOC_COMMENT => true);
234:
235: do {
236: $this->position++;
237: } while (isset($this->tokens[$this->position]) && isset($skipped[$this->tokens[$this->position][0]]) && ($skipDocBlocks || $this->tokens[$this->position][0] !== T_DOC_COMMENT));
238:
239: return $this;
240: }
241:
242: 243: 244: 245: 246: 247:
248: public function isWhitespace($docBlock = false)
249: {
250: static $skipped = array(T_WHITESPACE => true, T_COMMENT => true, T_DOC_COMMENT => false);
251:
252: if (!$this->valid()) {
253: return false;
254: }
255:
256: return $docBlock ? isset($skipped[$this->getType()]) : !empty($skipped[$this->getType()]);
257: }
258:
259: 260: 261: 262: 263: 264: 265:
266: public function is($type, $position = -1)
267: {
268: return $type === $this->getType($position);
269: }
270:
271: 272: 273: 274: 275: 276:
277: public function getType($position = -1)
278: {
279: if (-1 === $position) {
280: $position = $this->position;
281: }
282:
283: return isset($this->tokens[$position]) ? $this->tokens[$position][0] : null;
284: }
285:
286: 287: 288: 289: 290: 291:
292: public function getTokenValue($position = -1)
293: {
294: if (-1 === $position) {
295: $position = $this->position;
296: }
297:
298: return isset($this->tokens[$position]) ? $this->tokens[$position][1] : null;
299: }
300:
301: 302: 303: 304: 305: 306:
307: public function getTokenName($position = -1)
308: {
309: $type = $this->getType($position);
310: if (is_string($type)) {
311: return $type;
312: } elseif (T_TRAIT === $type) {
313: return 'T_TRAIT';
314: } elseif (T_INSTEADOF === $type) {
315: return 'T_INSTEADOF';
316: } elseif (T_CALLABLE === $type) {
317: return 'T_CALLABLE';
318: }
319:
320: return token_name($type);
321: }
322:
323: 324: 325: 326: 327:
328: public function serialize()
329: {
330: return serialize(array($this->fileName, $this->tokens));
331: }
332:
333: 334: 335: 336: 337: 338:
339: public function unserialize($serialized)
340: {
341: $data = @unserialize($serialized);
342: if (false === $data) {
343: throw new Exception\StreamException($this, 'Could not deserialize the serialized data.', Exception\StreamException::SERIALIZATION_ERROR);
344: }
345: if (2 !== count($data) || !is_string($data[0]) || !is_array($data[1])) {
346: throw new Exception\StreamException($this, 'Invalid serialization data.', Exception\StreamException::SERIALIZATION_ERROR);
347: }
348:
349: $this->fileName = $data[0];
350: $this->tokens = $data[1];
351: $this->count = count($this->tokens);
352: $this->position = 0;
353: }
354:
355: 356: 357: 358: 359: 360:
361: public function offsetExists($offset)
362: {
363: return isset($this->tokens[$offset]);
364: }
365:
366: 367: 368: 369: 370: 371: 372: 373:
374: public function offsetUnset($offset)
375: {
376: throw new Exception\StreamException($this, 'Removing of tokens from the stream is not supported.', Exception\StreamException::UNSUPPORTED);
377: }
378:
379: 380: 381: 382: 383: 384:
385: public function offsetGet($offset)
386: {
387: return isset($this->tokens[$offset]) ? $this->tokens[$offset] : null;
388: }
389:
390: 391: 392: 393: 394: 395: 396: 397: 398:
399: public function offsetSet($offset, $value)
400: {
401: throw new Exception\StreamException($this, 'Setting token values is not supported.', Exception\StreamException::UNSUPPORTED);
402: }
403:
404: 405: 406: 407: 408:
409: public function key()
410: {
411: return $this->position;
412: }
413:
414: 415: 416: 417: 418:
419: public function next()
420: {
421: $this->position++;
422: return $this;
423: }
424:
425: 426: 427: 428: 429:
430: public function rewind()
431: {
432: $this->position = 0;
433: return $this;
434: }
435:
436: 437: 438: 439: 440:
441: public function current()
442: {
443: return isset($this->tokens[$this->position]) ? $this->tokens[$this->position] : null;
444: }
445:
446: 447: 448: 449: 450:
451: public function valid()
452: {
453: return isset($this->tokens[$this->position]);
454: }
455:
456: 457: 458: 459: 460:
461: public function count()
462: {
463: return $this->count;
464: }
465:
466: 467: 468: 469: 470: 471:
472: public function seek($position)
473: {
474: $this->position = (int) $position;
475: return $this;
476: }
477:
478: 479: 480: 481: 482:
483: public function __toString()
484: {
485: return $this->getSource();
486: }
487: }
488: