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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

Optimizer.php

Zuletzt modifiziert: 02.04.2025, 15:04 - Dateigröße: 10.70 KiB


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  }