[ Index ]

PHP Cross Reference of phpBB-3.3.14-deutsch

title

Body

[close]

/vendor/s9e/text-formatter/src/Configurator/RendererGenerators/PHP/ -> BranchOutputOptimizer.php (source)

   1  <?php
   2  
   3  /**
   4  * @package   s9e\TextFormatter
   5  * @copyright Copyright (c) 2010-2022 The s9e authors
   6  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
   7  */
   8  namespace s9e\TextFormatter\Configurator\RendererGenerators\PHP;
   9  
  10  class BranchOutputOptimizer
  11  {
  12      /**
  13      * @var integer Number of tokens
  14      */
  15      protected $cnt;
  16  
  17      /**
  18      * @var integer Current token index
  19      */
  20      protected $i;
  21  
  22      /**
  23      * @var array Tokens from current source
  24      */
  25      protected $tokens;
  26  
  27      /**
  28      * Optimize the code used to output content
  29      *
  30      * This method will go through the array of tokens, identify if/elseif/else blocks that contain
  31      * identical code at the beginning or the end and move the common code outside of the block
  32      *
  33      * @param  array $tokens Array of tokens from token_get_all()
  34      * @return string        Optimized code
  35      */
  36  	public function optimize(array $tokens)
  37      {
  38          $this->tokens = $tokens;
  39          $this->i      = 0;
  40          $this->cnt    = count($this->tokens);
  41  
  42          $php = '';
  43          while (++$this->i < $this->cnt)
  44          {
  45              if ($this->tokens[$this->i][0] === T_IF)
  46              {
  47                  $php .= $this->serializeIfBlock($this->parseIfBlock());
  48              }
  49              else
  50              {
  51                  $php .= $this->serializeToken($this->tokens[$this->i]);
  52              }
  53          }
  54  
  55          // Free the memory taken up by the tokens
  56          unset($this->tokens);
  57  
  58          return $php;
  59      }
  60  
  61      /**
  62      * Capture the expressions used in any number of consecutive output statements
  63      *
  64      * Starts looking at current index. Ends at the first token that's not part of an output
  65      * statement
  66      *
  67      * @return string[]
  68      */
  69  	protected function captureOutput()
  70      {
  71          $expressions = [];
  72          while ($this->skipOutputAssignment())
  73          {
  74              do
  75              {
  76                  $expressions[] = $this->captureOutputExpression();
  77              }
  78              while ($this->tokens[$this->i++] === '.');
  79          }
  80  
  81          return $expressions;
  82      }
  83  
  84      /**
  85      * Capture an expression used in output at current index
  86      *
  87      * Ends on "." or ";"
  88      *
  89      * @return string
  90      */
  91  	protected function captureOutputExpression()
  92      {
  93          $parens = 0;
  94          $php = '';
  95          do
  96          {
  97              if ($this->tokens[$this->i] === ';')
  98              {
  99                  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  }


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1