|
@ -8,32 +8,45 @@ namespace dW\Highlighter\Scope; |
|
|
|
|
|
|
|
|
class Parser { |
|
|
class Parser { |
|
|
protected Data $data; |
|
|
protected Data $data; |
|
|
|
|
|
protected array $lastExceptionData = []; |
|
|
|
|
|
|
|
|
protected static Parser $instance; |
|
|
protected static Parser $instance; |
|
|
|
|
|
|
|
|
protected function __construct(string $selector) { |
|
|
protected function __construct(string $selector) { |
|
|
$this->data = new Data($selector); |
|
|
$this->data = new Data($selector); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public static function parse(string $selector): Matcher|bool { |
|
|
public static function parse(string $selector): Matcher|false { |
|
|
self::$instance = new self($selector); |
|
|
self::$instance = new self($selector); |
|
|
|
|
|
|
|
|
$output = false; |
|
|
$result = false; |
|
|
$s1 = self::parseSpace(); |
|
|
$s1 = self::parseSpace(); |
|
|
if ($s1 !== false) { |
|
|
if ($s1 !== false) { |
|
|
$s2 = self::parseSelector(); |
|
|
$s2 = self::parseSelector(); |
|
|
if ($s2 !== false) { |
|
|
if ($s2 !== false) { |
|
|
$s3 = self::parseSpace(); |
|
|
$s3 = self::parseSpace(); |
|
|
if ($s3 !== false) { |
|
|
if ($s3 !== false) { |
|
|
$output = $s2; |
|
|
$result = $s2; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (self::$instance->lastExceptionData !== []) { |
|
|
|
|
|
throw new Exception(self::$instance->lastExceptionData['expected'], self::$instance->lastExceptionData['found']); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return $result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return $output; |
|
|
protected static function fail(array|string $expected) { |
|
|
|
|
|
self::$instance->lastExceptionData = [ |
|
|
|
|
|
'expected' => $expected, |
|
|
|
|
|
'found' => self::$instance->data->peek() |
|
|
|
|
|
]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
protected static function parseComposite(): Matcher|bool { |
|
|
protected static function parseComposite(): Matcher|false { |
|
|
$output = false; |
|
|
$result = false; |
|
|
|
|
|
|
|
|
$s1 = self::parseExpression(); |
|
|
$s1 = self::parseExpression(); |
|
|
if ($s1 !== false) { |
|
|
if ($s1 !== false) { |
|
@ -41,31 +54,31 @@ class Parser { |
|
|
if ($s2 !== false) { |
|
|
if ($s2 !== false) { |
|
|
$s3 = self::$instance->data->consumeIf('|&-'); |
|
|
$s3 = self::$instance->data->consumeIf('|&-'); |
|
|
if (!in_array($s3, [ '|', '&', '-' ])) { |
|
|
if (!in_array($s3, [ '|', '&', '-' ])) { |
|
|
throw new Exception([ '|', '&', '-' ], self::$instance->data->peek()); |
|
|
self::fail([ '|', '&', '-' ]); |
|
|
} |
|
|
} else { |
|
|
|
|
|
|
|
|
$s4 = self::parseSpace(); |
|
|
$s4 = self::parseSpace(); |
|
|
if ($s4 !== false) { |
|
|
if ($s4 !== false) { |
|
|
$s5 = self::parseComposite(); |
|
|
$s5 = self::parseComposite(); |
|
|
if ($s5 !== false) { |
|
|
if ($s5 !== false) { |
|
|
$output = new Matcher\CompositeMatcher($s1, $s3, $s5); |
|
|
$result = new Matcher\CompositeMatcher($s1, $s3, $s5); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if ($output === false) { |
|
|
if ($result === false) { |
|
|
$output = self::parseExpression(); |
|
|
$result = self::parseExpression(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return $output; |
|
|
return $result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
protected static function parseExpression(): Matcher|bool { |
|
|
protected static function parseExpression(): Matcher|false { |
|
|
$output = false; |
|
|
$result = false; |
|
|
$s1 = self::$instance->data->consumeIf('-'); |
|
|
$s1 = self::$instance->data->consumeIf('-'); |
|
|
if ($s1 !== '-') { |
|
|
if ($s1 !== '-') { |
|
|
throw new Exception('-', self::$instance->data->peek()); |
|
|
self::fail('-'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
$s2 = self::parseSpace(); |
|
|
$s2 = self::parseSpace(); |
|
@ -74,59 +87,56 @@ class Parser { |
|
|
if ($s3 !== false) { |
|
|
if ($s3 !== false) { |
|
|
$s4 = self::parseSpace(); |
|
|
$s4 = self::parseSpace(); |
|
|
if ($s4 !== false) { |
|
|
if ($s4 !== false) { |
|
|
$output = new Matcher\NegateMatcher($s3); |
|
|
$result = new Matcher\NegateMatcher($s3); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if ($output === false) { |
|
|
if ($result === false) { |
|
|
$s1 = self::$instance->data->consumeIf('-', 1); |
|
|
$s1 = self::$instance->data->consumeIf('-', 1); |
|
|
if ($s1 !== '-') { |
|
|
if ($s1 !== '-') { |
|
|
throw new Exception('-', self::$instance->data->peek()); |
|
|
self::fail('-'); |
|
|
} |
|
|
} else { |
|
|
|
|
|
|
|
|
$s2 = self::parseSpace(); |
|
|
$s2 = self::parseSpace(); |
|
|
if ($s2 !== false) { |
|
|
if ($s2 !== false) { |
|
|
$s3 = self::parsePath(); |
|
|
$s3 = self::parsePath(); |
|
|
if ($s3 !== false) { |
|
|
if ($s3 !== false) { |
|
|
$s4 = self::parseSpace(); |
|
|
$s4 = self::parseSpace(); |
|
|
if ($s4 !== false) { |
|
|
if ($s4 !== false) { |
|
|
$output = new Matcher\NegateMatcher($s3); |
|
|
$result = new Matcher\NegateMatcher($s3); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if ($output === false) { |
|
|
if ($result === false) { |
|
|
$output = self::parseGroup(); |
|
|
$result = self::parseGroup(); |
|
|
if ($output === false) { |
|
|
if ($result === false) { |
|
|
$output = self::parsePath(); |
|
|
$result = self::parsePath(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return $output; |
|
|
return $result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
protected static function parseGroup(): Matcher|bool { |
|
|
protected static function parseGroup(): Matcher|false { |
|
|
$output = false; |
|
|
$result = false; |
|
|
|
|
|
|
|
|
$s2 = self::$instance->data->consumeIf('BLR'); |
|
|
$s2 = self::$instance->data->consumeIf('BLR'); |
|
|
if (!in_array($s2, [ 'B', 'L', 'R' ])) { |
|
|
if (!in_array($s2, [ 'B', 'L', 'R' ])) { |
|
|
throw new Exception([ 'B', 'L', 'R' ], self::$instance->data->peek()); |
|
|
self::fail([ 'B', 'L', 'R' ]); |
|
|
} |
|
|
} else { |
|
|
|
|
|
|
|
|
$s3 = self::$instance->data->consumeIf(':'); |
|
|
$s3 = self::$instance->data->consumeIf(':'); |
|
|
if ($s3 !== ':') { |
|
|
if ($s3 !== ':') { |
|
|
throw new Exception(':', self::$instance->data->peek()); |
|
|
self::fail(':'); |
|
|
} |
|
|
} else { |
|
|
|
|
|
|
|
|
$prefix = "$s2$s3"; |
|
|
$prefix = "$s2$s3"; |
|
|
|
|
|
|
|
|
$s2 = self::$instance->data->consumeIf('('); |
|
|
$s2 = self::$instance->data->consumeIf('('); |
|
|
if ($s2 !== '(') { |
|
|
if ($s2 !== '(') { |
|
|
throw new Exception('(', self::$instance->data->peek()); |
|
|
self::fail('('); |
|
|
} |
|
|
} else { |
|
|
|
|
|
|
|
|
$s3 = self::parseSpace(); |
|
|
$s3 = self::parseSpace(); |
|
|
if ($s3 !== false) { |
|
|
if ($s3 !== false) { |
|
|
$s4 = self::parseSelector(); |
|
|
$s4 = self::parseSelector(); |
|
@ -135,30 +145,31 @@ class Parser { |
|
|
if ($s5 !== false) { |
|
|
if ($s5 !== false) { |
|
|
$s6 = self::$instance->data->consumeIf(')'); |
|
|
$s6 = self::$instance->data->consumeIf(')'); |
|
|
if ($s6 !== ')') { |
|
|
if ($s6 !== ')') { |
|
|
throw new Exception(')', self::$instance->data->peek()); |
|
|
self::fail(')'); |
|
|
|
|
|
} else { |
|
|
|
|
|
$result = new GroupMatcher($prefix, $s4); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
$output = new GroupMatcher($prefix, $s4); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return $output; |
|
|
return $result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
protected static function parsePath(): Matcher|bool { |
|
|
protected static function parsePath(): Matcher|false { |
|
|
$output = false; |
|
|
$result = false; |
|
|
|
|
|
|
|
|
$s2 = self::$instance->data->consumeIf('BLR'); |
|
|
$s2 = self::$instance->data->consumeIf('BLR'); |
|
|
if (!in_array($s2, [ 'B', 'L', 'R' ])) { |
|
|
if (!in_array($s2, [ 'B', 'L', 'R' ])) { |
|
|
throw new Exception([ 'B', 'L', 'R' ], self::$instance->data->peek()); |
|
|
self::fail([ 'B', 'L', 'R' ]); |
|
|
} |
|
|
} else { |
|
|
|
|
|
|
|
|
$s3 = self::$instance->data->consumeIf(':'); |
|
|
$s3 = self::$instance->data->consumeIf(':'); |
|
|
if ($s3 !== ':') { |
|
|
if ($s3 !== ':') { |
|
|
throw new Exception(':', self::$instance->data->peek()); |
|
|
self::fail(':'); |
|
|
} |
|
|
} else { |
|
|
|
|
|
|
|
|
$prefix = "$s2$s3"; |
|
|
$prefix = "$s2$s3"; |
|
|
|
|
|
|
|
|
$s2 = self::parseScope(); |
|
|
$s2 = self::parseScope(); |
|
@ -180,51 +191,53 @@ class Parser { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (strlen($s3) > 0) { |
|
|
if (strlen($s3) > 0) { |
|
|
$output = new Matcher\PathMatcher($prefix, $s2, $s3); |
|
|
$result = new Matcher\PathMatcher($prefix, $s2, $s3); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return $output; |
|
|
return $result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
protected static function parseSpace(): string|bool { |
|
|
protected static function parseSpace(): string|false { |
|
|
return self::$instance->data->consumeIf(" \t"); |
|
|
return self::$instance->data->consumeIf(" \t"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
protected static function parseSegment(): string|bool { |
|
|
protected static function parseSegment(): string|false { |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
protected static function parseSelector(): Matcher|bool { |
|
|
protected static function parseSelector(): Matcher|false { |
|
|
$output = false; |
|
|
$result = false; |
|
|
$s1 = self::parseComposite(); |
|
|
$s1 = self::parseComposite(); |
|
|
if ($s1 !== false) { |
|
|
if ($s1 !== false) { |
|
|
$s2 = self::parseSpace(); |
|
|
$s2 = self::parseSpace(); |
|
|
if ($s2 !== false) { |
|
|
if ($s2 !== false) { |
|
|
$s3 = self::$instance->data->consumeIf(','); |
|
|
$s3 = self::$instance->data->consumeIf(','); |
|
|
if ($s3 !== ',') { |
|
|
if ($s3 !== ',') { |
|
|
throw new Exception(',', self::$instance->data->peek()); |
|
|
self::fail(','); |
|
|
} |
|
|
} else { |
|
|
|
|
|
|
|
|
$s4 = self::parseSpace(); |
|
|
$s4 = self::parseSpace(); |
|
|
if ($s4 !== false) { |
|
|
if ($s4 !== false) { |
|
|
$s5 = self::parseSelector(); |
|
|
$s5 = self::parseSelector(); |
|
|
if ($s5 !== false) { |
|
|
if ($s5 !== false) { |
|
|
$output = new Matcher\OrMatcher($s1, $s5); |
|
|
$result = new Matcher\OrMatcher($s1, $s5); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if ($output === false) { |
|
|
if ($result === false) { |
|
|
$output = self::parseComposite(); |
|
|
$result = self::parseComposite(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return $output; |
|
|
return $result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
protected static function parseScope(): string|bool { |
|
|
protected static function parseScope(): string|false { |
|
|
$output = false; |
|
|
$result = false; |
|
|
|
|
|
|
|
|
$s1 = self::parseSegment(); |
|
|
$s1 = self::parseSegment(); |
|
|
if ($s1 !== false) { |
|
|
if ($s1 !== false) { |
|
@ -237,20 +250,20 @@ class Parser { |
|
|
|
|
|
|
|
|
$s4 = self::$instance->data->consumeIf('.'); |
|
|
$s4 = self::$instance->data->consumeIf('.'); |
|
|
if ($s4 !== '.') { |
|
|
if ($s4 !== '.') { |
|
|
throw new Exception('.', self::$instance->data->peek()); |
|
|
self::fail('.'); |
|
|
} |
|
|
} else { |
|
|
|
|
|
|
|
|
$s5 = self::parseSegment(); |
|
|
$s5 = self::parseSegment(); |
|
|
if ($s5 !== false) { |
|
|
if ($s5 !== false) { |
|
|
$s3 = "$s4$s5"; |
|
|
$s3 = "$s4$s5"; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (strlen($s2) > 0) { |
|
|
if (strlen($s2) > 0) { |
|
|
$output = new Matcher\ScopeMatcher($s1, $s2); |
|
|
$result = new Matcher\ScopeMatcher($s1, $s2); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return $output; |
|
|
return $result; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|