Skip to content

Commit

Permalink
[DocParser] added support for constants
Browse files Browse the repository at this point in the history
  • Loading branch information
FabioBatSilva committed Feb 5, 2012
1 parent fc26d10 commit 03949c3
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 26 deletions.
16 changes: 16 additions & 0 deletions lib/Doctrine/Common/Annotations/AnnotationException.php
Expand Up @@ -52,6 +52,22 @@ public static function semanticalError($message)
return new self('[Semantical Error] ' . $message);
}

/**
* Creates a new AnnotationException describing a constant semantical error.
*
* @since 2.3
* @param string $identifier
* @param string $context
* @return AnnotationException
*/
public static function semanticalErrorConstants($identifier, $context = null)
{
return self::semanticalError(sprintf(
"Couldn't find constant %s%s", $identifier,
$context ? ", $context." : "."
));
}

/**
* Creates a new AnnotationException describing an error which occurred during
* the creation of the annotation.
Expand Down
13 changes: 7 additions & 6 deletions lib/Doctrine/Common/Annotations/DocLexer.php
Expand Up @@ -33,11 +33,12 @@
final class DocLexer extends Lexer
{
const T_NONE = 1;
const T_IDENTIFIER = 2;
const T_INTEGER = 3;
const T_STRING = 4;
const T_FLOAT = 5;
const T_INTEGER = 2;
const T_STRING = 3;
const T_FLOAT = 4;

// All tokens that are also identifiers should be >= 100
const T_IDENTIFIER = 100;
const T_AT = 101;
const T_CLOSE_CURLY_BRACES = 102;
const T_CLOSE_PARENTHESIS = 103;
Expand All @@ -57,7 +58,7 @@ final class DocLexer extends Lexer
protected function getCatchablePatterns()
{
return array(
'[a-z_][a-z0-9_:]*',
'[a-z_\\\][a-z0-9_\:\\\]*[a-z0-9_]{1}',
'(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?',
'"(?:[^"]|"")*"',
);
Expand Down Expand Up @@ -127,7 +128,7 @@ protected function getType(&$value)
return self::T_COLON;

default:
if (ctype_alpha($value[0]) || $value[0] === '_') {
if (ctype_alpha($value[0]) || $value[0] === '_' || $value[0] === '\\') {
return self::T_IDENTIFIER;
}

Expand Down
110 changes: 91 additions & 19 deletions lib/Doctrine/Common/Annotations/DocParser.php
Expand Up @@ -537,20 +537,7 @@ private function Annotation()
$this->match(DocLexer::T_AT);

// check if we have an annotation
if ($this->lexer->isNextTokenAny(self::$classIdentifiers)) {
$this->lexer->moveNext();
$name = $this->lexer->token['value'];
} else if ($this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) {
$name = '';
} else {
$this->syntaxError('namespace separator or identifier');
}

while ($this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value']) && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) {
$this->match(DocLexer::T_NAMESPACE_SEPARATOR);
$this->matchAny(self::$classIdentifiers);
$name .= '\\'.$this->lexer->token['value'];
}
$name = $this->Identifier();

// only process names which are not fully qualified, yet
// fully qualified names must start with a \
Expand Down Expand Up @@ -746,6 +733,82 @@ private function Values()
return $values;
}

/**
* Constant ::= integer | string | float | boolean
*
* @return mixed
*/
private function Constant()
{
$identifier = $this->Identifier();

if (!defined($identifier) && false !== strpos($identifier, '::') && '\\' !== $identifier[0]) {

list($className, $const) = explode('::', $identifier);
$alias = (false === $pos = strpos($className, '\\'))? $className : substr($className, 0, $pos);

$found = false;
switch (true) {
case !empty ($this->namespace):
foreach ($this->namespaces as $ns) {
if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) {
$className = $ns.'\\'.$className;
$found = true;
break;
}
}
break;

case isset($this->imports[$loweredAlias = strtolower($alias)]):
$found = true;
if (false !== $pos) {
$className = $this->imports[$loweredAlias].substr($className, $pos);
} else {
$className = $this->imports[$loweredAlias];
}
break;

default:
if(isset($this->imports['__NAMESPACE__'])) {
$ns = $this->imports['__NAMESPACE__'];
if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) {
$className = $ns.'\\'.$className;
$found = true;
}
}
break;
}

if ($found) {
$identifier = $className . '::' . $const;
}
}

if (!defined($identifier)) {
throw AnnotationException::semanticalErrorConstants($identifier, $this->context);
}

return constant($identifier);
}

/**
* Identifier ::= string
*
* @return string
*/
private function Identifier()
{
// check if we have an annotation
if ($this->lexer->isNextTokenAny(self::$classIdentifiers)) {
$this->lexer->moveNext();
$className = $this->lexer->token['value'];
} else {
$this->syntaxError('namespace separator or identifier');
}

return $className;
}

/**
* Value ::= PlainValue | FieldAssignment
*
Expand Down Expand Up @@ -777,6 +840,10 @@ private function PlainValue()
return $this->Annotation();
}

if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
return $this->Constant();
}

switch ($this->lexer->lookahead['type']) {
case DocLexer::T_STRING:
$this->match(DocLexer::T_STRING);
Expand Down Expand Up @@ -838,7 +905,7 @@ private function Arrayx()

$this->match(DocLexer::T_OPEN_CURLY_BRACES);
$values[] = $this->ArrayEntry();

while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
$this->match(DocLexer::T_COMMA);

Expand Down Expand Up @@ -867,8 +934,8 @@ private function Arrayx()

/**
* ArrayEntry ::= Value | KeyValuePair
* KeyValuePair ::= Key ("=" | ":") PlainValue
* Key ::= string | integer
* KeyValuePair ::= Key ("=" | ":") PlainValue | Constant
* Key ::= string | integer | Constant
*
* @return array
*/
Expand All @@ -878,9 +945,14 @@ private function ArrayEntry()

if (DocLexer::T_EQUALS === $peek['type']
|| DocLexer::T_COLON === $peek['type']) {
$this->matchAny(array(DocLexer::T_INTEGER, DocLexer::T_STRING));

$key = $this->lexer->token['value'];
if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
$key = $this->Constant();
} else {
$this->matchAny(array(DocLexer::T_INTEGER, DocLexer::T_STRING));
$key = $this->lexer->token['value'];
}

$this->matchAny(array(DocLexer::T_EQUALS, DocLexer::T_COLON));

return array($key, $this->PlainValue());
Expand Down
68 changes: 68 additions & 0 deletions tests/Doctrine/Tests/Common/Annotations/DocLexerTest.php
Expand Up @@ -24,4 +24,72 @@ public function testMarkerAnnotation()

$this->assertFalse($lexer->moveNext());
}

public function testScannerTokenizesDocBlockWhitConstants()
{
$lexer = new DocLexer();
$docblock = '@AnnotationWithConstants(PHP_EOL, ClassWithConstants::SOME_VALUE, \Doctrine\Tests\Common\Annotations\Fixtures\IntefaceWithConstants::SOME_VALUE)';

$tokens = array (
array(
'value' => '@',
'position' => 0,
'type' => DocLexer::T_AT,
),
array(
'value' => 'AnnotationWithConstants',
'position' => 1,
'type' => DocLexer::T_IDENTIFIER,
),
array(
'value' => '(',
'position' => 24,
'type' => DocLexer::T_OPEN_PARENTHESIS,
),
array(
'value' => 'PHP_EOL',
'position' => 25,
'type' => DocLexer::T_IDENTIFIER,
),
array(
'value' => ',',
'position' => 32,
'type' => DocLexer::T_COMMA,
),
array(
'value' => 'ClassWithConstants::SOME_VALUE',
'position' => 34,
'type' => DocLexer::T_IDENTIFIER,
),
array(
'value' => ',',
'position' => 64,
'type' => DocLexer::T_COMMA,
),
array(
'value' => '\\Doctrine\\Tests\\Common\\Annotations\\Fixtures\\IntefaceWithConstants::SOME_VALUE',
'position' => 66,
'type' => DocLexer::T_IDENTIFIER,
),
array(
'value' => ')',
'position' => 143,
'type' => DocLexer::T_CLOSE_PARENTHESIS,
)

);

$lexer->setInput($docblock);

foreach ($tokens as $expected) {
$lexer->moveNext();
$lookahead = $lexer->lookahead;
$this->assertEquals($expected['value'], $lookahead['value']);
$this->assertEquals($expected['type'], $lookahead['type']);
$this->assertEquals($expected['position'], $lookahead['position']);
}

$this->assertFalse($lexer->moveNext());
}

}

0 comments on commit 03949c3

Please sign in to comment.