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;
// 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:
if ($this->isInterface) {
$this->shortInterfaces[$classInterfaceIndex] .= $tokenContent;
} else {
$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 == ',' || $classContext == T_EXTENDS && $tokenContent == ',' && $this->isInterface) {
$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;
// 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] = ['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;
// goto no break needed
// goto no break needed
case ';':
$infos[$infoIndex]['tokenEnd'] = $tokenIndex;
goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
// goto no break needed
}
}
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;
// goto no break needed
}
}
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;
}