From 2067742f07735949219ca044675b3cff7a687a80 Mon Sep 17 00:00:00 2001 From: Sebastian Brix Date: Fri, 17 Feb 2023 09:31:10 +0100 Subject: [PATCH] major rewrite --- bin/generate_parser.php | 84 +++++ composer.json | 5 +- composer.lock | 10 +- psalm.xml | 1 + src/BaseParser.php | 124 ------- src/ConcatParser.php | 85 ----- src/EBNF/AltResult.php | 13 + src/EBNF/Anonymous11Result.php | 13 + src/EBNF/Anonymous14Result.php | 13 + src/EBNF/Anonymous16Result.php | 13 + src/EBNF/Anonymous18Result.php | 13 + src/EBNF/Anonymous20Result.php | 13 + src/EBNF/Anonymous22Result.php | 13 + src/EBNF/Anonymous23Result.php | 13 + src/EBNF/Anonymous25Result.php | 13 + src/EBNF/Anonymous26Result.php | 13 + src/EBNF/Anonymous28Result.php | 13 + src/EBNF/Anonymous29Result.php | 13 + src/EBNF/Anonymous2Result.php | 13 + src/EBNF/Anonymous31Result.php | 13 + src/EBNF/Anonymous3Result.php | 13 + src/EBNF/Anonymous7Result.php | 13 + src/EBNF/BarewordResult.php | 13 + src/EBNF/CommatermResult.php | 13 + src/EBNF/CommatermlistResult.php | 13 + src/EBNF/CommentResult.php | 13 + src/EBNF/ConcResult.php | 13 + src/EBNF/DqResult.php | 13 + src/EBNF/EBNFParser.php | 595 +++++++++++++++++++++++++++++++ src/EBNF/GroupResult.php | 13 + src/EBNF/OptionalResult.php | 13 + src/EBNF/PipeconcResult.php | 13 + src/EBNF/PipeconclistResult.php | 13 + src/EBNF/RegexResult.php | 13 + src/EBNF/RepetitionResult.php | 13 + src/EBNF/RuleResult.php | 13 + src/EBNF/RulesResult.php | 13 + src/EBNF/SpaceResult.php | 13 + src/EBNF/SqResult.php | 13 + src/EBNF/SyntaxResult.php | 13 + src/EBNF/TermResult.php | 13 + src/EBNF/WhitespaceResult.php | 13 + src/EBNFGenerator.php | 298 ---------------- src/EBNFParser.php | 57 --- src/EBNFParserBase.php | 302 ---------------- src/EBNFParserGenerator.php | 310 ++++++++++++++++ src/EBNFResultGenerator.php | 89 +++++ src/EBNFTransform.php | 495 +++++++++++++++++++++++++ src/EmptyParser.php | 34 -- src/GrammerException.php | 5 - src/GrammerParser.php | 205 ----------- src/GreedyMultiParser.php | 87 ----- src/LazyAltParser.php | 75 ---- src/RegexParser.php | 61 ---- src/StringParser.php | 57 --- 55 files changed, 2037 insertions(+), 1397 deletions(-) create mode 100755 bin/generate_parser.php delete mode 100644 src/BaseParser.php delete mode 100644 src/ConcatParser.php create mode 100644 src/EBNF/AltResult.php create mode 100644 src/EBNF/Anonymous11Result.php create mode 100644 src/EBNF/Anonymous14Result.php create mode 100644 src/EBNF/Anonymous16Result.php create mode 100644 src/EBNF/Anonymous18Result.php create mode 100644 src/EBNF/Anonymous20Result.php create mode 100644 src/EBNF/Anonymous22Result.php create mode 100644 src/EBNF/Anonymous23Result.php create mode 100644 src/EBNF/Anonymous25Result.php create mode 100644 src/EBNF/Anonymous26Result.php create mode 100644 src/EBNF/Anonymous28Result.php create mode 100644 src/EBNF/Anonymous29Result.php create mode 100644 src/EBNF/Anonymous2Result.php create mode 100644 src/EBNF/Anonymous31Result.php create mode 100644 src/EBNF/Anonymous3Result.php create mode 100644 src/EBNF/Anonymous7Result.php create mode 100644 src/EBNF/BarewordResult.php create mode 100644 src/EBNF/CommatermResult.php create mode 100644 src/EBNF/CommatermlistResult.php create mode 100644 src/EBNF/CommentResult.php create mode 100644 src/EBNF/ConcResult.php create mode 100644 src/EBNF/DqResult.php create mode 100644 src/EBNF/EBNFParser.php create mode 100644 src/EBNF/GroupResult.php create mode 100644 src/EBNF/OptionalResult.php create mode 100644 src/EBNF/PipeconcResult.php create mode 100644 src/EBNF/PipeconclistResult.php create mode 100644 src/EBNF/RegexResult.php create mode 100644 src/EBNF/RepetitionResult.php create mode 100644 src/EBNF/RuleResult.php create mode 100644 src/EBNF/RulesResult.php create mode 100644 src/EBNF/SpaceResult.php create mode 100644 src/EBNF/SqResult.php create mode 100644 src/EBNF/SyntaxResult.php create mode 100644 src/EBNF/TermResult.php create mode 100644 src/EBNF/WhitespaceResult.php delete mode 100644 src/EBNFGenerator.php delete mode 100644 src/EBNFParser.php delete mode 100644 src/EBNFParserBase.php create mode 100644 src/EBNFParserGenerator.php create mode 100644 src/EBNFResultGenerator.php create mode 100644 src/EBNFTransform.php delete mode 100644 src/EmptyParser.php delete mode 100644 src/GrammerException.php delete mode 100644 src/GrammerParser.php delete mode 100644 src/GreedyMultiParser.php delete mode 100644 src/LazyAltParser.php delete mode 100644 src/RegexParser.php delete mode 100644 src/StringParser.php diff --git a/bin/generate_parser.php b/bin/generate_parser.php new file mode 100755 index 0000000..aae6384 --- /dev/null +++ b/bin/generate_parser.php @@ -0,0 +1,84 @@ +#!/usr/bin/env php + 2; +} + +if (folderHasContents($path)) { + echo PHP_EOL; + echo 'Folder exists and contains files or sub-folders: ' . $path . PHP_EOL; + exit(1); +} + +$transformResult = EBNFTransform::transformSyntax($result); + +EBNFResultGenerator::generate($transformResult, $namespace, $path); + +EBNFParserGenerator::generate($transformResult, $namespace, $className, $path, $topMostFuncName); + diff --git a/composer.json b/composer.json index 76beec3..f233eb2 100644 --- a/composer.json +++ b/composer.json @@ -8,11 +8,12 @@ } ], "require": { - "php": ">=5.6.0" + "php": ">=8.1.0" }, "autoload": { "psr-4": { "Graphit\\Parser\\": "src/" } - } + }, + "bin": [ "bin/generate_parser.php" ] } diff --git a/composer.lock b/composer.lock index dff83d1..1db3bd5 100644 --- a/composer.lock +++ b/composer.lock @@ -1,11 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "hash": "58c59c1998c08b0e41abf3c52e03cf24", - "content-hash": "885de769d17478ea8942f54d26e6a439", + "content-hash": "b6e7241a4a8086d50eb538b9dc2583f6", "packages": [], "packages-dev": [], "aliases": [], @@ -14,7 +13,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=5.6.0" + "php": ">=8.1.0" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.3.0" } diff --git a/psalm.xml b/psalm.xml index 3240886..7228419 100644 --- a/psalm.xml +++ b/psalm.xml @@ -7,6 +7,7 @@ xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" > + diff --git a/src/BaseParser.php b/src/BaseParser.php deleted file mode 100644 index 33804b8..0000000 --- a/src/BaseParser.php +++ /dev/null @@ -1,124 +0,0 @@ - */ - protected $internals; - - /** @var callable */ - protected $generator; - - /** @var string */ - protected $description; - - /** @var bool */ - protected $acceptsEmpty = false; - - /** - * @param array $internals - * @param callable $generator - */ - public function __construct(array $internals = array(), $generator = null) - { - if ( !$this->description) { - throw new \Exception("No description prepared!"); - } - - foreach ($internals as $internal) { - if ( !is_string($internal) && !$internal instanceof BaseParser) { - throw new \Exception(var_export($internal, true).' is not a string and not a BaseParser'); - } - } - $this->internals = $internals; - - if ($generator) { - if ( !is_callable($generator)) { - throw new \Exception(var_export($generator, true).' is not callable'); - } - $this->generator = $generator; - } else { - $this->generator = array($this, 'defaultGenerator'); - } - } - - /** @return array */ - 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)) { - return false; - } - return array( - 'r' => call_user_func($this->generator, $r['r']), - 'p' => $r['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( - 't' => (string)$this, - 'r' => $result, - ); - } - - /** - * @param array $internals - * @return string - */ - protected function serializeInternals(array $internals) - { - $chunks = array(); - foreach ($internals as $key => $internal) { - $chunk = var_export($key, true).' => '; - if (is_string($internal)) { - $chunk .= var_export($internal, true).' => '; - } else { - $chunk .= (string)$internal; - } - $chunks[] = $chunk; - } - return 'array('.implode(', ', $chunks).')'; - } -} diff --git a/src/ConcatParser.php b/src/ConcatParser.php deleted file mode 100644 index 92d6e4f..0000000 --- a/src/ConcatParser.php +++ /dev/null @@ -1,85 +0,0 @@ - $internals - * @param callable $generator - */ - public function __construct(array $internals, $generator = null) - { - $this->description = 'new '.get_class().'('.$this->serializeInternals($internals).')'; - parent::__construct($internals, $generator); - } - - protected function accept($string, $p) - { - $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']; - - continue; - } - return false; - } - - return array( - 'r' => $r, - 'p' => $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; - } - } - return true; - } - - protected function firstSet() - { - $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) { - break; - } - } - return $firstSet; - } -} diff --git a/src/EBNF/AltResult.php b/src/EBNF/AltResult.php new file mode 100644 index 0000000..c185fa2 --- /dev/null +++ b/src/EBNF/AltResult.php @@ -0,0 +1,13 @@ + $result + */ + public function __construct( + public readonly array $result, + public readonly int $position + ) {} +} \ No newline at end of file diff --git a/src/EBNF/CommentResult.php b/src/EBNF/CommentResult.php new file mode 100644 index 0000000..3c4d7c7 --- /dev/null +++ b/src/EBNF/CommentResult.php @@ -0,0 +1,13 @@ +position != strlen($code)) { + return null; + } + + return $result; + } + + private static function anonymous14(string $code, int $position): ?Anonymous14Result + { + $regexp = '/^([a-z][a-z ]*[a-z]|[a-z])/'; + if (preg_match($regexp, substr($code, $position), $matches) === 1) { + return new Anonymous14Result($matches[0], $position + strlen($matches[0])); + } + + return null; + } + + private static function anonymous16(string $code, int $position): ?Anonymous16Result + { + $regexp = '/^\'([^\']*)\'/'; + if (preg_match($regexp, substr($code, $position), $matches) === 1) { + return new Anonymous16Result($matches[0], $position + strlen($matches[0])); + } + + return null; + } + + private static function anonymous18(string $code, int $position): ?Anonymous18Result + { + $regexp = '/^"([^"]*)"/'; + if (preg_match($regexp, substr($code, $position), $matches) === 1) { + return new Anonymous18Result($matches[0], $position + strlen($matches[0])); + } + + return null; + } + + private static function anonymous20(string $code, int $position): ?Anonymous20Result + { + $regexp = '/^\\/\\^([^\\/\\\\]*(\\\\\\/|\\\\[^\\/])?)*\\//'; + if (preg_match($regexp, substr($code, $position), $matches) === 1) { + return new Anonymous20Result($matches[0], $position + strlen($matches[0])); + } + + return null; + } + + private static function whitespace(string $code, int $position): ?WhitespaceResult + { + $regexp = '/^[ \\t\\r\\n]+/'; + if (preg_match($regexp, substr($code, $position), $matches) === 1) { + return new WhitespaceResult($matches[0], $position + strlen($matches[0])); + } + + return null; + } + + private static function comment(string $code, int $position): ?CommentResult + { + $regexp = '/^(\\(\\*\\s+[^*]*\\s+\\*\\)|\\(\\* \\*\\)|\\(\\*\\*\\))/'; + if (preg_match($regexp, substr($code, $position), $matches) === 1) { + return new CommentResult($matches[0], $position + strlen($matches[0])); + } + + return null; + } + + private static function anonymous2(string $code, int $position): ?Anonymous2Result + { + if (substr($code, $position, 1) === '=') { + return new Anonymous2Result('=', $position + 1); + } + + return null; + } + + private static function anonymous3(string $code, int $position): ?Anonymous3Result + { + if (substr($code, $position, 1) === ';') { + return new Anonymous3Result(';', $position + 1); + } + + return null; + } + + private static function anonymous7(string $code, int $position): ?Anonymous7Result + { + if (substr($code, $position, 1) === '|') { + return new Anonymous7Result('|', $position + 1); + } + + return null; + } + + private static function anonymous11(string $code, int $position): ?Anonymous11Result + { + if (substr($code, $position, 1) === ',') { + return new Anonymous11Result(',', $position + 1); + } + + return null; + } + + private static function anonymous22(string $code, int $position): ?Anonymous22Result + { + if (substr($code, $position, 1) === '(') { + return new Anonymous22Result('(', $position + 1); + } + + return null; + } + + private static function anonymous23(string $code, int $position): ?Anonymous23Result + { + if (substr($code, $position, 1) === ')') { + return new Anonymous23Result(')', $position + 1); + } + + return null; + } + + private static function anonymous25(string $code, int $position): ?Anonymous25Result + { + if (substr($code, $position, 1) === '{') { + return new Anonymous25Result('{', $position + 1); + } + + return null; + } + + private static function anonymous26(string $code, int $position): ?Anonymous26Result + { + if (substr($code, $position, 1) === '}') { + return new Anonymous26Result('}', $position + 1); + } + + return null; + } + + private static function anonymous28(string $code, int $position): ?Anonymous28Result + { + if (substr($code, $position, 1) === '[') { + return new Anonymous28Result('[', $position + 1); + } + + return null; + } + + private static function anonymous29(string $code, int $position): ?Anonymous29Result + { + if (substr($code, $position, 1) === ']') { + return new Anonymous29Result(']', $position + 1); + } + + return null; + } + + private static function term(string $code, int $position): ?TermResult + { + if ($result = self::bareword($code, $position)) { + return new TermResult($result, $result->position); + } + if ($result = self::sq($code, $position)) { + return new TermResult($result, $result->position); + } + if ($result = self::dq($code, $position)) { + return new TermResult($result, $result->position); + } + if ($result = self::regex($code, $position)) { + return new TermResult($result, $result->position); + } + if ($result = self::group($code, $position)) { + return new TermResult($result, $result->position); + } + if ($result = self::repetition($code, $position)) { + return new TermResult($result, $result->position); + } + if ($result = self::optional($code, $position)) { + return new TermResult($result, $result->position); + } + + return null; + } + + private static function anonymous31(string $code, int $position): ?Anonymous31Result + { + if ($result = self::whitespace($code, $position)) { + return new Anonymous31Result($result, $result->position); + } + if ($result = self::comment($code, $position)) { + return new Anonymous31Result($result, $result->position); + } + + return null; + } + + private static function syntax(string $code, int $position): ?SyntaxResult + { + $results = []; + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::rules($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new SyntaxResult($results, $position); + } + + private static function rule(string $code, int $position): ?RuleResult + { + $results = []; + if ($result = self::bareword($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::anonymous2($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::alt($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::anonymous3($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new RuleResult($results, $position); + } + + private static function alt(string $code, int $position): ?AltResult + { + $results = []; + if ($result = self::conc($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::pipeconclist($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new AltResult($results, $position); + } + + private static function pipeconc(string $code, int $position): ?PipeconcResult + { + $results = []; + if ($result = self::anonymous7($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::conc($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new PipeconcResult($results, $position); + } + + private static function conc(string $code, int $position): ?ConcResult + { + $results = []; + if ($result = self::term($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::commatermlist($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new ConcResult($results, $position); + } + + private static function commaterm(string $code, int $position): ?CommatermResult + { + $results = []; + if ($result = self::anonymous11($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::term($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new CommatermResult($results, $position); + } + + private static function bareword(string $code, int $position): ?BarewordResult + { + $results = []; + if ($result = self::anonymous14($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new BarewordResult($results, $position); + } + + private static function sq(string $code, int $position): ?SqResult + { + $results = []; + if ($result = self::anonymous16($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new SqResult($results, $position); + } + + private static function dq(string $code, int $position): ?DqResult + { + $results = []; + if ($result = self::anonymous18($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new DqResult($results, $position); + } + + private static function regex(string $code, int $position): ?RegexResult + { + $results = []; + if ($result = self::anonymous20($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new RegexResult($results, $position); + } + + private static function group(string $code, int $position): ?GroupResult + { + $results = []; + if ($result = self::anonymous22($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::alt($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::anonymous23($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new GroupResult($results, $position); + } + + private static function repetition(string $code, int $position): ?RepetitionResult + { + $results = []; + if ($result = self::anonymous25($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::alt($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::anonymous26($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new RepetitionResult($results, $position); + } + + private static function optional(string $code, int $position): ?OptionalResult + { + $results = []; + if ($result = self::anonymous28($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::alt($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::anonymous29($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + if ($result = self::space($code, $position)) { + $position = $result->position; + $results[] = $result; + } else { + return null; + } + + return new OptionalResult($results, $position); + } + + /** @psalm-suppress LessSpecificReturnType */ + private static function rules(string $code, int $position): ?RulesResult + { + $results = []; + while ($result = self::rule($code, $position)) { + $position = $result->position; + $results[] = $result; + } + + return new RulesResult($results, $position); + } + + /** @psalm-suppress LessSpecificReturnType */ + private static function pipeconclist(string $code, int $position): ?PipeconclistResult + { + $results = []; + while ($result = self::pipeconc($code, $position)) { + $position = $result->position; + $results[] = $result; + } + + return new PipeconclistResult($results, $position); + } + + /** @psalm-suppress LessSpecificReturnType */ + private static function commatermlist(string $code, int $position): ?CommatermlistResult + { + $results = []; + while ($result = self::commaterm($code, $position)) { + $position = $result->position; + $results[] = $result; + } + + return new CommatermlistResult($results, $position); + } + + /** @psalm-suppress LessSpecificReturnType */ + private static function space(string $code, int $position): ?SpaceResult + { + $results = []; + while ($result = self::anonymous31($code, $position)) { + $position = $result->position; + $results[] = $result; + } + + return new SpaceResult($results, $position); + } +} \ No newline at end of file diff --git a/src/EBNF/GroupResult.php b/src/EBNF/GroupResult.php new file mode 100644 index 0000000..6232b70 --- /dev/null +++ b/src/EBNF/GroupResult.php @@ -0,0 +1,13 @@ + $result + */ + public function __construct( + public readonly array $result, + public readonly int $position + ) {} +} \ No newline at end of file diff --git a/src/EBNF/RegexResult.php b/src/EBNF/RegexResult.php new file mode 100644 index 0000000..d8bcf76 --- /dev/null +++ b/src/EBNF/RegexResult.php @@ -0,0 +1,13 @@ + $result + */ + public function __construct( + public readonly array $result, + public readonly int $position + ) {} +} \ No newline at end of file diff --git a/src/EBNF/SpaceResult.php b/src/EBNF/SpaceResult.php new file mode 100644 index 0000000..79f45f8 --- /dev/null +++ b/src/EBNF/SpaceResult.php @@ -0,0 +1,13 @@ + $result + */ + public function __construct( + public readonly array $result, + public readonly int $position + ) {} +} \ No newline at end of file diff --git a/src/EBNF/SqResult.php b/src/EBNF/SqResult.php new file mode 100644 index 0000000..2c2742c --- /dev/null +++ b/src/EBNF/SqResult.php @@ -0,0 +1,13 @@ +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)) { - throw new \Exception('Unable to parse given EBNF'); - } - - if ($pos = strrpos($class, '\\')){ - $namespace = substr($class, 0, $pos); - $classname = substr($class, $pos + 1); - } else { - $namespace = null; - $classname = $class; - } - - $code = "indent($this->genParser($ast), 4)."\n"; - - $code.= " parent::__construct(\$s, \$internals);\n"; - $code.= " }\n"; - - foreach ($ast['r'][1]['r'] as $rule) { - $name = $rule['r'][0]['r'][0]['r'][0]; - - $code.= "\n"; - $code.= " protected function "; - $code.= $this->genGenerator($name); - $code.= "(\$result)\n"; - $code.= " {\n"; - $code.= " \$result = \$this->internals[".var_export($name, true); - $code.= "]->defaultGenerator(\$result);\n"; - $code.= " \$result['t'] = ".var_export($name, true).";\n"; - $code.= " return \$result;\n"; - $code.= " }\n"; - } - - $code.="}\n"; - - return $code; - } - - /** - * @param string $generator - * - * @return string - */ - protected function genParser(array $object, $generator = null) - { - $method = 'gen'.ucfirst($object['t']).'Parser'; - if (method_exists($this, $method)) { - return $this->$method($object, $generator); - } - - throw new \Exception("Unknow t '{$object['t']}' or missing method $method"); - } - - /** - * @param string $generator - * - * @return string - */ - protected function genSyntaxParser(array $object, $generator) - { - $code = "\$internals = array(\n"; - foreach ($object['r'][1]['r'] as $rule) { - $code.= $this->indent($this->genParser($rule), 2).",\n"; - } - $code.= ");"; - return $code; - } - - /** @return string */ - protected function genRuleParser(array $object) - { - $name = $object['r'][0]['r'][0]['r'][0]; - $generator = $this->genGenerator($name); - - $code = var_export($name, true)." => "; - $code.= $this->genParser($object['r'][4], $generator); - return $code; - } - - /** - * @param string $generator - * - * @return string - */ - protected function genAltParser(array $object, $generator = null) - { - $code = "new \\Graphit\\Parser\\LazyAltParser(\n"; - $code.= " array(\n"; - foreach ($object['r'] as $internal) { - $code.= $this->indent($this->genParser($internal), 4).",\n"; - } - $code.= " )"; - $code.= $this->genGeneratorCall($generator, true); - $code.= ")"; - return $code; - } - - /** - * @param string $generator - * - * @return string - */ - protected function genConcParser(array $object, $generator = null) - { - $code = "new \\Graphit\\Parser\\ConcatParser(\n"; - $code.= " array(\n"; - foreach ($object['r'] as $internal) { - $code.= $this->indent($this->genParser($internal), 4).",\n"; - } - $code.= " )"; - $code.= $this->genGeneratorCall($generator, true); - $code.= ")"; - return $code; - } - - /** - * @return string - */ - protected function genBarewordParser(array $object) - { - $code = var_export($object['r'][0]['r'][0], true); - return $code; - } - - /** - * @param string $generator - * - * @return string - */ - protected function genSqParser(array $object, $generator = null) - { - $code = "new \\Graphit\\Parser\\StringParser("; - $code.= $generator? "\n ": ''; - //$code.= var_export($object['string'], true); - $code.= "'".$object['r'][0]['r'][1]."'"; - $code.= $this->genGeneratorCall($generator, $generator? true: false); - $code.= ")"; - return $code; - } - - /** - * @param string $generator - * - * @return string - */ - protected function genDqParser(array $object, $generator = null) - { - $code = "new \\Graphit\\Parser\\StringParser("; - $code.= $generator? "\n ": ''; - //$code.= var_export($object['string'], true); - $code.= '"'.$object['r'][0]['r'][1].'"'; - $code.= $this->genGeneratorCall($generator, $generator? true: false); - $code.= ")"; - return $code; - } - - /** - * @param string $generator - * - * @return string - */ - protected function genRegexParser(array $object, $generator = null) - { - $code = "new \\Graphit\\Parser\\RegexParser("; - $code.= $generator? "\n ": ''; - $code.= var_export($object['r'][0]['r'][0], true); - $code.= $this->genGeneratorCall($generator, $generator? true: false); - $code.= ")"; - return $code; - } - - /** - * @param string $generator - * - * @return string - */ - protected function genGroupParser(array $object, $generator = null) - { - return $this->genParser($object['r'][2], $generator); - } - - /** - * @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); - $code.= ", 0, null"; - $code.= $this->genGeneratorCall($generator, true); - $code.= ")"; - return $code; - } - - /** - * @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); - $code.= ", 0, 1"; - $code.= $this->genGeneratorCall($generator, true); - $code.= ")"; - 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 $break ? "\n" : ''; - } - - $code = "," . ($break ? "\n " : ''); - $code.= "array(\$this, ".var_export($generator, true).")"; - $code.= ($break ? "\n" : ''); - 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); - $code = ''; - - $lines = explode("\n", $lines); - for ($i = 0; $i < $from; ++$i) { - if ( !($line = array_shift($lines))) { - break; - } - $code .= "{$line}\n"; - } - - foreach ($lines as $line) { - $code .= "{$pad}{$line}\n"; - } - - return substr($code, 0, -1); - } -} diff --git a/src/EBNFParser.php b/src/EBNFParser.php deleted file mode 100644 index 0750832..0000000 --- a/src/EBNFParser.php +++ /dev/null @@ -1,57 +0,0 @@ - 'alt', - 'r' => $results[1], - ); - } else { - return $results[0]; - } - } - - protected function pipeconclistGenerator($results) - { - return $results; - } - - protected function pipeconcGenerator($results) - { - return $results[2]; - } - - protected function concGenerator($results) - { - if ($results[1]) { - array_unshift($results[1], $results[0]['r']); - return array( - 't' => 'conc', - 'r' => $results[1], - ); - } else { - return $results[0]['r']; - } - } - - protected function commatermlistGenerator($results) - { - return $results; - } - - protected function commatermGenerator($results) - { - return $results[2]['r']; - } -} diff --git a/src/EBNFParserBase.php b/src/EBNFParserBase.php deleted file mode 100644 index 48a4e6a..0000000 --- a/src/EBNFParserBase.php +++ /dev/null @@ -1,302 +0,0 @@ - new \Graphit\Parser\ConcatParser( - array( - 'space', - 'rules', - ), - array($this, 'syntaxGenerator') - ), - 'rules' => new \Graphit\Parser\GreedyMultiParser( - 'rule', 0, null, - array($this, 'rulesGenerator') - ), - 'rule' => new \Graphit\Parser\ConcatParser( - array( - 'bareword', - 'space', - new \Graphit\Parser\StringParser("="), - 'space', - 'alt', - new \Graphit\Parser\StringParser(";"), - 'space', - ), - array($this, 'ruleGenerator') - ), - 'alt' => new \Graphit\Parser\ConcatParser( - array( - 'conc', - 'pipeconclist', - ), - array($this, 'altGenerator') - ), - 'pipeconclist' => new \Graphit\Parser\GreedyMultiParser( - 'pipeconc', 0, null, - array($this, 'pipeconclistGenerator') - ), - 'pipeconc' => new \Graphit\Parser\ConcatParser( - array( - new \Graphit\Parser\StringParser("|"), - 'space', - 'conc', - ), - array($this, 'pipeconcGenerator') - ), - 'conc' => new \Graphit\Parser\ConcatParser( - array( - 'term', - 'commatermlist', - ), - array($this, 'concGenerator') - ), - 'commatermlist' => new \Graphit\Parser\GreedyMultiParser( - 'commaterm', 0, null, - array($this, 'commatermlistGenerator') - ), - 'commaterm' => new \Graphit\Parser\ConcatParser( - array( - new \Graphit\Parser\StringParser(","), - 'space', - 'term', - ), - array($this, 'commatermGenerator') - ), - 'term' => new \Graphit\Parser\LazyAltParser( - array( - 'bareword', - 'sq', - 'dq', - 'regex', - 'group', - 'repetition', - 'optional', - ), - array($this, 'termGenerator') - ), - 'bareword' => new \Graphit\Parser\ConcatParser( - array( - new \Graphit\Parser\RegexParser('/^([a-z][a-z ]*[a-z]|[a-z])/'), - 'space', - ), - array($this, 'barewordGenerator') - ), - 'sq' => new \Graphit\Parser\ConcatParser( - array( - new \Graphit\Parser\RegexParser('/^\'([^\']*)\'/'), - 'space', - ), - array($this, 'sqGenerator') - ), - 'dq' => new \Graphit\Parser\ConcatParser( - array( - new \Graphit\Parser\RegexParser('/^"([^"]*)"/'), - 'space', - ), - array($this, 'dqGenerator') - ), - 'regex' => new \Graphit\Parser\ConcatParser( - array( - new \Graphit\Parser\RegexParser('/^\\/\\^([^\\/\\\\]*(\\\\\\/|\\\\[^\\/])?)*\\//'), - 'space', - ), - array($this, 'regexGenerator') - ), - 'group' => new \Graphit\Parser\ConcatParser( - array( - new \Graphit\Parser\StringParser("("), - 'space', - 'alt', - new \Graphit\Parser\StringParser(")"), - 'space', - ), - array($this, 'groupGenerator') - ), - 'repetition' => new \Graphit\Parser\ConcatParser( - array( - new \Graphit\Parser\StringParser("{"), - 'space', - 'alt', - new \Graphit\Parser\StringParser("}"), - 'space', - ), - array($this, 'repetitionGenerator') - ), - 'optional' => new \Graphit\Parser\ConcatParser( - array( - new \Graphit\Parser\StringParser("["), - 'space', - 'alt', - new \Graphit\Parser\StringParser("]"), - 'space', - ), - array($this, 'optionalGenerator') - ), - 'space' => new \Graphit\Parser\GreedyMultiParser( - new \Graphit\Parser\LazyAltParser( - array( - 'whitespace', - 'comment', - ) - ), 0, null, - array($this, 'spaceGenerator') - ), - 'whitespace' => new \Graphit\Parser\RegexParser( - '/^[ \\t\\r\\n]+/', - array($this, 'whitespaceGenerator') - ), - 'comment' => new \Graphit\Parser\RegexParser( - '/^(\\(\\*\\s+[^*]*\\s+\\*\\)|\\(\\* \\*\\)|\\(\\*\\*\\))/', - array($this, 'commentGenerator') - ), - ); - parent::__construct($s, $internals); - } - - protected function syntaxGenerator($result) - { - $result = $this->internals['syntax']->defaultGenerator($result); - $result['t'] = 'syntax'; - return $result; - } - - protected function rulesGenerator($result) - { - $result = $this->internals['rules']->defaultGenerator($result); - $result['t'] = 'rules'; - return $result; - } - - protected function ruleGenerator($result) - { - $result = $this->internals['rule']->defaultGenerator($result); - $result['t'] = 'rule'; - return $result; - } - - protected function altGenerator($result) - { - $result = $this->internals['alt']->defaultGenerator($result); - $result['t'] = 'alt'; - return $result; - } - - protected function pipeconclistGenerator($result) - { - $result = $this->internals['pipeconclist']->defaultGenerator($result); - $result['t'] = 'pipeconclist'; - return $result; - } - - protected function pipeconcGenerator($result) - { - $result = $this->internals['pipeconc']->defaultGenerator($result); - $result['t'] = 'pipeconc'; - return $result; - } - - protected function concGenerator($result) - { - $result = $this->internals['conc']->defaultGenerator($result); - $result['t'] = 'conc'; - return $result; - } - - protected function commatermlistGenerator($result) - { - $result = $this->internals['commatermlist']->defaultGenerator($result); - $result['t'] = 'commatermlist'; - return $result; - } - - protected function commatermGenerator($result) - { - $result = $this->internals['commaterm']->defaultGenerator($result); - $result['t'] = 'commaterm'; - return $result; - } - - protected function termGenerator($result) - { - $result = $this->internals['term']->defaultGenerator($result); - $result['t'] = 'term'; - return $result; - } - - protected function barewordGenerator($result) - { - $result = $this->internals['bareword']->defaultGenerator($result); - $result['t'] = 'bareword'; - return $result; - } - - protected function sqGenerator($result) - { - $result = $this->internals['sq']->defaultGenerator($result); - $result['t'] = 'sq'; - return $result; - } - - protected function dqGenerator($result) - { - $result = $this->internals['dq']->defaultGenerator($result); - $result['t'] = 'dq'; - return $result; - } - - protected function regexGenerator($result) - { - $result = $this->internals['regex']->defaultGenerator($result); - $result['t'] = 'regex'; - return $result; - } - - protected function groupGenerator($result) - { - $result = $this->internals['group']->defaultGenerator($result); - $result['t'] = 'group'; - return $result; - } - - protected function repetitionGenerator($result) - { - $result = $this->internals['repetition']->defaultGenerator($result); - $result['t'] = 'repetition'; - return $result; - } - - protected function optionalGenerator($result) - { - $result = $this->internals['optional']->defaultGenerator($result); - $result['t'] = 'optional'; - return $result; - } - - protected function spaceGenerator($result) - { - $result = $this->internals['space']->defaultGenerator($result); - $result['t'] = 'space'; - return $result; - } - - protected function whitespaceGenerator($result) - { - $result = $this->internals['whitespace']->defaultGenerator($result); - $result['t'] = 'whitespace'; - return $result; - } - - protected function commentGenerator($result) - { - $result = $this->internals['comment']->defaultGenerator($result); - $result['t'] = 'comment'; - return $result; - } -} diff --git a/src/EBNFParserGenerator.php b/src/EBNFParserGenerator.php new file mode 100644 index 0000000..34d5d81 --- /dev/null +++ b/src/EBNFParserGenerator.php @@ -0,0 +1,310 @@ + $transformResults + */ + public static function generate(array $transformResults, string $nameSpace, string $parserClassName, string $path, string $topMostFuncName): void + { + $regexFuncs = ''; + $stringFuncs = ''; + $unionFuncs = ''; + $concFuncs = ''; + $repetitionFuncs = ''; + $optionalType = ''; + $anonType = ''; + foreach ($transformResults as $ruleName => $types) { + + $className = ucfirst($ruleName) . 'Result'; + + if ($types instanceof RegexpType) { + // $regexFuncs .= 'RegexpType'; + $regexFuncs .= str_repeat(PHP_EOL, 2); + $regexFuncs .= self::regexpTypeTemplate($className, $ruleName, $types->regexp); + } + + if ($types instanceof StringType) { + $stringFuncs .= str_repeat(PHP_EOL, 2); + $stringFuncs .= self::stringTypeTemplate($className, $ruleName, $types->string); + } + + if ($types instanceof UnionType) { + $unionFuncs .= str_repeat(PHP_EOL, 2); + $unionFuncs .= self::unionTypeTemplate($className, $ruleName, $types); + } + + if ($types instanceof ConcType) { + $concFuncs .= str_repeat(PHP_EOL, 2); + $concFuncs .= self::concTypeTemplate($className, $ruleName, $types); + } + + if ($types instanceof RepetitionType) { + $repetitionFuncs .= str_repeat(PHP_EOL, 2); + $repetitionFuncs .= self::repetitionTypeTemplate($className, $ruleName, $types); + } + + if ($types instanceof OptionalType) { + $optionalType .= str_repeat(PHP_EOL, 2); + $optionalType .= self::optionalTypeTemplate($className, $ruleName, $types); + } + + if ($types instanceof AnonType) { + $anonType .= str_repeat(PHP_EOL, 2); + $anonType .= self::anonTypeTemplate($className, $ruleName, $types); + } + } + + $parserFunc = self::parserFuncTemplate($topMostFuncName); + //$parserFunc .= str_repeat(PHP_EOL, 2); + + $functions = $parserFunc . $regexFuncs . $stringFuncs . $unionFuncs . $concFuncs . $repetitionFuncs . $optionalType . $anonType; + + $content = self::classTemplate($nameSpace, $parserClassName, $functions); + self::writeFile($path, $parserClassName . '.php', $content); + } + + private static function classTemplate(string $nameSpace, string $className, string $functions): string + { + $template = <<