Created
May 28, 2012 08:47
-
-
Save ajshort/2818026 to your computer and use it in GitHub Desktop.
Parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* @package framework | |
* @subpackage reflection | |
*/ | |
namespace SilverStripe\Framework\Reflection; | |
/** | |
* Parses a token stream and extracts the classes, interfaces and traits. | |
* | |
* @package framework | |
* @subpackage reflection | |
*/ | |
class PhpParser { | |
protected $stream; | |
protected $namespace; | |
protected $aliases = array(); | |
protected $classes = array(); | |
protected $interfaces = array(); | |
protected $traits = array(); | |
public function __construct(TokenStream $stream) { | |
$this->stream = $stream; | |
} | |
public function getAliases() { | |
return $this->aliases; | |
} | |
public function getClasses() { | |
return $this->classes; | |
} | |
public function getInterfaces() { | |
return $this->interfaces; | |
} | |
public function getTraits() { | |
return $this->traits; | |
} | |
public function parse() { | |
while(!$this->stream->finished()) { | |
switch($this->stream->getToken()) { | |
case T_NAMESPACE: | |
$this->parseNamespace(); | |
break; | |
case T_USE: | |
$this->parseUse(); | |
break; | |
case T_ABSTRACT: | |
case T_FINAL: | |
case T_CLASS: | |
$this->parseClass(); | |
break; | |
case T_INTERFACE: | |
$this->parseInterface(); | |
break; | |
case T_TRAIT: | |
$this->parseTrait(); | |
break; | |
default: | |
$this->stream->next(); | |
break; | |
} | |
} | |
} | |
public function resolve($name) { | |
if($name[0] == '\\') { | |
return substr($name, 1); | |
} | |
if(($pos = strpos($name, '\\')) !== false) { | |
$namespace = substr($name, 0, $pos); | |
if(isset($this->aliases[$alias])) { | |
return $this->aliases[$alias] . '\\' . substr($name, $pos + 1); | |
} | |
} else { | |
if(isset($this->aliases[$name])) { | |
return $this->aliases[$name]; | |
} | |
} | |
if($this->namespace) { | |
return $this->namespace . '\\' . $name; | |
} else { | |
return $name; | |
} | |
} | |
private function parseNamespace() { | |
if(!$this->stream->is(T_NAMESPACE)) { | |
throw new \Exception('Expected a T_NAMESPACE token.'); | |
} | |
$this->stream->next(); | |
$name = $this->parseName(); | |
$name = ltrim($name, '\\'); | |
$this->namespace = $name; | |
} | |
private function parseUse() { | |
if(!$this->stream->is(T_USE)) { | |
throw new \Exception('Expected a T_USE token.'); | |
} | |
while(true) { | |
$this->stream->next(); | |
$name = $this->parseName(); | |
$name = ltrim($name, '\\'); | |
if($this->stream->is(T_AS)) { | |
$this->stream->next(); | |
$as = $this->stream->getValue(); | |
$this->stream->next(); | |
} else { | |
if(($pos = strrpos($name, '\\')) !== false) { | |
$as = substr($name, $pos + 1); | |
} else { | |
$as = $name; | |
} | |
} | |
if(array_key_exists($as, $this->aliases)) { | |
throw new Exception("The namespace alias '$as' already exists."); | |
} | |
$this->aliases[$as] = $name; | |
if(!$this->stream->is(',')) { | |
break; | |
} | |
} | |
} | |
private function parseClass() { | |
$extends = ''; | |
$implements = array(); | |
// Modifiers | |
$modifiers = $this->parseModifiers(); | |
// Class name | |
if(!$this->stream->is(T_STRING)) { | |
throw new \Exception('An unexpected token was encountered (expected T_STRING).'); | |
} | |
if($this->namespace) { | |
$name = $this->namespace . '\\' . $this->stream->getValue(); | |
} else { | |
$name = $this->stream->getValue(); | |
} | |
$this->stream->next(); | |
// Parent class | |
if($this->stream->is(T_EXTENDS)) { | |
$this->stream->next(); | |
$extends = $this->parseName(); | |
$extends = $this->resolve($extends); | |
} | |
// Implemented interfaces | |
if($this->stream->is(T_IMPLEMENTS)) { | |
$implements = $this->parseNameList(); | |
} | |
$this->classes[] = array( | |
'name' => $name, | |
'modifiers' => $modifiers, | |
'extends' => $extends, | |
'implements' => $implements | |
); | |
} | |
private function parseModifiers() { | |
$modifiers = 0; | |
while(true) { | |
switch($this->stream->getToken()) { | |
case T_ABSTRACT: | |
$modifiers |= \ReflectionClass::IS_EXPLICIT_ABSTRACT; | |
break; | |
case T_FINAL: | |
$modifiers |= \ReflectionClass::IS_FINAL; | |
break; | |
case T_CLASS: | |
$this->stream->next(); | |
break 2; | |
case null: | |
throw new \Exception('An invalid class declaration was encountered.'); | |
} | |
$this->stream->next(); | |
} | |
return $modifiers; | |
} | |
private function parseInterface() { | |
$extends = array(); | |
if(!$this->stream->is(T_INTERFACE)) { | |
throw new \Exception('An unexpected token was encountered (expected T_INTERFACE).'); | |
} | |
$this->stream->next(); | |
if(!$this->stream->is(T_STRING)) { | |
throw new \Exception('An unexpected token was encountered (expected T_STRING).'); | |
} | |
if($this->namespace) { | |
$name = $this->namespace . '\\' . $this->stream->getValue(); | |
} else { | |
$name = $this->stream->getValue(); | |
} | |
$this->stream->next(); | |
if($this->stream->is(T_EXTENDS)) { | |
$extends = $this->parseNameList(); | |
} | |
$this->interfaces[] = array( | |
'name' => $name, | |
'extends' => $extends | |
); | |
} | |
private function parseTrait() { | |
if(!$this->stream->is(T_TRAIT)) { | |
throw new \Exception('An unexpected token was encountered (expected T_TRAIT).'); | |
} | |
$this->stream->next(); | |
if(!$this->stream->is(T_STRING)) { | |
throw new \Exception('An unexpected token was encountered (expected T_STRING).'); | |
} | |
if($this->namespace) { | |
$name = $this->namespace . '\\' . $this->stream->getValue(); | |
} else { | |
$name = $this->stream->getValue(); | |
} | |
$this->stream->next(); | |
$this->traits[] = $name; | |
} | |
private function parseNameList() { | |
$names = array(); | |
while(true) { | |
$this->stream->next(); | |
$interface = $this->parseName(); | |
$extends[] = $this->resolve($interface); | |
if(!$this->stream->is(',')) { | |
break; | |
} | |
} | |
return $names; | |
} | |
private function parseName() { | |
$name = ''; | |
while($this->stream->is(T_STRING) || $this->stream->is(T_NS_SEPARATOR)) { | |
$name .= $this->stream->getValue(); | |
$this->stream->next(); | |
} | |
return $name; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* @package framework | |
* @subpackage reflection | |
*/ | |
namespace SilverStripe\Framework\Reflection; | |
if(!defined('T_TRAIT')) { | |
define('T_TRAIT', -1); | |
} | |
/** | |
* A wrapper around a list of tokens that exposes a nicer API for stepping | |
* through them. | |
* | |
* This class also normalises tokens, as well as adding basic support for | |
* parsing traits in PHP versions less than 5.4. | |
* | |
* @package framework | |
* @subpackage reflection | |
*/ | |
class TokenStream { | |
protected $source; | |
protected $tokens = array(); | |
protected $position = 0; | |
protected $count; | |
/** | |
* Constructs a new token stream instance from a string of PHP code. | |
* | |
* @param string $source | |
*/ | |
public function __construct($source) { | |
if(!extension_loaded('tokenizer')) { | |
throw new \Exception('The tokenizer extension is not loaded.'); | |
} | |
foreach(token_get_all($source) as $key => $token) { | |
if(is_array($token)) { | |
if(T_TRAIT == -1 && $token[0] == T_STRING && $token[1] == 'trait') { | |
$token[0] = T_TRAIT; | |
} | |
$this->tokens[] = $token; | |
} else { | |
$this->tokens[] = array($token, $token); | |
} | |
} | |
$this->source = $source; | |
$this->count = count($this->tokens); | |
} | |
/** | |
* @return string | |
*/ | |
public function getSource() { | |
return $this->source; | |
} | |
/** | |
* @return array | |
*/ | |
public function getTokens() { | |
return $this->tokens; | |
} | |
/** | |
* Returns TRUE if the token type at the current position matches the | |
* parameter. | |
* | |
* @param int|string $token | |
* @return bool | |
*/ | |
public function is($token) { | |
return $this->getToken() == $token; | |
} | |
/** | |
* Skips ahead until a non-whitespace token is encountered. | |
*/ | |
public function next() { | |
do { | |
$this->position++; | |
} while(!$this->finished() && $this->isWhitespace()); | |
} | |
/** | |
* @return bool | |
*/ | |
public function finished() { | |
return $this->position >= $this->count; | |
} | |
/** | |
* Returns the token code or type at the current position. | |
* | |
* @return int|string | |
*/ | |
public function getToken() { | |
if(isset($this->tokens[$this->position])) { | |
return $this->tokens[$this->position][0]; | |
} | |
} | |
/** | |
* Returns the token value at the current position. | |
* | |
* @return string | |
*/ | |
public function getValue() { | |
if(isset($this->tokens[$this->position])) { | |
return $this->tokens[$this->position][1]; | |
} | |
} | |
/** | |
* Returns a strig representation of the token type at the current position. | |
* | |
* @return string | |
*/ | |
public function getName() { | |
if($token = $this->getToken()) { | |
return $token == T_TRAIT ? 'T_TRAIT' : token_name($token); | |
} | |
} | |
/** | |
* @return int | |
*/ | |
public function getPosition() { | |
return $this->position; | |
} | |
/** | |
* Returns TRUE if the token at the current position is whitespace or a | |
* comment. | |
* | |
* @return bool | |
*/ | |
public function isWhitespace() { | |
return $this->is(T_WHITESPACE) || $this->is(T_COMMENT) || $this->is(T_DOC_COMMENT); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment