Browse Source

Working out scope parsing

main
Dustin Wilson 3 years ago
parent
commit
1f72a86207
  1. 1
      .gitignore
  2. 3
      composer.json
  3. 44
      lib/Scope/Data.php
  4. 13
      lib/Scope/Exception.php
  5. 11
      lib/Scope/Matcher/PathMatcher.php
  6. 11
      lib/Scope/Matcher/ScopeMatcher.php
  7. 4
      lib/Scope/Matchers/CompositeMatcher.php
  8. 4
      lib/Scope/Matchers/GroupMatcher.php
  9. 4
      lib/Scope/Matchers/NegateMatcher.php
  10. 4
      lib/Scope/Matchers/OrMatcher.php
  11. 17
      lib/Scope/Matchers/PathMatcher.php
  12. 15
      lib/Scope/Matchers/ScopeMatcher.php
  13. 15
      lib/Scope/Matchers/SegmentMatcher.php
  14. 15
      lib/Scope/Matchers/TrueMatcher.php
  15. 378
      lib/Scope/Parser.php

1
.gitignore

@ -1,5 +1,6 @@
# Temporary files and dependencies
test*.php
*-old.php
/vendor/

3
composer.json

@ -19,7 +19,8 @@
},
"autoload": {
"psr-4": {
"dW\\Highlighter\\": "lib/"
"dW\\Highlighter\\": "lib/",
"dW\\Highlighter\\Scope\\": "lib/Scope/Matchers/"
}
}
}

44
lib/Scope/Data.php

