From 4c9f8b7f162f654017fef37df28231b5de970b25 Mon Sep 17 00:00:00 2001 From: dantleech Date: Thu, 10 Jan 2013 20:56:54 +0000 Subject: [PATCH] CND Parser first commit - Not finished --- src/PHPCR/Util/CndParser/CndParser.php | 148 ++++++++++++++++ src/PHPCR/Util/CndParser/ParseError.php | 12 ++ src/PHPCR/Util/CndParser/ParseExpectError.php | 7 + src/PHPCR/Util/CndParser/Scanner.php | 162 ++++++++++++++++++ .../Tests/Util/CndParser/CndParserTest.php | 22 +++ .../Tests/Util/CndParser/cnd/example.cnd | 12 ++ .../Tests/Util/CndParser/cnd/verbose.cnd | 54 ++++++ 7 files changed, 417 insertions(+) create mode 100644 src/PHPCR/Util/CndParser/CndParser.php create mode 100644 src/PHPCR/Util/CndParser/ParseError.php create mode 100644 src/PHPCR/Util/CndParser/ParseExpectError.php create mode 100644 src/PHPCR/Util/CndParser/Scanner.php create mode 100644 tests/PHPCR/Tests/Util/CndParser/CndParserTest.php create mode 100644 tests/PHPCR/Tests/Util/CndParser/cnd/example.cnd create mode 100644 tests/PHPCR/Tests/Util/CndParser/cnd/verbose.cnd diff --git a/src/PHPCR/Util/CndParser/CndParser.php b/src/PHPCR/Util/CndParser/CndParser.php new file mode 100644 index 00000000..60c65230 --- /dev/null +++ b/src/PHPCR/Util/CndParser/CndParser.php @@ -0,0 +1,148 @@ +lines = $lines; + $this->scnr = new Scanner($lines); + $this->check('Cnd'); + } + + public function getScanner() + { + return $this->scnr; + } + + + protected function check($symbol, $optional = false) + { + $method = 'do'.$symbol; + try { + $this->$method(); + return true; + } catch (ParseError $e) { + if (false === $optional) { + throw $e; + } + + $this->scnr->reweindToLastCommit(); + return false; + } + } + + protected function checkMany($symbols) + { + foreach ($symbols as $symbol) { + if (false === $this->check($symbol, true)) { + break; + } + } + } + + protected function doCnd() + { + $this->check('NsMapping', false); + $this->check('NodeTypeDef', false); + } + + protected function doNsMapping() + { + $this->scnr->expect('symbol', '<'); + $this->scnr->expect('string', 'ns'); + $this->scnr->expect('symbol', '='); + $ns = $this->scnr->getValue('string'); + $this->scnr->expect('symbol', '>'); + $this->scnr->commit(); + } + + protected function doNodeTypeDef() + { + $this->check('NodeTypeName'); + $this->check('Supertypes', true); + $this->checkUnordered(array( + 'Orderable', + 'Mixin' + )); + $this->checkMany('PropertyDef'); + } + + protected function doNodeTypeName() + { + $this->scnr->expect('symbol', '['); + $fullName = $this->scnr->getValue('string'); + $this->scnr->expect('symbol', ']'); + $this->scnr->commit(); + } + + protected function doSupertypes() + { + $supertypes = array(); + $this->scnr->expect('symbol', '>'); + while ($string = $this->scnr->getValue('string')) { + $supertypes[] = $string; + if (null === $this->scnr->expect('symbol', ',', true)) { + break; + } + } + $this->scnr->commit(); + } + + protected function doOrderable() + { + $this->scnr->expect('string', array('orderable', 'ord', 'o')); + $this->commit(); + $orderable = true; + } + + protected function doMixin() + { + $this->scnr->expect('string', array('mixin', 'mix', 'm')); + $this->commit(); + $mixin = true; + } + + protected function doPropertyDef() + { + $this->scnr->expect('symbol', '-'); + $propName = $this->scnr->getValue('string'); + $this->scnr->expect('symbol', '('); + $propType = strtolower($this->scnr->getValue('string')); // just make them all lowercase + + // validate property type + if (!in_array($propType, $this->validPropTypes)) { + $t = $this->scnr->getCurrentToken(); + throw new ParseError(sprintf('Property "%s" has invalid type "%s", valid types are: "%s"', + $propName, $propType, implode(',', $this->validPropTypes)), $t['lineNo'] + ); + } + + $this->scnr->expect('symbol', ')'); + $this->scnr->commit(); + } + + public function __toString() + { + return "Dump:\n".implode("\n", $this->lines); + } +} diff --git a/src/PHPCR/Util/CndParser/ParseError.php b/src/PHPCR/Util/CndParser/ParseError.php new file mode 100644 index 00000000..7ed99850 --- /dev/null +++ b/src/PHPCR/Util/CndParser/ParseError.php @@ -0,0 +1,12 @@ +\[\-=\+\],])'; + const RGX_QUOTE = '(["\'])'; + const RGX_COMMENT_LINE = '&^\s*(//|/\*.*\*/).*$&'; + + public function __construct($lines) + { + $this->lines = $lines; + $this->tokens = $this->init(); + } + + public function init() + { + foreach ($this->lines as $i => $line) { + $this->lineNo = $i; + if (preg_match(self::RGX_COMMENT_LINE, $line)) { + continue; + } + if (trim($line) == '') { + continue; + } + + $this->tokenize($line); + $this->flushBuffer(); + } + + return $this->tokens; + } + + protected function addToken($type, $value) + { + $this->tokens[] = array( + 'type' => $type, + 'value' => $value, + 'lineNo' => $this->lineNo + ); + } + + protected function flushBuffer() + { + if ($this->buffer) { + $this->addToken('string', $this->buffer); + $this->buffer = ''; + } + } + + protected function tokenize($line) + { + $stringQuoteOpen = false; + + for ($i = 0; $i < strlen($line); $i++) { + $char = substr($line, $i, 1); + + if (preg_match(self::RGX_QUOTE, $char)) { + if ($stringQuoteOpen == $char) { + $this->flushBuffer(); + $stringQuoteOpen = false; + } elseif (false === $stringQuoteOpen) { + $stringQuoteOpen = $char; + } + + continue; + } + + if (preg_match(self::RGX_UNQUOTED_STRING, $char)) { + $this->buffer .= $char; + } + + if (false === $stringQuoteOpen && $char == ' ') { + $this->flushBuffer(); + } + + if (false === $stringQuoteOpen && preg_match(self::RGX_SYMBOL, $char)) { + $this->flushBuffer(); + $this->addToken('symbol', $char); + } + } + } + + protected function getCurrentToken() + { + return $this->tokens[$this->tokenPos]; + } + + public function getTokens() + { + return $this->tokens; + } + + public function expect($type, $values = null, $optional = false) + { + if (!is_array($values)) { + $values = array($values); + } + + $ret = null; + + $t = $this->getCurrentToken(); + + if ($type != $t['type'] && false === $optional) { + if ($value) { + $value = implode(',', $value); + throw new ParseError(sprintf( + 'Expected token of type "%s" with value "%s", got "%s" with value "%s"', + $type, $value, $t['type'], $t['value']), $t['lineNo'] + ); + } + throw new ParseError(sprintf('Expected token of type "%s", got "%s": %s', $type, $t['type'], $t['value']), $t['lineNo']); + } + + if (null === $value && $type == $t['type']) { + $ret = $t['value']; + } + + foreach ($values as $value) { + if ($t['type'] == $type && $t['value'] == $value) { + $ret = $value; + } + } + + $this->inc(); + + return $ret; + } + + public function inc() + { + $this->tokenPos++; + } + + public function commit() + { + $this->tokenCommitPos = $this->tokenPos; + } + + public function reweindToLastCommit() + { + $this->tokenPos = $this->tokenCommitPos; + } + + public function getValue() + { + $t = $this->getCurrentToken(); + $this->inc(); + return $t['value']; + + } +} diff --git a/tests/PHPCR/Tests/Util/CndParser/CndParserTest.php b/tests/PHPCR/Tests/Util/CndParser/CndParserTest.php new file mode 100644 index 00000000..1c73417b --- /dev/null +++ b/tests/PHPCR/Tests/Util/CndParser/CndParserTest.php @@ -0,0 +1,22 @@ +parser = new CndParser; + } + + public function testExample() + { + $example = file_get_contents(__DIR__.'/cnd/verbose.cnd'); + $lines = explode("\n", $example); + $this->parser->parse($lines); + die(print_r($this->parser->getScanner()->getTokens())); + $this->assertTrue(true); + } +} diff --git a/tests/PHPCR/Tests/Util/CndParser/cnd/example.cnd b/tests/PHPCR/Tests/Util/CndParser/cnd/example.cnd new file mode 100644 index 00000000..5828b2f0 --- /dev/null +++ b/tests/PHPCR/Tests/Util/CndParser/cnd/example.cnd @@ -0,0 +1,12 @@ +asdsad /* An example node type definition */ asdasd + +[ns:NodeType] > ns:ParentType1, ns:ParentType2 + orderable mixin + - ex:property (string) + = 'default1', 'default2' + primary mandatory autocreated protected multiple + version + < 'constraint1', 'constraint2' + + ns:node (ns:reqType1, ns:reqType2) + = ns:defaultType + mandatory autocreated protected multiple versionT diff --git a/tests/PHPCR/Tests/Util/CndParser/cnd/verbose.cnd b/tests/PHPCR/Tests/Util/CndParser/cnd/verbose.cnd new file mode 100644 index 00000000..aa5c01fe --- /dev/null +++ b/tests/PHPCR/Tests/Util/CndParser/cnd/verbose.cnd @@ -0,0 +1,54 @@ +/* An example node type definition */ + +// The namespace declaration + + +// Node type name +[ns:NodeType] + +// Supertypes +> ns:ParentType1, ns:ParentType2 + +// This node type supports orderable child nodes +orderable + +// This is a mixin node type +mixin + +// Nodes of this node type have a property called 'ex:property' of type STRING +- ex:property (string) + +// The default values for this +// (multi-value) property are... += 'default1', 'default2' + +// This property is the primary item +primary + +// and it is... +mandatory autocreated protected + +// and multi-valued +multiple + +// It has an on-parent-version setting of ... +version + +// The constraint settings are... +< 'constraint1', 'constraint2' + +// Nodes of this node type have a child node called ns:node which must be of +// at least the node types ns:reqType1 and ns:reqType2 ++ ns:node (ns:reqType1, ns:reqType2) + +// and the default primary node type of the child node is... += ns:defaultType + +// This child node is... +mandatory autocreated protected + +// and supports same name siblings +multiple + +// and has an on-parent-version setting of ... +version