Source of file TokenArrayScanner.php
Size: 19,317 Bytes - Last Modified: 2014-03-12T23:21:18+01:00
/home/theseer/Downloads/ZendFramework-2.3.0/library/Zend/Code/Scanner/TokenArrayScanner.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688 | <?php /** * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace Zend\Code\Scanner; use Zend\Code\Annotation\AnnotationManager; use Zend\Code\Exception; use Zend\Code\NameInformation; class TokenArrayScanner implements ScannerInterface { /** * @var bool */ protected $isScanned = false; /** * @var array */ protected $tokens = array(); /** * @var null */ protected $docComment = null; /** * @var NameInformation */ protected $nameInformation = null; /** * @var array */ protected $infos = array(); /** * @var AnnotationManager */ protected $annotationManager = null; /** * @param null|array $tokens * @param null|AnnotationManager $annotationManager */ public function __construct($tokens, AnnotationManager $annotationManager = null) { $this->tokens = $tokens; $this->annotationManager = $annotationManager; } /** * @return AnnotationManager */ public function getAnnotationManager() { return $this->annotationManager; } /** * Get doc comment * * @todo Assignment of $this->docComment should probably be done in scan() * and then $this->getDocComment() just retrieves it. * * @return string */ public function getDocComment() { foreach ($this->tokens as $token) { $type = $token[0]; $value = $token[1]; if (($type == T_OPEN_TAG) || ($type == T_WHITESPACE)) { continue; } elseif ($type == T_DOC_COMMENT) { $this->docComment = $value; return $this->docComment; } else { // Only whitespace is allowed before file docblocks return; } } } /** * @return array */ public function getNamespaces() { $this->scan(); $namespaces = array(); foreach ($this->infos as $info) { if ($info['type'] == 'namespace') { $namespaces[] = $info['namespace']; } } return $namespaces; } /** * @param null|string $namespace * @return array|null */ public function getUses($namespace = null) { $this->scan(); return $this->getUsesNoScan($namespace); } /** * @return array */ public function getIncludes() { $this->scan(); // @todo Implement getIncludes() in TokenArrayScanner } /** * @return array */ public function getClassNames() { $this->scan(); $return = array(); foreach ($this->infos as $info) { if ($info['type'] != 'class') { continue; } $return[] = $info['name']; } return $return; } /** * @return ClassScanner[] */ public function getClasses() { $this->scan(); $return = array(); foreach ($this->infos as $info) { if ($info['type'] != 'class') { continue; } $return[] = $this->getClass($info['name']); } return $return; } /** * Return the class object from this scanner * * @param string|int $name * @throws Exception\InvalidArgumentException * @return ClassScanner */ public function getClass($name) { $this->scan(); if (is_int($name)) { $info = $this->infos[$name]; if ($info['type'] != 'class') { throw new Exception\InvalidArgumentException('Index of info offset is not about a class'); } } elseif (is_string($name)) { $classFound = false; foreach ($this->infos as $info) { if ($info['type'] === 'class' && $info['name'] === $name) { $classFound = true; break; } } if (!$classFound) { return false; } } return new ClassScanner( array_slice( $this->tokens, $info['tokenStart'], ($info['tokenEnd'] - $info['tokenStart'] + 1) ), // zero indexed array new NameInformation($info['namespace'], $info['uses']) ); } /** * @param string $className * @return bool|null|NameInformation */ public function getClassNameInformation($className) { $this->scan(); $classFound = false; foreach ($this->infos as $info) { if ($info['type'] === 'class' && $info['name'] === $className) { $classFound = true; break; } } if (!$classFound) { return false; } if (!isset($info)) { return null; } return new NameInformation($info['namespace'], $info['uses']); } /** * @return array */ public function getFunctionNames() { $this->scan(); $functionNames = array(); foreach ($this->infos as $info) { if ($info['type'] == 'function') { $functionNames[] = $info['name']; } } return $functionNames; } /** * @return array */ public function getFunctions() { $this->scan(); $functions = array(); foreach ($this->infos as $info) { if ($info['type'] == 'function') { // @todo $functions[] = new FunctionScanner($info['name']); } } return $functions; } /** * Export * * @param $tokens */ public static function export($tokens) { // @todo } public function __toString() { // @todo } /** * Scan * * @todo: $this->docComment should be assigned for valid docblock during * the scan instead of $this->getDocComment() (starting with * T_DOC_COMMENT case) * * @throws Exception\RuntimeException */ protected function scan() { if ($this->isScanned) { return; } if (!$this->tokens) { throw new Exception\RuntimeException('No tokens were provided'); } /** * Define PHP 5.4 'trait' token constant. */ if (!defined('T_TRAIT')) { define('T_TRAIT', 42001); } /** * Variables & Setup */ $tokens = &$this->tokens; // localize $infos = &$this->infos; // localize $tokenIndex = null; $token = null; $tokenType = null; $tokenContent = null; $tokenLine = null; $namespace = null; $docCommentIndex = false; $infoIndex = 0; /* * MACRO creation */ $MACRO_TOKEN_ADVANCE = function () use (&$tokens, &$tokenIndex, &$token, &$tokenType, &$tokenContent, &$tokenLine) { $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1; if (!isset($tokens[$tokenIndex])) { $token = false; $tokenContent = false; $tokenType = false; $tokenLine = false; return false; } if (is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"') { do { $tokenIndex++; } while (!(is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"')); } $token = $tokens[$tokenIndex]; if (is_array($token)) { list($tokenType, $tokenContent, $tokenLine) = $token; } else { $tokenType = null; $tokenContent = $token; } return $tokenIndex; }; $MACRO_TOKEN_LOGICAL_START_INDEX = function () use (&$tokenIndex, &$docCommentIndex) { return ($docCommentIndex === false) ? $tokenIndex : $docCommentIndex; }; $MACRO_DOC_COMMENT_START = function () use (&$tokenIndex, &$docCommentIndex) { $docCommentIndex = $tokenIndex; return $docCommentIndex; }; $MACRO_DOC_COMMENT_VALIDATE = function () use (&$tokenType, &$docCommentIndex) { static $validTrailingTokens = null; if ($validTrailingTokens === null) { $validTrailingTokens = array(T_WHITESPACE, T_FINAL, T_ABSTRACT, T_INTERFACE, T_CLASS, T_FUNCTION); } if ($docCommentIndex !== false && !in_array($tokenType, $validTrailingTokens)) { $docCommentIndex = false; } return $docCommentIndex; }; $MACRO_INFO_ADVANCE = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) { $infos[$infoIndex]['tokenEnd'] = $tokenIndex; $infos[$infoIndex]['lineEnd'] = $tokenLine; $infoIndex++; return $infoIndex; }; /** * START FINITE STATE MACHINE FOR SCANNING TOKENS */ // Initialize token $MACRO_TOKEN_ADVANCE(); SCANNER_TOP: if ($token === false) { goto SCANNER_END; } // Validate current doc comment index $MACRO_DOC_COMMENT_VALIDATE(); switch ($tokenType) { case T_DOC_COMMENT: $MACRO_DOC_COMMENT_START(); goto SCANNER_CONTINUE; case T_NAMESPACE: $infos[$infoIndex] = array( 'type' => 'namespace', 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), 'tokenEnd' => null, 'lineStart' => $token[2], 'lineEnd' => null, 'namespace' => null, ); // start processing with next token if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } SCANNER_NAMESPACE_TOP: if ($tokenType === null && $tokenContent === ';' || $tokenContent === '{') { goto SCANNER_NAMESPACE_END; } if ($tokenType === T_WHITESPACE) { goto SCANNER_NAMESPACE_CONTINUE; } if ($tokenType === T_NS_SEPARATOR || $tokenType === T_STRING) { $infos[$infoIndex]['namespace'] .= $tokenContent; } SCANNER_NAMESPACE_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_NAMESPACE_TOP; SCANNER_NAMESPACE_END: $namespace = $infos[$infoIndex]['namespace']; $MACRO_INFO_ADVANCE(); goto SCANNER_CONTINUE; case T_USE: $infos[$infoIndex] = array( 'type' => 'use', 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), 'tokenEnd' => null, 'lineStart' => $tokens[$tokenIndex][2], 'lineEnd' => null, 'namespace' => $namespace, 'statements' => array(0 => array('use' => null, 'as' => null)), ); $useStatementIndex = 0; $useAsContext = false; // start processing with next token if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } SCANNER_USE_TOP: if ($tokenType === null) { if ($tokenContent === ';') { goto SCANNER_USE_END; } elseif ($tokenContent === ',') { $useAsContext = false; $useStatementIndex++; $infos[$infoIndex]['statements'][$useStatementIndex] = array('use' => null, 'as' => null); } } // ANALYZE if ($tokenType !== null) { if ($tokenType == T_AS) { $useAsContext = true; goto SCANNER_USE_CONTINUE; } if ($tokenType == T_NS_SEPARATOR || $tokenType == T_STRING) { if ($useAsContext == false) { $infos[$infoIndex]['statements'][$useStatementIndex]['use'] .= $tokenContent; } else { $infos[$infoIndex]['statements'][$useStatementIndex]['as'] = $tokenContent; } } } SCANNER_USE_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_USE_TOP; SCANNER_USE_END: $MACRO_INFO_ADVANCE(); goto SCANNER_CONTINUE; case T_INCLUDE: case T_INCLUDE_ONCE: case T_REQUIRE: case T_REQUIRE_ONCE: // Static for performance static $includeTypes = array( T_INCLUDE => 'include', T_INCLUDE_ONCE => 'include_once', T_REQUIRE => 'require', T_REQUIRE_ONCE => 'require_once' ); $infos[$infoIndex] = array( 'type' => 'include', 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), 'tokenEnd' => null, 'lineStart' => $tokens[$tokenIndex][2], 'lineEnd' => null, 'includeType' => $includeTypes[$tokens[$tokenIndex][0]], 'path' => '', ); // start processing with next token if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } SCANNER_INCLUDE_TOP: if ($tokenType === null && $tokenContent === ';') { goto SCANNER_INCLUDE_END; } $infos[$infoIndex]['path'] .= $tokenContent; SCANNER_INCLUDE_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_INCLUDE_TOP; SCANNER_INCLUDE_END: $MACRO_INFO_ADVANCE(); goto SCANNER_CONTINUE; case T_FUNCTION: case T_FINAL: case T_ABSTRACT: case T_CLASS: case T_INTERFACE: case T_TRAIT: $infos[$infoIndex] = array( 'type' => ($tokenType === T_FUNCTION) ? 'function' : 'class', 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), 'tokenEnd' => null, 'lineStart' => $tokens[$tokenIndex][2], 'lineEnd' => null, 'namespace' => $namespace, 'uses' => $this->getUsesNoScan($namespace), 'name' => null, 'shortName' => null, ); $classBraceCount = 0; // start processing with current token SCANNER_CLASS_TOP: // process the name if ($infos[$infoIndex]['shortName'] == '' && (($tokenType === T_CLASS || $tokenType === T_INTERFACE || $tokenType === T_TRAIT) && $infos[$infoIndex]['type'] === 'class' || ($tokenType === T_FUNCTION && $infos[$infoIndex]['type'] === 'function')) ) { $infos[$infoIndex]['shortName'] = $tokens[$tokenIndex + 2][1]; $infos[$infoIndex]['name'] = (($namespace != null) ? $namespace . '\\' : '') . $infos[$infoIndex]['shortName']; } if ($tokenType === null) { if ($tokenContent == '{') { $classBraceCount++; } if ($tokenContent == '}') { $classBraceCount--; if ($classBraceCount === 0) { goto SCANNER_CLASS_END; } } } SCANNER_CLASS_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_CLASS_TOP; SCANNER_CLASS_END: $MACRO_INFO_ADVANCE(); goto SCANNER_CONTINUE; } SCANNER_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_TOP; SCANNER_END: /** * END FINITE STATE MACHINE FOR SCANNING TOKENS */ $this->isScanned = true; } /** * Check for namespace * * @param string $namespace * @return bool */ public function hasNamespace($namespace) { $this->scan(); foreach ($this->infos as $info) { if ($info['type'] == 'namespace' && $info['namespace'] == $namespace) { return true; } } return false; } /** * @param string $namespace * @return null|array * @throws Exception\InvalidArgumentException */ protected function getUsesNoScan($namespace) { $namespaces = array(); foreach ($this->infos as $info) { if ($info['type'] == 'namespace') { $namespaces[] = $info['namespace']; } } if ($namespace === null) { $namespace = array_shift($namespaces); } elseif (!is_string($namespace)) { throw new Exception\InvalidArgumentException('Invalid namespace provided'); } elseif (!in_array($namespace, $namespaces)) { return null; } $uses = array(); foreach ($this->infos as $info) { if ($info['type'] !== 'use') { continue; } foreach ($info['statements'] as $statement) { if ($info['namespace'] == $namespace) { $uses[] = $statement; } } } return $uses; } } |