*/ protected readonly array $patterns; public function __construct(StringFragmentPattern ...$patterns) { $this->patterns = $patterns; } /** * @param string $value to be split into `raw` and `expression` fragments * @param int $flags (optional) `FLAG_UNMATCHED_AS_NULL` */ public function split(string $value, int $flags = 0): ?StringFragmentCollection { $pattern = chr(1) . implode('|', $this->preparePatterns()) . chr(1); $options = PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE | PREG_SET_ORDER; if (!preg_match_all($pattern, $value, $matches, $options)) { if (($flags & self::FLAG_UNMATCHED_AS_NULL) === self::FLAG_UNMATCHED_AS_NULL) { return null; } return new StringFragmentCollection(StringFragment::raw($value)); } $collection = new StringFragmentCollection(); foreach ($matches as $match) { // filters string keys (e.g. `expression_a1b2c3d4e5`) from matches, skips numeric indexes $types = array_filter( array_keys($match), static fn(int|string $type) => is_string($type) && $type !== '' ); foreach ($types as $type) { $matchOffset = $match[$type][1]; if ($matchOffset < 0) { continue; } $matchValue = $match[$type][0]; // matches contain only pattern matches, but no raw string literals - by comparing the // position of the collection with the current offset, missing raw literals are synchronized if ($collection->getLength() < $matchOffset) { $gapValue = substr($value, $collection->getLength(), $matchOffset - $collection->getLength()); $collection = $collection->with(StringFragment::raw($gapValue)); } $collection = $collection->with(StringFragment::expression($matchValue)); } } // synchronize missing raw string literals // (at the end of the given value after previous expression) if ($collection->getLength() < strlen($value)) { $gapValue = substr($value, $collection->getLength()); $collection = $collection->with(StringFragment::raw($gapValue)); } return $collection; } /** * @return list */ protected function preparePatterns(): array { return array_map( static fn(StringFragmentPattern $pattern) => $pattern->compilePattern(), $this->patterns ); } }