--- /dev/null
+{
+ "name": "graphit/parser",
+ "type": "library",
+ "authors": [
+ {
+ "name": "Graph-IT",
+ "email": "info@graph-it.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.6.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Graphit\\Parser\\": "src/"
+ }
+ }
+}
--- /dev/null
+{
+ "_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",
+ "This file is @generated automatically"
+ ],
+ "hash": "58c59c1998c08b0e41abf3c52e03cf24",
+ "content-hash": "885de769d17478ea8942f54d26e6a439",
+ "packages": [],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=5.6.0"
+ },
+ "platform-dev": []
+}
--- /dev/null
+<?php
+
+namespace Graphit\Parser;
+
+abstract class BaseParser
+{
+ protected $internals;
+
+ protected $generator;
+
+ protected $description;
+
+ protected $acceptsEmpty;
+
+ 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');
+ }
+ }
+
+ public function getInternals()
+ {
+ return $this->internals;
+ }
+
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ 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'],
+ );
+ }
+
+ protected abstract function accept($string, $p);
+
+ protected abstract function evalAcceptsEmpty();
+
+ protected abstract function firstSet();
+
+ public function __toString()
+ {
+ return $this->description;
+ }
+
+ protected function defaultGenerator($result)
+ {
+ return array(
+ 't' => (string)$this,
+ 'r' => $result,
+ );
+ }
+
+ 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).')';
+ }
+}
--- /dev/null
+<?php
+
+namespace Graphit\Parser;
+
+class ConcatParser extends BaseParser
+{
+ public function __construct(array $internals, $generator = null)
+ {
+ $this->description = 'new '.get_class().'('.$this->serializeInternals($internals).')';
+ parent::__construct($internals, $generator);
+ }
+
+ protected function accept($string, $p)
+ {
+ $r = array();
+ foreach ($this->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()
+ {
+ foreach ($this->internals as $internal) {
+ if ( !$internal->acceptsEmpty) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected function firstSet()
+ {
+ $firstSet = array();
+ foreach ($this->internals as $internal) {
+ $firstSet[] = $internal;
+
+ if ( !$internal->acceptsEmpty) {
+ break;
+ }
+ }
+ return $firstSet;
+ }
+}
--- /dev/null
+<?php
+
+namespace Graphit\Parser;
+
+class EBNFGenerator
+{
+ protected $parser;
+
+ public function __construct()
+ {
+ $this->parser = new EBNFParser();
+ }
+
+ 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 {
+ $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;
+ }
+
+ 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");
+ }
+
+ protected function genSyntaxParser($object, $generator)
+ {
+ $code = "\$internals = array(\n";
+ foreach ($object['r'][1]['r'] as $rule) {
+ $code.= $this->indent($this->genParser($rule), 2).",\n";
+ }
+ $code.= ");";
+ return $code;
+ }
+
+ protected function genRuleParser($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;
+ }
+
+ protected function genAltParser($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;
+ }
+
+ protected function genConcParser($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;
+ }
+
+ protected function genBarewordParser($object)
+ {
+ $code = var_export($object['r'][0]['r'][0], true);
+ return $code;
+ }
+
+ protected function genSqParser($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;
+ }
+
+ protected function genDqParser($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;
+ }
+
+ protected function genRegexParser($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;
+ }
+
+ protected function genGroupParser($object, $generator = null)
+ {
+ return $this->genParser($object['r'][2], $generator);
+ }
+
+ protected function genRepetitionParser($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;
+ }
+
+ protected function genOptionalParser($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;
+ }
+
+ protected function genGenerator($source)
+ {
+ return "{$source}Generator";
+ }
+
+ 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;
+ }
+
+ 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)) === false) {
+ break;
+ }
+ $code .= "{$line}\n";
+ }
+
+ foreach ($lines as $line) {
+ $code .= "{$pad}{$line}\n";
+ }
+
+ return substr($code, 0, -1);
+ }
+}
--- /dev/null
+<?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'];
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?php
+
+namespace Graphit\Parser;
+
+class EmptyParser extends BaseParser
+{
+ 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();
+ }
+}
--- /dev/null
+<?php
+
+namespace Graphit\Parser;
+
+class GrammerException extends \Exception { }
--- /dev/null
+<?php
+
+namespace Graphit\Parser;
+
+class GrammerParser extends BaseParser
+{
+ protected $s;
+
+ 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) {
+ $done = array();
+ $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;
+ }
+ }
+ }
+ }
+
+ protected function resolveParserNames()
+ {
+ $done = array();
+ $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];
+ }
+ }
+ }
+
+ protected function floodAcceptsEmpty()
+ {
+ $change = true;
+ while ($change) {
+ $change = false;
+
+ $done = array();
+ $todo = array($this);
+ while ($current = array_shift($todo)) {
+ $done[] = $current;
+
+ foreach ($current->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;
+ }
+ }
+ }
+ }
+
+ protected function testInifinitGreedy()
+ {
+ $done = array();
+ $todo = $this->internals;
+ while ($current = array_shift($todo)) {
+ $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]->acceptsEmpty) {
+ throw new GrammerException("{$current} will cause infinite loops, because the internal parser accepts empty!");
+ }
+ }
+ }
+
+ 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 ($r = $this->internals[$this->s]->parse($string, $p)) {
+ return $r;
+ }
+
+ return false;
+ }
+
+ protected function evalAcceptsEmpty()
+ {
+ return $this->internals[$this->s]->acceptsEmpty;
+ }
+
+ protected function firstSet()
+ {
+ return array($this->internals[$this->s]);
+ }
+}
+
--- /dev/null
+<?php
+
+namespace Graphit\Parser;
+
+class GreedyMultiParser extends BaseParser
+{
+ protected $lower;
+
+ protected $optional;
+
+ 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);
+ }
+
+ public function getLower()
+ {
+ return $this->lower;
+ }
+
+ public function getOptional()
+ {
+ return $this->optional;
+ }
+
+ protected function accept($string, $p)
+ {
+ $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()
+ {
+ return $this->lower == 0 || $this->internals[0]->acceptsEmpty;
+ }
+
+ protected function firstSet()
+ {
+ return $this->internals;
+ }
+}
--- /dev/null
+<?php
+
+namespace Graphit\Parser;
+
+class LazyAltParser extends BaseParser
+{
+ 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)
+ {
+ foreach ($this->internals as $internal) {
+ if ($i = $internal->parse($string, $p)) {
+ return array(
+ 'r' => $i['r'],
+ 'p' => $i['p'],
+ );
+ }
+ }
+ return false;
+ }
+
+ protected function evalAcceptsEmpty()
+ {
+ foreach ($this->internals as $internal) {
+ if ($internal->acceptsEmpty) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected function firstSet()
+ {
+ return $this->internals;
+ }
+}
--- /dev/null
+<?php
+
+namespace Graphit\Parser;
+
+class RegexParser extends BaseParser
+{
+ protected $pattern;
+
+ 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);
+ }
+
+ public function getPattern()
+ {
+ return $this->pattern;
+ }
+
+ 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]),
+ );
+ }
+
+ protected function evalAcceptsEmpty()
+ {
+ return preg_match($this->pattern, '') === 1;
+ }
+
+ protected function firstSet()
+ {
+ return array();
+ }
+}
+
--- /dev/null
+<?php
+
+namespace Graphit\Parser;
+
+class StringParser extends BaseParser
+{
+ protected $needle;
+
+ public function __construct($needle, $generator = null)
+ {
+ $this->needle = (string)$needle;
+
+ $this->description = 'new '.get_class().'('.var_export($this->needle, true).')';
+ parent::__construct(array(), $generator);
+ }
+
+ public function getNeedle()
+ {
+ return $this->needle;
+ }
+
+ 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),
+ );
+ }
+
+ protected function evalAcceptsEmpty()
+ {
+ return $this->needle === '';
+ }
+
+ protected function firstSet()
+ {
+ return array();
+ }
+}
+
--- /dev/null
+
+syntax
+ = space, rules ;
+
+rules
+ = { rule } ;
+
+rule
+ = bareword, space, "=", space, alt, ";", space ;
+
+alt
+ = conc, pipeconclist ;
+
+pipeconclist
+ = { pipeconc } ;
+
+pipeconc
+ = "|", space, conc ;
+
+conc
+ = term, commatermlist ;
+
+commatermlist
+ = { commaterm } ;
+
+commaterm
+ = ",", space, term ;
+
+term
+ = bareword | sq | dq | regex | group | repetition | optional ;
+
+bareword
+ = /^([a-z][a-z ]*[a-z]|[a-z])/, space ;
+
+sq
+ = /^'([^']*)'/, space ;
+
+dq
+ = /^"([^"]*)"/, space ;
+
+regex
+ = /^\/\^([^\/\\]*(\\\/|\\[^\/])?)*\//, space ;
+
+group
+ = "(", space, alt, ")", space ;
+
+repetition
+ = "{", space, alt, "}", space ;
+
+optional
+ = "[", space, alt, "]", space ;
+
+space
+ = { whitespace | comment } ;
+
+whitespace
+ = /^[ \t\r\n]+/ ;
+
+comment
+ = /^(\(\*\s+[^*]*\s+\*\)|\(\* \*\)|\(\*\*\))/ ;