Skip to content

Instantly share code, notes, and snippets.

@ajshort
Created May 28, 2012 08:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ajshort/2818026 to your computer and use it in GitHub Desktop.
Save ajshort/2818026 to your computer and use it in GitHub Desktop.
Parser
<?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;
}
}
<?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