annotationManager = $annotationManager; $this->docComment = $docComment; $this->nameInformation = $nameInformation; $this->scan($this->tokenize()); } /** * @param NameInformation $nameInformation */ public function setNameInformation(NameInformation $nameInformation) { $this->nameInformation = $nameInformation; } /** * @param array $tokens */ protected function scan(array $tokens) { $annotations = []; $annotationIndex = -1; $contentEnd = false; reset($tokens); SCANNER_TOP: $token = current($tokens); switch ($token[0]) { case 'ANNOTATION_CLASS': $contentEnd = false; $annotationIndex++; $class = substr($token[1], 1); $class = $this->nameInformation->resolveName($class); $annotations[$annotationIndex] = [$class, null]; goto SCANNER_CONTINUE; // goto no break needed case 'ANNOTATION_CONTENT_START': $annotations[$annotationIndex][1] = ''; // fall-through case 'ANNOTATION_CONTENT_END': case 'ANNOTATION_CONTENT': case 'ANNOTATION_WHITESPACE': case 'ANNOTATION_NEWLINE': if (! $contentEnd && isset($annotations[$annotationIndex]) && is_string($annotations[$annotationIndex][1]) ) { $annotations[$annotationIndex][1] .= $token[1]; } if ($token[0] === 'ANNOTATION_CONTENT_END') { $contentEnd = true; } goto SCANNER_CONTINUE; // goto no break needed } SCANNER_CONTINUE: if (next($tokens) === false) { goto SCANNER_END; } goto SCANNER_TOP; SCANNER_END: foreach ($annotations as $annotation) { $annotation[] = '@' . $annotation[0] . $annotation[1]; $annotationObject = $this->annotationManager->createAnnotation($annotation); if ($annotationObject) { $this->append($annotationObject); } } } /** * @return array */ protected function tokenize() { static $CONTEXT_DOCBLOCK = 0x01; static $CONTEXT_ASTERISK = 0x02; static $CONTEXT_CLASS = 0x04; static $CONTEXT_CONTENT = 0x08; $context = 0x00; $stream = $this->docComment; $streamIndex = null; $tokens = []; $tokenIndex = null; $currentChar = null; $currentWord = null; $currentLine = null; $annotationParentCount = 0; $MACRO_STREAM_ADVANCE_CHAR = function ($positionsForward = 1) use ( &$stream, &$streamIndex, &$currentChar, &$currentWord, &$currentLine ) { $positionsForward = $positionsForward > 0 ? $positionsForward : 1; $streamIndex = $streamIndex === null ? 0 : $streamIndex + $positionsForward; if (! isset($stream[$streamIndex])) { $currentChar = false; return false; } $currentChar = $stream[$streamIndex]; $matches = []; $currentLine = preg_match('#(.*?)(?:\n|\r\n?)#', $stream, $matches, null, $streamIndex) === 1 ? $matches[1] : substr($stream, $streamIndex); if ($currentChar === ' ') { $currentWord = preg_match('#( +)#', $currentLine, $matches) === 1 ? $matches[1] : $currentLine; } else { $currentWord = ($matches = strpos($currentLine, ' ')) !== false ? substr($currentLine, 0, $matches) : $currentLine; } return $currentChar; }; $MACRO_STREAM_ADVANCE_WORD = function () use (&$currentWord, &$MACRO_STREAM_ADVANCE_CHAR) { return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentWord)); }; $MACRO_STREAM_ADVANCE_LINE = function () use (&$currentLine, &$MACRO_STREAM_ADVANCE_CHAR) { return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentLine)); }; $MACRO_TOKEN_ADVANCE = function () use (&$tokenIndex, &$tokens) { $tokenIndex = $tokenIndex === null ? 0 : $tokenIndex + 1; $tokens[$tokenIndex] = ['ANNOTATION_UNKNOWN', '']; }; $MACRO_TOKEN_SET_TYPE = function ($type) use (&$tokenIndex, &$tokens) { $tokens[$tokenIndex][0] = $type; }; $MACRO_TOKEN_APPEND_CHAR = function () use (&$currentChar, &$tokens, &$tokenIndex) { $tokens[$tokenIndex][1] .= $currentChar; }; $MACRO_TOKEN_APPEND_WORD = function () use (&$currentWord, &$tokens, &$tokenIndex) { $tokens[$tokenIndex][1] .= $currentWord; }; $MACRO_TOKEN_APPEND_LINE = function () use (&$currentLine, &$tokens, &$tokenIndex) { $tokens[$tokenIndex][1] .= $currentLine; }; $MACRO_HAS_CONTEXT = function ($which) use (&$context) { return ($context & $which) === $which; }; $MACRO_STREAM_ADVANCE_CHAR(); $MACRO_TOKEN_ADVANCE(); TOKENIZER_TOP: if ($context === 0x00 && $currentChar === '/' && $currentWord === '/**') { $MACRO_TOKEN_SET_TYPE('ANNOTATION_COMMENTSTART'); $MACRO_TOKEN_APPEND_WORD(); $MACRO_TOKEN_ADVANCE(); $context |= $CONTEXT_DOCBLOCK; $context |= $CONTEXT_ASTERISK; if ($MACRO_STREAM_ADVANCE_WORD() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($MACRO_HAS_CONTEXT($CONTEXT_CLASS)) { if (in_array($currentChar, [' ', '(', "\n", "\r"])) { $context &= ~$CONTEXT_CLASS; $MACRO_TOKEN_ADVANCE(); } else { $MACRO_TOKEN_APPEND_CHAR(); if ($MACRO_STREAM_ADVANCE_CHAR() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } } // Since we don't know what line endings are used in the file, we check for all scenarios. If we find a // carriage return (\r), we check the next character for a line feed (\n). If so we consume it and act as // if the carriage return was a line feed. $lineEnded = $currentChar === "\n"; if ($currentChar === "\r") { $lineEnded = true; $nextChar = $MACRO_STREAM_ADVANCE_CHAR(); if ($nextChar !== "\n") { $streamIndex--; } $currentChar = "\n"; } if ($lineEnded) { $MACRO_TOKEN_SET_TYPE('ANNOTATION_NEWLINE'); $MACRO_TOKEN_APPEND_CHAR(); $MACRO_TOKEN_ADVANCE(); $context &= ~$CONTEXT_ASTERISK; $context &= ~$CONTEXT_CLASS; if ($MACRO_STREAM_ADVANCE_CHAR() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($currentChar === ' ') { $MACRO_TOKEN_SET_TYPE( $MACRO_HAS_CONTEXT($CONTEXT_ASTERISK) ? 'ANNOTATION_WHITESPACE' : 'ANNOTATION_WHITESPACE_INDENT' ); $MACRO_TOKEN_APPEND_WORD(); $MACRO_TOKEN_ADVANCE(); if ($MACRO_STREAM_ADVANCE_WORD() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($MACRO_HAS_CONTEXT($CONTEXT_CONTENT) && $MACRO_HAS_CONTEXT($CONTEXT_ASTERISK)) { $MACRO_TOKEN_SET_TYPE('ANNOTATION_CONTENT'); $annotationParentCount += substr_count($currentWord, '('); $annotationParentCount -= substr_count($currentWord, ')'); if ($annotationParentCount === 0) { $context &= ~$CONTEXT_CONTENT; $MACRO_TOKEN_SET_TYPE('ANNOTATION_CONTENT_END'); } $MACRO_TOKEN_APPEND_WORD(); $MACRO_TOKEN_ADVANCE(); if ($MACRO_STREAM_ADVANCE_WORD() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($currentChar === '(' && $tokens[$tokenIndex - 1][0] === 'ANNOTATION_CLASS') { $context |= $CONTEXT_CONTENT; $annotationParentCount = 1; $MACRO_TOKEN_SET_TYPE('ANNOTATION_CONTENT_START'); $MACRO_TOKEN_APPEND_CHAR(); $MACRO_TOKEN_ADVANCE(); if ($MACRO_STREAM_ADVANCE_CHAR() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($MACRO_HAS_CONTEXT($CONTEXT_DOCBLOCK) && $currentWord === '*/') { $MACRO_TOKEN_SET_TYPE('ANNOTATION_COMMENTEND'); $MACRO_TOKEN_APPEND_WORD(); $MACRO_TOKEN_ADVANCE(); $context &= ~$CONTEXT_DOCBLOCK; if ($MACRO_STREAM_ADVANCE_WORD() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($currentChar === '*') { if ($MACRO_HAS_CONTEXT($CONTEXT_DOCBLOCK) && ($MACRO_HAS_CONTEXT($CONTEXT_ASTERISK))) { $MACRO_TOKEN_SET_TYPE('ANNOTATION_IGNORE'); } else { $MACRO_TOKEN_SET_TYPE('ANNOTATION_ASTERISK'); $context |= $CONTEXT_ASTERISK; } $MACRO_TOKEN_APPEND_CHAR(); $MACRO_TOKEN_ADVANCE(); if ($MACRO_STREAM_ADVANCE_CHAR() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($currentChar === '@') { $MACRO_TOKEN_SET_TYPE('ANNOTATION_CLASS'); $context |= $CONTEXT_CLASS; $MACRO_TOKEN_APPEND_CHAR(); if ($MACRO_STREAM_ADVANCE_CHAR() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } TOKENIZER_CONTINUE: if ($context && $CONTEXT_CONTENT) { $MACRO_TOKEN_APPEND_CHAR(); if ($MACRO_STREAM_ADVANCE_CHAR() === false) { goto TOKENIZER_END; } } else { $MACRO_TOKEN_SET_TYPE('ANNOTATION_IGNORE'); $MACRO_TOKEN_APPEND_LINE(); $MACRO_TOKEN_ADVANCE(); if ($MACRO_STREAM_ADVANCE_LINE() === false) { goto TOKENIZER_END; } } goto TOKENIZER_TOP; TOKENIZER_END: array_pop($tokens); return $tokens; } }