1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\CssSelector\Parser; 13 14use Symfony\Component\CssSelector\Exception\InternalErrorException; 15use Symfony\Component\CssSelector\Exception\SyntaxErrorException; 16 17/** 18 * CSS selector token stream. 19 * 20 * This component is a port of the Python cssselect library, 21 * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 22 * 23 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> 24 * 25 * @internal 26 */ 27class TokenStream 28{ 29 /** 30 * @var Token[] 31 */ 32 private $tokens = array(); 33 34 /** 35 * @var Token[] 36 */ 37 private $used = array(); 38 39 /** 40 * @var int 41 */ 42 private $cursor = 0; 43 44 /** 45 * @var Token|null 46 */ 47 private $peeked; 48 49 /** 50 * @var bool 51 */ 52 private $peeking = false; 53 54 /** 55 * Pushes a token. 56 * 57 * @return $this 58 */ 59 public function push(Token $token) 60 { 61 $this->tokens[] = $token; 62 63 return $this; 64 } 65 66 /** 67 * Freezes stream. 68 * 69 * @return $this 70 */ 71 public function freeze() 72 { 73 return $this; 74 } 75 76 /** 77 * Returns next token. 78 * 79 * @return Token 80 * 81 * @throws InternalErrorException If there is no more token 82 */ 83 public function getNext() 84 { 85 if ($this->peeking) { 86 $this->peeking = false; 87 $this->used[] = $this->peeked; 88 89 return $this->peeked; 90 } 91 92 if (!isset($this->tokens[$this->cursor])) { 93 throw new InternalErrorException('Unexpected token stream end.'); 94 } 95 96 return $this->tokens[$this->cursor++]; 97 } 98 99 /** 100 * Returns peeked token. 101 * 102 * @return Token 103 */ 104 public function getPeek() 105 { 106 if (!$this->peeking) { 107 $this->peeked = $this->getNext(); 108 $this->peeking = true; 109 } 110 111 return $this->peeked; 112 } 113 114 /** 115 * Returns used tokens. 116 * 117 * @return Token[] 118 */ 119 public function getUsed() 120 { 121 return $this->used; 122 } 123 124 /** 125 * Returns nex identifier token. 126 * 127 * @return string The identifier token value 128 * 129 * @throws SyntaxErrorException If next token is not an identifier 130 */ 131 public function getNextIdentifier() 132 { 133 $next = $this->getNext(); 134 135 if (!$next->isIdentifier()) { 136 throw SyntaxErrorException::unexpectedToken('identifier', $next); 137 } 138 139 return $next->getValue(); 140 } 141 142 /** 143 * Returns nex identifier or star delimiter token. 144 * 145 * @return null|string The identifier token value or null if star found 146 * 147 * @throws SyntaxErrorException If next token is not an identifier or a star delimiter 148 */ 149 public function getNextIdentifierOrStar() 150 { 151 $next = $this->getNext(); 152 153 if ($next->isIdentifier()) { 154 return $next->getValue(); 155 } 156 157 if ($next->isDelimiter(array('*'))) { 158 return; 159 } 160 161 throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next); 162 } 163 164 /** 165 * Skips next whitespace if any. 166 */ 167 public function skipWhitespace() 168 { 169 $peek = $this->getPeek(); 170 171 if ($peek->isWhitespace()) { 172 $this->getNext(); 173 } 174 } 175} 176