[ Index ] |
PHP Cross Reference of phpBB-3.3.14-deutsch |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Mon Nov 25 19:05:08 2024 | Cross-referenced by PHPXref 0.7.1 |