From bc379103c34567e787eeee11836ee191b647e13a Mon Sep 17 00:00:00 2001 From: romanb Date: Sat, 14 Mar 2009 21:19:50 +0000 Subject: [PATCH] [2.0] Some more parser work. --- .../AST/IdentificationVariableDeclaration.php | 4 +- lib/Doctrine/ORM/Query/AST/InExpression.php | 56 +++++++++ lib/Doctrine/ORM/Query/Lexer.php | 107 +++++++++--------- lib/Doctrine/ORM/Query/Parser.php | 76 +++++++++++-- .../ORM/Query/LanguageRecognitionTest.php | 10 +- 5 files changed, 178 insertions(+), 75 deletions(-) create mode 100644 lib/Doctrine/ORM/Query/AST/InExpression.php diff --git a/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php b/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php index 2f13ffacd..e84c5b778 100644 --- a/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php +++ b/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php @@ -16,7 +16,7 @@ * * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see - * . + * . */ namespace Doctrine\ORM\Query\AST; @@ -26,7 +26,7 @@ namespace Doctrine\ORM\Query\AST; * * @author Guilherme Blanco * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link http://www.phpdoctrine.org + * @link http://www.doctrine-project.org * @since 2.0 * @version $Revision$ */ diff --git a/lib/Doctrine/ORM/Query/AST/InExpression.php b/lib/Doctrine/ORM/Query/AST/InExpression.php new file mode 100644 index 000000000..31c8db6b2 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/InExpression.php @@ -0,0 +1,56 @@ +_pathExpression = $pathExpression; + } + + public function setLiterals(array $literals) + { + $this->_literals = $literals; + } + + public function getLiterals() + { + return $this->_literals; + } + + public function setSubselect($subselect) + { + $this->_subselect = $subselect; + } + + public function getSubselect() + { + return $this->_subselect; + } + + public function setNot($bool) + { + $this->_not = $bool; + } + + public function getNot() + { + return $this->_not; + } +} + diff --git a/lib/Doctrine/ORM/Query/Lexer.php b/lib/Doctrine/ORM/Query/Lexer.php index ae9be4e08..1b499235d 100644 --- a/lib/Doctrine/ORM/Query/Lexer.php +++ b/lib/Doctrine/ORM/Query/Lexer.php @@ -16,7 +16,7 @@ * * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see - * . + * . */ namespace Doctrine\ORM\Query; @@ -28,7 +28,7 @@ namespace Doctrine\ORM\Query; * @author Janne Vanhala * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.phpdoctrine.org + * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ */ @@ -174,7 +174,6 @@ class Lexer if (defined($name)) { $type = constant($name); - if ($type > 100) { return $type; } @@ -319,68 +318,64 @@ class Lexer } /** - * @todo Doc + * Resets the lexer position on the input to the given position. */ public function resetPosition($position = 0) { $this->_position = $position; } - private function _addKeyword($token, $value) - { - $this->_keywordsTable[$token] = $value; - } - public function getLiteral($token) { if ( ! $this->_keywordsTable) { - $this->_addKeyword(self::T_ALL, "ALL"); - $this->_addKeyword(self::T_AND, "AND"); - $this->_addKeyword(self::T_ANY, "ANY"); - $this->_addKeyword(self::T_AS, "AS"); - $this->_addKeyword(self::T_ASC, "ASC"); - $this->_addKeyword(self::T_AVG, "AVG"); - $this->_addKeyword(self::T_BETWEEN, "BETWEEN"); - $this->_addKeyword(self::T_BY, "BY"); - $this->_addKeyword(self::T_COMMA, ","); - $this->_addKeyword(self::T_COUNT, "COUNT"); - $this->_addKeyword(self::T_DELETE, "DELETE"); - $this->_addKeyword(self::T_DESC, "DESC"); - $this->_addKeyword(self::T_DISTINCT, "DISTINCT"); - $this->_addKeyword(self::T_DOT, "."); - $this->_addKeyword(self::T_ESCAPE, "ESPACE"); - $this->_addKeyword(self::T_EXISTS, "EXISTS"); - $this->_addKeyword(self::T_FALSE, "FALSE"); - $this->_addKeyword(self::T_FROM, "FROM"); - $this->_addKeyword(self::T_GROUP, "GROUP"); - $this->_addKeyword(self::T_HAVING, "HAVING"); - $this->_addKeyword(self::T_IN, "IN"); - $this->_addKeyword(self::T_INDEX, "INDEX"); - $this->_addKeyword(self::T_INNER, "INNER"); - $this->_addKeyword(self::T_IS, "IS"); - $this->_addKeyword(self::T_JOIN, "JOIN"); - $this->_addKeyword(self::T_LEFT, "LEFT"); - $this->_addKeyword(self::T_LIKE, "LIKE"); - $this->_addKeyword(self::T_LIMIT, "LIMIT"); - $this->_addKeyword(self::T_MAX, "MAX"); - $this->_addKeyword(self::T_MIN, "MIN"); - $this->_addKeyword(self::T_MOD, "MOD"); - $this->_addKeyword(self::T_NOT, "NOT"); - $this->_addKeyword(self::T_NULL, "NULL"); - $this->_addKeyword(self::T_OFFSET, "OFFSET"); - $this->_addKeyword(self::T_ON, "ON"); - $this->_addKeyword(self::T_OR, "OR"); - $this->_addKeyword(self::T_ORDER, "ORDER"); - $this->_addKeyword(self::T_OUTER, "OUTER"); - $this->_addKeyword(self::T_SELECT, "SELECT"); - $this->_addKeyword(self::T_SET, "SET"); - $this->_addKeyword(self::T_SIZE, "SIZE"); - $this->_addKeyword(self::T_SOME, "SOME"); - $this->_addKeyword(self::T_SUM, "SUM"); - $this->_addKeyword(self::T_TRUE, "TRUE"); - $this->_addKeyword(self::T_UPDATE, "UPDATE"); - $this->_addKeyword(self::T_WHERE, "WHERE"); - $this->_addKeyword(self::T_WITH, "WITH"); + $this->_keywordsTable = array( + self::T_ALL => "ALL", + self::T_AND => "AND", + self::T_ANY => "ANY", + self::T_AS => "AS", + self::T_ASC => "ASC", + self::T_AVG => "AVG", + self::T_BETWEEN => "BETWEEN", + self::T_BY => "BY", + self::T_COMMA => ",", + self::T_COUNT => "COUNT", + self::T_DELETE => "DELETE", + self::T_DESC => "DESC", + self::T_DISTINCT => "DISTINCT", + self::T_DOT => ".", + self::T_ESCAPE => "ESCAPE", + self::T_EXISTS => "EXISTS", + self::T_FALSE => "FALSE", + self::T_FROM => "FROM", + self::T_GROUP => "GROUP", + self::T_HAVING => "HAVING", + self::T_IN => "IN", + self::T_INDEX => "INDEX", + self::T_INNER => "INNER", + self::T_IS => "IS", + self::T_JOIN => "JOIN", + self::T_LEFT => "LEFT", + self::T_LIKE => "LIKE", + self::T_LIMIT => "LIMIT", + self::T_MAX => "MAX", + self::T_MIN => "MIN", + self::T_MOD => "MOD", + self::T_NOT => "NOT", + self::T_NULL => "NULL", + self::T_OFFSET => "OFFSET", + self::T_ON => "ON", + self::T_OR => "OR", + self::T_ORDER => "ORDER", + self::T_OUTER => "OUTER", + self::T_SELECT => "SELECT", + self::T_SET => "SET", + self::T_SIZE => "SIZE", + self::T_SOME => "SOME", + self::T_SUM => "SUM", + self::T_TRUE => "TRUE", + self::T_UPDATE => "UPDATE", + self::T_WHERE => "WHERE", + self::T_WITH => "WITH"); } return isset($this->_keywordsTable[$token]) ? $this->_keywordsTable[$token] diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 5ae60bfe1..22124ad05 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -319,8 +319,6 @@ class Parser /** * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement - * - * @return */ private function _QueryLanguage() { @@ -346,8 +344,6 @@ class Parser /** * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] - * - * @return */ private function _SelectStatement() { @@ -427,11 +423,17 @@ class Parser } } + /** + * UpdateStatement ::= UpdateClause [WhereClause] + */ private function _UpdateStatement() { //TODO } + /** + * DeleteStatement ::= DeleteClause [WhereClause] + */ private function _DeleteStatement() { //TODO @@ -496,7 +498,7 @@ class Parser if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); $fieldIdentificationVariable = $this->_FieldAliasIdentificationVariable(); - } elseif ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { + } else if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { $fieldIdentificationVariable = $this->_FieldAliasIdentificationVariable(); } } else { @@ -677,10 +679,7 @@ class Parser $join = new AST\Join($joinType, $joinPathExpression, $aliasIdentificationVariable); // Check Join where type - if ( - $this->_lexer->isNextToken(Lexer::T_ON) || - $this->_lexer->isNextToken(Lexer::T_WITH) - ) { + if ($this->_lexer->isNextToken(Lexer::T_ON) || $this->_lexer->isNextToken(Lexer::T_WITH)) { if ($this->_lexer->isNextToken(Lexer::T_ON)) { $this->match(Lexer::T_ON); $join->setWhereType(AST\Join::JOIN_WHERE_ON); @@ -973,6 +972,12 @@ class Parser $this->_lexer->peek(); $peek = $this->_lexer->peek(); } + + // Also peek beyond a NOT if there is one + if ($peek['type'] === Lexer::T_NOT) { + $peek = $this->_lexer->peek(); + } + $this->_lexer->resetPeek(); $token = $peek; } @@ -1097,6 +1102,52 @@ class Parser return new AST\ArithmeticFactor($this->_ArithmeticPrimary(), $pSign, $nSign); } + /** + * InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (Literal {"," Literal}* | Subselect) ")" + */ + private function _InExpression() + { + $inExpression = new AST\InExpression($this->_PathExpression()); + if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + $this->match(Lexer::T_NOT); + $inExpression->setNot(true); + } + $this->match(Lexer::T_IN); + $this->match('('); + if ($this->_lexer->isNextToken(Lexer::T_SELECT)) { + $inExpression->setSubselect($this->_Subselect()); + } else { + $literals = array(); + $literals[] = $this->_Literal(); + while ($this->_lexer->isNextToken(',')) { + $this->match(','); + $literals[] = $this->_Literal(); + } + } + $this->match(')'); + + return $inExpression; + } + + /** + * Literal ::= string | char | integer | float | boolean | InputParameter + */ + private function _Literal() + { + switch ($this->_lexer->lookahead['type']) { + case Lexer::T_INPUT_PARAMETER: + $this->match($this->_lexer->lookahead['value']); + return new AST\InputParameter($this->_lexer->token['value']); + case Lexer::T_STRING: + case Lexer::T_INTEGER: + case Lexer::T_FLOAT: + $this->match($this->_lexer->lookahead['value']); + return $this->_lexer->token['value']; + default: + $this->syntaxError(); + } + } + /** * ArithmeticPrimary ::= StateFieldPathExpression | Literal | "(" SimpleArithmeticExpression ")" | Function | AggregateExpression */ @@ -1151,11 +1202,12 @@ class Parser break; case 'TRIM': + //TODO: This is not complete! See BNF $this->match($this->_lexer->lookahead['value']); $this->match('('); - //TODO: This is not complete! See BNF - $this->_StringPrimary(); - break; + $func = $this->_StringPrimary(); + $this->match(')'); + return $func; case 'LOWER': break; diff --git a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php index b63ce0b90..0347feeb7 100644 --- a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php +++ b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php @@ -87,17 +87,17 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase { $this->assertValidDql('SELECT COUNT(DISTINCT u.name) FROM Doctrine\Tests\Models\CMS\CmsUser u'); } -/* + public function testFunctionalExpressionsSupportedInWherePart() { $this->assertValidDql("SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE TRIM(u.name) = 'someone'"); } -*/ + public function testArithmeticExpressionsSupportedInWherePart() { $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); } -/* + public function testInExpressionSupportedInWherePart() { $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id IN (1, 2)'); @@ -107,8 +107,8 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase { $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id NOT IN (1)'); } -*/ - /*public function testExistsExpressionSupportedInWherePart() +/* + public function testExistsExpressionSupportedInWherePart() { $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE EXISTS (SELECT p.user_id FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.user_id = u.id)'); }