@ -9,7 +9,7 @@ namespace dW\Highlighter\Scope;
class Data {
protected string $data;
protected int $position = 0;
protected int $_position = 0;
protected int $endPosition;
public function __construct(string $data) {
@ -18,21 +18,21 @@ class Data {
}
public function consume(int $length = 1): string|bool {
if ($this->position === $this->endPosition) {
if ($this->_position === $this->endPosition) {
return false;
}
$stop = $this->position + $length;
if ($stop >= $this->endPosition) {
$stop = $this->_position + $length - 1;
if ($stop > $this->endPosition) {
$stop = $this->endPosition;
}
$output = '';
for ($i = $this->position; $i <= $stop; $i++) {
$output .= $this->data[$this->position++];
$result = '';
while ($this->_position <= $stop) {
$result .= $this->data[$this->_position++];
}
return $output;
return $result;
}
public function consumeIf(string $match): string|bool {
@ -40,11 +40,11 @@ class Data {
}
public function consumeUntil(string $match, $limit = null): string|bool {
if ($this->position === $this->endPosition) {
if ($this->_position === $this->endPosition) {
return false;
}
$length = strcspn($this->data, $match, $this->position + 1, $limit);
$length = strcspn($this->data, $match, $this->_position, $limit);
if ($length === 0) {
return '';
}
@ -53,11 +53,11 @@ class Data {
}
public function consumeWhile(string $match, $limit = null): string|bool {
if ($this->position === $this->endPosition) {
if ($this->_position === $this->endPosition) {
return false;
}
$length = strspn($this->data, $match, $this->position + 1, $limit);
$length = strspn($this->data, $match, $this->_position, $limit);
if ($length === 0) {
return '';
}
@ -65,29 +65,27 @@ class Data {
return $this->consume($length);
}
public function current(): string|bool {
if ($this->position === $this->endPosition) {
return false;
}
return $this->data[$this->position];
}
public function peek(int $length = 1): string|bool {
if ($this->position === $this->endPosition) {
if ($this->_position === $this->endPosition) {
return false;
}
$stop = $this->position + $length;
$stop = $this->_position + $length - 1;
if ($stop >= $this->endPosition) {
$stop = $this->endPosition;
}
$output = '';
for ($i = $this->position; $i <= $stop; $i++) {
for ($i = $this->_position; $i <= $stop; $i++) {
$output .= $this->data[$i];
}
return $output;
}
public function __get(string $name) {
if ($name === 'position') {
return $this->_position;
}
}
}

13
lib/Scope/Exception.php

@ -9,11 +9,14 @@ namespace dW\Highlighter\Scope;
class Exception extends \Exception {
const MESSAGE = '%s expected; found %s';
public function __construct(array|string $expected, string $found) {
if (is_array($expected)) {
$expected = array_map(function($n) {
return ($n !== false) ? "\"$n\"" : 'end of input';
}, $expected);
public function __construct(string $expected, string $found) {
$strlen = strlen($expected);
if ($strlen > 1) {
$temp = [];
for ($i = 0; $i < $strlen; $i++) {
$temp[] = ($expected[$i] !== false) ? "\"{$expected[$i]}\"" : 'end of input';
}
$expected = $temp;
if (count($expected) > 2) {
$last = array_pop($expected);

11
lib/Scope/Matcher/PathMatcher.php

@ -1,11 +0,0 @@
<?php
/** @license MIT
* Copyright 2021 Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace dW\Highlighter\Scope\Matcher;
class PathMatcher extends dW\Highlighter\Scope\Matcher {
public function __construct(string $prefix, string $first, string $others) {}
}

11
lib/Scope/Matcher/ScopeMatcher.php

@ -1,11 +0,0 @@
<?php
/** @license MIT
* Copyright 2021 Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace dW\Highlighter\Scope\Matcher;
class ScopeMatcher extends dW\Highlighter\Scope\Matcher {
public function __construct(string $first, string $others) {}
}

4
lib/Scope/Matcher/CompositeMatcher.php → lib/Scope/Matchers/CompositeMatcher.php

@ -4,8 +4,8 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace dW\Highlighter\Scope\Matcher;
namespace dW\Highlighter\Scope;
class CompositeMatcher extends dW\Highlighter\Scope\Matcher {
class CompositeMatcher extends Matcher {
public function __construct(Matcher $left, string $operator, Matcher $right) {}
}

4
lib/Scope/Matcher/GroupMatcher.php → lib/Scope/Matchers/GroupMatcher.php

@ -4,8 +4,8 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace dW\Highlighter\Scope\Matcher;
namespace dW\Highlighter\Scope;
class GroupMatcher extends dW\Highlighter\Scope\Matcher {
class GroupMatcher extends Matcher {
public function __construct(string $prefix, Matcher $selector) {}
}

4
lib/Scope/Matcher/NegateMatcher.php → lib/Scope/Matchers/NegateMatcher.php

@ -4,8 +4,8 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace dW\Highlighter\Scope\Matcher;
namespace dW\Highlighter\Scope;
class NegateMatcher extends dW\Highlighter\Scope\Matcher {
class NegateMatcher extends Matcher {
public function __construct(Matcher $groupOrPath) {}
}

4
lib/Scope/Matcher/OrMatcher.php → lib/Scope/Matchers/OrMatcher.php

@ -4,8 +4,8 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace dW\Highlighter\Scope\Matcher;
namespace dW\Highlighter\Scope;
class OrMatcher extends dW\Highlighter\Scope\Matcher {
class OrMatcher extends Matcher {
public function __construct(Matcher $left, Matcher $right) {}
}

17
lib/Scope/Matchers/PathMatcher.php

@ -0,0 +1,17 @@
<?php
/** @license MIT
* Copyright 2021 Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace dW\Highlighter\Scope;
class PathMatcher extends Matcher {
protected string|null $prefix;
protected array $matchers;
public function __construct(string|null $prefix, Matcher ...$matchers) {
$this->prefix = $prefix;
$this->matchers = $matchers;
}
}

15
lib/Scope/Matchers/ScopeMatcher.php

@ -0,0 +1,15 @@
<?php
/** @license MIT
* Copyright 2021 Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace dW\Highlighter\Scope;
class ScopeMatcher extends Matcher {
protected array $segments;
public function __construct(SegmentMatcher|TrueMatcher ...$matchers) {
$this->segments = $matchers;
}
}

15
lib/Scope/Matchers/SegmentMatcher.php

@ -0,0 +1,15 @@
<?php
/** @license MIT
* Copyright 2021 Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace dW\Highlighter\Scope;
class SegmentMatcher extends Matcher {
protected string $segment;
public function __construct(string $segment) {
$this->segment = $segment;
}
}

15
lib/Scope/Matchers/TrueMatcher.php

@ -0,0 +1,15 @@
<?php
/** @license MIT
* Copyright 2021 Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace dW\Highlighter\Scope;
class TrueMatcher extends Matcher {
protected string $scopeName;
public function __construct(string $scopeName) {
$this->scopeName = $scopeName;
}
}

378
lib/Scope/Parser.php

@ -7,15 +7,20 @@ declare(strict_types=1);
namespace dW\Highlighter\Scope;
class Parser {
public static $debug = false;
protected Data $data;
protected array $lastExceptionData = [];
protected static $debugCount = 1;
protected static Parser $instance;
protected function __construct(string $selector) {
$this->data = new Data($selector);
}
public static function parse(string $selector): Matcher|false {
self::$instance = new self($selector);
@ -31,21 +36,22 @@ class Parser {
}
}
if (self::$instance->lastExceptionData !== []) {
if (self::$debug === true) {
echo "------------------------------\n";
}
if ($result === false && self::$instance->lastExceptionData !== []) {
throw new Exception(self::$instance->lastExceptionData['expected'], self::$instance->lastExceptionData['found']);
}
return $result;
}
protected static function fail(array|string $expected) {
self::$instance->lastExceptionData = [
'expected' => $expected,
'found' => self::$instance->data->peek()
];
}
protected static function parseComposite(): Matcher|false {
if (self::$debug === true) {
self::debug();
}
$result = false;
$s1 = self::parseExpression();
@ -53,14 +59,17 @@ class Parser {
$s2 = self::parseSpace();
if ($s2 !== false) {
$s3 = self::$instance->data->consumeIf('|&-');
if (!in_array($s3, [ '|', '&', '-' ])) {
self::fail([ '|', '&', '-' ]);
} else {
if ($s3 === '' || $s3 === false) {
$s3 = false;
self::fail('|&-');
}
if ($s3 !== false) {
$s4 = self::parseSpace();
if ($s4 !== false) {
$s5 = self::parseComposite();
if ($s5 !== false) {
$result = new Matcher\CompositeMatcher($s1, $s3, $s5);
$result = new CompositeMatcher($s1, $s3, $s5);
}
}
}
@ -71,84 +80,122 @@ class Parser {
$result = self::parseExpression();
}
if (self::$debug === true) {
echo "parseComposite Result: " . var_export($result, true) . "\n";
}
return $result;
}
protected static function parseExpression(): Matcher|false {
if (self::$debug === true) {
self::debug();
}
$result = false;
$s1 = self::$instance->data->consumeIf('-');
if ($s1 !== '-') {
if ($s1 === '' || $s1 === false) {
$s1 = false;
self::fail('-');
}
$s2 = self::parseSpace();
if ($s2 !== false) {
$s3 = self::parseGroup();
if ($s3 !== false) {
$s4 = self::parseSpace();
if ($s4 !== false) {
$result = new Matcher\NegateMatcher($s3);
if ($s1 !== false) {
$s2 = self::parseSpace();
if ($s2 !== false) {
$s3 = self::parseGroup();
if ($s3 !== false) {
$s4 = self::parseSpace();
if ($s4 !== false) {
$result = new NegateMatcher($s3);
}
}
}
}
if ($result === false) {
$s1 = self::$instance->data->consumeIf('-', 1);
if ($s1 !== '-') {
$s1 = self::$instance->data->consumeIf('-');
if ($s1 === '' || $s1 === false) {
$s1 = false;
self::fail('-');
} else {
}
if ($s1 !== false) {
$s2 = self::parseSpace();
if ($s2 !== false) {
$s3 = self::parsePath();
if ($s3 !== false) {
$s4 = self::parseSpace();
if ($s4 !== false) {
$result = new Matcher\NegateMatcher($s3);
$result = new NegateMatcher($s3);
}
}
}
}
}
if ($result === false) {
$result = self::parseGroup();
if ($result === false) {
$result = self::parsePath();
$result = self::parseGroup();
if ($result === false) {
$result = self::parsePath();
}
}
}
if (self::$debug === true) {
echo "parseExpression Result: " . var_export($result, true) . "\n";
}
return $result;
}
protected static function parseGroup(): Matcher|false {
if (self::$debug === true) {
self::debug();
}
$result = false;
$prefix = null;
$s2 = self::$instance->data->consumeIf('BLR');
if (!in_array($s2, [ 'B', 'L', 'R' ])) {
self::fail([ 'B', 'L', 'R' ]);
} else {
$s2 = self::$instance->data->consumeIf('LRB');
if ($s2 === '' || $s2 === false) {
$s2 = false;
self::fail('LRB');
}
if ($s2 !== false) {
$s3 = self::$instance->data->consumeIf(':');
if ($s3 !== ':') {
if ($s3 === '' || $s3 === false) {
$s3 = false;
self::fail(':');
} else {
}
if ($s3 !== false) {
$prefix = "$s2$s3";
}
}
$s2 = self::$instance->data->consumeIf('(');
if ($s2 !== '(') {
self::fail('(');
} else {
$s3 = self::parseSpace();
if ($s3 !== false) {
$s4 = self::parseSelector();
if ($s4 !== false) {
$s5 = self::parseSpace();
if ($s5 !== false) {
$s6 = self::$instance->data->consumeIf(')');
if ($s6 !== ')') {
self::fail(')');
} else {
$result = new GroupMatcher($prefix, $s4);
}
if ($prefix !== null) {
$s2 = self::$instance->data->consumeIf('(');
if ($s2 === '' || $s2 === false) {
$s2 = false;
self::fail('(');
}
if ($s2 !== false) {
$s3 = self::parse();
if ($s3 !== false) {
$s4 = self::parseSelector();
if ($s4 !== false) {
$s5 = self::parseSpace();
if ($s5 !== false) {
$s6 = self::$instance->data->consumeIf(')');
if ($s6 === '' || $s6 === false) {
$s6 = false;
self::fail(')');
}
if ($s6 !== false) {
$result = new GroupMatcher($prefix, $s4);
}
}
}
@ -156,114 +203,241 @@ class Parser {
}
}
if (self::$debug === true) {
echo "parseGroup Result: " . var_export($result, true) . "\n";
}
return $result;
}
protected static function parsePath(): Matcher|false {
if (self::$debug === true) {
self::debug();
}
$result = false;
$prefix = null;
$s2 = self::$instance->data->consumeIf('LRB');
if ($s2 === '' || $s2 === false) {
$s2 = false;
self::fail('LRB');
}
$s2 = self::$instance->data->consumeIf('BLR');
if (!in_array($s2, [ 'B', 'L', 'R' ])) {
self::fail([ 'B', 'L', 'R' ]);
} else {
if ($s2 !== false) {
$s3 = self::$instance->data->consumeIf(':');
if ($s3 !== ':') {
if ($s3 === '' || $s3 === false) {
$s3 = false;
self::fail(':');
} else {
}
if ($s3 !== false) {
$prefix = "$s2$s3";
}
}
$s2 = self::parseScope();
if ($s2 !== false) {
$s3 = '';
$s4 = '';
$s2 = self::parseScope();
if ($s2 !== false) {
$s3 = [$s2];
do {
$s4 = false;
$s5 = self::parseSpace();
if ($s5 !== false) {
$s6 = self::parseScope();
if ($s6 !== false) {
$s3[] = $s6;
}
}
} while ($s4 !== false);
while ($s4 !== false) {
$s3 .= $s4;
$s4 = false;
$result = new PathMatcher($prefix, ...$s3);
}
$s5 = self::parseSpace();
if ($s5 !== false) {
$s6 = self::parseScope();
if ($s6 !== false) {
$s4 = "$s5$s6";
}
}
if (self::$debug === true) {
echo "parsePath Result: " . var_export($result, true) . "\n";
}
return $result;
}
protected static function parseSegment(): SegmentMatcher|TrueMatcher|false {
if (self::$debug === true) {
self::debug();
}
$result = false;
$s1 = self::parseSpace();
if ($s1 !== false) {
$s2 = self::$instance->data->consumeWhile('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_');
if ($s2 === '' || $s2 === false) {
$s2 = false;
self::fail('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_');
}
if ($s2 !== false) {
$s3 = self::$instance->data->consumeWhile('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+_');
if ($s3 === '' || $s2 === false) {
$s3 = false;
self::fail('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+_');
} else {
$s2 .= $s3;
}
}
if ($s2 !== false) {
$s3 = self::parseSpace();
if ($s3 !== false) {
$result = new SegmentMatcher($s2);
}
}
if ($result === false) {
$s1 = self::parseSpace();
if ($s1 !== false) {
$s2 = self::$instance->data->consumeIf('*');
if ($s2 === '' || $s2 === false) {
$s2 = false;
self::fail('*');
}
if (strlen($s3) > 0) {
$result = new Matcher\PathMatcher($prefix, $s2, $s3);
if ($s2 !== false) {
$s3 = self::parseSpace();
if ($s3 !== false) {
$result = new TrueMatcher($s2);
}
}
}
}
}
return $result;
}
protected static function parseSpace(): string|false {
return self::$instance->data->consumeIf(" \t");
}
if (self::$debug === true) {
echo "parseSegment Result: " . var_export($result, true) . "\n";
}
protected static function parseSegment(): string|false {
return false;
return $result;
}
protected static function parseSelector(): Matcher|false {
if (self::$debug === true) {
self::debug();
}
$result = false;
$s1 = self::parseComposite();
if ($s1 !== false) {
$s2 = self::parseSpace();
if ($s2 !== false) {
$s3 = self::$instance->data->consumeIf(',');
if ($s3 !== ',') {
if ($s3 === '' || $s3 === false) {
$s3 = false;
self::fail(',');
} else {
}
if ($s3 !== false) {
$s4 = self::parseSpace();
if ($s4 !== false) {
$s5 = self::parseSelector();
if ($s5 !== false) {
$result = new Matcher\OrMatcher($s1, $s5);
}
$result = ($s5 === false) ? $s1 : new OrMatcher($s1, $s5);
}
}
}
}
if ($result === false) {
$result = self::parseComposite();
if (self::$debug === true) {
echo "parseSelector Result: " . var_export($result, true) . "\n";
}
return $result;
}
protected static function parseScope(): string|false {
protected static function parseScope(): ScopeMatcher|false {
if (self::$debug === true) {
self::debug();
}
$result = false;
$s1 = self::parseSegment();
if ($s1 !== false) {
$s2 = '';
$s3 = '';
while ($s3 !== false) {
$s2 .= $s3;
$s2 = [$s1];
do {
$s3 = false;
$s4 = self::$instance->data->consumeIf('.');
if ($s4 !== '.') {
if ($s4 === '' || $s4 === false) {
$s4 = false;
self::fail('.');
} else {
$s5 = self::parseSegment();
if ($s5 !== false) {
$s3 = "$s4$s5";
}
if ($s4 !== false) {
$s3 = self::parseSegment();
if ($s3 !== false) {
$s2[] = $s3;
}
}
}
} while ($s3 !== false);
if (strlen($s2) > 0) {
$result = new Matcher\ScopeMatcher($s1, $s2);
}
$result = new ScopeMatcher(...$s2);
}
if (self::$debug === true) {
echo "parseScope Result: " . var_export($result, true) . "\n";
}
return $result;
}
protected static function parseSpace(): string|false {
if (self::$debug === true) {
self::debug();
}
$result = self::$instance->data->consumeWhile(" \t");
if ($result === false) {
self::fail(' \t');
}
if (self::$debug === true) {
echo "parseSpace Result: " . var_export($result, true) . "\n";
}
return $result;
}
protected static function debug() {
$message = <<<DEBUG
------------------------------
%s
Method: %s
Position: %s
Char: '%s'
DEBUG;
$methodTree = '';
$backtrace = debug_backtrace();
array_shift($backtrace);
array_pop($backtrace);
foreach ($backtrace as $b) {
$methodTree = "->{$b['function']}$methodTree";
}
printf($message,
self::$debugCount++,
ltrim($methodTree, '->'),
self::$instance->data->position,
self::$instance->data->peek()
);
}
protected static function fail(string $expected) {
self::$instance->lastExceptionData = [
'expected' => $expected,
'found' => self::$instance->data->peek()
];
}
}

Loading…
Cancel
Save