Mercurial > hg > isophonics-drupal-site
comparison vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php @ 17:129ea1e6d783
Update, including to Drupal core 8.6.10
author | Chris Cannam |
---|---|
date | Thu, 28 Feb 2019 13:21:36 +0000 |
parents | 5fb285c0d0e3 |
children |
comparison
equal
deleted
inserted
replaced
16:c2387f117808 | 17:129ea1e6d783 |
---|---|
1 <?php declare(strict_types=1); | 1 <?php declare(strict_types=1); |
2 | 2 |
3 namespace PhpParser\Lexer; | 3 namespace PhpParser\Lexer; |
4 | |
5 use PhpParser\Error; | |
6 use PhpParser\ErrorHandler; | |
7 use PhpParser\Parser; | |
4 | 8 |
5 class Emulative extends \PhpParser\Lexer | 9 class Emulative extends \PhpParser\Lexer |
6 { | 10 { |
7 /* No features requiring emulation have been added in PHP > 7.0 */ | 11 const PHP_7_3 = '7.3.0dev'; |
12 const PHP_7_4 = '7.4.0dev'; | |
13 | |
14 const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX' | |
15 /<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n | |
16 (?:.*\r?\n)*? | |
17 (?<indentation>\h*)\2(?![a-zA-Z_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x | |
18 REGEX; | |
19 | |
20 const T_COALESCE_EQUAL = 1007; | |
21 | |
22 /** | |
23 * @var mixed[] Patches used to reverse changes introduced in the code | |
24 */ | |
25 private $patches = []; | |
26 | |
27 /** | |
28 * @param mixed[] $options | |
29 */ | |
30 public function __construct(array $options = []) | |
31 { | |
32 parent::__construct($options); | |
33 | |
34 // add emulated tokens here | |
35 $this->tokenMap[self::T_COALESCE_EQUAL] = Parser\Tokens::T_COALESCE_EQUAL; | |
36 } | |
37 | |
38 public function startLexing(string $code, ErrorHandler $errorHandler = null) { | |
39 $this->patches = []; | |
40 | |
41 if ($this->isEmulationNeeded($code) === false) { | |
42 // Nothing to emulate, yay | |
43 parent::startLexing($code, $errorHandler); | |
44 return; | |
45 } | |
46 | |
47 $collector = new ErrorHandler\Collecting(); | |
48 | |
49 // 1. emulation of heredoc and nowdoc new syntax | |
50 $preparedCode = $this->processHeredocNowdoc($code); | |
51 parent::startLexing($preparedCode, $collector); | |
52 | |
53 // 2. emulation of ??= token | |
54 $this->processCoaleseEqual($code); | |
55 $this->fixupTokens(); | |
56 | |
57 $errors = $collector->getErrors(); | |
58 if (!empty($errors)) { | |
59 $this->fixupErrors($errors); | |
60 foreach ($errors as $error) { | |
61 $errorHandler->handleError($error); | |
62 } | |
63 } | |
64 } | |
65 | |
66 private function isCoalesceEqualEmulationNeeded(string $code): bool | |
67 { | |
68 // skip version where this works without emulation | |
69 if (version_compare(\PHP_VERSION, self::PHP_7_4, '>=')) { | |
70 return false; | |
71 } | |
72 | |
73 return strpos($code, '??=') !== false; | |
74 } | |
75 | |
76 private function processCoaleseEqual(string $code) | |
77 { | |
78 if ($this->isCoalesceEqualEmulationNeeded($code) === false) { | |
79 return; | |
80 } | |
81 | |
82 // We need to manually iterate and manage a count because we'll change | |
83 // the tokens array on the way | |
84 $line = 1; | |
85 for ($i = 0, $c = count($this->tokens); $i < $c; ++$i) { | |
86 if (isset($this->tokens[$i + 1])) { | |
87 if ($this->tokens[$i][0] === T_COALESCE && $this->tokens[$i + 1] === '=') { | |
88 array_splice($this->tokens, $i, 2, [ | |
89 [self::T_COALESCE_EQUAL, '??=', $line] | |
90 ]); | |
91 $c--; | |
92 continue; | |
93 } | |
94 } | |
95 if (\is_array($this->tokens[$i])) { | |
96 $line += substr_count($this->tokens[$i][1], "\n"); | |
97 } | |
98 } | |
99 } | |
100 | |
101 private function isHeredocNowdocEmulationNeeded(string $code): bool | |
102 { | |
103 // skip version where this works without emulation | |
104 if (version_compare(\PHP_VERSION, self::PHP_7_3, '>=')) { | |
105 return false; | |
106 } | |
107 | |
108 return strpos($code, '<<<') !== false; | |
109 } | |
110 | |
111 private function processHeredocNowdoc(string $code): string | |
112 { | |
113 if ($this->isHeredocNowdocEmulationNeeded($code) === false) { | |
114 return $code; | |
115 } | |
116 | |
117 if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) { | |
118 // No heredoc/nowdoc found | |
119 return $code; | |
120 } | |
121 | |
122 // Keep track of how much we need to adjust string offsets due to the modifications we | |
123 // already made | |
124 $posDelta = 0; | |
125 foreach ($matches as $match) { | |
126 $indentation = $match['indentation'][0]; | |
127 $indentationStart = $match['indentation'][1]; | |
128 | |
129 $separator = $match['separator'][0]; | |
130 $separatorStart = $match['separator'][1]; | |
131 | |
132 if ($indentation === '' && $separator !== '') { | |
133 // Ordinary heredoc/nowdoc | |
134 continue; | |
135 } | |
136 | |
137 if ($indentation !== '') { | |
138 // Remove indentation | |
139 $indentationLen = strlen($indentation); | |
140 $code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen); | |
141 $this->patches[] = [$indentationStart + $posDelta, 'add', $indentation]; | |
142 $posDelta -= $indentationLen; | |
143 } | |
144 | |
145 if ($separator === '') { | |
146 // Insert newline as separator | |
147 $code = substr_replace($code, "\n", $separatorStart + $posDelta, 0); | |
148 $this->patches[] = [$separatorStart + $posDelta, 'remove', "\n"]; | |
149 $posDelta += 1; | |
150 } | |
151 } | |
152 | |
153 return $code; | |
154 } | |
155 | |
156 private function isEmulationNeeded(string $code): bool | |
157 { | |
158 if ($this->isHeredocNowdocEmulationNeeded($code)) { | |
159 return true; | |
160 } | |
161 | |
162 if ($this->isCoalesceEqualEmulationNeeded($code)) { | |
163 return true; | |
164 } | |
165 | |
166 return false; | |
167 } | |
168 | |
169 private function fixupTokens() | |
170 { | |
171 if (\count($this->patches) === 0) { | |
172 return; | |
173 } | |
174 | |
175 // Load first patch | |
176 $patchIdx = 0; | |
177 | |
178 list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; | |
179 | |
180 // We use a manual loop over the tokens, because we modify the array on the fly | |
181 $pos = 0; | |
182 for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) { | |
183 $token = $this->tokens[$i]; | |
184 if (\is_string($token)) { | |
185 // We assume that patches don't apply to string tokens | |
186 $pos += \strlen($token); | |
187 continue; | |
188 } | |
189 | |
190 $len = \strlen($token[1]); | |
191 $posDelta = 0; | |
192 while ($patchPos >= $pos && $patchPos < $pos + $len) { | |
193 $patchTextLen = \strlen($patchText); | |
194 if ($patchType === 'remove') { | |
195 if ($patchPos === $pos && $patchTextLen === $len) { | |
196 // Remove token entirely | |
197 array_splice($this->tokens, $i, 1, []); | |
198 $i--; | |
199 $c--; | |
200 } else { | |
201 // Remove from token string | |
202 $this->tokens[$i][1] = substr_replace( | |
203 $token[1], '', $patchPos - $pos + $posDelta, $patchTextLen | |
204 ); | |
205 $posDelta -= $patchTextLen; | |
206 } | |
207 } elseif ($patchType === 'add') { | |
208 // Insert into the token string | |
209 $this->tokens[$i][1] = substr_replace( | |
210 $token[1], $patchText, $patchPos - $pos + $posDelta, 0 | |
211 ); | |
212 $posDelta += $patchTextLen; | |
213 } else { | |
214 assert(false); | |
215 } | |
216 | |
217 // Fetch the next patch | |
218 $patchIdx++; | |
219 if ($patchIdx >= \count($this->patches)) { | |
220 // No more patches, we're done | |
221 return; | |
222 } | |
223 | |
224 list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; | |
225 | |
226 // Multiple patches may apply to the same token. Reload the current one to check | |
227 // If the new patch applies | |
228 $token = $this->tokens[$i]; | |
229 } | |
230 | |
231 $pos += $len; | |
232 } | |
233 | |
234 // A patch did not apply | |
235 assert(false); | |
236 } | |
237 | |
238 /** | |
239 * Fixup line and position information in errors. | |
240 * | |
241 * @param Error[] $errors | |
242 */ | |
243 private function fixupErrors(array $errors) { | |
244 foreach ($errors as $error) { | |
245 $attrs = $error->getAttributes(); | |
246 | |
247 $posDelta = 0; | |
248 $lineDelta = 0; | |
249 foreach ($this->patches as $patch) { | |
250 list($patchPos, $patchType, $patchText) = $patch; | |
251 if ($patchPos >= $attrs['startFilePos']) { | |
252 // No longer relevant | |
253 break; | |
254 } | |
255 | |
256 if ($patchType === 'add') { | |
257 $posDelta += strlen($patchText); | |
258 $lineDelta += substr_count($patchText, "\n"); | |
259 } else { | |
260 $posDelta -= strlen($patchText); | |
261 $lineDelta -= substr_count($patchText, "\n"); | |
262 } | |
263 } | |
264 | |
265 $attrs['startFilePos'] += $posDelta; | |
266 $attrs['endFilePos'] += $posDelta; | |
267 $attrs['startLine'] += $lineDelta; | |
268 $attrs['endLine'] += $lineDelta; | |
269 $error->setAttributes($attrs); | |
270 } | |
271 } | |
8 } | 272 } |