[ 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 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 }
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 |