Browse Source

Maybe have scope selector matchers working

main
Dustin Wilson 3 years ago
parent
commit
379d9c791d
  1. 22
      lib/Scope/Composite.php
  2. 22
      lib/Scope/Expression.php
  3. 26
      lib/Scope/Filter.php
  4. 26
      lib/Scope/Group.php
  5. 2
      lib/Scope/Node.php
  6. 115
      lib/Scope/Parser.php
  7. 2
      lib/Scope/ParserException.php
  8. 62
      lib/Scope/Path.php
  9. 34
      lib/Scope/Scope.php
  10. 28
      lib/Scope/Selector.php

22
lib/Scope/Composite.php

@ -8,25 +8,14 @@ namespace dW\Lit\Scope;
class Composite extends Node {
protected array $_expressions = [];
protected bool $frozen = false;
public function __construct(Selector $parent) {
$this->_parent = \WeakReference::create($parent);
}
public function add(Expression ...$expressions): bool {
if ($this->frozen) {
return false;
}
public function __construct(Expression ...$expressions) {
$this->_expressions = $expressions;
$this->frozen = true;
return true;
}
public function matches(Path $path): bool {
public function matches(array $scopes): bool {
$result = false;
foreach ($this->_expressions as $expression) {
$operator = $expression->operator;
@ -38,10 +27,7 @@ class Composite extends Node {
continue;
}
$local = $expression->child->matches($path);
if ($expression->negate) {
$local = !$local;
}
$local = $expression->matches($scopes);
switch ($operator) {
case Expression::OPERATOR_NONE: $result = $local;

22
lib/Scope/Expression.php

@ -8,7 +8,6 @@ namespace dW\Lit\Scope;
class Expression extends Node {
protected Filter|Group|Path $_child;
protected bool $frozen = false;
protected bool $_negate = false;
protected int $_operator;
@ -18,27 +17,16 @@ class Expression extends Node {
const OPERATOR_NOT = 3;
public function __construct(Composite $parent, int $operator = self::OPERATOR_NONE, bool $negate = false) {
public function __construct(Filter|Group|Path $child, int $operator = self::OPERATOR_NONE, bool $negate = false) {
$this->_child = $child;
$this->_negate = $negate;
$this->_operator = $operator;
$this->_parent = \WeakReference::create($parent);
}
public function __set(string $name, $value) {
if ($name !== 'child') {
$trace = debug_backtrace();
trigger_error("Cannot set undefined property $name in {$trace[0]['file']} on line {$trace[0]['line']}", E_USER_NOTICE);
}
if ($this->frozen) {
$trace = debug_backtrace();
trigger_error("Cannot set readonly $name property in {$trace[0]['file']} on line {$trace[0]['line']}", E_USER_NOTICE);
return;
}
$this->frozen = true;
$this->_child = $value;
public function matches(array $scopes): bool {
$matches = $this->_child->matches($scopes);
return ($this->_negate) ? !$matches : $matches;
}

26
lib/Scope/Filter.php

@ -8,7 +8,6 @@ namespace dW\Lit\Scope;
class Filter extends Node {
protected Group|Path $_child;
protected bool $frozen = false;
protected int $_prefix;
const SIDE_LEFT = 0;
@ -16,8 +15,8 @@ class Filter extends Node {
const SIDE_BOTH = 2;
public function __construct(Expression $parent, string $prefix) {
$this->_parent = \WeakReference::create($parent);
public function __construct(Group|Path $child, string $prefix) {
$this->_child = $child;
switch ($prefix) {
case 'L': $this->_prefix = self::SIDE_LEFT;
@ -30,27 +29,10 @@ class Filter extends Node {
}
public function matches(Path $path): bool {
public function matches(array $scopes): bool {
// No idea if prefixes are supposed to affect matches. Appears to in the
// TextMate original but not in Atom's implementation...
return $this->_child->matches($path);
}
public function __set(string $name, $value) {
if ($name !== 'child') {
$trace = debug_backtrace();
trigger_error("Cannot set undefined property $name in {$trace[0]['file']} on line {$trace[0]['line']}", E_USER_NOTICE);
}
if ($this->frozen) {
$trace = debug_backtrace();
trigger_error("Cannot set readonly $name property in {$trace[0]['file']} on line {$trace[0]['line']}", E_USER_NOTICE);
return;
}
$this->frozen = true;
$this->_child = $value;
return $this->_child->matches($scopes);
}

26
lib/Scope/Group.php

@ -8,33 +8,15 @@ namespace dW\Lit\Scope;
class Group extends Node {
protected Selector $_child;
protected bool $frozen = false;
public function __construct(Expression|Filter $parent) {
$this->_parent = \WeakReference::create($parent);
public function __construct(Selector $child) {
$this->_child = $child;
}
public function matches(Path $path): bool {
return $this->_child->matches($path);
}
public function __set(string $name, $value) {
if ($name !== 'child') {
$trace = debug_backtrace();
trigger_error("Cannot set undefined property $name in {$trace[0]['file']} on line {$trace[0]['line']}", E_USER_NOTICE);
}
if ($this->frozen) {
$trace = debug_backtrace();
trigger_error("Cannot set readonly $name property in {$trace[0]['file']} on line {$trace[0]['line']}", E_USER_NOTICE);
return;
}
$this->frozen = true;
$this->_child = $value;
public function matches(array $scopes): bool {
return $this->_child->matches($scopes);
}

2
lib/Scope/Node.php

@ -9,6 +9,4 @@ use dW\Lit\FauxReadOnly;
class Node {
use FauxReadOnly;
protected ?\WeakReference $_parent;
}

115
lib/Scope/Parser.php

@ -8,8 +8,11 @@ namespace dW\Lit\Scope;
/** Parses strings into a scope selector */
class Parser {
// Used to cache parsed selectors
protected static array $cache = [];
// Used to cache parsed scopes and selectors
protected static array $cache = [
'selector' => [],
'scope' => []
];
// The tokenized scope string
protected Data $data;
@ -30,8 +33,8 @@ class Parser {
/** Parses strings into Selectors */
public static function parseSelector(string $string): Selector {
if (isset(self::$cache[$string])) {
return self::$cache[$string];
if (isset(self::$cache['selector'][$string])) {
return self::$cache['selector'][$string];
}
self::$instance = new self($string);
@ -43,18 +46,18 @@ class Parser {
self::throw(false, $token);
}
self::$cache[$string] = $result;
self::$cache['selector'][$string] = $result;
return $result;
}
/** Parses strings into Selectors */
public static function parsePath(string $string): Path {
if (isset(self::$cache[$string])) {
return self::$cache[$string];
/** Parses strings into Scopes */
public static function parseScope(string $string): Scope {
if (isset(self::$cache['scope'][$string])) {
return self::$cache['scope'][$string];
}
self::$instance = new self($string);
$result = self::_parsePath();
$result = self::_parseScope();
// If not at the end of input throw an exception.
$token = self::$instance->data->consume();
@ -62,16 +65,15 @@ class Parser {
self::throw(false, $token);
}
self::$cache[$string] = $result;
self::$cache['scope'][$string] = $result;
return $result;
}
protected static function parseComposite(?Selector $parent = null): Composite {
protected static function parseComposite(): Composite {
assert((fn() => self::debug())());
$result = new Composite($parent);
$expressions = [ self::parseExpression($result) ];
$expressions = [ self::parseExpression() ];
$peek = self::$instance->data->peek();
while ($peek !== false && in_array($peek, [ '&', '|', '-' ])) {
@ -85,18 +87,16 @@ class Parser {
break;
}
$expressions[] = self::parseExpression($result, $operator);
$expressions[] = self::parseExpression($operator);
$peek = self::$instance->data->peek();
}
$result->add(...$expressions);
$result = new Composite(...$expressions);
assert((fn() => self::debugResult($result))());
return $result;
}
protected static function parseExpression(Composite $parent, int $operator = Expression::OPERATOR_NONE): Expression {
protected static function parseExpression(int $operator = Expression::OPERATOR_NONE): Expression {
assert((fn() => self::debug())());
$peek = self::$instance->data->peek();
@ -107,71 +107,64 @@ class Parser {
$negate = true;
}
$result = new Expression($parent, $operator, $negate);
if (in_array($peek[0], [ 'B', 'L', 'R' ])) {
$result->child = self::parseFilter($result, self::$instance->data->consume()[0]);
$child = self::parseFilter(self::$instance->data->consume()[0]);
} elseif ($peek === '(') {
$result->child = self::parseGroup($result);
$child = self::parseGroup();
} else {
$result->child = self::_parsePath($result);
$child = self::parsePath();
}
$result = new Expression($child, $operator, $negate);
assert((fn() => self::debugResult($result))());
return $result;
}
protected static function parseFilter(Expression $parent, string $prefix): Filter {
protected static function parseFilter(string $prefix): Filter {
assert((fn() => self::debug())());
$result = new Filter($parent, $prefix);
$peek = self::$instance->data->peek();
if ($peek === '(') {
$result->child = self::parseGroup($result);
$child = self::parseGroup();
} else {
$result->child = self::_parsePath($result);
$child = self::parsePath();
}
$result = new Filter($child, $prefix);
assert((fn() => self::debugResult($result))());
return $result;
}
protected static function parseGroup(Expression|Filter $parent): Group {
protected static function parseGroup(): Group {
assert((fn() => self::debug())());
$result = new Group($parent);
$token = self::$instance->data->consume();
if ($token !== '(') {
self::throw('"("', $token);
}
$result->child = self::_parseSelector($result);
$child = self::_parseSelector();
$token = self::$instance->data->consume();
if ($token !== ')') {
self::throw('")"', $token);
}
$result = new Group($child);
assert((fn() => self::debugResult($result))());
return $result;
}
protected static function _parsePath(Expression $parent): Path {
protected static function parsePath(): Path {
assert((fn() => self::debug())());
$result = new Path($parent);
$anchorStart = false;
if (self::$instance->data->peek() === '^') {
$anchorStart = true;
self::$instance->data->consume();
}
$first = self::parseScope($result);
$first = self::_parseScope();
if ($first->anchorToPrevious) {
self::throw('first scope', '>');
}
@ -185,12 +178,10 @@ class Parser {
$peek = self::$instance->data->peek();
$anchorToPrevious = true;
}
$scopes[] = self::parseScope($result, $anchorToPrevious);
$scopes[] = self::_parseScope(end($scopes), $anchorToPrevious);
$peek = self::$instance->data->peek();
}
$result->add(...$scopes);
$anchorEnd = false;
if ($peek === '$') {
$anchorEnd = true;
@ -198,49 +189,39 @@ class Parser {
}
if ($anchorStart && $anchorEnd) {
$result->anchor = Path::ANCHOR_BOTH;
$anchor = Path::ANCHOR_BOTH;
} elseif ($anchorStart) {
$result->anchor = Path::ANCHOR_START;
$anchor = Path::ANCHOR_START;
} elseif ($anchorEnd) {
$result->anchor = Path::ANCHOR_END;
$anchor = Path::ANCHOR_END;
} else {
$result->anchor = Path::ANCHOR_NONE;
$anchor = Path::ANCHOR_NONE;
}
$result = new Path($anchor, ...$scopes);
assert((fn() => self::debugResult($result))());
return $result;
}
protected static function _parseSelector(?Group $parent = null): Selector {
protected static function _parseSelector(): Selector {
assert((fn() => self::debug())());
$result = new Selector($parent);
$composites = [ self::parseComposite($result) ];
$composites = [ self::parseComposite() ];
$peek = self::$instance->data->peek();
while ($peek === ',') {
self::$instance->data->consume();
$composites[] = self::parseComposite($result);
$composites[] = self::parseComposite();
$peek = self::$instance->data->peek();
}
$result->add(...$composites);
$result = new Selector(...$composites);
assert((fn() => self::debugResult($result))());
return $result;
}
protected static function parseScope(Path $parent): Scope {
protected static function _parseScope(?Scope $parent = null, bool $anchorToPrevious = false): Scope {
assert((fn() => self::debug())());
$peek = self::$instance->data->peek();
if ($peek === '>') {
self::$instance->data->consume();
}
$result = new Scope($parent, ($peek === '>'));
$atoms = [];
$first = true;
do {
@ -258,10 +239,8 @@ class Parser {
$first = false;
} while (self::$instance->data->peek() === '.');
$result->add(...$atoms);
$result = new Scope($parent, $anchorToPrevious, ...$atoms);
assert((fn() => self::debugResult($result))());
return $result;
}
@ -300,11 +279,13 @@ class Parser {
printf("%s Result: %s\n",
debug_backtrace()[2]['function'],
// Removes bullshit from var_exported classes for easier reading
str_replace([ '::__set_state(array', __NAMESPACE__.'\\', '))' ], [ '', '', ')' ], var_export($result, true)));
str_replace([ '::__set_state(array', __NAMESPACE__.'\\', '))' ], [ '', '', ')' ], var_export($result, true))
);
return true;
}
protected static function throw(array|string|bool $expected, string|bool $found) {
throw new Exception($expected, $found, self::$instance->data->offset());
throw new ParserException($expected, $found, self::$instance->data->offset());
}
}

2
lib/Scope/Exception.php → lib/Scope/ParserException.php

@ -6,7 +6,7 @@
declare(strict_types=1);
namespace dW\Lit\Scope;
class Exception extends \Exception {
class ParserException extends \Exception {
const MESSAGE = '%s expected; found %s at offset %s'.\PHP_EOL;
public function __construct(array|string|bool $expected, string|bool $found, int $offset) {

62
lib/Scope/Path.php

@ -8,12 +8,6 @@ namespace dW\Lit\Scope;
class Path extends Node {
protected int $_anchor;
protected array $frozen = [
'add' => false,
'anchor' => false
];
protected array $_scopes = [];
const ANCHOR_NONE = 0;
@ -22,55 +16,37 @@ class Path extends Node {
const ANCHOR_BOTH = 3;
public function __construct(Expression $parent) {
$this->_parent = \WeakReference::create($parent);
public function __construct(int $anchor, Scope ...$scopes) {
if ($anchor < 0 || $anchor > 3) {
throw new \Exception("Anchor must be a value between 0 and 3.\n");
}
$this->_anchor = $anchor;
$this->_scopes = $scopes;
}
public function matches(Path $path): bool {
$start = reset($this->_scopes);
$bt = end($this->_scopes);
$node = $this;
public function matches(array $scopes): bool {
// TODO: Handle anchors; while they are parsed they're not factored in when
// matching because I can't find any documentation anywhere on what they do, and
// my brain can't tie itself into knots to read that part of the original C++.
if ($this->_anchor === self::ANCHOR_END || $this->_anchor === self::ANCHOR_BOTH) {
while ($node && $node->isAuxiliary()) {
$node = $node->parent->get();
$index = 0;
$cur = $this->_scopes[$index];
foreach ($scopes as $s) {
if ($cur->matches($s)) {
$cur = $this->_scopes[++$index] ?? null;
}
$bt = $start;
if ($cur === null) {
return true;
}
}
return false;
}
public function add(Scope ...$scopes): bool {
if ($this->frozen['add']) {
return false;
}
$this->_scopes = $scopes;
$this->frozen['add'] = true;
return true;
}
public function __set(string $name, $value) {
if ($name !== 'anchor') {
$trace = debug_backtrace();
trigger_error("Cannot set undefined property $name in {$trace[0]['file']} on line {$trace[0]['line']}", E_USER_NOTICE);
}
if ($this->frozen['anchor']) {
$trace = debug_backtrace();
trigger_error("Cannot set readonly $name property in {$trace[0]['file']} on line {$trace[0]['line']}", E_USER_NOTICE);
return;
}
$this->frozen['anchor'] = true;
$this->_anchor = $value;
}
public function __toString(): string {
$result = '';

34
lib/Scope/Scope.php

@ -9,30 +9,42 @@ namespace dW\Lit\Scope;
class Scope extends Node {
protected bool $_anchorToPrevious;
protected array $_atoms = [];
protected bool $frozen = false;
protected ?\WeakReference $_parent;
public function __construct(Path $parent, bool $anchorToPrevious = false) {
public function __construct(?Scope $parent = null, bool $anchorToPrevious = false, string ...$atoms) {
$this->_anchorToPrevious = $anchorToPrevious;
$this->_parent = \WeakReference::create($parent);
$this->_atoms = $atoms;
$this->_parent = ($parent !== null) ? \WeakReference::create($parent) : null;
}
public function add(string ...$atoms): bool {
if ($this->frozen) {
public function matches(Scope $scope): bool {
/*if (count($this->_atoms) !== count($scope->atoms)) {
return false;
}*/
foreach ($this->_atoms as $index => $atom) {
if ($atom === '*') {
continue;
}
if ($atom !== $scope->atoms[$index]) {
return false;
}
}
$this->_atoms = $atoms;
$this->frozen = true;
return true;
}
public function isAuxiliary(): bool {
$serialized = (string)$this;
return(strncmp($serialized, 'attr.', 5) === 0 || strncmp($serialized, 'dyn.', 4) === 0);
}
public function __get(string $name) {
if ($name === 'parent') {
return ($this->_parent !== null) ? $this->_parent->get() : null;
}
return parent::__get($name);
}
public function __toString(): string {
$result = '';

28
lib/Scope/Selector.php

@ -8,31 +8,27 @@ namespace dW\Lit\Scope;
class Selector extends Node {
protected array $_composites = [];
protected bool $frozen = false;
public function __construct(?Group $parent = null) {
$this->_parent = ($parent === null) ? null : \WeakReference::create($parent);
public function __construct(Composite ...$composites) {
$this->_composites = $composites;
}
public function add(Composite ...$composites): bool {
if ($this->frozen) {
return false;
}
$this->_composites = $composites;
$this->frozen = true;
return true;
}
public function matches(array $scopes, &$match = null): bool {
foreach ($scopes as &$s) {
$isString = is_string($s);
if (!$isString && !$s instanceof Scope) {
throw new \Exception("Argument \$scopes must be an array containing only Scopes and/or strings.\n");
}
public function matches(Path|string $path, &$match = null): bool {
if (is_string($selector)) {
$path = Parser::parsePath($path);
if ($isString) {
$s = Parser::parseScope($s);
}
}
foreach ($this->_composites as $composite) {
if ($composite->matches($path)) {
if ($composite->matches($scopes)) {
$match = $composite;
return true;
}

Loading…
Cancel
Save