1<?php 2 3/* 4 * This file is part of Twig. 5 * 6 * (c) Fabien Potencier 7 * (c) Armin Ronacher 8 * 9 * For the full copyright and license information, please view the LICENSE 10 * file that was distributed with this source code. 11 */ 12 13namespace Twig; 14 15use Twig\Error\SyntaxError; 16 17/** 18 * Represents a token stream. 19 * 20 * @author Fabien Potencier <fabien@symfony.com> 21 */ 22final class TokenStream 23{ 24 private $current = 0; 25 26 public function __construct( 27 private array $tokens, 28 private ?Source $source = null, 29 ) { 30 if (null === $this->source) { 31 trigger_deprecation('twig/twig', '3.16', \sprintf('Not passing a "%s" object to "%s" constructor is deprecated.', Source::class, __CLASS__)); 32 33 $this->source = new Source('', ''); 34 } 35 } 36 37 public function __toString(): string 38 { 39 return implode("\n", $this->tokens); 40 } 41 42 /** 43 * @return void 44 */ 45 public function injectTokens(array $tokens) 46 { 47 $this->tokens = array_merge(\array_slice($this->tokens, 0, $this->current), $tokens, \array_slice($this->tokens, $this->current)); 48 } 49 50 /** 51 * Sets the pointer to the next token and returns the old one. 52 */ 53 public function next(): Token 54 { 55 if (!isset($this->tokens[++$this->current])) { 56 throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source); 57 } 58 59 return $this->tokens[$this->current - 1]; 60 } 61 62 /** 63 * Tests a token, sets the pointer to the next one and returns it or throws a syntax error. 64 * 65 * @return Token|null The next token if the condition is true, null otherwise 66 */ 67 public function nextIf($primary, $secondary = null) 68 { 69 return $this->tokens[$this->current]->test($primary, $secondary) ? $this->next() : null; 70 } 71 72 /** 73 * Tests a token and returns it or throws a syntax error. 74 */ 75 public function expect($type, $value = null, ?string $message = null): Token 76 { 77 $token = $this->tokens[$this->current]; 78 if (!$token->test($type, $value)) { 79 $line = $token->getLine(); 80 throw new SyntaxError(\sprintf('%sUnexpected token "%s"%s ("%s" expected%s).', 81 $message ? $message.'. ' : '', 82 $token->toEnglish(), 83 $token->getValue() ? \sprintf(' of value "%s"', $token->getValue()) : '', 84 Token::typeToEnglish($type), $value ? \sprintf(' with value "%s"', $value) : ''), 85 $line, 86 $this->source 87 ); 88 } 89 $this->next(); 90 91 return $token; 92 } 93 94 /** 95 * Looks at the next token. 96 */ 97 public function look(int $number = 1): Token 98 { 99 if (!isset($this->tokens[$this->current + $number])) { 100 throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source); 101 } 102 103 return $this->tokens[$this->current + $number]; 104 } 105 106 /** 107 * Tests the current token. 108 */ 109 public function test($primary, $secondary = null): bool 110 { 111 return $this->tokens[$this->current]->test($primary, $secondary); 112 } 113 114 /** 115 * Checks if end of stream was reached. 116 */ 117 public function isEOF(): bool 118 { 119 return $this->tokens[$this->current]->test(Token::EOF_TYPE); 120 } 121 122 public function getCurrent(): Token 123 { 124 return $this->tokens[$this->current]; 125 } 126 127 public function getSourceContext(): Source 128 { 129 return $this->source; 130 } 131} 132