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 |
Optimizer.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 * This class optimizes the code produced by the PHP renderer. It is not meant to be used on general
012 * purpose code
013 */
014 class Optimizer
015 {
016 /**
017 * @var BranchOutputOptimizer
018 */
019 public $branchOutputOptimizer;
020
021 /**
022 * @var integer Number of tokens in $this->tokens
023 */
024 protected $cnt;
025
026 /**
027 * @var integer Current token index
028 */
029 protected $i;
030
031 /**
032 * @var integer Maximum number iterations over the optimization passes
033 */
034 public $maxLoops = 10;
035
036 /**
037 * @var array Array of tokens from token_get_all()
038 */
039 protected $tokens;
040
041 /**
042 * Constructor
043 */
044 public function __construct()
045 {
046 $this->branchOutputOptimizer = new BranchOutputOptimizer;
047 }
048
049 /**
050 * Optimize the code generated by the PHP renderer generator
051 *
052 * @param string $php Original code
053 * @return string Optimized code
054 */
055 public function optimize($php)
056 {
057 $this->tokens = token_get_all('<?php ' . $php);
058 $this->cnt = count($this->tokens);
059 $this->i = 0;
060
061 // Remove line numbers from tokens
062 foreach ($this->tokens as &$token)
063 {
064 if (is_array($token))
065 {
066 unset($token[2]);
067 }
068 }
069 unset($token);
070
071 // Optimization passes, in order of execution
072 $passes = [
073 'optimizeOutConcatEqual',
074 'optimizeConcatenations',
075 'optimizeHtmlspecialchars'
076 ];
077
078 // Limit the number of loops, in case something would make it loop indefinitely
079 $remainingLoops = $this->maxLoops;
080 do
081 {
082 $continue = false;
083
084 foreach ($passes as $pass)
085 {
086 // Run the pass
087 $this->$pass();
088
089 // If the array was modified, reset the keys and keep going
090 $cnt = count($this->tokens);
091 if ($this->cnt !== $cnt)
092 {
093 $this->tokens = array_values($this->tokens);
094 $this->cnt = $cnt;
095 $continue = true;
096 }
097 }
098 }
099 while ($continue && --$remainingLoops);
100
101 // Optimize common output expressions in if-else-elseif conditionals
102 $php = $this->branchOutputOptimizer->optimize($this->tokens);
103
104 // Reclaim some memory
105 unset($this->tokens);
106
107 return $php;
108 }
109
110 /**
111 * Test whether current token is between two htmlspecialchars() calls
112 *
113 * @return bool
114 */
115 protected function isBetweenHtmlspecialcharCalls()
116 {
117 return ($this->tokens[$this->i + 1] === [T_STRING, 'htmlspecialchars']
118 && $this->tokens[$this->i + 2] === '('
119 && $this->tokens[$this->i - 1] === ')'
120 && $this->tokens[$this->i - 2][0] === T_LNUMBER
121 && $this->tokens[$this->i - 3] === ',');
122 }
123
124 /**
125 * Test whether current token is at the beginning of an htmlspecialchars()-safe var
126 *
127 * Tests whether current var is either $node->localName or $node->nodeName
128 *
129 * @return bool
130 */
131 protected function isHtmlspecialcharSafeVar()
132 {
133 return ($this->tokens[$this->i ] === [T_VARIABLE, '$node']
134 && $this->tokens[$this->i + 1] === [T_OBJECT_OPERATOR, '->']
135 && ($this->tokens[$this->i + 2] === [T_STRING, 'localName']
136 || $this->tokens[$this->i + 2] === [T_STRING, 'nodeName'])
137 && $this->tokens[$this->i + 3] === ','
138 && $this->tokens[$this->i + 4][0] === T_LNUMBER
139 && $this->tokens[$this->i + 5] === ')');
140 }
141
142 /**
143 * Test whether the cursor is at the beginning of an output assignment
144 *
145 * @return bool
146 */
147 protected function isOutputAssignment()
148 {
149 return ($this->tokens[$this->i ] === [T_VARIABLE, '$this']
150 && $this->tokens[$this->i + 1] === [T_OBJECT_OPERATOR, '->']
151 && $this->tokens[$this->i + 2] === [T_STRING, 'out']
152 && $this->tokens[$this->i + 3] === [T_CONCAT_EQUAL, '.=']);
153 }
154
155 /**
156 * Test whether the cursor is immediately after the output variable
157 *
158 * @return bool
159 */
160 protected function isPrecededByOutputVar()
161 {
162 return ($this->tokens[$this->i - 1] === [T_STRING, 'out']
163 && $this->tokens[$this->i - 2] === [T_OBJECT_OPERATOR, '->']
164 && $this->tokens[$this->i - 3] === [T_VARIABLE, '$this']);
165 }
166
167 /**
168 * Merge concatenated htmlspecialchars() calls together
169 *
170 * Must be called when the cursor is at the concatenation operator
171 *
172 * @return bool Whether calls were merged
173 */
174 protected function mergeConcatenatedHtmlSpecialChars()
175 {
176 if (!$this->isBetweenHtmlspecialcharCalls())
177 {
178 return false;
179 }
180
181 // Save the escape mode of the first call
182 $escapeMode = $this->tokens[$this->i - 2][1];
183
184 // Save the index of the comma that comes after the first argument of the first call
185 $startIndex = $this->i - 3;
186
187 // Save the index of the parenthesis that follows the second htmlspecialchars
188 $endIndex = $this->i + 2;
189
190 // Move the cursor to the first comma of the second call
191 $this->i = $endIndex;
192 $parens = 0;
193 while (++$this->i < $this->cnt)
194 {
195 if ($this->tokens[$this->i] === ',' && !$parens)
196 {
197 break;
198 }
199
200 if ($this->tokens[$this->i] === '(')
201 {
202 ++$parens;
203 }
204 elseif ($this->tokens[$this->i] === ')')
205 {
206 --$parens;
207 }
208 }
209
210 if ($this->tokens[$this->i + 1] !== [T_LNUMBER, $escapeMode])
211 {
212 return false;
213 }
214
215 // Replace the first comma of the first call with a concatenator operator
216 $this->tokens[$startIndex] = '.';
217
218 // Move the cursor back to the first comma then advance it and delete everything up to the
219 // parenthesis of the second call, included
220 $this->i = $startIndex;
221 while (++$this->i <= $endIndex)
222 {
223 unset($this->tokens[$this->i]);
224 }
225
226 return true;
227 }
228
229 /**
230 * Merge concatenated strings together
231 *
232 * Must be called when the cursor is at the concatenation operator
233 *
234 * @return bool Whether strings were merged
235 */
236 protected function mergeConcatenatedStrings()
237 {
238 if ($this->tokens[$this->i - 1][0] !== T_CONSTANT_ENCAPSED_STRING
239 || $this->tokens[$this->i + 1][0] !== T_CONSTANT_ENCAPSED_STRING
240 || $this->tokens[$this->i - 1][1][0] !== $this->tokens[$this->i + 1][1][0])
241 {
242 return false;
243 }
244
245 // Merge both strings into the right string
246 $this->tokens[$this->i + 1][1] = substr($this->tokens[$this->i - 1][1], 0, -1)
247 . substr($this->tokens[$this->i + 1][1], 1);
248
249 // Unset the tokens that have been optimized away
250 unset($this->tokens[$this->i - 1]);
251 unset($this->tokens[$this->i]);
252
253 // Advance the cursor
254 ++$this->i;
255
256 return true;
257 }
258
259 /**
260 * Optimize T_CONCAT_EQUAL assignments in an array of PHP tokens
261 *
262 * Will only optimize $this->out.= assignments
263 *
264 * @return void
265 */
266 protected function optimizeOutConcatEqual()
267 {
268 // Start at offset 4 to skip the first four tokens: <?php $this->out.=
269 $this->i = 3;
270
271 while ($this->skipTo([T_CONCAT_EQUAL, '.=']))
272 {
273 // Test whether this T_CONCAT_EQUAL is preceded with $this->out
274 if (!$this->isPrecededByOutputVar())
275 {
276 continue;
277 }
278
279 while ($this->skipPast(';'))
280 {
281 // Test whether the assignment is followed by another $this->out.= assignment
282 if (!$this->isOutputAssignment())
283 {
284 break;
285 }
286
287 // Replace the semicolon between assignments with a concatenation operator
288 $this->tokens[$this->i - 1] = '.';
289
290 // Remove the following $this->out.= assignment and move the cursor past it
291 unset($this->tokens[$this->i++]);
292 unset($this->tokens[$this->i++]);
293 unset($this->tokens[$this->i++]);
294 unset($this->tokens[$this->i++]);
295 }
296 }
297 }
298
299 /**
300 * Optimize concatenations in an array of PHP tokens
301 *
302 * - Will precompute the result of the concatenation of constant strings
303 * - Will replace the concatenation of two compatible htmlspecialchars() calls with one call to
304 * htmlspecialchars() on the concatenation of their first arguments
305 *
306 * @return void
307 */
308 protected function optimizeConcatenations()
309 {
310 $this->i = 1;
311 while ($this->skipTo('.'))
312 {
313 $this->mergeConcatenatedStrings() || $this->mergeConcatenatedHtmlSpecialChars();
314 }
315 }
316
317 /**
318 * Optimize htmlspecialchars() calls
319 *
320 * - The result of htmlspecialchars() on literals is precomputed
321 * - By default, the generator escapes all values, including variables that cannot contain
322 * special characters such as $node->localName. This pass removes those calls
323 *
324 * @return void
325 */
326 protected function optimizeHtmlspecialchars()
327 {
328 $this->i = 0;
329
330 while ($this->skipPast([T_STRING, 'htmlspecialchars']))
331 {
332 if ($this->tokens[$this->i] === '(')
333 {
334 ++$this->i;
335 $this->replaceHtmlspecialcharsLiteral() || $this->removeHtmlspecialcharsSafeVar();
336 }
337 }
338 }
339
340 /**
341 * Remove htmlspecialchars() calls on variables that are known to be safe
342 *
343 * Must be called when the cursor is at the first argument of the call
344 *
345 * @return bool Whether the call was removed
346 */
347 protected function removeHtmlspecialcharsSafeVar()
348 {
349 if (!$this->isHtmlspecialcharSafeVar())
350 {
351 return false;
352 }
353
354 // Remove the htmlspecialchars() call, except for its first argument
355 unset($this->tokens[$this->i - 2]);
356 unset($this->tokens[$this->i - 1]);
357 unset($this->tokens[$this->i + 3]);
358 unset($this->tokens[$this->i + 4]);
359 unset($this->tokens[$this->i + 5]);
360
361 // Move the cursor past the call
362 $this->i += 6;
363
364 return true;
365 }
366
367 /**
368 * Precompute the result of a htmlspecialchars() call on a string literal
369 *
370 * Must be called when the cursor is at the first argument of the call
371 *
372 * @return bool Whether the call was replaced
373 */
374 protected function replaceHtmlspecialcharsLiteral()
375 {
376 // Test whether a constant string is being escaped
377 if ($this->tokens[$this->i ][0] !== T_CONSTANT_ENCAPSED_STRING
378 || $this->tokens[$this->i + 1] !== ','
379 || $this->tokens[$this->i + 2][0] !== T_LNUMBER
380 || $this->tokens[$this->i + 3] !== ')')
381 {
382 return false;
383 }
384
385 // Escape the content of the T_CONSTANT_ENCAPSED_STRING token
386 $this->tokens[$this->i][1] = var_export(
387 htmlspecialchars(
388 stripslashes(substr($this->tokens[$this->i][1], 1, -1)),
389 $this->tokens[$this->i + 2][1]
390 ),
391 true
392 );
393
394 // Remove the htmlspecialchars() call, except for the T_CONSTANT_ENCAPSED_STRING token
395 unset($this->tokens[$this->i - 2]);
396 unset($this->tokens[$this->i - 1]);
397 unset($this->tokens[++$this->i]);
398 unset($this->tokens[++$this->i]);
399 unset($this->tokens[++$this->i]);
400
401 return true;
402 }
403
404 /**
405 * Move the cursor past given token
406 *
407 * @param array|string $token Target token
408 * @return bool Whether a matching token was found and the cursor is within bounds
409 */
410 protected function skipPast($token)
411 {
412 return ($this->skipTo($token) && ++$this->i < $this->cnt);
413 }
414
415 /**
416 * Move the cursor until it reaches given token
417 *
418 * @param array|string $token Target token
419 * @return bool Whether a matching token was found
420 */
421 protected function skipTo($token)
422 {
423 while (++$this->i < $this->cnt)
424 {
425 if ($this->tokens[$this->i] === $token)
426 {
427 return true;
428 }
429 }
430
431 return false;
432 }
433 }