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

ControlStructuresOptimizer.php

Zuletzt modifiziert: 02.04.2025, 15:04 - Dateigröße: 7.22 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  * Optimize the control structures of a script
012  *
013  * Removes brackets in control structures wherever possible. Prevents the generation of EXT_STMT
014  * opcodes where they're not strictly required.
015  */
016  class ControlStructuresOptimizer extends AbstractOptimizer
017  {
018      /**
019      * @var integer Number of braces encountered in current source
020      */
021      protected $braces;
022   
023      /**
024      * @var array Current context
025      */
026      protected $context;
027   
028      /**
029      * Test whether current block ends with an if or elseif control structure
030      *
031      * @return bool
032      */
033      protected function blockEndsWithIf()
034      {
035          return in_array($this->context['lastBlock'], [T_IF, T_ELSEIF], true);
036      }
037   
038      /**
039      * Test whether the token at current index is a control structure
040      *
041      * @return bool
042      */
043      protected function isControlStructure()
044      {
045          return in_array(
046              $this->tokens[$this->i][0],
047              [T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_IF, T_WHILE],
048              true
049          );
050      }
051   
052      /**
053      * Test whether current block is followed by an elseif/else structure
054      *
055      * @return bool
056      */
057      protected function isFollowedByElse()
058      {
059          if ($this->i > $this->cnt - 4)
060          {
061              // It doesn't have room for another block
062              return false;
063          }
064   
065          // Compute the index of the next non-whitespace token
066          $k = $this->i + 1;
067   
068          if ($this->tokens[$k][0] === T_WHITESPACE)
069          {
070              ++$k;
071          }
072   
073          return in_array($this->tokens[$k][0], [T_ELSEIF, T_ELSE], true);
074      }
075   
076      /**
077      * Test whether braces must be preserved in current context
078      *
079      * @return bool
080      */
081      protected function mustPreserveBraces()
082      {
083          // If current block ends with if/elseif and is followed by elseif/else, we must preserve
084          // its braces to prevent it from merging with the outer elseif/else. IOW, we must preserve
085          // the braces if "if{if{}}else" would become "if{if else}"
086          return ($this->blockEndsWithIf() && $this->isFollowedByElse());
087      }
088   
089      /**
090      * Optimize control structures in stored tokens
091      *
092      * @return void
093      */
094      protected function optimizeTokens()
095      {
096          while (++$this->i < $this->cnt)
097          {
098              if ($this->tokens[$this->i] === ';')
099              {
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  }