Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
ControlStructuresOptimizer.php
001 <?php
002
003 /**
004 * @package s9e\TextFormatter
005 * @copyright Copyright (c) 2010-2022 The s9e authors
006 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
007 */
008 namespace s9e\TextFormatter\Configurator\RendererGenerators\PHP;
009
010 /**
011 * Optimize the control structures of a script
012 *
013 * Removes brackets in control structures wherever possible. Prevents the generation of EXT_STMT
014 * opcodes where they're not strictly required.
015 */
016 class ControlStructuresOptimizer extends AbstractOptimizer
017 {
018 /**
019 * @var integer Number of braces encountered in current source
020 */
021 protected $braces;
022
023 /**
024 * @var array Current context
025 */
026 protected $context;
027
028 /**
029 * Test whether current block ends with an if or elseif control structure
030 *
031 * @return bool
032 */
033 protected function blockEndsWithIf()
034 {
035 return in_array($this->context['lastBlock'], [T_IF, T_ELSEIF], true);
036 }
037
038 /**
039 * Test whether the token at current index is a control structure
040 *
041 * @return bool
042 */
043 protected function isControlStructure()
044 {
045 return in_array(
046 $this->tokens[$this->i][0],
047 [T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_IF, T_WHILE],
048 true
049 );
050 }
051
052 /**
053 * Test whether current block is followed by an elseif/else structure
054 *
055 * @return bool
056 */
057 protected function isFollowedByElse()
058 {
059 if ($this->i > $this->cnt - 4)
060 {
061 // It doesn't have room for another block
062 return false;
063 }
064
065 // Compute the index of the next non-whitespace token
066 $k = $this->i + 1;
067
068 if ($this->tokens[$k][0] === T_WHITESPACE)
069 {
070 ++$k;
071 }
072
073 return in_array($this->tokens[$k][0], [T_ELSEIF, T_ELSE], true);
074 }
075
076 /**
077 * Test whether braces must be preserved in current context
078 *
079 * @return bool
080 */
081 protected function mustPreserveBraces()
082 {
083 // If current block ends with if/elseif and is followed by elseif/else, we must preserve
084 // its braces to prevent it from merging with the outer elseif/else. IOW, we must preserve
085 // the braces if "if{if{}}else" would become "if{if else}"
086 return ($this->blockEndsWithIf() && $this->isFollowedByElse());
087 }
088
089 /**
090 * Optimize control structures in stored tokens
091 *
092 * @return void
093 */
094 protected function optimizeTokens()
095 {
096 while (++$this->i < $this->cnt)
097 {
098 if ($this->tokens[$this->i] === ';')
099 {
100 ++$this->context['statements'];
101 }
102 elseif ($this->tokens[$this->i] === '{')
103 {
104 ++$this->braces;
105 }
106 elseif ($this->tokens[$this->i] === '}')
107 {
108 if ($this->context['braces'] === $this->braces)
109 {
110 $this->processEndOfBlock();
111 }
112
113 --$this->braces;
114 }
115 elseif ($this->isControlStructure())
116 {
117 $this->processControlStructure();
118 }
119 }
120 }
121
122 /**
123 * Process the control structure starting at current index
124 *
125 * @return void
126 */
127 protected function processControlStructure()
128 {
129 // Save the index so we can rewind back to it in case of failure
130 $savedIndex = $this->i;
131
132 // Count this control structure in this context's statements unless it's an elseif/else
133 // in which case it's already been counted as part of the if
134 if (!in_array($this->tokens[$this->i][0], [T_ELSE, T_ELSEIF], true))
135 {
136 ++$this->context['statements'];
137 }
138
139 // If the control structure is anything but an "else", skip its condition to reach the first
140 // brace or statement
141 if ($this->tokens[$this->i][0] !== T_ELSE)
142 {
143 $this->skipCondition();
144 }
145
146 $this->skipWhitespace();
147
148 // Abort if this control structure does not use braces
149 if ($this->tokens[$this->i] !== '{')
150 {
151 // Rewind all the way to the original token
152 $this->i = $savedIndex;
153
154 return;
155 }
156
157 ++$this->braces;
158
159 // Replacement for the first brace
160 $replacement = [T_WHITESPACE, ''];
161
162 // Add a space after "else" if the brace is removed and it's not followed by whitespace or a
163 // variable
164 if ($this->tokens[$savedIndex][0] === T_ELSE
165 && $this->tokens[$this->i + 1][0] !== T_VARIABLE
166 && $this->tokens[$this->i + 1][0] !== T_WHITESPACE)
167 {
168 $replacement = [T_WHITESPACE, ' '];
169 }
170
171 // Record the token of the control structure (T_IF, T_WHILE, etc...) in the current context
172 $this->context['lastBlock'] = $this->tokens[$savedIndex][0];
173
174 // Create a new context
175 $this->context = [
176 'braces' => $this->braces,
177 'index' => $this->i,
178 'lastBlock' => null,
179 'parent' => $this->context,
180 'replacement' => $replacement,
181 'savedIndex' => $savedIndex,
182 'statements' => 0
183 ];
184 }
185
186 /**
187 * Process the block ending at current index
188 *
189 * @return void
190 */
191 protected function processEndOfBlock()
192 {
193 if ($this->context['statements'] < 2 && !$this->mustPreserveBraces())
194 {
195 $this->removeBracesInCurrentContext();
196 }
197
198 $this->context = $this->context['parent'];
199
200 // Propagate the "lastBlock" property upwards to handle multiple nested if statements
201 $this->context['parent']['lastBlock'] = $this->context['lastBlock'];
202 }
203
204 /**
205 * Remove the braces surrounding current context
206 *
207 * @return void
208 */
209 protected function removeBracesInCurrentContext()
210 {
211 // Replace the first brace with the saved replacement
212 $this->tokens[$this->context['index']] = $this->context['replacement'];
213
214 // Remove the second brace or replace it with a semicolon if there are no statements in this
215 // block
216 $this->tokens[$this->i] = ($this->context['statements']) ? [T_WHITESPACE, ''] : ';';
217
218 // Remove the whitespace before braces. This is mainly cosmetic
219 foreach ([$this->context['index'] - 1, $this->i - 1] as $tokenIndex)
220 {
221 if ($this->tokens[$tokenIndex][0] === T_WHITESPACE)
222 {
223 $this->tokens[$tokenIndex][1] = '';
224 }
225 }
226
227 // Test whether the current block followed an else statement then test whether this
228 // else was followed by an if
229 if ($this->tokens[$this->context['savedIndex']][0] === T_ELSE)
230 {
231 $j = 1 + $this->context['savedIndex'];
232
233 while ($this->tokens[$j][0] === T_WHITESPACE
234 || $this->tokens[$j][0] === T_COMMENT
235 || $this->tokens[$j][0] === T_DOC_COMMENT)
236 {
237 ++$j;
238 }
239
240 if ($this->tokens[$j][0] === T_IF)
241 {
242 // Replace if with elseif
243 $this->tokens[$j] = [T_ELSEIF, 'elseif'];
244
245 // Remove the original else
246 $j = $this->context['savedIndex'];
247 $this->tokens[$j] = [T_WHITESPACE, ''];
248
249 // Remove any whitespace before the original else
250 if ($this->tokens[$j - 1][0] === T_WHITESPACE)
251 {
252 $this->tokens[$j - 1][1] = '';
253 }
254
255 // Unindent what was the else's content
256 $this->unindentBlock($j, $this->i - 1);
257
258 // Ensure that the brace after the now-removed "else" was not replaced with a space
259 $this->tokens[$this->context['index']] = [T_WHITESPACE, ''];
260 }
261 }
262
263 $this->changed = true;
264 }
265
266 /**
267 * {@inheritdoc}
268 */
269 protected function reset($php)
270 {
271 parent::reset($php);
272
273 $this->braces = 0;
274 $this->context = [
275 'braces' => 0,
276 'index' => -1,
277 'parent' => [],
278 'preventElse' => false,
279 'savedIndex' => 0,
280 'statements' => 0
281 ];
282 }
283
284 /**
285 * Skip the condition of a control structure
286 *
287 * @return void
288 */
289 protected function skipCondition()
290 {
291 // Reach the opening parenthesis
292 $this->skipToString('(');
293
294 // Iterate through tokens until we have a match for every left parenthesis
295 $parens = 0;
296 while (++$this->i < $this->cnt)
297 {
298 if ($this->tokens[$this->i] === ')')
299 {
300 if ($parens)
301 {
302 --$parens;
303 }
304 else
305 {
306 break;
307 }
308 }
309 elseif ($this->tokens[$this->i] === '(')
310 {
311 ++$parens;
312 }
313 }
314 }
315 }