public function testNamespaceResolverPersistsNamespace() { $nr = new NameInformation('Foo\\Bar'); $this->assertEquals('Foo\\Bar', $nr->getNamespace()); $nr = new NameInformation(); $nr->setNamespace('Bar\\Baz'); $this->assertEquals('Bar\\Baz', $nr->getNamespace()); }
/** * Scan tokens * * @return void * @throws Exception\RuntimeException */ protected function scan() { if ($this->isScanned) { return; } if (!$this->tokens) { throw new Exception\RuntimeException('No tokens were provided'); } /** * Variables & Setup */ $tokens =& $this->tokens; // localize $infos =& $this->infos; // localize $tokenIndex = null; $token = null; $tokenType = null; $tokenContent = null; $tokenLine = null; $namespace = null; $infoIndex = 0; $braceCount = 0; /* * MACRO creation */ $MACRO_TOKEN_ADVANCE = function () use(&$tokens, &$tokenIndex, &$token, &$tokenType, &$tokenContent, &$tokenLine) { static $lastTokenArray = null; $tokenIndex = $tokenIndex === null ? 0 : $tokenIndex + 1; if (!isset($tokens[$tokenIndex])) { $token = false; $tokenContent = false; $tokenType = false; $tokenLine = false; return false; } $token = $tokens[$tokenIndex]; if (is_string($token)) { $tokenType = null; $tokenContent = $token; $tokenLine = $tokenLine + substr_count($lastTokenArray[1], "\n"); // adjust token line by last known newline count } else { $lastTokenArray = $token; list($tokenType, $tokenContent, $tokenLine) = $token; } return $tokenIndex; }; $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: switch ($tokenType) { case T_DOC_COMMENT: $this->docComment = $tokenContent; goto SCANNER_CONTINUE; //goto no break needed //goto no break needed case T_FINAL: case T_ABSTRACT: case T_CLASS: case T_INTERFACE: // CLASS INFORMATION $classContext = null; $classInterfaceIndex = 0; SCANNER_CLASS_INFO_TOP: if (is_string($tokens[$tokenIndex + 1]) && $tokens[$tokenIndex + 1] === '{') { goto SCANNER_CLASS_INFO_END; } $this->lineStart = $tokenLine; switch ($tokenType) { case T_FINAL: $this->isFinal = true; goto SCANNER_CLASS_INFO_CONTINUE; //goto no break needed //goto no break needed case T_ABSTRACT: $this->isAbstract = true; goto SCANNER_CLASS_INFO_CONTINUE; //goto no break needed //goto no break needed case T_INTERFACE: $this->isInterface = true; //fall-through //fall-through case T_CLASS: $this->shortName = $tokens[$tokenIndex + 2][1]; if ($this->nameInformation && $this->nameInformation->hasNamespace()) { $this->name = $this->nameInformation->getNamespace() . '\\' . $this->shortName; } else { $this->name = $this->shortName; } goto SCANNER_CLASS_INFO_CONTINUE; //goto no break needed //goto no break needed case T_NS_SEPARATOR: case T_STRING: switch ($classContext) { case T_EXTENDS: $this->shortParentClass .= $tokenContent; break; case T_IMPLEMENTS: $this->shortInterfaces[$classInterfaceIndex] .= $tokenContent; break; } goto SCANNER_CLASS_INFO_CONTINUE; //goto no break needed //goto no break needed case T_EXTENDS: case T_IMPLEMENTS: $classContext = $tokenType; if ($this->isInterface && $classContext === T_EXTENDS || $classContext === T_IMPLEMENTS) { $this->shortInterfaces[$classInterfaceIndex] = ''; } elseif (!$this->isInterface && $classContext === T_EXTENDS) { $this->shortParentClass = ''; } goto SCANNER_CLASS_INFO_CONTINUE; //goto no break needed //goto no break needed case null: if ($classContext == T_IMPLEMENTS && $tokenContent == ',') { $classInterfaceIndex++; $this->shortInterfaces[$classInterfaceIndex] = ''; } } SCANNER_CLASS_INFO_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_CLASS_INFO_TOP; SCANNER_CLASS_INFO_END: goto SCANNER_CONTINUE; } if ($tokenType === null && $tokenContent === '{' && $braceCount === 0) { $braceCount++; if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } SCANNER_CLASS_BODY_TOP: if ($braceCount === 0) { goto SCANNER_CLASS_BODY_END; } switch ($tokenType) { case T_CONST: $infos[$infoIndex] = array('type' => 'constant', 'tokenStart' => $tokenIndex, 'tokenEnd' => null, 'lineStart' => $tokenLine, 'lineEnd' => null, 'name' => null, 'value' => null); SCANNER_CLASS_BODY_CONST_TOP: if ($tokenContent === ';') { goto SCANNER_CLASS_BODY_CONST_END; } if ($tokenType === T_STRING && null === $infos[$infoIndex]['name']) { $infos[$infoIndex]['name'] = $tokenContent; } SCANNER_CLASS_BODY_CONST_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_CLASS_BODY_CONST_TOP; SCANNER_CLASS_BODY_CONST_END: $MACRO_INFO_ADVANCE(); goto SCANNER_CLASS_BODY_CONTINUE; //goto no break needed //goto no break needed case T_DOC_COMMENT: case T_PUBLIC: case T_PROTECTED: case T_PRIVATE: case T_ABSTRACT: case T_FINAL: case T_VAR: case T_FUNCTION: $infos[$infoIndex] = array('type' => null, 'tokenStart' => $tokenIndex, 'tokenEnd' => null, 'lineStart' => $tokenLine, 'lineEnd' => null, 'name' => null); $memberContext = null; $methodBodyStarted = false; SCANNER_CLASS_BODY_MEMBER_TOP: if ($memberContext === 'method') { switch ($tokenContent) { case '{': $methodBodyStarted = true; $braceCount++; goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; //goto no break needed //goto no break needed case '}': $braceCount--; goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; } } if ($memberContext !== null) { if ($memberContext === 'property' && $tokenContent === ';' || $memberContext === 'method' && $methodBodyStarted && $braceCount === 1 || $memberContext === 'method' && $this->isInterface && $tokenContent === ';') { goto SCANNER_CLASS_BODY_MEMBER_END; } } switch ($tokenType) { case T_CONST: $memberContext = 'constant'; $infos[$infoIndex]['type'] = 'constant'; goto SCANNER_CLASS_BODY_CONST_CONTINUE; //goto no break needed //goto no break needed case T_VARIABLE: if ($memberContext === null) { $memberContext = 'property'; $infos[$infoIndex]['type'] = 'property'; $infos[$infoIndex]['name'] = ltrim($tokenContent, '$'); } goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; //goto no break needed //goto no break needed case T_FUNCTION: $memberContext = 'method'; $infos[$infoIndex]['type'] = 'method'; goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; //goto no break needed //goto no break needed case T_STRING: if ($memberContext === 'method' && null === $infos[$infoIndex]['name']) { $infos[$infoIndex]['name'] = $tokenContent; } goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; //goto no break needed } SCANNER_CLASS_BODY_MEMBER_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_CLASS_BODY_MEMBER_TOP; SCANNER_CLASS_BODY_MEMBER_END: $memberContext = null; $MACRO_INFO_ADVANCE(); goto SCANNER_CLASS_BODY_CONTINUE; //goto no break needed //goto no break needed case null: // no type, is a string switch ($tokenContent) { case '{': $braceCount++; goto SCANNER_CLASS_BODY_CONTINUE; //fall-through //fall-through case '}': $braceCount--; goto SCANNER_CLASS_BODY_CONTINUE; } } SCANNER_CLASS_BODY_CONTINUE: if ($braceCount === 0 || $MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_CONTINUE; } goto SCANNER_CLASS_BODY_TOP; SCANNER_CLASS_BODY_END: goto SCANNER_CONTINUE; } SCANNER_CONTINUE: if ($tokenContent === '}') { $this->lineEnd = $tokenLine; } if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_TOP; SCANNER_END: // process short names if ($this->nameInformation) { if ($this->shortParentClass) { $this->parentClass = $this->nameInformation->resolveName($this->shortParentClass); } if ($this->shortInterfaces) { foreach ($this->shortInterfaces as $siIndex => $si) { $this->interfaces[$siIndex] = $this->nameInformation->resolveName($si); } } } else { $this->parentClass = $this->shortParentClass; $this->interfaces = $this->shortInterfaces; } $this->isScanned = true; return; }
/** * Scan tokens * * @return void * @throws Exception\RuntimeException */ protected function scan() { if ($this->isScanned) { return; } if (!$this->tokens) { throw new Exception\RuntimeException('No tokens were provided'); } /** * Variables & Setup */ $tokens =& $this->tokens; // localize $infos =& $this->infos; // localize $tokenIndex = null; $token = null; $tokenType = null; $tokenContent = null; $tokenLine = null; $namespace = null; $infoIndex = 0; $braceCount = 0; /* * MACRO creation */ $MACRO_TOKEN_ADVANCE = function () use(&$tokens, &$tokenIndex, &$token, &$tokenType, &$tokenContent, &$tokenLine) { static $lastTokenArray = null; $tokenIndex = $tokenIndex === null ? 0 : $tokenIndex + 1; if (!isset($tokens[$tokenIndex])) { $token = false; $tokenContent = false; $tokenType = false; $tokenLine = false; return false; } $token = $tokens[$tokenIndex]; if (is_string($token)) { $tokenType = null; $tokenContent = $token; $tokenLine = $tokenLine + substr_count($lastTokenArray[1], "\n"); // adjust token line by last known newline count } else { $lastTokenArray = $token; list($tokenType, $tokenContent, $tokenLine) = $token; } return $tokenIndex; }; $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: switch ($tokenType) { case T_DOC_COMMENT: $this->docComment = $tokenContent; goto SCANNER_CONTINUE; //goto no break needed //goto no break needed case T_FINAL: case T_ABSTRACT: case T_CLASS: case T_INTERFACE: case T_TRAIT: // CLASS INFORMATION $classContext = null; $classInterfaceIndex = 0; SCANNER_CLASS_INFO_TOP: if (is_string($tokens[$tokenIndex + 1]) && $tokens[$tokenIndex + 1] === '{') { goto SCANNER_CLASS_INFO_END; } $this->lineStart = $tokenLine; switch ($tokenType) { case T_FINAL: $this->isFinal = true; goto SCANNER_CLASS_INFO_CONTINUE; // goto no break needed // goto no break needed case T_ABSTRACT: $this->isAbstract = true; goto SCANNER_CLASS_INFO_CONTINUE; // goto no break needed // goto no break needed case T_TRAIT: $this->isTrait = true; $this->shortName = $tokens[$tokenIndex + 2][1]; if ($this->nameInformation && $this->nameInformation->hasNamespace()) { $this->name = $this->nameInformation->getNamespace() . '\\' . $this->shortName; } else { $this->name = $this->shortName; } goto SCANNER_CLASS_INFO_CONTINUE; case T_INTERFACE: $this->isInterface = true; //fall-through //fall-through case T_CLASS: $this->shortName = $tokens[$tokenIndex + 2][1]; if ($this->nameInformation && $this->nameInformation->hasNamespace()) { $this->name = $this->nameInformation->getNamespace() . '\\' . $this->shortName; } else { $this->name = $this->shortName; } goto SCANNER_CLASS_INFO_CONTINUE; //goto no break needed //goto no break needed case T_NS_SEPARATOR: case T_STRING: switch ($classContext) { case T_EXTENDS: $this->shortParentClass .= $tokenContent; break; case T_IMPLEMENTS: $this->shortInterfaces[$classInterfaceIndex] .= $tokenContent; break; } goto SCANNER_CLASS_INFO_CONTINUE; //goto no break needed //goto no break needed case T_EXTENDS: case T_IMPLEMENTS: $classContext = $tokenType; if ($this->isInterface && $classContext === T_EXTENDS || $classContext === T_IMPLEMENTS) { $this->shortInterfaces[$classInterfaceIndex] = ''; } elseif (!$this->isInterface && $classContext === T_EXTENDS) { $this->shortParentClass = ''; } goto SCANNER_CLASS_INFO_CONTINUE; //goto no break needed //goto no break needed case null: if ($classContext == T_IMPLEMENTS && $tokenContent == ',') { $classInterfaceIndex++; $this->shortInterfaces[$classInterfaceIndex] = ''; } } SCANNER_CLASS_INFO_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_CLASS_INFO_TOP; SCANNER_CLASS_INFO_END: goto SCANNER_CONTINUE; } if ($tokenType === null && $tokenContent === '{' && $braceCount === 0) { $braceCount++; if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } SCANNER_CLASS_BODY_TOP: if ($braceCount === 0) { goto SCANNER_CLASS_BODY_END; } switch ($tokenType) { case T_CONST: $infos[$infoIndex] = ['type' => 'constant', 'tokenStart' => $tokenIndex, 'tokenEnd' => null, 'lineStart' => $tokenLine, 'lineEnd' => null, 'name' => null, 'value' => null]; SCANNER_CLASS_BODY_CONST_TOP: if ($tokenContent === ';') { goto SCANNER_CLASS_BODY_CONST_END; } if ($tokenType === T_STRING && null === $infos[$infoIndex]['name']) { $infos[$infoIndex]['name'] = $tokenContent; } SCANNER_CLASS_BODY_CONST_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_CLASS_BODY_CONST_TOP; SCANNER_CLASS_BODY_CONST_END: $MACRO_INFO_ADVANCE(); goto SCANNER_CLASS_BODY_CONTINUE; // goto no break needed // goto no break needed case T_USE: // ensure php backwards compatibility if (!defined('T_INSTEADOF')) { define('T_INSTEADOF', 24000); } $infos[$infoIndex] = ['type' => 'use', 'tokenStart' => $tokenIndex, 'tokenEnd' => null, 'lineStart' => $tokens[$tokenIndex][2], 'lineEnd' => null, 'name' => $namespace, 'use_statements' => [0 => null], 'aliases' => [0 => null]]; $isOriginalName = [T_STRING, T_DOUBLE_COLON]; $isAlias = [T_STRING]; $isVisibility = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC]; $isAliasType = [T_AS, T_INSTEADOF]; $isValidAlias = array_merge($isOriginalName, $isAlias, $isVisibility, $isAliasType); $useStatementIndex = 0; $aliasStatementIndex = 0; $useAliasContext = false; $useAsContext = false; // start processing with next token if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } SCANNER_USE_TOP: if ($tokenType === null) { if ($tokenContent === "{") { $useStatementIndex = 0; $useAliasContext = true; $infos[$infoIndex]['aliases'][$useStatementIndex] = ['original' => null, 'alias' => null, 'visibility' => null, 'type' => 'as']; } elseif ($tokenContent === "}") { $useAliasContext = false; goto SCANNER_USE_END; } elseif ($tokenContent === ';') { if ($useAliasContext === true) { $useStatementIndex++; $useAsContext = false; } // only end if we aren't inside braces if (false === $useAliasContext) { goto SCANNER_USE_END; } } elseif ($tokenContent === ',') { $useStatementIndex++; $infos[$infoIndex]['use_statements'][$useStatementIndex] = ''; } } // ANALYZE if ($tokenType !== null) { // use context if (false === $useAliasContext) { if ($tokenType == T_NS_SEPARATOR || $tokenType == T_STRING) { $infos[$infoIndex]['use_statements'][$useStatementIndex] .= $tokenContent; } } else { if (in_array($tokenType, $isValidAlias) && empty($infos[$infoIndex]['aliases'][$useStatementIndex])) { $infos[$infoIndex]['aliases'][$useStatementIndex] = ['original' => null, 'visibility' => null, 'alias' => null, 'type' => null]; } if ($tokenType == T_AS || $tokenType == T_INSTEADOF) { $useAsContext = true; $infos[$infoIndex]['aliases'][$useStatementIndex]['type'] = $tokenType == T_INSTEADOF ? 'insteadof' : 'as'; goto SCANNER_USE_CONTINUE; } // in alias context if ($useAsContext === true && in_array($tokenType, $isAlias)) { $infos[$infoIndex]['aliases'][$useStatementIndex]['alias'] = $tokenContent; } elseif (in_array($tokenType, $isOriginalName)) { $infos[$infoIndex]['aliases'][$useStatementIndex]['original'] .= $tokenContent; } elseif (in_array($tokenType, $isVisibility)) { //add whitespace (will trim later) $infos[$infoIndex]['aliases'][$useStatementIndex]['visibility'] = $tokenType; } } } SCANNER_USE_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_USE_TOP; SCANNER_USE_END: $MACRO_INFO_ADVANCE(); goto SCANNER_CLASS_BODY_CONTINUE; case T_DOC_COMMENT: case T_PUBLIC: case T_PROTECTED: case T_PRIVATE: case T_ABSTRACT: case T_FINAL: case T_VAR: case T_FUNCTION: $infos[$infoIndex] = ['type' => null, 'tokenStart' => $tokenIndex, 'tokenEnd' => null, 'lineStart' => $tokenLine, 'lineEnd' => null, 'name' => null]; $memberContext = null; $methodBodyStarted = false; SCANNER_CLASS_BODY_MEMBER_TOP: if ($memberContext === 'method') { switch ($tokenContent) { case '{': $methodBodyStarted = true; $braceCount++; goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; // goto no break needed // goto no break needed case '}': $braceCount--; goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; case ';': $infos[$infoIndex]['tokenEnd'] = $tokenIndex; goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; } } if ($memberContext !== null) { if ($memberContext === 'property' && $tokenContent === ';' || $memberContext === 'method' && $methodBodyStarted && $braceCount === 1 || $memberContext === 'method' && $this->isInterface && $tokenContent === ';') { goto SCANNER_CLASS_BODY_MEMBER_END; } } switch ($tokenType) { case T_CONST: $memberContext = 'constant'; $infos[$infoIndex]['type'] = 'constant'; goto SCANNER_CLASS_BODY_CONST_CONTINUE; //goto no break needed //goto no break needed case T_VARIABLE: if ($memberContext === null) { $memberContext = 'property'; $infos[$infoIndex]['type'] = 'property'; $infos[$infoIndex]['name'] = ltrim($tokenContent, '$'); } goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; // goto no break needed // goto no break needed case T_FUNCTION: $memberContext = 'method'; $infos[$infoIndex]['type'] = 'method'; goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; // goto no break needed // goto no break needed case T_STRING: if ($memberContext === 'method' && null === $infos[$infoIndex]['name']) { $infos[$infoIndex]['name'] = $tokenContent; } goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; // goto no break needed } SCANNER_CLASS_BODY_MEMBER_CONTINUE: if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_CLASS_BODY_MEMBER_TOP; SCANNER_CLASS_BODY_MEMBER_END: $memberContext = null; $MACRO_INFO_ADVANCE(); goto SCANNER_CLASS_BODY_CONTINUE; // goto no break needed // goto no break needed case null: // no type, is a string switch ($tokenContent) { case '{': $braceCount++; goto SCANNER_CLASS_BODY_CONTINUE; // goto no break needed // goto no break needed case '}': $braceCount--; goto SCANNER_CLASS_BODY_CONTINUE; } } SCANNER_CLASS_BODY_CONTINUE: if ($braceCount === 0 || $MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_CONTINUE; } goto SCANNER_CLASS_BODY_TOP; SCANNER_CLASS_BODY_END: goto SCANNER_CONTINUE; } SCANNER_CONTINUE: if ($tokenContent === '}') { $this->lineEnd = $tokenLine; } if ($MACRO_TOKEN_ADVANCE() === false) { goto SCANNER_END; } goto SCANNER_TOP; SCANNER_END: // process short names if ($this->nameInformation) { if ($this->shortParentClass) { $this->parentClass = $this->nameInformation->resolveName($this->shortParentClass); } if ($this->shortInterfaces) { foreach ($this->shortInterfaces as $siIndex => $si) { $this->interfaces[$siIndex] = $this->nameInformation->resolveName($si); } } } else { $this->parentClass = $this->shortParentClass; $this->interfaces = $this->shortInterfaces; } $this->isScanned = true; return; }