[ Index ]

PHP Cross Reference of phpBB-3.3.14-deutsch

title

Body

[close]

/vendor/s9e/text-formatter/src/Configurator/RendererGenerators/PHP/ -> ControlStructuresOptimizer.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  /**
  11  * Optimize the control structures of a script
  12  *
  13  * Removes brackets in control structures wherever possible. Prevents the generation of EXT_STMT
  14  * opcodes where they're not strictly required.
  15  */
  16  class ControlStructuresOptimizer extends AbstractOptimizer
  17  {
  18      /**
  19      * @var integer Number of braces encountered in current source
  20      */
  21      protected $braces;
  22  
  23      /**
  24      * @var array Current context
  25      */
  26      protected $context;
  27  
  28      /**
  29      * Test whether current block ends with an if or elseif control structure
  30      *
  31      * @return bool
  32      */
  33  	protected function blockEndsWithIf()
  34      {
  35          return in_array($this->context['lastBlock'], [T_IF, T_ELSEIF], true);
  36      }
  37  
  38      /**
  39      * Test whether the token at current index is a control structure
  40      *
  41      * @return bool
  42      */
  43  	protected function isControlStructure()
  44      {
  45          return in_array(
  46              $this->tokens[$this->i][0],
  47              [T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_IF, T_WHILE],
  48              true
  49          );
  50      }
  51  
  52      /**
  53      * Test whether current block is followed by an elseif/else structure
  54      *
  55      * @return bool
  56      */
  57  	protected function isFollowedByElse()
  58      {
  59          if ($this->i > $this->cnt - 4)
  60          {
  61              // It doesn't have room for another block
  62              return false;
  63          }
  64  
  65          // Compute the index of the next non-whitespace token
  66          $k = $this->i + 1;
  67  
  68          if ($this->tokens[$k][0] === T_WHITESPACE)
  69          {
  70              ++$k;
  71          }
  72  
  73          return in_array($this->tokens[$k][0], [T_ELSEIF, T_ELSE], true);
  74      }
  75  
  76      /**
  77      * Test whether braces must be preserved in current context
  78      *
  79      * @return bool
  80      */
  81  	protected function mustPreserveBraces()
  82      {
  83          // If current block ends with if/elseif and is followed by elseif/else, we must preserve
  84          // its braces to prevent it from merging with the outer elseif/else. IOW, we must preserve
  85          // the braces if "if{if{}}else" would become "if{if else}"
  86          return ($this->blockEndsWithIf() && $this->isFollowedByElse());
  87      }
  88  
  89      /**
  90      * Optimize control structures in stored tokens
  91      *
  92      * @return void
  93      */
  94  	protected function optimizeTokens()
  95      {
  96          while (++$this->i < $this->cnt)
  97          {
  98              if ($this->tokens[$this->i] === ';')
  99              {
 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  }


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