friendship ended with social-app. php is my new best friend
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 array $tokens = [];
33
34 /**
35 * @var Token[]
36 */
37 private array $used = [];
38
39 private int $cursor = 0;
40 private ?Token $peeked;
41 private bool $peeking = false;
42
43 /**
44 * Pushes a token.
45 *
46 * @return $this
47 */
48 public function push(Token $token): static
49 {
50 $this->tokens[] = $token;
51
52 return $this;
53 }
54
55 /**
56 * Freezes stream.
57 *
58 * @return $this
59 */
60 public function freeze(): static
61 {
62 return $this;
63 }
64
65 /**
66 * Returns next token.
67 *
68 * @throws InternalErrorException If there is no more token
69 */
70 public function getNext(): Token
71 {
72 if ($this->peeking) {
73 $this->peeking = false;
74 $this->used[] = $this->peeked;
75
76 return $this->peeked;
77 }
78
79 if (!isset($this->tokens[$this->cursor])) {
80 throw new InternalErrorException('Unexpected token stream end.');
81 }
82
83 return $this->tokens[$this->cursor++];
84 }
85
86 /**
87 * Returns peeked token.
88 */
89 public function getPeek(): Token
90 {
91 if (!$this->peeking) {
92 $this->peeked = $this->getNext();
93 $this->peeking = true;
94 }
95
96 return $this->peeked;
97 }
98
99 /**
100 * Returns used tokens.
101 *
102 * @return Token[]
103 */
104 public function getUsed(): array
105 {
106 return $this->used;
107 }
108
109 /**
110 * Returns next identifier token.
111 *
112 * @throws SyntaxErrorException If next token is not an identifier
113 */
114 public function getNextIdentifier(): string
115 {
116 $next = $this->getNext();
117
118 if (!$next->isIdentifier()) {
119 throw SyntaxErrorException::unexpectedToken('identifier', $next);
120 }
121
122 return $next->getValue();
123 }
124
125 /**
126 * Returns next identifier or null if star delimiter token is found.
127 *
128 * @throws SyntaxErrorException If next token is not an identifier or a star delimiter
129 */
130 public function getNextIdentifierOrStar(): ?string
131 {
132 $next = $this->getNext();
133
134 if ($next->isIdentifier()) {
135 return $next->getValue();
136 }
137
138 if ($next->isDelimiter(['*'])) {
139 return null;
140 }
141
142 throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next);
143 }
144
145 /**
146 * Skips next whitespace if any.
147 */
148 public function skipWhitespace(): void
149 {
150 $peek = $this->getPeek();
151
152 if ($peek->isWhitespace()) {
153 $this->getNext();
154 }
155 }
156}