Skip to content

Instantly share code, notes, and snippets.

@saxenap
Created November 17, 2015 20:13
Show Gist options
  • Save saxenap/f203461bcdea365d2131 to your computer and use it in GitHub Desktop.
Save saxenap/f203461bcdea365d2131 to your computer and use it in GitHub Desktop.
class TokenNotifier
{
private $callables;
public function __construct()
{
$this->callables = new SplQueue();
$this->callables->setIteratorMode(SplDoublyLinkedList::IT_MODE_KEEP);
}
public function register(TokenParser $parser)
{
$this->callables->push($parser);
return $this;
}
public function notify(TokenInfo $tokenInfo, TokenList $tokenList)
{
foreach($this->callables as $callable)
$callable->parse($tokenInfo, $tokenList);
return $this;
}
}
class TokenInfo
{
private $type;
private $value;
private $content;
private $lineNumber;
public function __construct($type, $value, $content, $lineNumber)
{
$this->type = $type;
$this->value = $value;
$this->content = $content;
$this->lineNumber = $lineNumber;
}
public function getType()
{
return $this->type;
}
public function getContent()
{
return $this->content;
}
public function getValue()
{
return $this->value;
}
public function getLineNumber()
{
return $this->lineNumber;
}
}
class TokenList extends ArrayIterator
{
public function nextNonWhitespace()
{
do
$this->next();
while
($this->current()->getType() === 'T_WHITESPACE');
return $this->current();
}
public function fetch($offset, $limit = null)
{
return
$this->makeFromIterator(
new LimitIterator(clone $this, $offset, $limit ?: -1)
);
}
public function fetchUptoContent($content)
{
return
$this->makeFromIterator(
new TokensUptoContentFilter(clone $this, $content)
);
}
public function ltrim($str = 'T_WHITESPACE')
{
return
$this->makeFromIterator(
new TrimTypeFilter(clone $this, $str)
);
}
private function makeFromIterator(Iterator $iterator)
{
return new static(iterator_to_array($iterator));
}
}
class PhpStringTokenizer
{
private $notifier, $tokenList;
public function __construct(TokenNotifier $notifier)
{
$this->notifier = $notifier;
}
public function tokenize($string)
{
$tokenList = new TokenList;
foreach(token_get_all($string) as $token)
{
list($type, $value, $content, $lineNumber) =
is_array($token)
? $this->mapArray($token)
: $this->mapString($token);
$tokenList->append(new TokenInfo(
$type, $value, $content, $lineNumber
));
}
$this->tokenList = $tokenList;
}
private function mapString($token)
{
return [null, null, $token, null];
}
private function mapArray(array $token)
{
return [
token_name($token[0]),
$token[0],
$token[1],
isset($token[2]) ? $token[2] : null
];
}
public function iterate()
{
foreach($this->tokenList as $index => $tokenInfo)
{
$this->notifier->notify($tokenInfo, $this->tokenList->fetch($index));
}
}
}
class TrimTypeFilter extends FilterIterator
{
private $type, $trimming = true;
public function __construct(\Iterator $tokens, $type)
{
parent::__construct($tokens);
$this->type = $type;
}
public function accept()
{
$token = $this->getInnerIterator()->current();
if($this->trimming === true)
{
if($token->getType() === $this->type)
return false;
else
$this->trimming = false;
}
return true;
}
}
class TokensWithContentFilter extends FilterIterator
{
private $content, $offset;
public function __construct(Iterator $tokens, $content, $offset)
{
parent::__construct($tokens);
$this->content = $content;
$this->offset = $offset;
}
public function accept()
{
$tokens = $this->getInnerIterator();
$token = $tokens->current();
if($token->getContent() === $this->content)
{
if($tokens->key() >= $this->offset)
return true;
}
return false;
}
}
class TokensUptoContentFilter extends FilterIterator
{
private $content, $stop = false;
public function __construct(Iterator $tokens, $content)
{
parent::__construct($tokens);
$this->content = $content;
}
public function accept()
{
$tokens = $this->getInnerIterator();
$token = $tokens->current();
if($this->stop === true)
return false;
if($token->getContent() === $this->content)
$this->stop = true;
return true;
}
}
interface TokenParser
{
public function parse(TokenInfo $tokenInfo, TokenList $tokenList);
}
class UseStatementParser implements TokenParser
{
private $declaredClasses = [];
public function parse(TokenInfo $tokenInfo, TokenList $tokenList)
{
if($tokenInfo->getType() === 'T_USE')
{
$lineOfTokens = $tokenList->fetchUptoContent(';');
$this->parseDeclaredClass($lineOfTokens);
}
}
private function parseDeclaredClass(Iterator $iterator)
{
$className = $alias = null;
while($iterator->valid())
{
$tokenInfo = $iterator->current();
switch($tokenInfo->getType())
{
case 'T_STRING':
case 'T_NS_SEPARATOR':
$className .= $tokenInfo->getContent();
break;
case 'T_AS':
$alias = $iterator->nextNonWhitespace()->getContent();
break;
case null:
switch($tokenInfo->getContent())
{
case ',':
$iterator->next();
$this->parseDeclaredClass($iterator);
case ';':
break;
}
break 2;
}
$iterator->next();
}
$this->declaredClasses[] = [$className, $alias];
}
}
class TokensOfTypeFilter extends FilterIterator
{
private $type, $offset;
public function __construct(\Iterator $tokens, $type, $offset)
{
parent::__construct($tokens);
$this->type = $type;
$this->offset = $offset;
}
public function accept()
{
$tokens = $this->getInnerIterator();
$token = $tokens->current();
if($token->getType() === $this->type)
{
if($tokens->key() >= $this->offset)
return true;
}
return false;
}
}
class TokensFromLineFilter extends FilterIterator
{
private $lineNumber;
public function __construct(\Iterator $tokens, $lineNumber)
{
parent::__construct($tokens);
$this->lineNumber = $lineNumber;
}
public function accept()
{
$token = $this->getInnerIterator()->current();
$tokenLineNo = $token->getLineNumber();
if($tokenLineNo === $this->lineNumber)
return true;
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment