--- /dev/null
+<?xml version="1.0"?>
+<psalm
+ errorLevel="1"
+ resolveFromConfigFile="true"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="https://getpsalm.org/schema/config"
+ xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
+>
+ <projectFiles>
+ <directory name="src" />
+ <ignoreFiles>
+ <directory name="vendor" />
+ </ignoreFiles>
+ </projectFiles>
+</psalm>
abstract class BaseParser
{
+ /** @var array<BaseParser|string> */
protected $internals;
+ /** @var callable */
protected $generator;
+ /** @var string */
protected $description;
- protected $acceptsEmpty;
+ /** @var bool */
+ protected $acceptsEmpty = false;
+ /**
+ * @param array<BaseParser|string> $internals
+ * @param callable $generator
+ */
public function __construct(array $internals = array(), $generator = null)
{
if ( !$this->description) {
}
}
+ /** @return array<string, BaseParser|string> */
public function getInternals()
{
return $this->internals;
}
+ /** @return string */
public function getDescription()
{
return $this->description;
}
+ /**
+ * @param string $string
+ * @param int $p the position
+ *
+ * @return array{ r: mixed, p: int }|false
+ */
protected function parse($string, $p = 0)
{
if ( !$r = $this->accept($string, $p)) {
);
}
+ /**
+ * @param string $string
+ * @param int $p the position
+ *
+ * @return array{ r: mixed, p: int }|false
+ */
protected abstract function accept($string, $p);
+ /** @return bool */
protected abstract function evalAcceptsEmpty();
+ /** @return BaseParser[] */
protected abstract function firstSet();
public function __toString()
return $this->description;
}
+ /**
+ * @param mixed $result
+ * @return array{ t: string, r: mixed }
+ */
protected function defaultGenerator($result)
{
return array(
);
}
+ /**
+ * @param array<BaseParser|string> $internals
+ * @return string
+ */
protected function serializeInternals(array $internals)
{
$chunks = array();
class ConcatParser extends BaseParser
{
+ /**
+ * @param array<BaseParser|string> $internals
+ * @param callable $generator
+ */
public function __construct(array $internals, $generator = null)
{
$this->description = 'new '.get_class().'('.$this->serializeInternals($internals).')';
protected function accept($string, $p)
{
- $r = array();
+ $internals = [];
foreach ($this->internals as $internal) {
+ if ($internal instanceof BaseParser) {
+ $internals[] = $internal;
+ } else {
+ throw new \Exception('Expected a BaseParser!');
+ }
+ }
+
+ $r = array();
+ foreach ($internals as $internal) {
if ($i = $internal->parse($string, $p)) {
$r[] = $i['r'];
$p = $i['p'];
protected function evalAcceptsEmpty()
{
+ $internals = [];
foreach ($this->internals as $internal) {
+ if ($internal instanceof BaseParser) {
+ $internals[] = $internal;
+ } else {
+ throw new \Exception('Expected a BaseParser!');
+ }
+ }
+
+ foreach ($internals as $internal) {
if ( !$internal->acceptsEmpty) {
return false;
}
protected function firstSet()
{
- $firstSet = array();
+ $internals = [];
foreach ($this->internals as $internal) {
+ if ($internal instanceof BaseParser) {
+ $internals[] = $internal;
+ } else {
+ throw new \Exception('Expected a BaseParser!');
+ }
+ }
+
+ $firstSet = array();
+ foreach ($internals as $internal) {
$firstSet[] = $internal;
if ( !$internal->acceptsEmpty) {
class EBNFGenerator
{
+ /** @var EBNFParser */
protected $parser;
public function __construct()
$this->parser = new EBNFParser();
}
+ /**
+ * @param string $ebnf
+ * @param string $start
+ * @param string $class
+ *
+ * @return string
+ */
public function generate($ebnf, $start, $class)
{
if ( !$ast = $this->parser->ast($ebnf)) {
$namespace = substr($class, 0, $pos);
$classname = substr($class, $pos + 1);
} else {
+ $namespace = null;
$classname = $class;
}
return $code;
}
+ /**
+ * @param string $generator
+ *
+ * @return string
+ */
protected function genParser(array $object, $generator = null)
{
$method = 'gen'.ucfirst($object['t']).'Parser';
throw new \Exception("Unknow t '{$object['t']}' or missing method $method");
}
- protected function genSyntaxParser($object, $generator)
+ /**
+ * @param string $generator
+ *
+ * @return string
+ */
+ protected function genSyntaxParser(array $object, $generator)
{
$code = "\$internals = array(\n";
foreach ($object['r'][1]['r'] as $rule) {
return $code;
}
- protected function genRuleParser($object)
+ /** @return string */
+ protected function genRuleParser(array $object)
{
$name = $object['r'][0]['r'][0]['r'][0];
$generator = $this->genGenerator($name);
return $code;
}
- protected function genAltParser($object, $generator = null)
+ /**
+ * @param string $generator
+ *
+ * @return string
+ */
+ protected function genAltParser(array $object, $generator = null)
{
$code = "new \\Graphit\\Parser\\LazyAltParser(\n";
$code.= " array(\n";
return $code;
}
- protected function genConcParser($object, $generator = null)
+ /**
+ * @param string $generator
+ *
+ * @return string
+ */
+ protected function genConcParser(array $object, $generator = null)
{
$code = "new \\Graphit\\Parser\\ConcatParser(\n";
$code.= " array(\n";
return $code;
}
- protected function genBarewordParser($object)
+ /**
+ * @return string
+ */
+ protected function genBarewordParser(array $object)
{
$code = var_export($object['r'][0]['r'][0], true);
return $code;
}
- protected function genSqParser($object, $generator = null)
+ /**
+ * @param string $generator
+ *
+ * @return string
+ */
+ protected function genSqParser(array $object, $generator = null)
{
$code = "new \\Graphit\\Parser\\StringParser(";
$code.= $generator? "\n ": '';
return $code;
}
- protected function genDqParser($object, $generator = null)
+ /**
+ * @param string $generator
+ *
+ * @return string
+ */
+ protected function genDqParser(array $object, $generator = null)
{
$code = "new \\Graphit\\Parser\\StringParser(";
$code.= $generator? "\n ": '';
return $code;
}
- protected function genRegexParser($object, $generator = null)
+ /**
+ * @param string $generator
+ *
+ * @return string
+ */
+ protected function genRegexParser(array $object, $generator = null)
{
$code = "new \\Graphit\\Parser\\RegexParser(";
$code.= $generator? "\n ": '';
return $code;
}
- protected function genGroupParser($object, $generator = null)
+ /**
+ * @param string $generator
+ *
+ * @return string
+ */
+ protected function genGroupParser(array $object, $generator = null)
{
return $this->genParser($object['r'][2], $generator);
}
- protected function genRepetitionParser($object, $generator = null)
+ /**
+ * @param string $generator
+ *
+ * @return string
+ */
+ protected function genRepetitionParser(array $object, $generator = null)
{
$code = "new \\Graphit\\Parser\\GreedyMultiParser(\n";
$code.= $this->indent($this->genParser($object['r'][2]), 2);
return $code;
}
- protected function genOptionalParser($object, $generator = null)
+ /**
+ * @param string $generator
+ *
+ * @return string
+ */
+ protected function genOptionalParser(array $object, $generator = null)
{
$code = "new \\Graphit\\Parser\\GreedyMultiParser(\n";
$code.= $this->indent($this->genParser($object['r'][2]), 2);
return $code;
}
+ /**
+ * @param string $source
+ *
+ * @return string
+ */
protected function genGenerator($source)
{
return "{$source}Generator";
}
+ /**
+ * @param string|null $generator
+ * @param bool $break
+ *
+ * @return string
+ */
protected function genGeneratorCall($generator, $break = false)
{
if ( !$generator) {
return $code;
}
+ /**
+ * @param string $lines
+ * @param int $size
+ * @param int $from
+ *
+ * @return string
+ */
protected function indent($lines, $size = 2, $from = 0)
{
$pad = str_repeat(' ', $size);
$lines = explode("\n", $lines);
for ($i = 0; $i < $from; ++$i) {
- if (($line = array_shift($lines)) === false) {
+ if ( !($line = array_shift($lines))) {
break;
}
$code .= "{$line}\n";
class EmptyParser extends BaseParser
{
+ /**
+ * @param callable $generator
+ */
public function __construct($generator = null)
{
$this->description = 'new '.get_class().'()';
class GrammerParser extends BaseParser
{
+ /** @var string */
protected $s;
+ /**
+ * @param string $s
+ * @param array<string, BaseParser|string> $internals
+ * @param callable $generator
+ */
public function __construct($s, array $internals, $generator = null)
{
$this->description = 'new '.get_class().'('.$this->serializeInternals($internals).')';
$this->testInifinitGreedy();
foreach ($this->internals as $internal) {
+ /** @var BaseParser[] */
$done = array();
+ /** @var BaseParser[] */
$todo = array($internal);
while ($current = array_shift($todo)) {
$done[] = $current;
}
}
+ /** @return void */
protected function resolveParserNames()
{
+ /** @var BaseParser[] */
$done = array();
+ /** @var BaseParser[] */
$todo = array($this);
+
while ($current = array_shift($todo)) {
$done[] = $current;
}
}
+ /** @return void */
protected function floodAcceptsEmpty()
{
$change = true;
while ($change) {
$change = false;
+ /** @var BaseParser[] */
$done = array();
+ /** @var BaseParser[] */
$todo = array($this);
while ($current = array_shift($todo)) {
$done[] = $current;
+ $internals = [];
foreach ($current->internals as $internal) {
+ if ($internal instanceof BaseParser) {
+ $internals[] = $internal;
+ } else {
+ throw new \Exception('Expected a BaseParser!');
+ }
+ }
+
+ foreach ($internals as $internal) {
if ($internal->acceptsEmpty) {
continue;
}
}
}
+
+ /** @return void */
protected function testInifinitGreedy()
{
$done = array();
$todo = $this->internals;
while ($current = array_shift($todo)) {
+ if ( !($current instanceof BaseParser)) {
+ throw new \Exception('Expected a BaseParser!');
+ }
$done[] = $current;
foreach ($current->internals as $internal) {
if ($current->getOptional() !== null) {
continue;
}
+
+ if ( !($current->internals[0] instanceof BaseParser)) {
+ throw new \Exception('Expected a BaseParser!');
+ }
if ($current->internals[0]->acceptsEmpty) {
throw new GrammerException("{$current} will cause infinite loops, because the internal parser accepts empty!");
}
}
}
+ /**
+ * @param string $code
+ * @return mixed
+ */
public function ast($code)
{
if ($r = $this->parse($code, 0)) {
protected function accept($string, $p)
{
- if ($r = $this->internals[$this->s]->parse($string, $p)) {
- return $r;
+ if ($this->internals[$this->s] instanceof BaseParser) {
+ if ($r = $this->internals[$this->s]->parse($string, $p)) {
+ return $r;
+ }
+ return false;
}
-
- return false;
+ throw new \Exception('BaseParser expected');
}
protected function evalAcceptsEmpty()
{
- return $this->internals[$this->s]->acceptsEmpty;
+ if ($this->internals[$this->s] instanceof BaseParser) {
+ return $this->internals[$this->s]->acceptsEmpty;
+ }
+ throw new \Exception('BaseParser expected');
}
protected function firstSet()
{
- return array($this->internals[$this->s]);
+ if ($this->internals[$this->s] instanceof BaseParser) {
+ return array($this->internals[$this->s]);
+ }
+ throw new \Exception('BaseParser expected');
}
}
class GreedyMultiParser extends BaseParser
{
+ /** @var int */
protected $lower;
+ /** @var int|null */
protected $optional;
+ /**
+ * @param BaseParser|string $internal
+ * @param int $lower
+ * @param int|null $optional
+ * @param callable $generator
+ */
public function __construct($internal, $lower = 0, $optional = null, $generator = null)
{
$this->lower = $lower;
parent::__construct(array($internal), $generator);
}
+ /** @return int */
public function getLower()
{
return $this->lower;
}
+ /** @return int|null */
public function getOptional()
{
return $this->optional;
protected function accept($string, $p)
{
+ if ( !($this->internals[0] instanceof BaseParser)) {
+ throw new \Exception('Expected a BaseParser!');
+ }
$r = array();
for ($j = 0; $j < $this->lower; $j++) {
if ($i = $this->internals[0]->parse($string, $p)) {
protected function evalAcceptsEmpty()
{
- return $this->lower == 0 || $this->internals[0]->acceptsEmpty;
+ if ($this->internals[0] instanceof BaseParser) {
+ return $this->lower == 0 || $this->internals[0]->acceptsEmpty;
+ }
+ throw new \Exception('Expected a BaseParser!');
}
protected function firstSet()
{
- return $this->internals;
+ if ($this->internals[0] instanceof BaseParser) {
+ return array($this->internals[0]);
+ }
+ throw new \Exception('Expected a BaseParser!');
}
}
class LazyAltParser extends BaseParser
{
+ /**
+ * @param array<BaseParser|string> $internals
+ * @param callable $generator
+ */
public function __construct(array $internals, $generator = null)
{
if ( !$internals) {
protected function accept($string, $p)
{
+ $internals = [];
foreach ($this->internals as $internal) {
+ if ($internal instanceof BaseParser) {
+ $internals[] = $internal;
+ } else {
+ throw new \Exception('Expected a BaseParser!');
+ }
+ }
+
+ foreach ($internals as $internal) {
if ($i = $internal->parse($string, $p)) {
return array(
'r' => $i['r'],
protected function evalAcceptsEmpty()
{
+ $internals = [];
foreach ($this->internals as $internal) {
+ if ($internal instanceof BaseParser) {
+ $internals[] = $internal;
+ } else {
+ throw new \Exception('Expected a BaseParser!');
+ }
+ }
+
+ foreach ($internals as $internal) {
if ($internal->acceptsEmpty) {
return true;
}
protected function firstSet()
{
- return $this->internals;
+ $internals = [];
+ foreach ($this->internals as $internal) {
+ if ($internal instanceof BaseParser) {
+ $internals[] = $internal;
+ } else {
+ throw new \Exception('Expected a BaseParser!');
+ }
+ }
+
+ return $internals;
}
}
class RegexParser extends BaseParser
{
+ /** @var string */
protected $pattern;
+ /**
+ * @param string $pattern
+ * @param callable $generator
+ */
public function __construct($pattern, $generator = null)
{
$this->pattern = (string)$pattern;
parent::__construct(array(), $generator);
}
+ /** @return string */
public function getPattern()
{
return $this->pattern;
}
+ /**
+ * @param string $string
+ * @param int $p the position
+ *
+ * @return array{ r: mixed, p: int }|false
+ */
protected function accept($string, $p)
{
$matches = array();
);
}
+ /** @return bool */
protected function evalAcceptsEmpty()
{
return preg_match($this->pattern, '') === 1;
class StringParser extends BaseParser
{
+ /** @var string */
protected $needle;
+ /**
+ * @param string $needle
+ * @param callable $generator
+ */
public function __construct($needle, $generator = null)
{
$this->needle = (string)$needle;
parent::__construct(array(), $generator);
}
+ /** @return string */
public function getNeedle()
{
return $this->needle;
}
+ /**
+ * @param string $string
+ * @param int $p
+ *
+ * @return array{ r: mixed ,p: int }|false
+ */
protected function accept($string, $p)
{
if ($this->needle !== '' && strpos($string, $this->needle, $p) !== $p) {
);
}
+ /** @return bool */
protected function evalAcceptsEmpty()
{
return $this->needle === '';