1: <?php declare(strict_types=1);
2:
3: namespace PhpParser\Lexer\TokenEmulator;
4:
5: use PhpParser\Lexer\Emulative;
6:
7: final class FlexibleDocStringEmulator extends TokenEmulator
8: {
9: const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
10: /<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
11: (?:.*\r?\n)*?
12: (?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
13: REGEX;
14:
15: public function getPhpVersion(): string
16: {
17: return Emulative::PHP_7_3;
18: }
19:
20: public function isEmulationNeeded(string $code) : bool
21: {
22: return strpos($code, '<<<') !== false;
23: }
24:
25: public function emulate(string $code, array $tokens): array
26: {
27: // Handled by preprocessing + fixup.
28: return $tokens;
29: }
30:
31: public function reverseEmulate(string $code, array $tokens): array
32: {
33: // Not supported.
34: return $tokens;
35: }
36:
37: public function preprocessCode(string $code, array &$patches): string {
38: if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
39: // No heredoc/nowdoc found
40: return $code;
41: }
42:
43: // Keep track of how much we need to adjust string offsets due to the modifications we
44: // already made
45: $posDelta = 0;
46: foreach ($matches as $match) {
47: $indentation = $match['indentation'][0];
48: $indentationStart = $match['indentation'][1];
49:
50: $separator = $match['separator'][0];
51: $separatorStart = $match['separator'][1];
52:
53: if ($indentation === '' && $separator !== '') {
54: // Ordinary heredoc/nowdoc
55: continue;
56: }
57:
58: if ($indentation !== '') {
59: // Remove indentation
60: $indentationLen = strlen($indentation);
61: $code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
62: $patches[] = [$indentationStart + $posDelta, 'add', $indentation];
63: $posDelta -= $indentationLen;
64: }
65:
66: if ($separator === '') {
67: // Insert newline as separator
68: $code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
69: $patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
70: $posDelta += 1;
71: }
72: }
73:
74: return $code;
75: }
76: }
77: