From: Andrey Kutejko Date: Sun, 11 Aug 2013 11:10:38 +0000 (+0300) Subject: initial version X-Git-Tag: 0.1~1 X-Git-Url: https://git.andy128k.dev/?a=commitdiff_plain;h=52db5ee8c04ec7e411af0f73aa1e54ce208d08e0;p=pegp.git initial version --- 52db5ee8c04ec7e411af0f73aa1e54ce208d08e0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fcac4a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..269856a --- /dev/null +++ b/composer.json @@ -0,0 +1,18 @@ +{ + "name": "andy128k/pegp", + "description": "PEG", + "license": "MIT", + "authors": [ + { + "name": "Andrey Kutejko", + "email": "andy128k@gmail.com" + } + ], + "autoload": { + "classmap" : ["lib"] + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + } +} + diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..2b4d50a --- /dev/null +++ b/composer.lock @@ -0,0 +1,439 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" + ], + "hash": "3e9668f4e1ddee877ae96c3d3e4cd05f", + "packages": [ + + ], + "packages-dev": [ + { + "name": "phpunit/php-code-coverage", + "version": "1.2.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "1.2.12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1.2.12", + "reference": "1.2.12", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": ">=1.3.0@stable", + "phpunit/php-text-template": ">=1.1.1@stable", + "phpunit/php-token-stream": ">=1.1.3@stable" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*@dev" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.0.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2013-07-06 06:26:16" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.3.3", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "1.3.3" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/php-file-iterator/zipball/1.3.3", + "reference": "1.3.3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "File/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2012-10-11 04:44:38" + }, + { + "name": "phpunit/php-text-template", + "version": "1.1.4", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/php-text-template.git", + "reference": "1.1.4" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/php-text-template/zipball/1.1.4", + "reference": "1.1.4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "Text/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2012-10-31 11:15:28" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "1.0.5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1.0.5", + "reference": "1.0.5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2013-08-02 07:42:54" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1.2.0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1.2.0", + "reference": "1.2.0", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2013-08-04 05:57:48" + }, + { + "name": "phpunit/phpunit", + "version": "3.7.23", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "3.7.23" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3.7.23", + "reference": "3.7.23", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpunit/php-code-coverage": "~1.2.1", + "phpunit/php-file-iterator": ">=1.3.1", + "phpunit/php-text-template": ">=1.1.1", + "phpunit/php-timer": ">=1.0.4", + "phpunit/phpunit-mock-objects": "~1.2.0", + "symfony/yaml": "~2.0" + }, + "require-dev": { + "pear-pear/pear": "1.9.4" + }, + "suggest": { + "ext-json": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "phpunit/php-invoker": ">=1.1.0,<1.2.0" + }, + "bin": [ + "composer/bin/phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "", + "../../symfony/yaml/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2013-08-02 19:14:44" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "1.2.3", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "1.2.3" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects/archive/1.2.3.zip", + "reference": "1.2.3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-text-template": ">=1.1.1@stable" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2013-01-13 10:24:48" + }, + { + "name": "symfony/yaml", + "version": "v2.3.2", + "target-dir": "Symfony/Component/Yaml", + "source": { + "type": "git", + "url": "https://github.com/symfony/Yaml.git", + "reference": "v2.3.2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/v2.3.2", + "reference": "v2.3.2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "http://symfony.com", + "time": "2013-07-11 19:36:36" + } + ], + "aliases": [ + + ], + "minimum-stability": "stable", + "stability-flags": [ + + ], + "platform": [ + + ], + "platform-dev": [ + + ] +} diff --git a/lib/input.php b/lib/input.php new file mode 100644 index 0000000..9441af4 --- /dev/null +++ b/lib/input.php @@ -0,0 +1,28 @@ +data = $data; + $this->length = $length ? $length : strlen($data); + $this->pos = $pos; + } + + public function pick($length=PHP_INT_MAX) + { + $length = min($length, $this->length - $this->pos); + if ($length > 0) + return substr($this->data, $this->pos, $length); + else + return ''; + } + + public function advance($len) + { + return new PegpInput($this->data, $this->length, $this->pos + $len); + } +} + diff --git a/lib/parser.php b/lib/parser.php new file mode 100644 index 0000000..5f2950a --- /dev/null +++ b/lib/parser.php @@ -0,0 +1,260 @@ +parse($input); + if (!$result->isSuccess()) + throw new Exception('Parse failed. Expected ' . $result->result->getComment() . ' at ' . $result->input->pos); + if ($input->length != $result->input->pos) + throw new Exception('Parse failed. Unparsed tail at ' . $result->input->pos); + return $result->result; + } + + public function parse(PegpInput $input) + { + $result = $this->doParse($input); + if ($result->isSuccess()) { + return $this->process($result); + } else { + return $result; + } + } + + public abstract function doParse(PegpInput $input); + + protected function process($result) + { + if ($this->drop) { + return PegpResult::success($result->input, null); + } elseif ($this->value !== null) { + return PegpResult::success($result->input, $this->value); + } else { + return $result; + } + } + + public function getComment() + { + return $this->comment; + } + + public function setComment($comment) + { + $this->comment = $comment; + return $this; + } + + public function drop() + { + $this->drop = true; + return $this; + } + + public function value($value) + { + $this->value = $value; + return $this; + } +} + +class PegpString extends PegpParser +{ + private $str, $caseInsensitive; + + public function __construct($str, $caseInsensitive=false) + { + $this->str = $str; + $this->caseInsensitive = $caseInsensitive; + } + + public function doParse(PegpInput $input) + { + $len = strlen($this->str); + if (0 == substr_compare($input->data, $this->str, $input->pos, $len, $this->caseInsensitive)) { // ?? len + return PegpResult::success( + $input->advance($len), + $this->str + ); + } else { + return PegpResult::failure($input, $this); + } + } + + public function getComment() + { + if ($this->comment !== null) + return $this->comment; + else + return 'str.'>'; + } +} + +class PegpRegex extends PegpParser +{ + private $expr; + + public function __construct($expr, $flags='', $delimiter='#') + { + $this->expr = $delimiter . '^' . $expr . $delimiter . $flags; + } + + public function doParse(PegpInput $input) + { + if (preg_match($this->expr, $input->pick(), $m)) { + return PegpResult::success( + $input->advance(strlen($m[0])), + $m + ); + } else { + return PegpResult::failure($input, $this); + } + } + + public function getComment() + { + if ($this->comment !== null) + return $this->comment; + else + return 'expr.'>'; + } +} + +class PegpSequence extends PegpParser +{ + private $parsers, $op = null; + + public function __construct(array $parsers) + { + $this->parsers = $parsers; + } + + public function doParse(PegpInput $input) + { + $inp = $input; + $result = array(); + foreach ($this->parsers as $parser) { + $r = $parser->parse($inp); + if ($r->isSuccess()) { + $inp = $r->input; + if ($r->result !== null) + $result[] = $r->result; + } else { + return PegpResult::failure($inp, $parser); + } + } + return PegpResult::success($inp, $result); + } + + protected function process($result) + { + switch ($this->op) { + case 'join': + $result = PegpResult::success($result->input, implode('', $result->result)); + break; + case 'bitOr': + $value = 0; + foreach ($result->result as $v) + $value |= $v; + $result = PegpResult::success($result->input, $value); + break; + case 'sum': + $value = 0; + foreach ($result->result as $v) + $value += $v; + $result = PegpResult::success($result->input, $value); + break; + case 'product': + $value = 1; + foreach ($result->result as $v) + $value *= $v; + $result = PegpResult::success($result->input, $value); + break; + } + return parent::process($result); + } + + public function join() + { + $this->op = 'join'; + return $this; + } + + public function bitOr() + { + $this->op = 'bitOr'; + return $this; + } + + public function sum() + { + $this->op = 'sum'; + return $this; + } + + public function product() + { + $this->op = 'product'; + return $this; + } +} + +class PegpOneOf extends PegpParser +{ + private $parsers; + + public function __construct(array $parsers) + { + $this->parsers = $parsers; + } + + public function doParse(PegpInput $input) + { + foreach ($this->parsers as $parser) { + $r = $parser->parse($input); + if ($r->isSuccess()) { + return $r; + } + } + return PegpResult::failure($input, $this); + } + + public function getComment() + { + if ($this->comment !== null) + return $this->comment; + $comment = array(); + foreach ($this->parsers as $parser) { + $comment[] = $parser->getComment(); + } + return 'one of: '.implode(', ', $comment); + } +} + +class PegpOptional extends PegpParser +{ + private $parser, $emptyValue; + + public function __construct(PegpParser $parser, $emptyValue='') + { + $this->parser = $parser; + $this->emptyValue = $emptyValue; + } + + public function doParse(PegpInput $input) + { + $r = $this->parser->parse($input); + if ($r->isSuccess()) { + return $r; + } else { + return PegpResult::success($input, $this->emptyValue); + } + } +} + diff --git a/lib/result.php b/lib/result.php new file mode 100644 index 0000000..f2e559f --- /dev/null +++ b/lib/result.php @@ -0,0 +1,30 @@ +success = $success; + $this->input = $input; + $this->result = $result; + } + + public static function success(PegpInput $input, $result=null) + { + return new PegpResult(true, $input, $result); + } + + public static function failure(PegpInput $input, $parser=null) + { + return new PegpResult(false, $input, $parser); + } + + public function isSuccess() + { + return $this->success; + } +} + diff --git a/lib/shortcuts.php b/lib/shortcuts.php new file mode 100644 index 0000000..91efa2c --- /dev/null +++ b/lib/shortcuts.php @@ -0,0 +1,35 @@ +parse($i); + $this->assertInstanceOf('PegpResult', $r); + $inputAfter = $r->input; + $this->assertInstanceOf('PegpInput', $inputAfter); + + if (!$r->isSuccess()) { + echo 'Parse failed. Expected ' . $r->result . ' at ' . $r->input->pos . "\n"; + } + + $this->assertTrue($r->isSuccess()); + $this->assertEquals($i->data, $inputAfter->data); + $this->assertEquals($i->length, $inputAfter->length); + $this->assertEquals($i->length, $inputAfter->pos); + return $r->result; + } + + public function testString() + { + $p = Pegp::str('test'); + $this->assertParse($p, 'test'); + } + + public function testRegex() + { + $p = Pegp::re('te[xs]t'); + $this->assertParse($p, 'test'); + $this->assertParse($p, 'text'); + } + + public function testSQLStringLiteral() + { + $p = Pegp::re("'([^\\\\']*[\\\\']')*[^\\\\']*'"); + $this->assertParse($p, "''"); + $this->assertParse($p, "'Hello there'"); + $this->assertParse($p, "'Don\\'t worry. Be happy!'"); + $this->assertParse($p, "'Don''t worry. Be happy!'"); + } + + public function testDecimal() + { + $p = Pegp::re("\\d+(\\.\\d+)?"); + $r = $this->assertParse($p, '123.45'); + $this->assertEquals('.45', $r[1]); + } + + public function testSequence() + { + $word = Pegp::re('[a-z]+'); + $space = Pegp::re('\s+'); + $optSpace = Pegp::re('\s*'); + $punctuation = Pegp::re('[.!?]')->setComment('punctiation mark'); + $p = Pegp::seq($optSpace, $word, $space, $word, $space, $word, $optSpace, $punctuation, $optSpace); + + $r = $this->assertParse($p, "\thow are you \n ? "); + } +} + diff --git a/t/PropertiesTest.php b/t/PropertiesTest.php new file mode 100644 index 0000000..6e22b72 --- /dev/null +++ b/t/PropertiesTest.php @@ -0,0 +1,53 @@ +parse($i); + $this->assertInstanceOf('PegpResult', $r); + $inputAfter = $r->input; + $this->assertInstanceOf('PegpInput', $inputAfter); + + if (!$r->isSuccess()) { + echo 'Parse failed. Expected ' . $r->result->getComment() . ' at ' . $r->input->pos . "\n"; + } + + $this->assertTrue($r->isSuccess()); + $this->assertEquals($i->data, $inputAfter->data); + $this->assertEquals($i->length, $inputAfter->length); + $this->assertEquals($i->length, $inputAfter->pos); + $this->assertEquals($expected, $r->result); + } + + public function testProperties() + { + $p = + Pegp::oneOf( + Pegp::stri('default'), + Pegp::stri('us'), + Pegp::stri('european'), + Pegp::seq( + Pegp::optional( + Pegp::seq( + Pegp::oneOf( + Pegp::stri('big-endian'), + Pegp::stri('little-endian'), + Pegp::stri('middle-endian')), + Pegp::re('\s+')->drop())->join()), + Pegp::oneOf( + Pegp::stri('slashes')->value('/'), + Pegp::stri('dots')->value('.'), + Pegp::stri('hyphens')->value('-'), + Pegp::stri('spaces')->value(' ')))); + + $this->assertParse('us', $p, 'us'); + $this->assertParse(array('big-endian', '-'), $p, 'Big-endian hyphEns'); + $this->assertParse(array('little-endian', '.'), $p, 'little-endian dots'); + $this->assertParse(array('middle-endian', '/'), $p, 'Middle-Endian Slashes'); + $this->assertParse(array('big-endian', ' '), $p, 'big-endian spaces'); + $this->assertParse(array('', '-'), $p, 'Hyphens'); + } +} +