data = new Data($selector); } protected static function parseComposite(): Matcher { if (self::$debug) { self::debug(); } $result = self::parseExpression(); $peek = self::$instance->data->peek(); while (in_array($peek, [ '|', '&', '-' ])) { $token = self::$instance->data->consume(); $new = self::parseExpression(); switch ($token) { case '|': if ($result instanceof OrMatcher) { $result = $result->add($new); } $result = new OrMatcher($result, $new); break; case '-': $new = new NegateMatcher($new); case '&': if ($result instanceof AndMatcher) { $result = $result->add($new); } $result = new AndMatcher($result, $new); break; } $peek = self::$instance->data->peek(); } if (self::$debug) { self::debugResult($result); } return $result; } protected static function parseExpression(): Matcher { if (self::$debug) { self::debug(); } $peek = self::$instance->data->peek(); $prefix = null; if (in_array($peek[0], [ 'B', 'L', 'R' ]) && $peek[1] === ':') { $prefix = $peek[0]; self::$instance->data->consume(); } $peek = self::$instance->data->peek(); if ($peek === '(') { self::$instance->data->consume(); $result = self::parseGroup($prefix); } elseif (preg_match(self::SCOPE_REGEX, $peek)) { $result = self::parsePath($prefix); } else { die('Group or path expected.'); } if (self::$debug) { self::debugResult($result); } return $result; } protected static function parseGroup(string|null $prefix = null): Matcher { if (self::$debug) { self::debug(); } $result = self::parseSelector(); if (self::$instance->data->consume() !== ')') { die('Close parenthesis expected'); } $result = ($prefix === null) ? $result : new GroupMatcher($prefix, $result); if (self::$debug) { self::debugResult($result); } return $result; } protected static function parsePath(string|null $prefix = null): Matcher { if (self::$debug) { self::debug(); } $result = []; $result[] = self::parseScope(); $peek = self::$instance->data->peek(); while ($peek != '-' && preg_match(self::SCOPE_REGEX, $peek)) { self::$instance->data->consume(); $result[] = self::parseScope(); $peek = self::$instance->data->peek(); } $result = ($prefix !== null || count($result) > 1) ? new PathMatcher($prefix, ...$result) : $result[0]; if (self::$debug) { self::debugResult($result); } return $result; } protected static function parseSelector(): Matcher { if (self::$debug) { self::debug(); } $result = []; $result[] = self::parseComposite(); $peek = self::$instance->data->peek(); while ($peek === ',') { self::$instance->data->consume(); $result[] = self::parseComposite(); $peek = self::$instance->data->peek(); } $result = (count($result) > 1) ? new OrMatcher(...$result) : $result[0]; if (self::$debug) { self::debugResult($result); } return $result; } protected static function parseScope(): Matcher { if (self::$debug) { self::debug(); } $token = self::$instance->data->consume(); if (!preg_match('/^(?:[A-Za-z0-9-_]+|\*)(?:\.(?:[A-Za-z0-9-+_]+|\*))*$/S', $token)) { die('Invalid scope'); } $segments = explode('.', $token); foreach ($segments as $index => $segment) { $segments[$index] = ($segment !== '*') ? new SegmentMatcher($segment) : new TrueMatcher(); } $result = new ScopeMatcher(...$segments); if (self::$debug) { self::debugResult($result); } return $result; } protected static function debug() { $message = <<debugCount++, ltrim($methodTree, '->'), self::$instance->data->position + 1, var_export(self::$instance->data->peek(), true) ); } protected static function debugResult($result) { printf("%s Result: %s\n", debug_backtrace()[1]['function'], var_export($result, true)); } }