|
|
@ -5,14 +5,18 @@ |
|
|
|
|
|
|
|
declare(strict_types=1); |
|
|
|
namespace dW\Lit; |
|
|
|
use dW\Lit\Grammar\CaptureList, |
|
|
|
use dW\Lit\Grammar\BaseReference, |
|
|
|
dW\Lit\Grammar\CaptureList, |
|
|
|
dW\Lit\Grammar\Exception, |
|
|
|
dW\Lit\Grammar\GrammarInclude, |
|
|
|
dW\Lit\Grammar\GrammarReference, |
|
|
|
dW\Lit\Grammar\InjectionList, |
|
|
|
dW\Lit\Grammar\Rule, |
|
|
|
dW\Lit\Grammar\RuleList, |
|
|
|
dW\Lit\Grammar\Pattern, |
|
|
|
dW\Lit\Grammar\PatternList, |
|
|
|
dW\Lit\Grammar\Reference, |
|
|
|
dW\Lit\Grammar\Registry, |
|
|
|
dW\Lit\Grammar\Repository; |
|
|
|
dW\Lit\Grammar\Repository, |
|
|
|
dW\Lit\Grammar\RepositoryReference, |
|
|
|
dW\Lit\Grammar\SelfReference; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
@ -26,52 +30,76 @@ class Grammar { |
|
|
|
protected ?string $_firstLineMatch; |
|
|
|
protected ?InjectionList $_injections; |
|
|
|
protected ?string $_name; |
|
|
|
protected RuleList $_patterns; |
|
|
|
protected ?\WeakReference $_ownerGrammar; |
|
|
|
protected ?PatternList $_patterns; |
|
|
|
protected ?Repository $_repository; |
|
|
|
protected string $_scopeName; |
|
|
|
protected ?string $_scopeName; |
|
|
|
|
|
|
|
|
|
|
|
public function __construct(string $scopeName, RuleList $rules, ?string $name = null, ?string $contentRegex = null, ?string $firstLineMatch = null, ?InjectionList $injections = null, ?Repository $repository = null) { |
|
|
|
public function __construct(?string $scopeName = null, ?PatternList $patterns = null, ?string $name = null, ?string $contentRegex = null, ?string $firstLineMatch = null, ?InjectionList $injections = null, ?Repository $repository = null, ?Grammar $ownerGrammar = null) { |
|
|
|
$this->_name = $name; |
|
|
|
$this->_scopeName = $scopeName; |
|
|
|
$this->_patterns = $rules; |
|
|
|
$this->_patterns = $patterns; |
|
|
|
$this->_contentRegex = $contentRegex; |
|
|
|
$this->_firstLineMatch = $firstLineMatch; |
|
|
|
$this->_injections = $injections; |
|
|
|
$this->_repository = $repository; |
|
|
|
$this->_ownerGrammar = (is_null($ownerGrammar)) ? null : \WeakReference::create($ownerGrammar); |
|
|
|
} |
|
|
|
|
|
|
|
/** Parses an Atom JSON grammar and converts to a Grammar object */ |
|
|
|
public static function fromJSON(string $jsonPath): self { |
|
|
|
if (!is_file($jsonPath)) { |
|
|
|
throw new Exception(Exception::JSON_INVALID_FILE, $jsonPath); |
|
|
|
|
|
|
|
/** Clones the supplied grammar with this grammar set as its owner grammar */ |
|
|
|
public function adoptGrammar(self $grammar): self { |
|
|
|
return new self($grammar->name, $grammar->scopeName, $grammar->patterns, $grammar->contentRegex, $grammar->firstLineMatch, $grammar->injections, $this, $grammar->repository); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** Imports an Atom JSON grammar into the Grammar object */ |
|
|
|
public function loadJSON(string $filename) { |
|
|
|
if (!is_file($filename)) { |
|
|
|
throw new Exception(Exception::JSON_INVALID_FILE, $filename); |
|
|
|
} |
|
|
|
|
|
|
|
$json = json_decode(file_get_contents($jsonPath), true); |
|
|
|
$json = json_decode(file_get_contents($filename), true); |
|
|
|
if ($json === null) { |
|
|
|
throw new Exception(json_last_error() + 200, $jsonPath); |
|
|
|
throw new Exception(json_last_error() + 200, $filename); |
|
|
|
} |
|
|
|
|
|
|
|
if (!isset($json['scopeName'])) { |
|
|
|
throw new Exception(Exception::JSON_MISSING_PROPERTY, $jsonPath, 'scopeName'); |
|
|
|
throw new Exception(Exception::JSON_MISSING_PROPERTY, $filename, 'scopeName'); |
|
|
|
} |
|
|
|
|
|
|
|
if (!isset($json['patterns'])) { |
|
|
|
throw new Exception(Exception::JSON_MISSING_PROPERTY, $jsonPath, 'patterns'); |
|
|
|
throw new Exception(Exception::JSON_MISSING_PROPERTY, $filename, 'patterns'); |
|
|
|
} |
|
|
|
|
|
|
|
$name = $json['name'] ?? null; |
|
|
|
$scopeName = $json['scopeName']; |
|
|
|
$contentRegex = (isset($json['contentRegex'])) ? "/{$json['contentRegex']}/" : null; |
|
|
|
$firstLineMatch = (isset($json['firstLineMatch'])) ? "/{$json['firstLineMatch']}/" : null; |
|
|
|
$this->_name = $json['name'] ?? null; |
|
|
|
$this->_scopeName = $json['scopeName']; |
|
|
|
$this->_contentRegex = (isset($json['contentRegex'])) ? "/{$json['contentRegex']}/" : null; |
|
|
|
$this->_firstLineMatch = (isset($json['firstLineMatch'])) ? "/{$json['firstLineMatch']}/" : null; |
|
|
|
|
|
|
|
$rules = self::parseJSONRuleList($json['patterns'], $jsonPath); |
|
|
|
$repository = null; |
|
|
|
if (isset($json['repository'])) { |
|
|
|
$respository = []; |
|
|
|
foreach ($json['repository'] as $key => $r) { |
|
|
|
$repository[$key] = $this->parseJSONPattern($r, $filename); |
|
|
|
} |
|
|
|
|
|
|
|
if (count($repository) > 0) { |
|
|
|
$repository = new Repository($repository); |
|
|
|
} else { |
|
|
|
$repository = null; |
|
|
|
} |
|
|
|
} |
|
|
|
$this->_repository = $repository; |
|
|
|
|
|
|
|
$this->_patterns = $this->parseJSONPatternList($json['patterns'], $filename); |
|
|
|
|
|
|
|
$injections = null; |
|
|
|
if (isset($json['injections'])) { |
|
|
|
$injections = []; |
|
|
|
foreach ($json['injections'] as $key => $injection) { |
|
|
|
$injsections[$key] = (count($injection) === 1 && key($injection) === 'patterns') ? self::parseJSONRuleList($injection['patterns'], $jsonPath) : self::parseJSONRule($injection, $jsonPath); |
|
|
|
$injections[$key] = $this->parseJSONPattern($injection, $filename); |
|
|
|
} |
|
|
|
|
|
|
|
if (count($injections) > 0) { |
|
|
@ -80,30 +108,23 @@ class Grammar { |
|
|
|
$injections = null; |
|
|
|
} |
|
|
|
} |
|
|
|
$this->_injections = $injections; |
|
|
|
} |
|
|
|
|
|
|
|
$repository = null; |
|
|
|
if (isset($json['repository'])) { |
|
|
|
$respository = []; |
|
|
|
foreach ($json['repository'] as $key => $r) { |
|
|
|
$repository[$key] = (count($r) === 1 && key($r) === 'patterns') ? self::parseJSONRuleList($r['patterns'], $jsonPath) : self::parseJSONRule($r, $jsonPath); |
|
|
|
} |
|
|
|
|
|
|
|
if (count($repository) > 0) { |
|
|
|
$repository = new Repository($repository); |
|
|
|
protected function parseJSONPattern(array $pattern, string $filename): Pattern|Reference|\WeakReference|null { |
|
|
|
if (isset($pattern['include'])) { |
|
|
|
if ($pattern['include'][0] === '#') { |
|
|
|
return new RepositoryReference(substr($pattern['include'], 1), $this); |
|
|
|
} elseif ($pattern['include'] === '$base') { |
|
|
|
return new BaseReference($this); |
|
|
|
} elseif ($pattern['include'] === '$self') { |
|
|
|
return \WeakReference::create($this); |
|
|
|
} else { |
|
|
|
$repository = null; |
|
|
|
return new GrammarReference($pattern['include'], $this); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return new self($scopeName, $rules, $name, $contentRegex, $firstLineMatch, $injections, $repository); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected static function parseJSONRule(array $rule, string $jsonPath): GrammarInclude|Rule|null { |
|
|
|
if (array_keys($rule) === [ 'include' ]) { |
|
|
|
return new GrammarInclude($rule['include']); |
|
|
|
} |
|
|
|
|
|
|
|
$p = [ |
|
|
|
'name' => null, |
|
|
|
'contentName' => null, |
|
|
@ -118,11 +139,11 @@ class Grammar { |
|
|
|
]; |
|
|
|
|
|
|
|
$modified = false; |
|
|
|
foreach ($rule as $key => $value) { |
|
|
|
foreach ($pattern as $key => $value) { |
|
|
|
switch ($key) { |
|
|
|
case 'applyEndPatternLast': |
|
|
|
if (!is_bool($value) || (!is_int($value) && ($value !== 0 && $value !== 1))) { |
|
|
|
throw new Exception(Exception::JSON_INVALID_TYPE, 'Boolean, 0, or 1', 'applyEndPatternLast', gettype($value), $jsonPath); |
|
|
|
throw new Exception(Exception::JSON_INVALID_TYPE, 'Boolean, 0, or 1', 'applyEndPatternLast', gettype($value), $filename); |
|
|
|
} |
|
|
|
|
|
|
|
$value = (bool)$value; |
|
|
@ -141,27 +162,27 @@ class Grammar { |
|
|
|
case 'beginCaptures': |
|
|
|
case 'endCaptures': |
|
|
|
if (!is_array($value)) { |
|
|
|
throw new Exception(Exception::JSON_INVALID_TYPE, 'Array', $key, gettype($value), $jsonPath); |
|
|
|
throw new Exception(Exception::JSON_INVALID_TYPE, 'Array', $key, gettype($value), $filename); |
|
|
|
} |
|
|
|
|
|
|
|
if (count($value) === 0) { |
|
|
|
continue 2; |
|
|
|
} |
|
|
|
|
|
|
|
$k = array_map(function($n) use ($jsonPath) { |
|
|
|
$k = array_map(function($n) use ($filename) { |
|
|
|
if (is_int($n)) { |
|
|
|
return $n; |
|
|
|
} |
|
|
|
|
|
|
|
if (strspn($n, '0123456789') !== strlen($n)) { |
|
|
|
throw new Exception(Exception::JSON_INVALID_TYPE, 'Integer', 'capture list index', $n, $jsonPath); |
|
|
|
throw new Exception(Exception::JSON_INVALID_TYPE, 'Integer', 'capture list index', $n, $filename); |
|
|
|
} |
|
|
|
|
|
|
|
return (int)$n; |
|
|
|
}, array_keys($value)); |
|
|
|
|
|
|
|
$v = array_map(function($n) use ($jsonPath) { |
|
|
|
return (count($n) === 1 && key($n) === 'patterns') ? self::parseJSONRuleList($n['patterns'], $jsonPath) : self::parseJSONRule($n, $jsonPath); |
|
|
|
$v = array_map(function($n) use ($filename) { |
|
|
|
return $this->parseJSONPattern($n, $filename); |
|
|
|
}, array_values($value)); |
|
|
|
|
|
|
|
$p[$key] = new CaptureList(array_combine($k, $v)); |
|
|
@ -169,27 +190,27 @@ class Grammar { |
|
|
|
break; |
|
|
|
case 'patterns': |
|
|
|
if (!is_array($value)) { |
|
|
|
throw new Exception(Exception::JSON_INVALID_TYPE, 'Array', $key, gettype($value), $jsonPath); |
|
|
|
throw new Exception(Exception::JSON_INVALID_TYPE, 'Array', $key, gettype($value), $filename); |
|
|
|
} |
|
|
|
|
|
|
|
$p[$key] = self::parseJSONRuleList($value, $jsonPath); |
|
|
|
$p[$key] = $this->parseJSONPatternList($value, $filename); |
|
|
|
$modified = true; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return ($modified) ? new Rule(...$p) : null; |
|
|
|
return ($modified) ? new Pattern(...$p) : null; |
|
|
|
} |
|
|
|
|
|
|
|
protected static function parseJSONRuleList(array $list, string $jsonPath): ?RuleList { |
|
|
|
protected function parseJSONPatternList(array $list, string $filename): Pattern|PatternList|null { |
|
|
|
$result = []; |
|
|
|
foreach ($list as $rule) { |
|
|
|
$p = self::parseJSONRule($rule, $jsonPath); |
|
|
|
foreach ($list as $pattern) { |
|
|
|
$p = $this->parseJSONPattern($pattern, $filename); |
|
|
|
if ($p !== null) { |
|
|
|
$result[] = $p; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return (count($result) > 0) ? new RuleList(...$result) : null; |
|
|
|
return (count($result) > 0) ? new PatternList(...$result) : null; |
|
|
|
} |
|
|
|
} |