Browse Source

Wrote JSON to Grammar converter, successfully parses all grammars tested

main
Dustin Wilson 3 years ago
parent
commit
900e462bcd
  1. 12
      data/xml/xml.json
  2. 6
      lib/FauxReadOnly.php
  3. 168
      lib/Grammar.php
  4. 18
      lib/Grammar/CaptureList.php
  5. 35
      lib/Grammar/GrammarInclude.php
  6. 31
      lib/Grammar/ImmutableList.php
  7. 32
      lib/Grammar/Include.php
  8. 2
      lib/Grammar/InjectionList.php
  9. 2
      lib/Grammar/NamedPatternListList.php
  10. 10
      lib/Grammar/Pattern.php
  11. 4
      lib/Grammar/PatternList.php
  12. 4
      lib/Scope/Matchers/GroupMatcher.php
  13. 4
      lib/Scope/Matchers/PathMatcher.php
  14. 4
      lib/Scope/Parser.php

12
data/xml/xml.json

@ -431,12 +431,12 @@
{
"begin": "<%--",
"captures": {
"0": {
"name": "punctuation.definition.comment.xml"
},
"end": "--%>",
"name": "comment.block.xml"
}
"0": {
"name": "punctuation.definition.comment.xml"
}
},
"end": "--%>",
"name": "comment.block.xml"
},
{
"begin": "<!--",

6
lib/FauxReadOnly.php

@ -8,11 +8,11 @@ namespace dW\Lit;
trait FauxReadOnly {
public function __get(string $name) {
if ($name[0] !== '_') {
if ($name[0] === '_') {
return;
}
$name = substr($name, 1);
return $this->$name;
$prop = "_$name";
return $this->$prop;
}
}

168
lib/Grammar.php

@ -5,23 +5,26 @@
declare(strict_types=1);
namespace dW\Lit;
use dW\Lit\Grammar\InjectionList,
use dW\Lit\Grammar\CaptureList,
dW\Lit\Grammar\GrammarInclude,
dW\Lit\Grammar\InjectionList,
dW\Lit\Grammar\Pattern,
dW\Lit\Grammar\PatternList,
dW\Lit\Grammar\Repository;
class Grammar {
use FauxReadOnly;
protected string|null $_contentRegex;
protected string|null $_firstLineMatch;
protected InjectionList|null $_injections;
protected string $_name;
protected ?string $_contentRegex;
protected ?string $_firstLineMatch;
protected ?InjectionList $_injections;
protected ?string $_name;
protected PatternList $_patterns;
protected Repository|null $_repository;
protected ?Repository $_repository;
protected string $_scopeName;
public function __construct(string $name, string $scopeName, PatternList $patterns, string|null $contentRegex = null, string|null $firstLineMatch = null, InjectionList|null $injections = null, Repository|null $repository = null) {
public function __construct(string $scopeName, PatternList $patterns, ?string $name = null, ?string $contentRegex = null, ?string $firstLineMatch = null, ?InjectionList $injections = null, ?Repository $repository = null) {
$this->_name = $name;
$this->_scopeName = $scopeName;
$this->_patterns = $patterns;
@ -35,58 +38,159 @@ class Grammar {
public static function fromJSON(string $jsonPath): self {
assert(is_file($jsonPath), new \Exception("\"$jsonPath\" is either not a file or you do not have permission to read the file\n"));
$json = json_decode($jsonPath, true);
assert($json, new \Exception("\"$jsonPath\" is not a valid JSON file.\n"));
$json = json_decode(file_get_contents($jsonPath), true);
if ($json === null) {
$message = "Parsing \"$jsonPath\" failed with the following error: ";
switch (json_last_error()) {
case JSON_ERROR_DEPTH:
$message .= 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$message .= 'Underflow or mode mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$message .= 'Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
$message .= 'Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
$message .= 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$message .= 'Unknown error';
break;
}
throw new \Exception("$message\n");
}
assert(isset($json['name']), new \Exception("\"$jsonPath\" does not have the required name property"));
assert(isset($json['scopeName']), new \Exception("\"$jsonPath\" does not have the required scopeName property"));
assert(isset($json['patterns']), new \Exception("\"$jsonPath\" does not have the required patterns property"));
$name = $json['name'];
$name = $json['name'] ?? null;
$scopeName = $json['scopeName'];
$contentRegex = (isset($json['contentRegex'])) ? "/{$json['contentRegex']}/" : null;
$firstLineMatch = (isset($json['firstLineMatch'])) ? "/{$json['firstLineMatch']}/" : null;
$patterns = [];
foreach ($json['patterns'] as $pattern) {
foreach ($pattern as $key => $p) {
}
}
if (count($patterns) > 0) {
$patterns = new PatternList(...$patterns);
} else {
$patterns = null;
}
$patterns = self::parseJSONPatternList($json['patterns']);
$injections = null;
if (isset($json['injections'])) {
$injections = [];
foreach ($json['injections'] as $injection) {
foreach ($json['injections'] as $key => $injection) {
$injsections[$key] = (count($injection) === 1 && key($injection) === 'patterns') ? self::parseJSONPatternList($injection['patterns']) : self::parseJSONPattern($injection);
}
if (count($injections) > 0) {
$injections = new InjectionList($injections);
} else {
$patterns = null;
$injections = null;
}
}
$repository = null;
if (isset($json['repository'])) {
$respository = [];
foreach ($json['repository'] as $r) {
foreach ($json['repository'] as $key => $r) {
$repository[$key] = (count($r) === 1 && key($r) === 'patterns') ? self::parseJSONPatternList($r['patterns']) : self::parseJSONPattern($r);
}
if (count($repository) > 0) {
$repository = new InjectionList($repository);
$repository = new Repository($repository);
} else {
$repository = null;
}
}
return new self($name, $scopeName, $patterns, $contentRegex, $firstLineMatch, $injections, $repository);
return new self($scopeName, $patterns, $name, $contentRegex, $firstLineMatch, $injections, $repository);
}
protected static function parseJSONPattern(array $pattern): GrammarInclude|Pattern|null {
if (array_keys($pattern) === [ 'include' ]) {
return new GrammarInclude($pattern['include']);
}
$p = [
'name' => null,
'contentName' => null,
'begin' => null,
'end' => null,
'match' => null,
'patterns' => null,
'captures' => null,
'beginCaptures' => null,
'endCaptures' => null,
'applyEndPatternLast' => false
];
$modified = false;
foreach ($pattern as $key => $value) {
switch ($key) {
case 'applyEndPatternLast':
assert(is_bool($value) || (is_int($value) && ($value === 0 || $value === 1)), new \Exception("The value for applyEndPatternLast must be either a boolean, 0, or 1\n"));
$value = (bool)$value;
case 'name':
case 'contentName':
$p[$key] = $value;
$modified = true;
break;
case 'begin':
case 'end':
case 'match':
$p[$key] = "/$value/";
$modified = true;
break;
case 'captures':
case 'beginCaptures':
case 'endCaptures':
assert(is_array($value), new \Exception("Array value expected for '$key', found " . gettype($value) . "\n"));
if (count($value) === 0) {
continue 2;
}
$kk = array_keys($value);
$v = array_values($value);
// Skipping that bad three k variable name here... :)
foreach ($kk as &$kkkk) {
if (is_int($kkkk)) {
continue;
}
assert(strspn($kkkk, '0123456789') === strlen($kkkk), new \Exception("\"$kkkk\" is not castable to an integer for use in a capture list\n"));
$kkk = (int)$kkkk;
}
$v = array_map(function ($n) {
return (count($n) === 1 && key($n) === 'patterns') ? self::parseJSONPatternList($n['patterns']) : self::parseJSONPattern($n);
}, $v);
$p[$key] = new CaptureList(array_combine($kk, $v));
$modified = true;
break;
case 'patterns':
assert(is_array($value), new \Exception("Array value expected for '$key', found " . gettype($value) . "\n"));
$p[$key] = self::parseJSONPatternList($value);
$modified = true;
break;
}
}
return ($modified) ? new Pattern(...$p) : null;
}
protected static function parseJSONPatternList(array $list): ?PatternList {
$result = [];
foreach ($list as $pattern) {
$p = self::parseJSONPattern($pattern);
if ($p !== null) {
$result[] = $p;
}
}
return (count($result) > 0) ? new PatternList(...$result) : null;
}
}

18
lib/Grammar/CaptureList.php

@ -0,0 +1,18 @@
<?php
/** @license MIT
* Copyright 2021 Dustin Wilson et al.
* See LICENSE file for details */
declare(strict_types=1);
namespace dW\Lit\Grammar;
class CaptureList extends ImmutableList {
public function __construct(array $array) {
foreach ($array as $k => $v) {
assert(is_int($k), new \Exception('Integer index expected for supplied array, found ' . gettype($k) . "\n"));
assert($v instanceof GrammarInclude || $v instanceof Pattern || $v instanceof PatternList, new \Exception(__NAMESPACE__ . '\GrammarInclude, ' . __NAMESPACE__ . '\Pattern, or ' . __NAMESPACE__ . '\PatternList value expected for supplied array, found ' . gettype($v) . "\n"));
}
$this->storage = $array;
}
}

35
lib/Grammar/GrammarInclude.php

@ -0,0 +1,35 @@
<?php
/** @license MIT
* Copyright 2021 Dustin Wilson et al.
* See LICENSE file for details */
declare(strict_types=1);
namespace dW\Lit\Grammar;
use dW\Lit\FauxReadOnly;
class GrammarInclude {
use FauxReadOnly;
const BASE_TYPE = 0;
const REPOSITORY_TYPE = 1;
const SCOPE_TYPE = 2;
const SELF_TYPE = 3;
protected ?string $_name;
protected int $_type;
public function __construct(string $string) {
if ($string[0] === '#') {
$this->_type = self::REPOSITORY_TYPE;
$this->_name = substr($string, 1);
} elseif ($string === '$base') {
$this->_type = self::BASE_TYPE;
} elseif ($string === '$self') {
$this->_type = self::SELF_TYPE;
} else {
$this->_type = self::SCOPE_TYPE;
$this->_name = $string;
}
}
}

31
lib/Grammar/ImmutableList.php

@ -6,12 +6,14 @@
declare(strict_types=1);
namespace dW\Lit\Grammar;
abstract class ImmutableList implements \ArrayAccess, \Countable {
protected $storage = [];
protected $count = 0;
abstract class ImmutableList implements \ArrayAccess, \Countable, \Iterator {
protected int $count = 0;
protected int|string|null $position;
protected array $storage = [];
public function __construct(...$values) {
$this->storage = $values;
$this->count = count($this->storage);
}
public function offsetSet($offset, $value) {
@ -31,6 +33,29 @@ abstract class ImmutableList implements \ArrayAccess, \Countable {
return $this->storage[$offset];
}
public function rewind() {
reset($this->storage);
$this->position = key($this->storage);
}
public function current() {
return current($this->storage);
}
public function key(){
$this->position = key($this->storage);
return $this->position;
}
public function next() {
next($this->storage);
$this->position = key($this->storage);
}
public function valid() {
return $this->offsetExists($this->position);
}
public function count(): int {
return $this->count;
}

32
lib/Grammar/Include.php

@ -1,32 +0,0 @@
<?php
/** @license MIT
* Copyright 2021 Dustin Wilson et al.
* See LICENSE file for details */
declare(strict_types=1);
namespace dW\Lit\Grammar;
use dW\Lit\FauxReadOnly;
class Include {
use FauxReadOnly;
const REPOSITORY_TYPE = 0;
const SCOPE_TYPE = 1;
const SELF_TYPE = 2;
protected ?string $name;
protected int $type;
public function __construct(string $string) {
if ($string[0] === '#') {
$this->type = self::REPOSITORY_TYPE;
$this->name = substr($string, 1);
} elseif ($string === '$self') {
$this->type = self::SELF_TYPE;
}
$this->type = self::SCOPE_TYPE;
$this->name = $string;
}
}

2
lib/Grammar/InjectionList.php

@ -6,4 +6,4 @@
declare(strict_types=1);
namespace dW\Lit\Grammar;
class Repository extends NamedPatternListList {}
class InjectionList extends NamedPatternListList {}

2
lib/Grammar/NamedPatternListList.php

@ -10,7 +10,7 @@ abstract class NamedPatternListList extends ImmutableList {
public function __construct(array $array) {
foreach ($array as $k => $v) {
assert(is_string($k), new \Exception('String index expected for supplied array, found ' . gettype($k) . "\n"));
assert($v instanceof PatternList, new \Exception(__NAMESPACE__ . '\PatternList value expected for supplied array, found ' . gettype($v) . "\n"));
assert($v instanceof GrammarInclude || $v instanceof Pattern || $v instanceof PatternList, new \Exception(__NAMESPACE__ . '\GrammarInclude, ' . __NAMESPACE__ . '\Pattern, or ' . __NAMESPACE__ . '\PatternList value expected for supplied array, found ' . gettype($v) . "\n"));
}
$this->storage = $array;

10
lib/Grammar/Pattern.php

@ -13,26 +13,26 @@ class Pattern {
protected bool $_applyEndPatternLast = false;
protected ?string $_begin;
protected ?array $_beginCaptures;
protected ?array $_captures;
protected ?CaptureList $_beginCaptures;
protected ?CaptureList $_captures;
protected ?string $_contentName;
protected ?string $_end;
protected ?array $_endCaptures;
protected ?CaptureList $_endCaptures;
protected ?string $_match;
protected ?string $_name;
protected ?PatternList $_patterns;
public function __construct(?string $name = null, ?string $contentName = null, ?string $begin = null, ?string $end = null, ?string $match = null, ?PatternList $patterns = null, ?string $include = null, ?array $captures = null, ?array $beginCaptures = null, ?array $endCaptures = null) {
public function __construct(?string $name = null, ?string $contentName = null, ?string $begin = null, ?string $end = null, ?string $match = null, ?PatternList $patterns = null, ?CaptureList $captures = null, ?CaptureList $beginCaptures = null, ?CaptureList $endCaptures = null, bool $applyEndPatternLast = false) {
$this->_name = $name;
$this->_contentName = $contentName;
$this->_begin = $begin;
$this->_end = $end;
$this->_match = $match;
$this->_patterns = $patterns;
$this->_captures = $captures;
$this->_beginCaptures = $beginCaptures;
$this->_endCaptures = $endCaptures;
$this->_applyEndPatternLast = $applyEndPatternLast;
}
}

4
lib/Grammar/PatternList.php

@ -7,7 +7,7 @@ declare(strict_types=1);
namespace dW\Lit\Grammar;
class PatternList extends ImmutableList {
public function __construct(Pattern|Include ...$values) {
parent::__construct($values);
public function __construct(Pattern|GrammarInclude ...$values) {
parent::__construct(...$values);
}
}

4
lib/Scope/Matchers/GroupMatcher.php

@ -7,10 +7,10 @@ declare(strict_types=1);
namespace dW\Lit\Scope;
class GroupMatcher extends Matcher {
protected string|null $prefix;
protected ?string $prefix;
protected Matcher $selector;
public function __construct(string|null $prefix, Matcher $selector) {
public function __construct(?string $prefix, Matcher $selector) {
$this->prefix = ($prefix !== null) ? $prefix[0] : null;
$this->selector = $selector;
}

4
lib/Scope/Matchers/PathMatcher.php

@ -7,10 +7,10 @@ declare(strict_types=1);
namespace dW\Lit\Scope;
class PathMatcher extends Matcher {
protected string|null $prefix;
protected ?string $prefix;
protected array $matchers;
public function __construct(string|null $prefix, ScopeMatcher ...$matchers) {
public function __construct(?string $prefix, ScopeMatcher ...$matchers) {
$this->prefix = ($prefix !== null) ? $prefix[0] : null;
$this->matchers = $matchers;
}

4
lib/Scope/Parser.php

@ -103,7 +103,7 @@ class Parser {
return $result;
}
protected static function parseGroup(string|null $prefix = null): Matcher {
protected static function parseGroup(?string $prefix = null): Matcher {
if (self::$debug) {
self::debug();
}
@ -123,7 +123,7 @@ class Parser {
return $result;
}
protected static function parsePath(string|null $prefix = null): PathMatcher|ScopeMatcher {
protected static function parsePath(?string $prefix = null): PathMatcher|ScopeMatcher {
if (self::$debug) {
self::debug();
}

Loading…
Cancel
Save