1<?php declare(strict_types=1);
2
3namespace PhpParser\Lexer\TokenEmulator;
4
5use PhpParser\Lexer\Emulative;
6
7final 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
13REGEX;
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