From: root Date: Fri, 14 Oct 2016 09:04:56 +0000 (+0200) Subject: Initaler Commit X-Git-Tag: v0.1.0~1 X-Git-Url: http://git.graph-it.com/?a=commitdiff_plain;h=0bd2d06b20ca001037c24bea7c212234de3b25fd;p=graphit%2Fparser.git Initaler Commit --- 0bd2d06b20ca001037c24bea7c212234de3b25fd diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor/ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..76beec3 --- /dev/null +++ b/composer.json @@ -0,0 +1,18 @@ +{ + "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/" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..dff83d1 --- /dev/null +++ b/composer.lock @@ -0,0 +1,20 @@ +{ + "_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": [] +} diff --git a/src/BaseParser.php b/src/BaseParser.php new file mode 100644 index 0000000..fe8a71a --- /dev/null +++ b/src/BaseParser.php @@ -0,0 +1,92 @@ +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).')'; + } +} diff --git a/src/ConcatParser.php b/src/ConcatParser.php new file mode 100644 index 0000000..df9f80a --- /dev/null +++ b/src/ConcatParser.php @@ -0,0 +1,54 @@ +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; + } +} diff --git a/src/EBNFGenerator.php b/src/EBNFGenerator.php new file mode 100644 index 0000000..000565a --- /dev/null +++ b/src/EBNFGenerator.php @@ -0,0 +1,217 @@ +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 = "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); + } +} diff --git a/src/EBNFParser.php b/src/EBNFParser.php new file mode 100644 index 0000000..0750832 --- /dev/null +++ b/src/EBNFParser.php @@ -0,0 +1,57 @@ + '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 new file mode 100644 index 0000000..48a4e6a --- /dev/null +++ b/src/EBNFParserBase.php @@ -0,0 +1,302 @@ + 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/EmptyParser.php b/src/EmptyParser.php new file mode 100644 index 0000000..47a4601 --- /dev/null +++ b/src/EmptyParser.php @@ -0,0 +1,31 @@ +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 new file mode 100644 index 0000000..4c20c8c --- /dev/null +++ b/src/GrammerException.php @@ -0,0 +1,5 @@ +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]); + } +} + diff --git a/src/GreedyMultiParser.php b/src/GreedyMultiParser.php new file mode 100644 index 0000000..1e4aa73 --- /dev/null +++ b/src/GreedyMultiParser.php @@ -0,0 +1,68 @@ +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; + } +} diff --git a/src/LazyAltParser.php b/src/LazyAltParser.php new file mode 100644 index 0000000..7c2efcf --- /dev/null +++ b/src/LazyAltParser.php @@ -0,0 +1,44 @@ +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; + } +} diff --git a/src/RegexParser.php b/src/RegexParser.php new file mode 100644 index 0000000..43aae7b --- /dev/null +++ b/src/RegexParser.php @@ -0,0 +1,48 @@ +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(); + } +} + diff --git a/src/StringParser.php b/src/StringParser.php new file mode 100644 index 0000000..d63e862 --- /dev/null +++ b/src/StringParser.php @@ -0,0 +1,44 @@ +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(); + } +} + diff --git a/src/ebnf.ebnf b/src/ebnf.ebnf new file mode 100644 index 0000000..1dea68d --- /dev/null +++ b/src/ebnf.ebnf @@ -0,0 +1,60 @@ + +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+\*\)|\(\* \*\)|\(\*\*\))/ ;