Commit ac5fe1f9 authored by guilhermeblanco's avatar guilhermeblanco

[2.0] Some Annotations parser docblocks, optimizations, etc. Fixed wrong...

[2.0] Some Annotations parser docblocks, optimizations, etc. Fixed wrong syntax error token report in DQL parser
parent 33fc28ff
...@@ -68,16 +68,18 @@ class Lexer extends \Doctrine\Common\Lexer ...@@ -68,16 +68,18 @@ class Lexer extends \Doctrine\Common\Lexer
protected function _getType(&$value) protected function _getType(&$value)
{ {
$type = self::T_NONE; $type = self::T_NONE;
$newVal = $this->_getNumeric($value); $newVal = $this->_getNumeric($value);
if ($newVal !== false){ if ($newVal !== false){
$value = $newVal; $value = $newVal;
if (strpos($value, '.') !== false || stripos($value, 'e') !== false) { if (strpos($value, '.') !== false || stripos($value, 'e') !== false) {
$type = self::T_FLOAT; $type = self::T_FLOAT;
} else { } else {
$type = self::T_INTEGER; $type = self::T_INTEGER;
} }
} }
if ($value[0] === '"') { if ($value[0] === '"') {
$type = self::T_STRING; $type = self::T_STRING;
$value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2));
...@@ -89,7 +91,10 @@ class Lexer extends \Doctrine\Common\Lexer ...@@ -89,7 +91,10 @@ class Lexer extends \Doctrine\Common\Lexer
} }
/** /**
* @todo Doc * Checks if a value is numeric or not
*
* @param mixed $value Value to be inspected
* @return boolean|integer|float Processed value
*/ */
private function _getNumeric($value) private function _getNumeric($value)
{ {
...@@ -108,7 +113,7 @@ class Lexer extends \Doctrine\Common\Lexer ...@@ -108,7 +113,7 @@ class Lexer extends \Doctrine\Common\Lexer
* Checks if an identifier is a keyword and returns its correct type. * Checks if an identifier is a keyword and returns its correct type.
* *
* @param string $identifier identifier name * @param string $identifier identifier name
* @return int token type * @return integer token type
*/ */
private function _checkLiteral($identifier) private function _checkLiteral($identifier)
{ {
......
...@@ -24,23 +24,57 @@ namespace Doctrine\Common\Annotations; ...@@ -24,23 +24,57 @@ namespace Doctrine\Common\Annotations;
/** /**
* A simple parser for docblock annotations. * A simple parser for docblock annotations.
* *
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision: 3938 $
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
*/ */
class Parser class Parser
{ {
/** Tags that are stripped prior to parsing in order to reduce parsing overhead. */ /**
* Tags that are stripped prior to parsing in order to reduce parsing overhead.
*
* @var array
*/
private static $_strippedInlineTags = array( private static $_strippedInlineTags = array(
"{@example", "{@id", "{@internal", "{@inheritdoc", "{@example", "{@id", "{@internal", "{@inheritdoc",
"{@link", "{@source", "{@toc", "{@tutorial", "*/" "{@link", "{@source", "{@toc", "{@tutorial", "*/"
); );
/**
* The lexer.
*
* @var Doctrine\Common\Annotations\Lexer
*/
private $_lexer; private $_lexer;
/**
* Flag to control if the current Annotation is nested or not.
*
* @var boolean
*/
private $_isNestedAnnotation = false; private $_isNestedAnnotation = false;
/**
* Default namespace for Annotations.
*
* @var string
*/
private $_defaultAnnotationNamespace = ''; private $_defaultAnnotationNamespace = '';
/**
* Hashmap to store namespace aliases.
*
* @var array
*/
private $_namespaceAliases = array(); private $_namespaceAliases = array();
/** /**
* Constructs a new AnnotationParser. * Constructs a new AnnotationParser.
*
*/ */
public function __construct() public function __construct()
{ {
...@@ -63,7 +97,6 @@ class Parser ...@@ -63,7 +97,6 @@ class Parser
* *
* @param $namespace * @param $namespace
* @param $alias * @param $alias
* @return unknown_type
*/ */
public function setAnnotationNamespaceAlias($namespace, $alias) public function setAnnotationNamespaceAlias($namespace, $alias)
{ {
...@@ -74,13 +107,13 @@ class Parser ...@@ -74,13 +107,13 @@ class Parser
* Parses the given docblock string for annotations. * Parses the given docblock string for annotations.
* *
* @param $docBlockString * @param $docBlockString
* @return array An array of Annotation instances. If no annotations are found, an empty * @return array Array of Annotations. If no annotations are found, an empty array is returned.
* array is returned.
*/ */
public function parse($docBlockString) public function parse($docBlockString)
{ {
// Strip out some known inline tags. // Strip out some known inline tags.
$input = str_replace(self::$_strippedInlineTags, '', $docBlockString); $input = str_replace(self::$_strippedInlineTags, '', $docBlockString);
// Cut of the beginning of the input until the first '@'. // Cut of the beginning of the input until the first '@'.
$input = substr($input, strpos($input, '@')); $input = substr($input, strpos($input, '@'));
...@@ -91,72 +124,77 @@ class Parser ...@@ -91,72 +124,77 @@ class Parser
if ($this->_lexer->isNextToken('@')) { if ($this->_lexer->isNextToken('@')) {
return $this->Annotations(); return $this->Annotations();
} }
return array(); return array();
} }
/** /**
* Attempts to match the given token with the current lookahead token. * Attempts to match the given token with the current lookahead token.
* * If they match, updates the lookahead token; otherwise raises a syntax error.
* If they match, updates the lookahead token; otherwise raises a syntax
* error.
* *
* @param int|string token type or value * @param int|string token type or value
* @return bool True, if tokens match; false otherwise. * @return bool True if tokens match; false otherwise.
*/ */
public function match($token) public function match($token)
{ {
if (is_string($token)) { $key = (is_string($token)) ? 'value' : 'type';
$isMatch = ($this->_lexer->lookahead['value'] === $token);
} else {
$isMatch = ($this->_lexer->lookahead['type'] === $token);
}
if ( ! $isMatch) { if ( ! ($this->_lexer->lookahead[$key] === $token)) {
$this->syntaxError($token['value'], $this->_lexer->lookahead['value']); $token = (is_string($token)) ? $token : $token['value'];
$this->syntaxError($token);
} }
$this->_lexer->moveNext(); $this->_lexer->moveNext();
} }
/** /**
* Raises a syntax error. * Generates a new syntax error.
* *
* @param $expected * @param string $expected Expected string.
* @param array $token Optional token.
* @throws Exception * @throws Exception
*/ */
private function syntaxError($expected, $got = "") private function syntaxError($expected, $token = null)
{ {
throw \Doctrine\Common\DoctrineException::syntaxError("Expected: $expected. Got: $got"); if ($token === null) {
$token = $this->_lexer->lookahead;
}
$message = "Expected '{$expected}', got ";
if ($this->_lexer->lookahead === null) {
$message .= 'end of string.';
} else {
$message .= "'{$token['value']}'";
}
throw \Doctrine\Common\DoctrineException::syntaxError($message);
} }
/** /**
* Annotations ::= Annotation ("*")* (Annotation)* * Annotations ::= Annotation {[ "*" ]* [Annotation]}*
*
* @return array
*/ */
public function Annotations() public function Annotations()
{ {
$this->_isNestedAnnotation = false; $this->_isNestedAnnotation = false;
$annotations = array(); $annotations = array();
$annot = $this->Annotation(); $annot = $this->Annotation();
if ($annot === false) {
// Not a valid annotation. Go on to next annotation. if ($annot !== false) {
$this->_lexer->skipUntil('@');
} else {
$annotations[get_class($annot)] = $annot; $annotations[get_class($annot)] = $annot;
} }
while (true) { while ($this->_lexer->lookahead !== null && $this->_lexer->lookahead['value'] == '@') {
if ($this->_lexer->lookahead['value'] == '@') {
$this->_isNestedAnnotation = false; $this->_isNestedAnnotation = false;
$annot = $this->Annotation(); $annot = $this->Annotation();
if ($annot === false) {
// Not a valid annotation. Go on to next annotation. if ($annot !== false) {
$this->_lexer->skipUntil('@');
} else {
$annotations[get_class($annot)] = $annot; $annotations[get_class($annot)] = $annot;
} }
} else {
break;
}
} }
return $annotations; return $annotations;
...@@ -164,71 +202,103 @@ class Parser ...@@ -164,71 +202,103 @@ class Parser
/** /**
* Annotation ::= "@" AnnotationName [ "(" [Values] ")" ] * Annotation ::= "@" AnnotationName [ "(" [Values] ")" ]
* AnnotationName ::= SimpleName | QualifiedName * AnnotationName ::= QualifiedName | SimpleName
* SimpleName ::= identifier * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName
* QualifiedName ::= NameSpacePart "\" (NameSpacePart "\")* SimpleName
* NameSpacePart ::= identifier * NameSpacePart ::= identifier
* SimpleName ::= identifier
*
* @return mixed False if it is not a valid Annotation; instance of Annotation subclass otherwise.
*/ */
public function Annotation() public function Annotation()
{ {
$values = array(); $values = array();
$nameParts = array(); $nameParts = array();
$this->match('@'); $this->match('@');
$this->match(Lexer::T_IDENTIFIER); $this->match(Lexer::T_IDENTIFIER);
$nameParts[] = $this->_lexer->token['value']; $nameParts[] = $this->_lexer->token['value'];
while ($this->_lexer->isNextToken('\\')) { while ($this->_lexer->isNextToken('\\')) {
$this->match('\\'); $this->match('\\');
$this->match(Lexer::T_IDENTIFIER); $this->match(Lexer::T_IDENTIFIER);
$nameParts[] = $this->_lexer->token['value']; $nameParts[] = $this->_lexer->token['value'];
} }
// Effectively pick the name of class (append default NS if none, grab from NS alias, etc)
if (count($nameParts) == 1) { if (count($nameParts) == 1) {
$name = $this->_defaultAnnotationNamespace . $nameParts[0]; $name = $this->_defaultAnnotationNamespace . $nameParts[0];
} else { } else if (count($nameParts) == 2 && isset($this->_namespaceAliases[$nameParts[0]])) {
if (count($nameParts) == 2 && isset($this->_namespaceAliases[$nameParts[0]])) {
$name = $this->_namespaceAliases[$nameParts[0]] . $nameParts[1]; $name = $this->_namespaceAliases[$nameParts[0]] . $nameParts[1];
} else { } else {
$name = implode('\\', $nameParts); $name = implode('\\', $nameParts);
} }
}
if ( ! $this->_isNestedAnnotation && $this->_lexer->lookahead != null // If it really an annotation class?
&& $this->_lexer->lookahead['value'] != '(' if (
&& $this->_lexer->lookahead['value'] != '@' ! $this->_isNestedAnnotation && $this->_lexer->lookahead != null &&
|| ! is_subclass_of($name, 'Doctrine\Common\Annotations\Annotation')) { ! $this->_lexer->isNextToken('(') &&
! $this->_lexer->isNextToken('@') ||
! is_subclass_of($name, 'Doctrine\Common\Annotations\Annotation')
) {
$this->_lexer->skipUntil('@');
return false; return false;
} }
$this->_isNestedAnnotation = true; // Next will be nested // Next will be nested
$this->_isNestedAnnotation = true;
if ($this->_lexer->isNextToken('(')) { if ($this->_lexer->isNextToken('(')) {
$this->match('('); $this->match('(');
if ($this->_lexer->isNextToken(')')) {
$this->match(')'); if ( ! $this->_lexer->isNextToken(')')) {
} else {
$values = $this->Values(); $values = $this->Values();
$this->match(')');
} }
$this->match(')');
} }
return new $name($values); return new $name($values);
} }
/** /**
* Values ::= Value ("," Value)* * Values ::= Value {"," Value}*
*/ */
public function Values() public function Values()
{ {
$values = array(); $values = array();
$values[] = $this->Value();
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
$value = $this->Value(); $value = $this->Value();
if ($this->_lexer->isNextToken(')')) { if ( ! is_array($value)) {
$this->syntaxError('FieldAssignment', $value);
}
$values[] = $value;
}
foreach ($values as $k => $value) {
if (is_array($value) && is_string(key($value))) {
$key = key($value);
$values[$key] = $value[$key];
} else {
$values['value'] = $value;
}
unset($values[$k]);
}
return $values;
/*if ($this->_lexer->isNextToken(')')) {
// Single value // Single value
if (is_array($value)) { if (is_array($value)) {
$k = key($value); $k = key($value);
$v = $value[$k]; $v = $value[$k];
if (is_string($k)) { if (is_string($k)) {
// FieldAssignment // FieldAssignment
$values[$k] = $v; $values[$k] = $v;
...@@ -238,6 +308,7 @@ class Parser ...@@ -238,6 +308,7 @@ class Parser
} else { } else {
$values['value'] = $value; $values['value'] = $value;
} }
return $values; return $values;
} else { } else {
// FieldAssignment // FieldAssignment
...@@ -259,7 +330,7 @@ class Parser ...@@ -259,7 +330,7 @@ class Parser
$values[$k] = $v; $values[$k] = $v;
} }
return $values; return $values;*/
} }
/** /**
...@@ -268,9 +339,11 @@ class Parser ...@@ -268,9 +339,11 @@ class Parser
public function Value() public function Value()
{ {
$peek = $this->_lexer->glimpse(); $peek = $this->_lexer->glimpse();
if ($peek['value'] === '=') { if ($peek['value'] === '=') {
return $this->FieldAssignment(); return $this->FieldAssignment();
} }
return $this->PlainValue(); return $this->PlainValue();
} }
...@@ -280,8 +353,9 @@ class Parser ...@@ -280,8 +353,9 @@ class Parser
public function PlainValue() public function PlainValue()
{ {
if ($this->_lexer->lookahead['value'] == '{') { if ($this->_lexer->lookahead['value'] == '{') {
return $this->Array_(); return $this->Arrayx();
} }
if ($this->_lexer->lookahead['value'] == '@') { if ($this->_lexer->lookahead['value'] == '@') {
return $this->Annotation(); return $this->Annotation();
} }
...@@ -290,18 +364,23 @@ class Parser ...@@ -290,18 +364,23 @@ class Parser
case Lexer::T_STRING: case Lexer::T_STRING:
$this->match(Lexer::T_STRING); $this->match(Lexer::T_STRING);
return $this->_lexer->token['value']; return $this->_lexer->token['value'];
case Lexer::T_INTEGER: case Lexer::T_INTEGER:
$this->match(Lexer::T_INTEGER); $this->match(Lexer::T_INTEGER);
return $this->_lexer->token['value']; return $this->_lexer->token['value'];
case Lexer::T_FLOAT: case Lexer::T_FLOAT:
$this->match(Lexer::T_FLOAT); $this->match(Lexer::T_FLOAT);
return $this->_lexer->token['value']; return $this->_lexer->token['value'];
case Lexer::T_TRUE: case Lexer::T_TRUE:
$this->match(Lexer::T_TRUE); $this->match(Lexer::T_TRUE);
return true; return true;
case Lexer::T_FALSE: case Lexer::T_FALSE:
$this->match(Lexer::T_FALSE); $this->match(Lexer::T_FALSE);
return false; return false;
default: default:
var_dump($this->_lexer->lookahead); var_dump($this->_lexer->lookahead);
throw new \Exception("Invalid value."); throw new \Exception("Invalid value.");
...@@ -317,13 +396,14 @@ class Parser ...@@ -317,13 +396,14 @@ class Parser
$this->match(Lexer::T_IDENTIFIER); $this->match(Lexer::T_IDENTIFIER);
$fieldName = $this->_lexer->token['value']; $fieldName = $this->_lexer->token['value'];
$this->match('='); $this->match('=');
return array($fieldName => $this->PlainValue()); return array($fieldName => $this->PlainValue());
} }
/** /**
* Array ::= "{" arrayEntry ("," arrayEntry)* "}" * Array ::= "{" ArrayEntry {"," ArrayEntry}* "}"
*/ */
public function Array_() public function Arrayx()
{ {
$this->match('{'); $this->match('{');
$array = array(); $array = array();
...@@ -333,6 +413,7 @@ class Parser ...@@ -333,6 +413,7 @@ class Parser
$this->match(','); $this->match(',');
$this->ArrayEntry($array); $this->ArrayEntry($array);
} }
$this->match('}'); $this->match('}');
return $array; return $array;
...@@ -346,17 +427,20 @@ class Parser ...@@ -346,17 +427,20 @@ class Parser
public function ArrayEntry(array &$array) public function ArrayEntry(array &$array)
{ {
$peek = $this->_lexer->glimpse(); $peek = $this->_lexer->glimpse();
if ($peek['value'] == '=') { if ($peek['value'] == '=') {
if ($this->_lexer->lookahead['type'] === Lexer::T_INTEGER) { if ($this->_lexer->lookahead['type'] === Lexer::T_INTEGER) {
$this->match(Lexer::T_INTEGER); $this->match(Lexer::T_INTEGER);
} else { } else {
$this->match(Lexer::T_STRING); $this->match(Lexer::T_STRING);
} }
$key = $this->_lexer->token['value']; $key = $this->_lexer->token['value'];
$this->match('='); $this->match('=');
return $array[$key] = $this->Value(); return $array[$key] = $this->Value();
} else {
return $array[] = $this->Value();
} }
return $array[] = $this->Value();
} }
} }
\ No newline at end of file
...@@ -223,8 +223,9 @@ class Parser ...@@ -223,8 +223,9 @@ class Parser
{ {
$key = (is_string($token)) ? 'value' : 'type'; $key = (is_string($token)) ? 'value' : 'type';
if ( ! ($this->_lexer->lookahead[$key] === $token)) if ( ! ($this->_lexer->lookahead[$key] === $token)) {
$this->syntaxError($this->_lexer->getLiteral($token)); $this->syntaxError($this->_lexer->getLiteral($token));
}
$this->_lexer->moveNext(); $this->_lexer->moveNext();
} }
...@@ -301,7 +302,7 @@ class Parser ...@@ -301,7 +302,7 @@ class Parser
if ($this->_lexer->lookahead === null) { if ($this->_lexer->lookahead === null) {
$message .= 'end of string.'; $message .= 'end of string.';
} else { } else {
$message .= "'{$this->_lexer->lookahead['value']}'"; $message .= "'{$token['value']}'";
} }
throw \Doctrine\ORM\Query\QueryException::syntaxError($message); throw \Doctrine\ORM\Query\QueryException::syntaxError($message);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment