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

BranchOutputOptimizer.php

Zuletzt modifiziert: 02.04.2025, 15:04 - Dateigröße: 10.11 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  class BranchOutputOptimizer
011  {
012      /**
013      * @var integer Number of tokens
014      */
015      protected $cnt;
016   
017      /**
018      * @var integer Current token index
019      */
020      protected $i;
021   
022      /**
023      * @var array Tokens from current source
024      */
025      protected $tokens;
026   
027      /**
028      * Optimize the code used to output content
029      *
030      * This method will go through the array of tokens, identify if/elseif/else blocks that contain
031      * identical code at the beginning or the end and move the common code outside of the block
032      *
033      * @param  array $tokens Array of tokens from token_get_all()
034      * @return string        Optimized code
035      */
036      public function optimize(array $tokens)
037      {
038          $this->tokens = $tokens;
039          $this->i      = 0;
040          $this->cnt    = count($this->tokens);
041   
042          $php = '';
043          while (++$this->i < $this->cnt)
044          {
045              if ($this->tokens[$this->i][0] === T_IF)
046              {
047                  $php .= $this->serializeIfBlock($this->parseIfBlock());
048              }
049              else
050              {
051                  $php .= $this->serializeToken($this->tokens[$this->i]);
052              }
053          }
054   
055          // Free the memory taken up by the tokens
056          unset($this->tokens);
057   
058          return $php;
059      }
060   
061      /**
062      * Capture the expressions used in any number of consecutive output statements
063      *
064      * Starts looking at current index. Ends at the first token that's not part of an output
065      * statement
066      *
067      * @return string[]
068      */
069      protected function captureOutput()
070      {
071          $expressions = [];
072          while ($this->skipOutputAssignment())
073          {
074              do
075              {
076                  $expressions[] = $this->captureOutputExpression();
077              }
078              while ($this->tokens[$this->i++] === '.');
079          }
080   
081          return $expressions;
082      }
083   
084      /**
085      * Capture an expression used in output at current index
086      *
087      * Ends on "." or ";"
088      *
089      * @return string
090      */
091      protected function captureOutputExpression()
092      {
093          $parens = 0;
094          $php = '';
095          do
096          {
097              if ($this->tokens[$this->i] === ';')
098              {
099                  break;
100              }
101              elseif ($this->tokens[$this->i] === '.' && !$parens)
102              {
103                  break;
104              }
105              elseif ($this->tokens[$this->i] === '(')
106              {
107                  ++$parens;
108              }
109              elseif ($this->tokens[$this->i] === ')')
110              {
111                  --$parens;
112              }
113   
114              $php .= $this->serializeToken($this->tokens[$this->i]);
115          }
116          while (++$this->i < $this->cnt);
117   
118          return $php;
119      }
120   
121      /**
122      * Capture the source of a control structure from its keyword to its opening brace
123      *
124      * Ends after the brace, but the brace itself is not returned
125      *
126      * @return string
127      */
128      protected function captureStructure()
129      {
130          $php = '';
131          do
132          {
133              $php .= $this->serializeToken($this->tokens[$this->i]);
134          }
135          while ($this->tokens[++$this->i] !== '{');
136   
137          // Move past the {
138          ++$this->i;
139   
140          return $php;
141      }
142   
143      /**
144      * Test whether the token at current index is an if/elseif/else token
145      *
146      * @return bool
147      */
148      protected function isBranchToken()
149      {
150          return in_array($this->tokens[$this->i][0], [T_ELSE, T_ELSEIF, T_IF], true);
151      }
152   
153      /**
154      * Merge the branches of an if/elseif/else block
155      *
156      * Returns an array that contains the following:
157      *
158      *  - before: array of PHP expressions to be output before the block
159      *  - source: PHP code for the if block
160      *  - after:  array of PHP expressions to be output after the block
161      *
162      * @param  array $branches
163      * @return array
164      */
165      protected function mergeIfBranches(array $branches)
166      {
167          // Test whether the branches cover all code paths. Without a "else" branch at the end, we
168          // cannot optimize
169          $lastBranch = end($branches);
170          if ($lastBranch['structure'] === 'else')
171          {
172              $before = $this->optimizeBranchesHead($branches);
173              $after  = $this->optimizeBranchesTail($branches);
174          }
175          else
176          {
177              $before = $after = [];
178          }
179   
180          $source = '';
181          foreach ($branches as $branch)
182          {
183              $source .= $this->serializeBranch($branch);
184          }
185   
186          return [
187              'before' => $before,
188              'source' => $source,
189              'after'  => $after
190          ];
191      }
192   
193      /**
194      * Merge two consecutive series of consecutive output expressions together
195      *
196      * @param  array $left  First series
197      * @param  array $right Second series
198      * @return array        Merged series
199      */
200      protected function mergeOutput(array $left, array $right)
201      {
202          if (empty($left))
203          {
204              return $right;
205          }
206   
207          if (empty($right))
208          {
209              return $left;
210          }
211   
212          // Test whether we can merge the last expression on the left with the first expression on
213          // the right
214          $k = count($left) - 1;
215   
216          if (substr($left[$k], -1) === "'" && $right[0][0] === "'")
217          {
218              $right[0] = substr($left[$k], 0, -1) . substr($right[0], 1);
219              unset($left[$k]);
220          }
221   
222          return array_merge($left, $right);
223      }
224   
225      /**
226      * Optimize the "head" part of a series of branches in-place
227      *
228      * @param  array    &$branches Array of branches, modified in-place
229      * @return string[]            PHP expressions removed from the "head" part of the branches
230      */
231      protected function optimizeBranchesHead(array &$branches)
232      {
233          // Capture common output
234          $before = $this->optimizeBranchesOutput($branches, 'head');
235   
236          // Move the branch output to the tail for branches that have no body
237          foreach ($branches as &$branch)
238          {
239              if ($branch['body'] !== '' || !empty($branch['tail']))
240              {
241                  continue;
242              }
243   
244              $branch['tail'] = array_reverse($branch['head']);
245              $branch['head'] = [];
246          }
247          unset($branch);
248   
249          return $before;
250      }
251   
252      /**
253      * Optimize the output of given branches
254      *
255      * @param  array    &$branches Array of branches
256      * @param  string    $which    Which end to optimize ("head" or "tail")
257      * @return string[]            PHP expressions removed from the given part of the branches
258      */
259      protected function optimizeBranchesOutput(array &$branches, $which)
260      {
261          $expressions = [];
262          while (isset($branches[0][$which][0]))
263          {
264              $expr = $branches[0][$which][0];
265              foreach ($branches as $branch)
266              {
267                  if (!isset($branch[$which][0]) || $branch[$which][0] !== $expr)
268                  {
269                      break 2;
270                  }
271              }
272   
273              $expressions[] = $expr;
274              foreach ($branches as &$branch)
275              {
276                  array_shift($branch[$which]);
277              }
278              unset($branch);
279          }
280   
281          return $expressions;
282      }
283   
284      /**
285      * Optimize the "tail" part of a series of branches in-place
286      *
287      * @param  array    &$branches Array of branches, modified in-place
288      * @return string[]            PHP expressions removed from the "tail" part of the branches
289      */
290      protected function optimizeBranchesTail(array &$branches)
291      {
292          return $this->optimizeBranchesOutput($branches, 'tail');
293      }
294   
295      /**
296      * Parse the if, elseif or else branch starting at current index
297      *
298      * Ends at the last }
299      *
300      * @return array Branch's data ("structure", "head", "body", "tail")
301      */
302      protected function parseBranch()
303      {
304          // Record the control structure
305          $structure = $this->captureStructure();
306   
307          // Record the output expressions at the start of this branch
308          $head = $this->captureOutput();
309          $body = '';
310          $tail = [];
311   
312          $braces = 0;
313          do
314          {
315              $tail = $this->mergeOutput($tail, array_reverse($this->captureOutput()));
316              if ($this->tokens[$this->i] === '}' && !$braces)
317              {
318                  break;
319              }
320   
321              $body .= $this->serializeOutput(array_reverse($tail));
322              $tail  = [];
323   
324              if ($this->tokens[$this->i][0] === T_IF)
325              {
326                  $child = $this->parseIfBlock();
327   
328                  // If this is the start of current branch, what's been optimized away and moved
329                  // outside, before the child branch is the head of this one. Otherwise it's just
330                  // part of its body
331                  if ($body === '')
332                  {
333                      $head = $this->mergeOutput($head, $child['before']);
334                  }
335                  else
336                  {
337                      $body .= $this->serializeOutput($child['before']);
338                  }
339   
340                  $body .= $child['source'];
341                  $tail  = $child['after'];
342              }
343              else
344              {
345                  $body .= $this->serializeToken($this->tokens[$this->i]);
346   
347                  if ($this->tokens[$this->i] === '{')
348                  {
349                      ++$braces;
350                  }
351                  elseif ($this->tokens[$this->i] === '}')
352                  {
353                      --$braces;
354                  }
355              }
356          }
357          while (++$this->i < $this->cnt);
358   
359          return [
360              'structure' => $structure,
361              'head'      => $head,
362              'body'      => $body,
363              'tail'      => $tail
364          ];
365      }
366   
367      /**
368      * Parse the if block (including elseif/else branches) starting at current index
369      *
370      * @return array
371      */
372      protected function parseIfBlock()
373      {
374          $branches = [];
375          do
376          {
377              $branches[] = $this->parseBranch();
378          }
379          while (++$this->i < $this->cnt && $this->isBranchToken());
380   
381          // Move the index back to the last token used
382          --$this->i;
383   
384          return $this->mergeIfBranches($branches);
385      }
386   
387      /**
388      * Serialize a recorded branch back to PHP
389      *
390      * @param  array  $branch
391      * @return string
392      */
393      protected function serializeBranch(array $branch)
394      {
395          // Optimize away "else{}" completely
396          if ($branch['structure'] === 'else'
397           && $branch['body']      === ''
398           && empty($branch['head'])
399           && empty($branch['tail']))
400          {
401              return '';
402          }
403   
404          return $branch['structure'] . '{' . $this->serializeOutput($branch['head']) . $branch['body'] . $this->serializeOutput(array_reverse($branch['tail'])) . '}';
405      }
406   
407      /**
408      * Serialize a series of recorded branch back to PHP
409      *
410      * @param  array  $block
411      * @return string
412      */
413      protected function serializeIfBlock(array $block)
414      {
415          return $this->serializeOutput($block['before']) . $block['source'] . $this->serializeOutput(array_reverse($block['after']));
416      }
417   
418      /**
419      * Serialize a series of output expressions
420      *
421      * @param  string[] $expressions Array of PHP expressions
422      * @return string                PHP code used to append given expressions to the output
423      */
424      protected function serializeOutput(array $expressions)
425      {
426          if (empty($expressions))
427          {
428              return '';
429          }
430   
431          return '$this->out.=' . implode('.', $expressions) . ';';
432      }
433   
434      /**
435      * Serialize a token back to PHP
436      *
437      * @param  array|string $token Token from token_get_all()
438      * @return string              PHP code
439      */
440      protected function serializeToken($token)
441      {
442          return (is_array($token)) ? $token[1] : $token;
443      }
444   
445      /**
446      * Attempt to move past output assignment at current index
447      *
448      * @return bool Whether if an output assignment was skipped
449      */
450      protected function skipOutputAssignment()
451      {
452          if ($this->tokens[$this->i    ][0] !== T_VARIABLE
453           || $this->tokens[$this->i    ][1] !== '$this'
454           || $this->tokens[$this->i + 1][0] !== T_OBJECT_OPERATOR
455           || $this->tokens[$this->i + 2][0] !== T_STRING
456           || $this->tokens[$this->i + 2][1] !== 'out'
457           || $this->tokens[$this->i + 3][0] !== T_CONCAT_EQUAL)
458          {
459               return false;
460          }
461   
462          // Move past the concat assignment
463          $this->i += 4;
464   
465          return true;
466      }
467  }