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. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
BranchOutputOptimizer.php
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 class BranchOutputOptimizer
011 {
012 /**
013 * @var integer Number of tokens
014 */
015 protected $cnt;
016
017 /**
018 * @var integer Current token index
019 */
020 protected $i;
021
022 /**
023 * @var array Tokens from current source
024 */
025 protected $tokens;
026
027 /**
028 * Optimize the code used to output content
029 *
030 * This method will go through the array of tokens, identify if/elseif/else blocks that contain
031 * identical code at the beginning or the end and move the common code outside of the block
032 *
033 * @param array $tokens Array of tokens from token_get_all()
034 * @return string Optimized code
035 */
036 public function optimize(array $tokens)
037 {
038 $this->tokens = $tokens;
039 $this->i = 0;
040 $this->cnt = count($this->tokens);
041
042 $php = '';
043 while (++$this->i < $this->cnt)
044 {
045 if ($this->tokens[$this->i][0] === T_IF)
046 {
047 $php .= $this->serializeIfBlock($this->parseIfBlock());
048 }
049 else
050 {
051 $php .= $this->serializeToken($this->tokens[$this->i]);
052 }
053 }
054
055 // Free the memory taken up by the tokens
056 unset($this->tokens);
057
058 return $php;
059 }
060
061 /**
062 * Capture the expressions used in any number of consecutive output statements
063 *
064 * Starts looking at current index. Ends at the first token that's not part of an output
065 * statement
066 *
067 * @return string[]
068 */
069 protected function captureOutput()
070 {
071 $expressions = [];
072 while ($this->skipOutputAssignment())
073 {
074 do
075 {
076 $expressions[] = $this->captureOutputExpression();
077 }
078 while ($this->tokens[$this->i++] === '.');
079 }
080
081 return $expressions;
082 }
083
084 /**
085 * Capture an expression used in output at current index
086 *
087 * Ends on "." or ";"
088 *
089 * @return string
090 */
091 protected function captureOutputExpression()
092 {
093 $parens = 0;
094 $php = '';
095 do
096 {
097 if ($this->tokens[$this->i] === ';')
098 {
099 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 }