major rewrite master v0.2.0
authorSebastian Brix <sebastian.brix@graph-it.com>
Fri, 17 Feb 2023 08:31:10 +0000 (09:31 +0100)
committerSebastian Brix <sebastian.brix@graph-it.com>
Fri, 17 Feb 2023 08:31:10 +0000 (09:31 +0100)
55 files changed:
bin/generate_parser.php [new file with mode: 0755]
composer.json
composer.lock
psalm.xml
src/BaseParser.php [deleted file]
src/ConcatParser.php [deleted file]
src/EBNF/AltResult.php [new file with mode: 0644]
src/EBNF/Anonymous11Result.php [new file with mode: 0644]
src/EBNF/Anonymous14Result.php [new file with mode: 0644]
src/EBNF/Anonymous16Result.php [new file with mode: 0644]
src/EBNF/Anonymous18Result.php [new file with mode: 0644]
src/EBNF/Anonymous20Result.php [new file with mode: 0644]
src/EBNF/Anonymous22Result.php [new file with mode: 0644]
src/EBNF/Anonymous23Result.php [new file with mode: 0644]
src/EBNF/Anonymous25Result.php [new file with mode: 0644]
src/EBNF/Anonymous26Result.php [new file with mode: 0644]
src/EBNF/Anonymous28Result.php [new file with mode: 0644]
src/EBNF/Anonymous29Result.php [new file with mode: 0644]
src/EBNF/Anonymous2Result.php [new file with mode: 0644]
src/EBNF/Anonymous31Result.php [new file with mode: 0644]
src/EBNF/Anonymous3Result.php [new file with mode: 0644]
src/EBNF/Anonymous7Result.php [new file with mode: 0644]
src/EBNF/BarewordResult.php [new file with mode: 0644]
src/EBNF/CommatermResult.php [new file with mode: 0644]
src/EBNF/CommatermlistResult.php [new file with mode: 0644]
src/EBNF/CommentResult.php [new file with mode: 0644]
src/EBNF/ConcResult.php [new file with mode: 0644]
src/EBNF/DqResult.php [new file with mode: 0644]
src/EBNF/EBNFParser.php [new file with mode: 0644]
src/EBNF/GroupResult.php [new file with mode: 0644]
src/EBNF/OptionalResult.php [new file with mode: 0644]
src/EBNF/PipeconcResult.php [new file with mode: 0644]
src/EBNF/PipeconclistResult.php [new file with mode: 0644]
src/EBNF/RegexResult.php [new file with mode: 0644]
src/EBNF/RepetitionResult.php [new file with mode: 0644]
src/EBNF/RuleResult.php [new file with mode: 0644]
src/EBNF/RulesResult.php [new file with mode: 0644]
src/EBNF/SpaceResult.php [new file with mode: 0644]
src/EBNF/SqResult.php [new file with mode: 0644]
src/EBNF/SyntaxResult.php [new file with mode: 0644]
src/EBNF/TermResult.php [new file with mode: 0644]
src/EBNF/WhitespaceResult.php [new file with mode: 0644]
src/EBNFGenerator.php [deleted file]
src/EBNFParser.php [deleted file]
src/EBNFParserBase.php [deleted file]
src/EBNFParserGenerator.php [new file with mode: 0644]
src/EBNFResultGenerator.php [new file with mode: 0644]
src/EBNFTransform.php [new file with mode: 0644]
src/EmptyParser.php [deleted file]
src/GrammerException.php [deleted file]
src/GrammerParser.php [deleted file]
src/GreedyMultiParser.php [deleted file]
src/LazyAltParser.php [deleted file]
src/RegexParser.php [deleted file]
src/StringParser.php [deleted file]

diff --git a/bin/generate_parser.php b/bin/generate_parser.php
new file mode 100755 (executable)
index 0000000..aae6384
--- /dev/null
@@ -0,0 +1,84 @@
+#!/usr/bin/env php
+<?php
+
+$options = getopt("e:r:c:p:");
+
+if (
+   !is_array($options) || 
+   !isset($options['e'], $options['r'], $options['c'], $options['p']) || 
+   !is_string($options['e']) || 
+   !is_string($options['r']) || 
+   !is_string($options['c']) || 
+   !is_string($options['p'])
+) {
+  echo "Parameters:", PHP_EOL;
+  echo " -e: ebnf file", PHP_EOL;
+  echo " -r: root rule", PHP_EOL;
+  echo " -c: classname", PHP_EOL;
+  echo " -p: path for generated files", PHP_EOL;
+  echo PHP_EOL , 'Examples:';
+  echo PHP_EOL , $argv[0].' -e "./src/ebnf.ebnf" -r "syntax" -c "Graphit\Parser\EBNF\EBNFParser" -p "./src/EBNF_new"';
+  echo PHP_EOL , $argv[0].' -e "./src/attribute.ebnf" -r "attribute" -c "Graphit\Graph\DSLParser\Attribute\AttributeParser" -p "./src/Attribute/"';
+  echo PHP_EOL , $argv[0].' -e "./src/reihenfolge.ebnf" -r "reihenfolge" -c "Graphit\Graph\DSLParser\Reihenfolge\ReihenfolgeParser" -p "./src/Reihenfolge/"';
+  echo PHP_EOL , $argv[0].' -e "./src/eingrenzung.ebnf" -r "eingrenzung" -c "Graphit\Graph\DSLParser\Eingrenzung\EingrenzungParser" -p "./src/Eingrenzung/"';
+  echo PHP_EOL;
+  exit(1);
+};
+
+$className = $options['c'];
+$path = $options['p'];
+$EBNFFile = $options['e'];
+$topMostFuncName = $options['r'];
+
+$pos = strrpos($className, '\\');
+if ($pos === false) {
+  echo PHP_EOL;
+  echo $className . ' does not contain namespace!' . PHP_EOL;
+  exit(1);  
+} 
+
+$namespace = substr($className, 0, $pos);
+$className = substr($className, $pos + 1);
+
+if ( !is_file($EBNFFile)) {
+  echo PHP_EOL;
+  echo $EBNFFile . ' does not exists!' . PHP_EOL;
+  exit(1);  
+}
+
+require_once __DIR__.'/../vendor/autoload.php';
+
+use Graphit\Parser\EBNF\EBNFParser;
+use Graphit\Parser\EBNFTransform;
+use Graphit\Parser\EBNFResultGenerator;
+use Graphit\Parser\EBNFParserGenerator;
+
+$EBNFFileContent = file_get_contents($EBNFFile);
+$result = EBNFParser::parse($EBNFFileContent);
+if ($result === null) {
+  echo PHP_EOL;
+  echo 'EBNFParser::parse returns NULL' . PHP_EOL;
+  exit(1);
+}
+
+function folderHasContents(string $folder): bool {
+  if ( !is_dir($folder)) {
+    return false;
+  }
+
+  $files = scandir($folder);
+  return count($files) > 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);
+
index 76beec32aba20dbc121e82d9bf9fdf92985d17e8..f233eb2f47452d6262802ee2242347382ddb74a3 100644 (file)
@@ -8,11 +8,12 @@
         }
     ],
     "require": {
-        "php": ">=5.6.0"
+        "php": ">=8.1.0"
     },
     "autoload": {
         "psr-4": {
           "Graphit\\Parser\\": "src/"
         }
-    }
+    },
+    "bin": [ "bin/generate_parser.php" ]
 }
