| @@ -22,8 +22,10 @@ | ||
| 22 | 22 | * @internal |
| 23 | 23 | */ |
| 24 | 24 | class Reader |
| 25 | 25 | { |
| 26 | + private static array $anchoredPatterns = []; | |
| 27 | + | |
| 26 | 28 | private int $length; |
| 27 | 29 | private int $position = 0; |
| 28 | 30 | |
| 29 | 31 | public function __construct( |
| @@ -60,11 +62,14 @@ | ||
| 60 | 62 | } |
| 61 | 63 | |
| 62 | 64 | public function findPattern(string $pattern): array|false |
| 63 | 65 | { |
| 64 | - $source = substr($this->source, $this->position); | |
| 66 | + // Match in place instead of copying the remaining source before every probe. | |
| 67 | + // Combined with an offset, "^" still anchors at the start of the whole subject, | |
| 68 | + // so a leading anchor is turned into "\\G", which anchors at the offset instead. | |
| 69 | + $pattern = self::$anchoredPatterns[$pattern] ??= '^' === ($pattern[1] ?? '') ? $pattern[0].'\\G'.substr($pattern, 2) : $pattern; | |
| 65 | 70 | |
| 66 | - if (preg_match($pattern, $source, $matches)) { | |
| 71 | + if (preg_match($pattern, $this->source, $matches, 0, $this->position)) { | |
| 67 | 72 | return $matches; |
| 68 | 73 | } |
| 69 | 74 | |
| 70 | 75 | return false; |