|
|
@ -7,11 +7,12 @@ declare(strict_types=1); |
|
|
|
namespace dW\Highlighter\Scope; |
|
|
|
|
|
|
|
class Parser { |
|
|
|
public static bool $debug = false; |
|
|
|
|
|
|
|
protected Data $data; |
|
|
|
protected int $debugCount = 1; |
|
|
|
|
|
|
|
protected static Parser $instance; |
|
|
|
protected static bool $debug = false; |
|
|
|
|
|
|
|
protected const SCOPE_REGEX = '/^[A-Za-z0-9-+_\.\*]+$/S'; |
|
|
|
|
|
|
|
|
|
|
@ -26,6 +27,10 @@ class Parser { |
|
|
|
} |
|
|
|
|
|
|
|
protected static function parseComposite(): Matcher { |
|
|
|
if (self::$debug) { |
|
|
|
self::debug(); |
|
|
|
} |
|
|
|
|
|
|
|
$result = self::parseExpression(); |
|
|
|
|
|
|
|
$peek = self::$instance->data->peek(); |
|
|
@ -56,52 +61,90 @@ class Parser { |
|
|
|
$peek = self::$instance->data->peek(); |
|
|
|
} |
|
|
|
|
|
|
|
if (self::$debug) { |
|
|
|
self::debugResult($result); |
|
|
|
} |
|
|
|
|
|
|
|
return $result; |
|
|
|
} |
|
|
|
|
|
|
|
protected static function parseExpression(): Matcher { |
|
|
|
$token = self::$instance->data->consume(); |
|
|
|
if (self::$debug) { |
|
|
|
self::debug(); |
|
|
|
} |
|
|
|
|
|
|
|
$peek = self::$instance->data->peek(); |
|
|
|
$prefix = null; |
|
|
|
if (in_array($token[0], [ 'B', 'L', 'R' ]) && $token[1] === ':') { |
|
|
|
$prefix = $token[0]; |
|
|
|
if (in_array($peek[0], [ 'B', 'L', 'R' ]) && $peek[1] === ':') { |
|
|
|
$prefix = $peek[0]; |
|
|
|
self::$instance->data->consume(); |
|
|
|
} |
|
|
|
|
|
|
|
$token = self::$instance->data->consume(); |
|
|
|
if ($token === '(') { |
|
|
|
$peek = self::$instance->data->peek(); |
|
|
|
if ($peek === '(') { |
|
|
|
self::$instance->data->consume(); |
|
|
|
$result = self::parseGroup($prefix); |
|
|
|
} elseif (preg_match(self::SCOPE_REGEX, $token)) { |
|
|
|
} 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'); |
|
|
|
} |
|
|
|
|
|
|
|
return ($prefix === null) ? $result : new GroupMatcher($prefix, $result); |
|
|
|
$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 (preg_match(self::SCOPE_REGEX, $peek)) { |
|
|
|
while ($peek != '-' && preg_match(self::SCOPE_REGEX, $peek)) { |
|
|
|
self::$instance->data->consume(); |
|
|
|
$result[] = self::parseScope(); |
|
|
|
$peek = self::$instance->data->peek(); |
|
|
|
} |
|
|
|
|
|
|
|
return ($prefix !== null || count($result) > 1) ? new PathMatcher($prefix, ...$result) : $result; |
|
|
|
$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(); |
|
|
|
|
|
|
@ -112,10 +155,20 @@ class Parser { |
|
|
|
$peek = self::$instance->data->peek(); |
|
|
|
} |
|
|
|
|
|
|
|
return (count($result) > 1) ? new OrMatcher(...$result) : $result[0]; |
|
|
|
$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'); |
|
|
@ -126,6 +179,44 @@ class Parser { |
|
|
|
$segments[$index] = ($segment !== '*') ? new SegmentMatcher($segment) : new TrueMatcher(); |
|
|
|
} |
|
|
|
|
|
|
|
return new ScopeMatcher(...$segments); |
|
|
|
$result = new ScopeMatcher(...$segments); |
|
|
|
|
|
|
|
if (self::$debug) { |
|
|
|
self::debugResult($result); |
|
|
|
} |
|
|
|
|
|
|
|
return $result; |
|
|
|
} |
|
|
|
|
|
|
|
protected static function debug() { |
|
|
|
$message = <<<DEBUG |
|
|
|
------------------------------ |
|
|
|
%s |
|
|
|
Method: %s |
|
|
|
Position: %s |
|
|
|
Token: %s |
|
|
|
|
|
|
|
DEBUG; |
|
|
|
|
|
|
|
$methodTree = ''; |
|
|
|
$backtrace = debug_backtrace(); |
|
|
|
array_shift($backtrace); |
|
|
|
array_pop($backtrace); |
|
|
|
foreach ($backtrace as $b) { |
|
|
|
$methodTree = "->{$b['function']}$methodTree"; |
|
|
|
} |
|
|
|
|
|
|
|
printf($message, |
|
|
|
self::$instance->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)); |
|
|
|
} |
|
|
|
} |
|
|
|