index dff83d1d0dd1c50ff85623f4d03bbbc67434f63d..1db3bd53314378ddd940661309f72a2f6bfd387c 100644 (file)
@@ -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"
 }
index 32408861d98fb019f6e5662eb88af56e5a20fa21..7228419bb0ddae75b874ba8fc22df06f7b5437d6 100644 (file)
--- a/psalm.xml
+++ b/psalm.xml
@@ -7,6 +7,7 @@
     xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
 >
     <projectFiles>
+        <directory name="bin" />
         <directory name="src" />
         <ignoreFiles>
             <directory name="vendor" />
diff --git a/src/BaseParser.php b/src/BaseParser.php
deleted file mode 100644 (file)
index 33804b8..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-abstract class BaseParser
-{
-  /** @var array<BaseParser|string> */
-  protected $internals;
-
-  /** @var callable */
-  protected $generator;
-
-  /** @var string */
-  protected $description;
-
-  /** @var bool */
-  protected $acceptsEmpty = false;
-
-  /**
-   * @param array<BaseParser|string> $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<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)) {
-      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<BaseParser|string> $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 (file)
index 92d6e4f..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-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).')';
-    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 (file)
index 0000000..c185fa2
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class AltResult 
+{
+  /**
+   * @param list{ConcResult, PipeconclistResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous11Result.php b/src/EBNF/Anonymous11Result.php
new file mode 100644 (file)
index 0000000..2970530
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous11Result 
+{
+  /**
+   * @param ',' $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous14Result.php b/src/EBNF/Anonymous14Result.php
new file mode 100644 (file)
index 0000000..9a636c7
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous14Result 
+{
+  /**
+   * @param string $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous16Result.php b/src/EBNF/Anonymous16Result.php
new file mode 100644 (file)
index 0000000..498c579
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous16Result 
+{
+  /**
+   * @param string $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous18Result.php b/src/EBNF/Anonymous18Result.php
new file mode 100644 (file)
index 0000000..e8fbf15
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous18Result 
+{
+  /**
+   * @param string $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous20Result.php b/src/EBNF/Anonymous20Result.php
new file mode 100644 (file)
index 0000000..ea042b5
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous20Result 
+{
+  /**
+   * @param string $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous22Result.php b/src/EBNF/Anonymous22Result.php
new file mode 100644 (file)
index 0000000..d435b2a
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous22Result 
+{
+  /**
+   * @param '(' $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous23Result.php b/src/EBNF/Anonymous23Result.php
new file mode 100644 (file)
index 0000000..8b0f4ff
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous23Result 
+{
+  /**
+   * @param ')' $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous25Result.php b/src/EBNF/Anonymous25Result.php
new file mode 100644 (file)
index 0000000..ce3caa0
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous25Result 
+{
+  /**
+   * @param '{' $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous26Result.php b/src/EBNF/Anonymous26Result.php
new file mode 100644 (file)
index 0000000..e98dbb8
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous26Result 
+{
+  /**
+   * @param '}' $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous28Result.php b/src/EBNF/Anonymous28Result.php
new file mode 100644 (file)
index 0000000..d8ba184
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous28Result 
+{
+  /**
+   * @param '[' $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous29Result.php b/src/EBNF/Anonymous29Result.php
new file mode 100644 (file)
index 0000000..2a1404e
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous29Result 
+{
+  /**
+   * @param ']' $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous2Result.php b/src/EBNF/Anonymous2Result.php
new file mode 100644 (file)
index 0000000..069c377
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous2Result 
+{
+  /**
+   * @param '=' $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous31Result.php b/src/EBNF/Anonymous31Result.php
new file mode 100644 (file)
index 0000000..2d298c8
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous31Result 
+{
+  /**
+   * @param WhitespaceResult|CommentResult $result
+   */ 
+  public function __construct(
+    public readonly WhitespaceResult|CommentResult $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous3Result.php b/src/EBNF/Anonymous3Result.php
new file mode 100644 (file)
index 0000000..437001f
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous3Result 
+{
+  /**
+   * @param ';' $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/Anonymous7Result.php b/src/EBNF/Anonymous7Result.php
new file mode 100644 (file)
index 0000000..1062b7a
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class Anonymous7Result 
+{
+  /**
+   * @param '|' $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/BarewordResult.php b/src/EBNF/BarewordResult.php
new file mode 100644 (file)
index 0000000..f47d5d0
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class BarewordResult 
+{
+  /**
+   * @param list{Anonymous14Result, SpaceResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/CommatermResult.php b/src/EBNF/CommatermResult.php
new file mode 100644 (file)
index 0000000..d4ff027
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class CommatermResult 
+{
+  /**
+   * @param list{Anonymous11Result, SpaceResult, TermResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/CommatermlistResult.php b/src/EBNF/CommatermlistResult.php
new file mode 100644 (file)
index 0000000..7cfb798
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class CommatermlistResult 
+{
+  /**
+   * @param list<CommatermResult> $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 (file)
index 0000000..3c4d7c7
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class CommentResult 
+{
+  /**
+   * @param string $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/ConcResult.php b/src/EBNF/ConcResult.php
new file mode 100644 (file)
index 0000000..39ecf59
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class ConcResult 
+{
+  /**
+   * @param list{TermResult, CommatermlistResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/DqResult.php b/src/EBNF/DqResult.php
new file mode 100644 (file)
index 0000000..ccd021a
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class DqResult 
+{
+  /**
+   * @param list{Anonymous18Result, SpaceResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/EBNFParser.php b/src/EBNF/EBNFParser.php
new file mode 100644 (file)
index 0000000..19f7039
--- /dev/null
@@ -0,0 +1,595 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class EBNFParser 
+{
+  public static function parse(string $code): ?SyntaxResult
+  {
+    $result = self::syntax($code, 0);
+    if ($result && $result->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 (file)
index 0000000..6232b70
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class GroupResult 
+{
+  /**
+   * @param list{Anonymous22Result, SpaceResult, AltResult, Anonymous23Result, SpaceResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/OptionalResult.php b/src/EBNF/OptionalResult.php
new file mode 100644 (file)
index 0000000..5f225ca
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class OptionalResult 
+{
+  /**
+   * @param list{Anonymous28Result, SpaceResult, AltResult, Anonymous29Result, SpaceResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/PipeconcResult.php b/src/EBNF/PipeconcResult.php
new file mode 100644 (file)
index 0000000..f6efd01
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class PipeconcResult 
+{
+  /**
+   * @param list{Anonymous7Result, SpaceResult, ConcResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/PipeconclistResult.php b/src/EBNF/PipeconclistResult.php
new file mode 100644 (file)
index 0000000..e73617d
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class PipeconclistResult 
+{
+  /**
+   * @param list<PipeconcResult> $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 (file)
index 0000000..d8bcf76
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class RegexResult 
+{
+  /**
+   * @param list{Anonymous20Result, SpaceResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/RepetitionResult.php b/src/EBNF/RepetitionResult.php
new file mode 100644 (file)
index 0000000..653e00d
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class RepetitionResult 
+{
+  /**
+   * @param list{Anonymous25Result, SpaceResult, AltResult, Anonymous26Result, SpaceResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/RuleResult.php b/src/EBNF/RuleResult.php
new file mode 100644 (file)
index 0000000..cea4357
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class RuleResult 
+{
+  /**
+   * @param list{BarewordResult, SpaceResult, Anonymous2Result, SpaceResult, AltResult, Anonymous3Result, SpaceResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/RulesResult.php b/src/EBNF/RulesResult.php
new file mode 100644 (file)
index 0000000..154f127
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class RulesResult 
+{
+  /**
+   * @param list<RuleResult> $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 (file)
index 0000000..79f45f8
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class SpaceResult 
+{
+  /**
+   * @param list<Anonymous31Result> $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 (file)
index 0000000..2c2742c
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class SqResult 
+{
+  /**
+   * @param list{Anonymous16Result, SpaceResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/SyntaxResult.php b/src/EBNF/SyntaxResult.php
new file mode 100644 (file)
index 0000000..f830f53
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class SyntaxResult 
+{
+  /**
+   * @param list{SpaceResult, RulesResult} $result
+   */ 
+  public function __construct(
+    public readonly array $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/TermResult.php b/src/EBNF/TermResult.php
new file mode 100644 (file)
index 0000000..bc4c729
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class TermResult 
+{
+  /**
+   * @param BarewordResult|SqResult|DqResult|RegexResult|GroupResult|RepetitionResult|OptionalResult $result
+   */ 
+  public function __construct(
+    public readonly BarewordResult|SqResult|DqResult|RegexResult|GroupResult|RepetitionResult|OptionalResult $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNF/WhitespaceResult.php b/src/EBNF/WhitespaceResult.php
new file mode 100644 (file)
index 0000000..7dba2ac
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace Graphit\Parser\EBNF;
+
+class WhitespaceResult 
+{
+  /**
+   * @param string $result
+   */ 
+  public function __construct(
+    public readonly string $result,
+    public readonly int $position
+  ) {}
+}
\ No newline at end of file
diff --git a/src/EBNFGenerator.php b/src/EBNFGenerator.php
deleted file mode 100644 (file)
index 14690d5..0000000
+++ /dev/null
@@ -1,298 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-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)) {
-      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 = "<?php\n\n";
-    if ($namespace) {
-      $code.= "namespace {$namespace};\n\n";
-    }
-    $code.= "class {$classname} extends \Graphit\Parser\GrammerParser\n{\n";
-
-    $code.= "  public function __construct()\n";
-    $code.= "  {\n";
-    $code.= "    \$s = ".var_export($start, true).";\n";
-
-    $code.= $this->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 (file)
index 0750832..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-class EBNFParser extends EBNFParserBase
-{
-  public function __construct()
-  {
-    parent::__construct();
-  }
-
-  protected function altGenerator($results)
-  {
-    if ($results[1]) {
-      array_unshift($results[1], $results[0]);
-      return array(
-        't' => '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 (file)
index 48a4e6a..0000000
+++ /dev/null
@@ -1,302 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-class EBNFParserBase extends \Graphit\Parser\GrammerParser
-{
-  public function __construct()
-  {
-    $s = 'syntax';
-    $internals = array(
-      'syntax' => 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 (file)
index 0000000..34d5d81
--- /dev/null
@@ -0,0 +1,310 @@
+<?php
+
+namespace Graphit\Parser;
+
+use Graphit\Parser\ParserType;
+
+class EBNFParserGenerator
+{
+  /**
+   * @param array<string, ParserType> $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 = <<<TEMPLATE
+        <?php
+        namespace <NAME_SPACE>;
+
+        class <CLASS_NAME> 
+        {
+        <FUNCTIONS>
+        }
+        TEMPLATE;
+
+    return strtr($template, [
+      '<NAME_SPACE>' => $nameSpace,
+      '<CLASS_NAME>' => $className,
+      '<FUNCTIONS>' => $functions
+    ]);
+  }
+
+  private static function writeFile(string $path, string $fileName, string $content): void
+  {
+    if (!is_dir($path) && mkdir($path, 0755, true) === false) {
+      throw new \Exception("Path: {$path} does not exist and could not be created!");
+    }
+
+    if (substr($path, -1) !== '/') {
+      $path .= '/';
+    }
+
+    if (file_put_contents("{$path}{$fileName}", $content) === false) {
+      throw new \Exception("File: {$path}{$fileName} could not be written!");
+    }
+  }
+
+  private static function parserFuncTemplate(string $topMostFunctionName): string
+  {
+    $regexpFuncTemplate = <<<TEMPLATE
+      public static function parse(string \$code): ?<TOPMOST_CLASS_NAME>
+      {
+        \$result = self::<TOPMOST_FUNC_NAME>(\$code, 0);
+        if (\$result && \$result->position != strlen(\$code)) {
+          return null;
+        }
+    
+        return \$result;
+      }
+    TEMPLATE;
+
+    return strtr($regexpFuncTemplate, [
+      '<TOPMOST_CLASS_NAME>' => ucfirst($topMostFunctionName) . 'Result',
+      '<TOPMOST_FUNC_NAME>' => $topMostFunctionName,
+    ]);
+  }
+
+  private static function regexpTypeTemplate(string $className, string $functionName, string $regexp): string
+  {
+    $regexpFuncTemplate = <<<TEMPLATE
+      private static function <FUNC_NAME>(string \$code, int \$position): ?<CLASS_NAME>
+      {
+        \$regexp = <REGEX_PATTERN>;
+        if (preg_match(\$regexp, substr(\$code, \$position), \$matches) === 1) {
+          return new <CLASS_NAME>(\$matches[0], \$position + strlen(\$matches[0]));
+        }
+
+        return null;
+      }
+    TEMPLATE;
+
+    return strtr($regexpFuncTemplate, [
+      '<REGEX_PATTERN>' => var_export($regexp, true),
+      '<CLASS_NAME>' => $className,
+      '<FUNC_NAME>' => $functionName,
+    ]);
+  }
+
+  private static function stringTypeTemplate(string $className, string $functionName, string $string): string
+  {
+    $stringFuncTemplate = <<<TEMPLATE
+      private static function <FUNC_NAME>(string \$code, int \$position): ?<CLASS_NAME>
+      {
+        if (substr(\$code, \$position, <LENGTH>) === <STRING>) {
+          return new <CLASS_NAME>(<STRING_PARAM>, \$position + <LENGTH>);
+        }
+
+        return null;
+      }
+    TEMPLATE;
+
+    return strtr($stringFuncTemplate, [
+      '<STRING>' => var_export($string, true),
+      '<STRING_PARAM>' => var_export($string, true),
+      // '<STRING>' => $string,
+      // '<STRING_PARAM>' => $string,
+      '<LENGTH>' => strlen($string),
+      '<CLASS_NAME>' => $className,
+      '<FUNC_NAME>' => $functionName,
+    ]);
+  }  
+
+  private static function unionTypeTemplate(string $className, string $functionName, UnionType $unionType): string
+  {
+    $contentTemplate = <<<TEMPLATE
+        if (\$result = self::<FUNC_NAME>(\$code, \$position)) {
+          return new <CLASS_NAME>(\$result, \$result->position);
+        }
+    TEMPLATE;
+
+    $content = '';
+    foreach($unionType->types as $type) {
+      $content .= strtr($contentTemplate, [
+        '<CLASS_NAME>' => $className,
+        '<FUNC_NAME>' => isset($type->name) ? $type->name : throw new \Exception(),
+      ]);
+      $content .= str_repeat(PHP_EOL, 1);
+    }
+
+    $unionFuncTemplate = <<<TEMPLATE
+      private static function <FUNC_NAME>(string \$code, int \$position): ?<CLASS_NAME>
+      {
+    <CONTENT>
+        return null;
+      }
+    TEMPLATE;
+
+    return strtr($unionFuncTemplate, [
+      '<CONTENT>' => $content,
+      '<CLASS_NAME>' => $className,
+      '<FUNC_NAME>' => $functionName,
+    ]);
+  }  
+
+  private static function concTypeTemplate(string $className, string $functionName, ConcType $concType): string
+  {
+    $contentTemplate = <<<TEMPLATE
+        if (\$result = self::<FUNC_NAME>(\$code, \$position)) {
+          \$position = \$result->position;
+          \$results[] = \$result;
+        } else {
+          return null;
+        }
+    TEMPLATE;
+
+    $content = '';
+    foreach($concType->types as $type) {
+      $content .= strtr($contentTemplate, [
+        '<CLASS_NAME>' => $className,
+        '<FUNC_NAME>' => isset($type->name) ? $type->name : throw new \Exception(),
+      ]);
+      $content .= str_repeat(PHP_EOL, 1);
+    }
+
+    $concFuncTemplate = <<<TEMPLATE
+      private static function <FUNC_NAME>(string \$code, int \$position): ?<CLASS_NAME>
+      {
+        \$results = [];
+    <CONTENT>
+        return new <CLASS_NAME>(\$results, \$position);
+      }
+    TEMPLATE;
+
+    return strtr($concFuncTemplate, [
+      '<CONTENT>' => $content,
+      '<CLASS_NAME>' => $className,
+      '<FUNC_NAME>' => $functionName,
+    ]);
+  }    
+
+  private static function repetitionTypeTemplate(string $className, string $functionName, RepetitionType $repetitionType): string
+  {
+    $repetitionFuncTemplate = <<<TEMPLATE
+      /** @psalm-suppress LessSpecificReturnType */
+      private static function <FUNC_NAME>(string \$code, int \$position): ?<CLASS_NAME>
+      {
+        \$results = [];
+        while (\$result = self::<CALLED_FUNC>(\$code, \$position)) {
+          \$position = \$result->position;
+          \$results[] = \$result;
+        }
+    
+        return new <CLASS_NAME>(\$results, \$position);
+      }
+    TEMPLATE;
+
+    return strtr($repetitionFuncTemplate, [
+      '<CLASS_NAME>' => $className,
+      '<FUNC_NAME>' => $functionName,
+      '<CALLED_FUNC>' => isset($repetitionType->type->name) ? $repetitionType->type->name : throw new \Exception()
+    ]);
+  }
+
+  private static function optionalTypeTemplate(string $className, string $functionName, OptionalType $optionalType): string
+  {
+    $optionalFuncTemplate = <<<TEMPLATE
+      /** @psalm-suppress LessSpecificReturnType */
+      private static function <FUNC_NAME>(string \$code, int \$position): ?<CLASS_NAME>
+      {
+        \$result = self::<CALLED_FUNC>(\$code, \$position);
+
+        if (!is_null(\$result)) {
+          \$position = \$result->position;
+        }
+    
+        return new <CLASS_NAME>(\$result, \$position);
+      }
+    TEMPLATE;
+
+    return strtr($optionalFuncTemplate, [
+      '<CLASS_NAME>' => $className,
+      '<FUNC_NAME>' => $functionName,
+      '<CALLED_FUNC>' => isset($optionalType->type->name) ? $optionalType->type->name : throw new \Exception()
+    ]);    
+  }
+
+  private static function anonTypeTemplate(string $className, string $functionName, AnonType $anonType): string
+  {
+    $anonFuncTemplate = <<<TEMPLATE
+      private static function <FUNC_NAME>(string \$code, int \$position): ?<CLASS_NAME>
+      {
+        \$result = self::<CALLED_FUNC>(\$code, \$position);
+
+        if (is_null(\$result)) {
+          return null;
+        }
+    
+        return new <CLASS_NAME>(\$result, \$result->position);
+      }
+    TEMPLATE;
+
+    return strtr($anonFuncTemplate, [
+      '<CLASS_NAME>' => $className,
+      '<FUNC_NAME>' => $functionName,
+      '<CALLED_FUNC>' => $anonType->name
+    ]);    
+  }  
+
+}
+
+
+
+
diff --git a/src/EBNFResultGenerator.php b/src/EBNFResultGenerator.php
new file mode 100644 (file)
index 0000000..e20dcd4
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+namespace Graphit\Parser;
+
+use Graphit\Parser\ParserType;
+
+class EBNFResultGenerator
+{
+  /**
+   * @param array<string, ParserType> $transformResults 
+   */
+  public static function generate(array $transformResults, string $nameSpace, string $path): void
+  {
+    $nsTemplate = self::nameSpaceTemplate($nameSpace);
+    
+    foreach ($transformResults as $ruleName => $types) {
+      $docTYpe = str_replace('"', "", $types->asDocType());
+
+      $className = ucfirst($ruleName) . 'Result';
+      $classContent = self::classTemplate($className, $types->asPHPType(), $docTYpe);
+            
+      self::writeFile($path, $className . '.php', $nsTemplate . $classContent);
+    }
+  }
+  
+  private static function nameSpaceTemplate(string $nameSpace): string
+  {
+    $nsTemplate = <<<TEMPLATE
+    <?php
+    namespace <NAME_SPACE>;
+    TEMPLATE;
+
+    $nsTemplate .= str_repeat(PHP_EOL, 2);
+
+    return strtr($nsTemplate, [
+      '<NAME_SPACE>' => $nameSpace
+    ]);
+  }
+
+  private static function classTemplate(string $className, string $phpType, ?string $psalmType = null): string
+  {
+    $template = <<<TEMPLATE
+        class <CLASS_NAME> 
+        {<PSALM_TYPE>
+          public function __construct(
+            public readonly <PHP_TYPE> \$result,
+            public readonly int \$position
+          ) {}
+        }
+        TEMPLATE;
+
+    $psalmTemplate = <<<PSALM
+
+          /**
+           * @param <PSALM_TYPE> \$result
+           */ 
+        PSALM;
+
+    if ($psalmType) {
+      $psalmTemplate = strtr($psalmTemplate, [
+        '<PSALM_TYPE>' => $psalmType
+      ]);
+
+      $psalmType = $psalmTemplate;
+    }
+
+    return strtr($template, [
+      '<CLASS_NAME>' => $className,
+      '<PHP_TYPE>' => $phpType,
+      '<PSALM_TYPE>' => $psalmType
+    ]);
+  }
+
+  private static function writeFile(string $path, string $fileName, string $content): void
+  {
+    if (!is_dir($path) && mkdir($path, 0755, true) === false) {
+      throw new \Exception("Path: {$path} does not exist and could not be created!");
+    }
+
+    if (substr($path, -1) !== '/') {
+      $path .= '/';
+    }
+
+    if (file_put_contents("{$path}{$fileName}", $content) === false) {
+      throw new \Exception("File: {$path}{$fileName} could not be written!");
+    }
+  }
+}
+
diff --git a/src/EBNFTransform.php b/src/EBNFTransform.php
new file mode 100644 (file)
index 0000000..d94d1bc
--- /dev/null
@@ -0,0 +1,495 @@
+<?php
+
+namespace Graphit\Parser;
+
+use Graphit\Parser\EBNF\{
+  AltResult,
+  BarewordResult,
+  CommatermlistResult,
+  CommatermResult,
+  ConcResult,
+  DqResult,
+  GroupResult,
+  OptionalResult,
+  PipeconclistResult,
+  PipeconcResult,
+  RegexResult,
+  RepetitionResult,
+  RuleResult,
+  RulesResult,
+  SqResult,
+  SyntaxResult,
+  TermResult
+};
+
+abstract class ParserType {
+
+  public abstract function asPHPType(): string;
+
+  public abstract function asDocType(): string;
+
+  public abstract function needsDocType(): bool;
+}
+
+class RuleType extends ParserType {
+  private function __construct(
+    public readonly string $name
+  ) {}
+
+  public function asPHPType(): string {
+    return ucfirst($this->name).'Result';
+  }
+
+  public function asDocType(): string {
+    return $this->asPHPType();
+  }
+
+  public function needsDocType(): bool {
+    return false;
+  }
+
+  public static function create(string $name): ParserType {
+    return new RuleType($name);
+  }
+}
+
+class AnonType extends ParserType {
+  private function __construct(
+    public readonly string $name
+  ) {}
+
+  public function asPHPType(): string {
+    return ucfirst($this->name).'Result';
+  }
+
+  public function asDocType(): string {
+    return $this->asPHPType();
+  }
+
+  public function needsDocType(): bool {
+    return false;
+  }
+
+  public static function create(string $name): ParserType {
+    return new AnonType($name);
+  }
+}
+
+class StringType extends ParserType {
+  private function __construct(
+    public readonly string $string
+  ) {}
+
+  public function asPHPType(): string {
+    return 'string';
+  }
+
+  public function asDocType(): string {
+    return var_export($this->string, true);
+  }
+
+  public function needsDocType(): bool {
+    return true;
+  }
+
+  public static function create(string $string): ParserType {
+    if (strlen($string) < 2) throw new \Exception();
+    return new StringType(substr($string, 1, -1));
+  }
+}
+
+class RegexpType extends ParserType {
+  private function __construct(
+    public readonly string $regexp
+  ) {}
+
+  public function asPHPType(): string {
+    return 'string';
+  }
+
+  public function asDocType(): string {
+    return $this->asPHPType();
+  }
+
+  public function needsDocType(): bool {
+    return false;
+  }
+
+  public static function create(string $regexp): ParserType {
+    return new RegexpType($regexp);
+  }
+}
+
+class ConcType extends ParserType {
+
+  /** @param non-empty-list<ParserType> $types */
+  private function __construct(
+    public readonly array $types
+  ) { }
+
+  public function asPHPType(): string {
+    return 'array';
+  }
+
+  public function asDocType(): string {
+    $types = [];
+    foreach($this->types as $type) {
+      $types[] = $type->asDocType();
+    }
+    return 'list{'.implode(', ', $types).'}';
+  }
+
+  public function needsDocType(): bool {
+    return true;
+  }
+
+  public static function create(ParserType ...$others): ParserType {
+    if (count($others) < 2) throw new \Exception();
+
+    $types = [];
+    foreach($others as $other) {
+      if ($other instanceof ConcType) {
+        $types = array_merge($types, $other->types);
+      } else {
+        $types[] = $other;
+      }
+    }
+    return new ConcType($types);
+  }
+}
+
+class UnionType extends ParserType {
+
+  /** @param non-empty-array<string, ParserType> $types */
+  private function __construct(
+    public readonly array $types
+  ) {}
+
+  public function asPHPType(): string {
+    if ($this->needsDocType()) {
+      return 'mixed';
+    }
+    return $this->asDocType();
+  }
+
+  public function asDocType(): string {
+    $types = [];
+    foreach($this->types as $type) {
+      $types[] = $type->asDocType();
+    }
+    return implode('|', $types);
+  }
+
+  public function needsDocType(): bool {
+    foreach($this->types as $type) {
+      if ($type->needsDocType()) return true;
+    }
+    return false;
+  }
+
+  public static function create(ParserType ...$others): ParserType {
+    if (count($others) < 2) throw new \Exception();
+
+    $types = [];
+    $optionals = [];
+    foreach ($others as $other) {
+      if ($other instanceof UnionType) {
+        foreach ($other->types as $type) {
+          if ($type instanceof OptionalType) {
+            $inner = $type->type->asDocType();
+            $optionals[$inner] = true;
+            unset($types[$inner]);
+
+            $types += [$type->asDocType() => $type];
+          } else {
+            $docType = $type->asDocType();
+            if ( !isset($optionals[$docType])) {
+              $types += [$docType => $type];
+            }
+          }
+        }
+      }
+      else if ($other instanceof OptionalType) {
+        $inner = $other->type->asDocType();
+        $optionals[$inner] = true;
+        unset($types[$inner]);
+
+        $types += [$other->asDocType() => $other];
+      } else {
+        $docType = $other->asDocType();
+        if ( !isset($optionals[$docType])) {
+          $types += [$docType => $other];
+        }
+      }
+    }
+    if ( !$types) throw new \Exception();
+    if (count($types) === 1) {
+      return reset($types);
+    }
+    return new UnionType($types);
+  }
+}
+
+class OptionalType extends ParserType {
+  private function __construct(
+    public readonly ParserType $type
+  ) {}
+
+  public function asPHPType(): string {
+    if ($this->needsDocType()) {
+      return 'mixed';
+    }
+    return $this->asDocType();
+  }
+
+  public function asDocType(): string {
+    return '?'.$this->type->asDocType();
+  }
+
+  public function needsDocType(): bool {
+    return $this->type->needsDocType();
+  }
+
+  public static function create(ParserType $type): ParserType {
+    if ($type instanceof OptionalType) return $type;
+    return new OptionalType($type);
+  }
+}
+
+class RepetitionType extends ParserType {
+  private function __construct(
+    public readonly ParserType $type
+  ) {}
+
+  public function asPHPType(): string {
+    return 'array';
+  }
+
+  public function asDocType(): string {
+    return 'list<'.$this->type->asDocType().'>';
+  }
+
+  public function needsDocType(): bool {
+    return true;
+  }
+
+  public static function create(ParserType $type): ParserType {
+    if ($type instanceof RepetitionType) return $type;
+    return new RepetitionType($type);
+  }
+}
+
+class TypeInfos {
+
+  /** @param array<string, ParserType> $named */
+  private function __construct(
+    public readonly ParserType $type,
+    public readonly array $named = []
+  ) {}
+
+  public function conc(TypeInfos $other): TypeInfos {
+    return new TypeInfos(
+      ConcType::create($this->type, $other->type),
+      array_merge($this->named, $other->named)
+    );
+  }
+
+  public function union(TypeInfos $other): TypeInfos {
+    return new TypeInfos(
+      UnionType::create($this->type, $other->type),
+      array_merge($this->named, $other->named)
+    );
+  }
+
+  public function optional(): TypeInfos {
+    return new TypeInfos(
+      OptionalType::create($this->type),
+      $this->named
+    );
+  }
+
+  public function repetition(): TypeInfos {
+    return new TypeInfos(
+      RepetitionType::create($this->type),
+      $this->named
+    );
+  }
+
+  public function generateName(): TypeInfos {
+    $name = TypeInfos::newName();
+    return new TypeInfos(
+      AnonType::create($name),
+      array_merge($this->named, [$name => $this->type])
+    );
+  }
+
+  public static function rule(string $rule): TypeInfos {
+    return new TypeInfos(
+      RuleType::create($rule),
+    );
+  }
+
+  public static function string(string $string): TypeInfos {
+    return new TypeInfos(
+      StringType::create($string)
+    );
+  }
+
+  public static function regexp(string $regexp): TypeInfos {
+    return new TypeInfos(
+      RegexpType::create($regexp)
+    );
+  }
+
+  private static int $counter = 0;
+
+  public static function newName(): string {
+    $counter = static::$counter++;
+    return "anonymous{$counter}";
+  }
+}
+
+class EBNFTransform
+{
+  public static function toRuleName(BarewordResult $result): string {
+    return $result->result[0]->result;
+  }
+
+  /** @return array<string, ParserType> */
+  public static function transformSyntax(SyntaxResult $result): array {
+    return static::transformRules($result->result[1]);
+  }
+
+  /** @return array<string, ParserType> */
+  public static function transformRules(RulesResult $result): array {
+    $named = [];
+    foreach($result->result as $ruleResult) {
+      $named = array_merge($named, static::transformRule($ruleResult));
+    }
+    return $named;
+  }
+
+  /** @return array<string, ParserType> */
+  public static function transformRule(RuleResult $result): array {
+    $ruleName = static::toRuleName($result->result[0]);
+    $typeInfo = static::transformAlt($result->result[4]);
+
+    if ( $typeInfo->type instanceof RuleType) {
+      throw new \RuntimeException("stupid rule!");
+    }
+    if ( !$typeInfo->type instanceof AnonType) {
+      throw new \Exception('bug!');
+    }
+    $name = $typeInfo->type->name;
+
+    $named = $typeInfo->named;
+    $named[$ruleName] = $named[$name];
+    unset($named[$name]);
+
+    return $named;
+  }
+
+  public static function transformAlt(AltResult $result): TypeInfos {
+    $concTypeInfo = static::transformConc($result->result[0]);
+    $pipeconclistTypeInfo = static::transformPipeconclist($result->result[1]);
+    if ($pipeconclistTypeInfo) {
+      $temp = $concTypeInfo->union($pipeconclistTypeInfo);
+      if ($temp->type instanceof UnionType) {
+        return $temp->generateName();
+      } else {
+        return $temp;
+      }
+    }
+    return $concTypeInfo;
+  }
+
+  public static function transformPipeconclist(PipeconclistResult $result): ?TypeInfos {
+    $typeInfos = null;
+    foreach ($result->result as $aResult) {
+      if ($typeInfos instanceof TypeInfos) {
+        $typeInfos = $typeInfos->union(static::transformPipeconc($aResult));
+      } else {
+        $typeInfos = static::transformPipeconc($aResult);
+      }
+    }
+    return $typeInfos;
+  }
+
+  public static function transformPipeconc(PipeconcResult $result): TypeInfos {
+    return static::transformConc($result->result[2]);
+  }
+
+  public static function transformConc(ConcResult $result): TypeInfos {
+    $termTypeInfos = static::transformTerm($result->result[0]);
+    $commatermlistTypeInfos = static::transformCommatermlist($result->result[1]);
+    if ($commatermlistTypeInfos) {
+      return $termTypeInfos->conc($commatermlistTypeInfos)->generateName();
+    }
+    return $termTypeInfos;
+  }
+
+  public static function transformCommatermlist(CommatermlistResult $result): ?TypeInfos {
+    $typeInfos = null;
+    foreach ($result->result as $aResult) {
+      if ($typeInfos instanceof TypeInfos) {
+        $typeInfos = $typeInfos->conc(static::transformCommaterm($aResult));
+      } else {
+        $typeInfos = static::transformCommaterm($aResult);
+      }
+    }
+    return $typeInfos;
+  }
+
+  public static function transformCommaterm(CommatermResult $result): TypeInfos {
+    return static::transformTerm($result->result[2]);
+  }
+
+  // expr = num, { ('+'|'-'), num }
+  // conc <expr>
+  //   bare <num>
+  //   rep <>
+  //     conc <>
+  //       pipe <>
+  //         sq <>
+  //         sq <>
+  //       bare <num>
+  //         
+  // term = bareword | sq | dq | regex | group | repetition | optional 
+  public static function transformTerm(TermResult $result): TypeInfos {
+    if ($result->result instanceof BarewordResult) {
+      return TypeInfos::rule(static::toRuleName($result->result));
+    }
+    if ($result->result instanceof SqResult) {
+      return TypeInfos::string($result->result->result[0]->result)->generateName();
+    }
+    if ($result->result instanceof DqResult) {
+      return TypeInfos::string($result->result->result[0]->result)->generateName();
+    }
+    if ($result->result instanceof RegexResult) {
+      return TypeInfos::regexp($result->result->result[0]->result)->generateName();
+    }
+    if ($result->result instanceof GroupResult) {
+      return static::transformGroup($result->result)->generateName();
+    }
+    if ($result->result instanceof OptionalResult) {
+      return static::transformOptional($result->result)->generateName();
+    }
+    return static::transformRepetition($result->result)->generateName();
+  }
+
+  // group = "(", space, alt, ")", space ;
+  public static function transformGroup(GroupResult $result): TypeInfos {
+    return static::transformAlt($result->result[2]);
+  }
+  public static function transformOptional(OptionalResult $result): TypeInfos {
+    return static::transformAlt($result->result[2])->optional();
+  }
+  public static function transformRepetition(RepetitionResult $result): TypeInfos {
+    return static::transformAlt($result->result[2])->repetition();
+  }
+}
+
+
+
+
+
diff --git a/src/EmptyParser.php b/src/EmptyParser.php
deleted file mode 100644 (file)
index ffdd253..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-class EmptyParser extends BaseParser
-{
-  /**
-   * @param callable $generator
-   */
-  public function __construct($generator = null)
-  {
-    $this->description = 'new '.get_class().'()';
-
-    parent::__construct(array(), $generator);
-  }
-
-  protected function accept($string, $p)
-  {
-    return array(
-      'r' => null,
-      'p' => $p,
-    );
-  }
-
-  protected function evalAcceptsEmpty()
-  {
-    return true;
-  }
-
-  protected function firstSet()
-  {
-    return array();
-  }
-}
diff --git a/src/GrammerException.php b/src/GrammerException.php
deleted file mode 100644 (file)
index 4c20c8c..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-class GrammerException extends \Exception { }
diff --git a/src/GrammerParser.php b/src/GrammerParser.php
deleted file mode 100644 (file)
index 640a9c2..0000000
+++ /dev/null
@@ -1,205 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-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).')';
-    parent::__construct($internals, $generator);
-
-    $this->s = (string)$s;
-    if ( !isset($this->internals[$this->s])) {
-      throw new GrammerException("No parser given for start rule {$this->s}!");
-    }
-
-    $this->resolveParserNames();
-
-    $this->floodAcceptsEmpty();
-
-    $this->testInifinitGreedy();
-
-    foreach ($this->internals as $internal) {
-      /** @var BaseParser[] */
-      $done = array();
-      /** @var BaseParser[] */
-      $todo = array($internal);
-      while ($current = array_shift($todo)) {
-        $done[] = $current;
-
-        foreach ($current->firstSet() as $next) {
-          if ($next === $current) {
-            throw new GrammerException("Grammer is left recursive in {$internal}!");
-          }
-
-          if (in_array($next, $done, true)) {
-            continue;
-          }
-
-          $todo[] = $next;
-        }
-      }
-    }
-  }
-
-  /** @return void */
-  protected function resolveParserNames()
-  {
-    /** @var BaseParser[] */
-    $done = array();
-    /** @var BaseParser[] */
-    $todo = array($this);
-
-    while ($current = array_shift($todo)) {
-      $done[] = $current;
-
-      foreach ($current->internals as $key => $internal) {
-        if ($internal instanceof BaseParser) {
-          if ( !in_array($internal, $done, true) && !in_array($internal, $todo, true)) {
-            $todo[] = $internal;
-          }
-          continue;
-        }
-
-        if ( !isset($this->internals[$internal])) {
-          throw new GrammerException("No parser given for rule {$internal} used by {$current}!");
-        }
-
-        $current->internals[$key] = $this->internals[$internal];
-      }
-    }
-  }
-
-  /** @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;
-          }
-          if ( !in_array($internal, $done, true) && !in_array($internal, $todo, true)) {
-            $todo[] = $internal;
-          }
-          if ( !$internal->evalAcceptsEmpty()) {
-            continue;
-          }
-
-          $internal->acceptsEmpty = true;
-          $change = true;
-          break;
-        }
-
-        if ($change) {
-          break;
-        }
-      }
-    }
-  }
-
-
-  /** @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 ( !in_array($internal, $done, true) && !in_array($internal, $todo, true)) {
-          $todo[] = $internal;
-        }
-      }
-
-      if ( !$current instanceof GreedyMultiParser) {
-        continue;
-      }
-      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)) {
-      if ($r['p'] !== strlen($code)) {
-        return false;
-      }
-
-      return $r['r']['r'];
-    }
-    return false;
-  }
-
-  protected function accept($string, $p)
-  {
-    if ($this->internals[$this->s] instanceof BaseParser) {
-      if ($r = $this->internals[$this->s]->parse($string, $p)) {
-        return $r;
-      }
-      return false;
-    }
-    throw new \Exception('BaseParser expected');
-  }
-
-  protected function evalAcceptsEmpty()
-  {
-    if ($this->internals[$this->s] instanceof BaseParser) {
-      return $this->internals[$this->s]->acceptsEmpty;
-    }
-    throw new \Exception('BaseParser expected');
-  }
-
-  protected function firstSet()
-  {
-    if ($this->internals[$this->s] instanceof BaseParser) {
-      return array($this->internals[$this->s]);
-    }
-    throw new \Exception('BaseParser expected');
-  }
-}
-
diff --git a/src/GreedyMultiParser.php b/src/GreedyMultiParser.php
deleted file mode 100644 (file)
index b117663..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-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;
-    $this->optional = $optional;
-
-    $this->description = 'new '.get_class()."({$internal}, {$lower}, {$optional})";
-    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)) {
-        $r[] = $i['r'];
-        $p = $i['p'];
-
-        continue;
-      }
-      return false;
-    }
-
-    for ($j = 0; $this->optional === null || $j < $this->optional; $j++) {
-      if ($i = $this->internals[0]->parse($string, $p)) {
-        $r[] = $i['r'];
-        $p = $i['p'];
-
-        continue;
-      }
-      break;
-    }
-
-    return array(
-      'r' => $r,
-      'p' => $p,
-    );
-  }
-
-  protected function evalAcceptsEmpty()
-  {
-    if ($this->internals[0] instanceof BaseParser) {
-      return $this->lower == 0 || $this->internals[0]->acceptsEmpty;
-    }
-    throw new \Exception('Expected a BaseParser!');
-  }
-
-  protected function firstSet()
-  {
-    if ($this->internals[0] instanceof BaseParser) {
-      return array($this->internals[0]);
-    }
-    throw new \Exception('Expected a BaseParser!');
-  }
-}
diff --git a/src/LazyAltParser.php b/src/LazyAltParser.php
deleted file mode 100644 (file)
index 7e8fd50..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-class LazyAltParser extends BaseParser
-{
-  /**
-   * @param array<BaseParser|string> $internals
-   * @param callable                 $generator
-   */
-  public function __construct(array $internals, $generator = null)
-  {
-    if ( !$internals) {
-      throw new GrammerException('At least one internal parser is needed!');
-    }
-
-    $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!');
-      }
-    }
-
-    foreach ($internals as $internal) {
-      if ($i = $internal->parse($string, $p)) {
-        return array(
-          'r' => $i['r'],
-          'p' => $i['p'],
-        );
-      }
-    }
-    return false;
-  }
-
-  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;
-      }
-    }
-    return false;
-  }
-
-  protected function firstSet()
-  {
-    $internals = [];
-    foreach ($this->internals as $internal) {
-      if ($internal instanceof BaseParser) {
-        $internals[] = $internal;
-      } else {
-        throw new \Exception('Expected a BaseParser!');
-      }
-    }
-
-    return $internals;
-  }
-}
diff --git a/src/RegexParser.php b/src/RegexParser.php
deleted file mode 100644 (file)
index ac36e94..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-class RegexParser extends BaseParser
-{
-  /** @var string */
-  protected $pattern;
-
-  /**
-   * @param string $pattern
-   * @param callable $generator
-   */
-  public function __construct($pattern, $generator = null)
-  {
-    $this->pattern = (string)$pattern;
-    if (substr($this->pattern, 1, 1) !== '^') {
-      throw new GrammerException('Pattern {$this->pattern} must anchor at the beginning of the string!');
-    }
-
-    $this->description = 'new '.get_class().'('.var_export($this->pattern, true).')';
-    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();
-    if (preg_match($this->pattern, substr($string, $p), $matches) !== 1) {
-      return false;
-    }
-
-    return array(
-      'r' => $matches,
-      'p' => $p + strlen($matches[0]),
-    );
-  }
-
-  /** @return bool */
-  protected function evalAcceptsEmpty()
-  {
-    return preg_match($this->pattern, '') === 1;
-  }
-
-  protected function firstSet()
-  {
-    return array();
-  }
-}
-
diff --git a/src/StringParser.php b/src/StringParser.php
deleted file mode 100644 (file)
index d07eead..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-namespace Graphit\Parser;
-
-class StringParser extends BaseParser
-{
-  /** @var string */
-  protected $needle;
-
-  /**
-   * @param string   $needle
-   * @param callable $generator
-   */
-  public function __construct($needle, $generator = null)
-  {
-    $this->needle = (string)$needle;
-
-    $this->description = 'new '.get_class().'('.var_export($this->needle, true).')';
-    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 false;
-    }
-
-    return array(
-      'r' => $this->needle,
-      'p' => $p + strlen($this->needle),
-    );
-  }
-
-  /** @return bool */
-  protected function evalAcceptsEmpty()
-  {
-    return $this->needle === '';
-  }
-
-  protected function firstSet()
-  {
-    return array();
-  }
-}
-