Skip to content

Instantly share code, notes, and snippets.

Created December 23, 2012 19:16
Show Gist options
  • Save anonymous/4365484 to your computer and use it in GitHub Desktop.
Save anonymous/4365484 to your computer and use it in GitHub Desktop.
First go at a pretty printer that tries to preserve formatting
<?php
class PrettyPrinterTryingToPreserveFormatting extends PHPParser_PrettyPrinter_Zend {
/** @var TokenArray */
protected $tokens;
protected $indentationStack;
protected $indentationStackPos;
protected $additionalIndentation;
/**
* $tokens is expected to have indentation normalized to four spaces.
*/
public function prettyPrint2(array $nodes, TokenArray $tokens) {
$this->tokens = $tokens;
$this->indentationStack = array(0);
$this->indentationStackPos = 0;
return parent::prettyPrint($nodes);
}
protected function pIndent($string, $levels) {
$string = preg_replace(
'~\n(?!$|' . $this->noIndentToken . ')~',
"\n" . str_repeat(' ', $levels),
$string
);
return $string;
}
protected function p(PHPParser_Node $node) {
$changed = $node->hasAttribute('changed') || !$node->hasAttribute('startOffset');
if (!$changed) {
$curStartOffset = $node->getAttribute('startOffset');
$indentation = $this->getIndentation($curStartOffset);
$this->indentationStack[$this->indentationStackPos + 1] = $indentation;
$this->indentationStackPos++;
$result = '';
/* We need to iterate SELF_FIRST because RAI does not consider objects to be leafs */
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($node->getSubNodes()),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $subNode) {
if (!$subNode instanceof PHPParser_Node) continue;
if (!$subNode->hasAttribute('startOffset') || !$subNode->hasAttribute('endOffset')) {
// Resort to normal pretty printing
$this->indentationStackPos--;
return $this->{'p' . $node->getType()}($node);
}
$curEndOffset = $subNode->getAttribute('startOffset') - 1;
$result .= $this->handleTokens($indentation, $curStartOffset, $curEndOffset);
$result .= $this->pIndent(
$this->p($subNode),
$this->indentationStack[$this->indentationStackPos] - $indentation
);
$curStartOffset = $subNode->getAttribute('endOffset') + 1;
}
$curEndOffset = $node->getAttribute('endOffset');
$result .= $this->handleTokens($indentation, $curStartOffset, $curEndOffset);
$this->indentationStackPos--;
return $result;
} else {
return $this->{'p' . $node->getType()}($node);
}
}
protected function getIndentation($offset) {
for ($i = $offset; $i >= 0; --$i) {
$token = $this->tokens->get($i);
if (!$token->hasType(T_WHITESPACE)) continue;
$tokenContent = $token->getContent();
if (false !== $newlinePos = strrpos($tokenContent, "\n")) {
return substr_count($tokenContent, ' ', $newlinePos + 1);
}
$prevToken = $this->tokens->get($i - 1);
if (!$prevToken->hasType(T_COMMENT) || !$prevToken->endsWith("\n")) {
continue;
}
return substr_count($tokenContent, ' ');
}
return 0;
}
protected function handleTokens($baseIndentation, $startOffset, $endOffset) {
$result = '';
for ($i = $startOffset; $i <= $endOffset; ++$i) {
$token = $this->tokens->get($i);
$tokenContent = $token->getContent();
if ($token->hasType(T_WHITESPACE)) {
if (false !== $newlinePos = strrpos($tokenContent, "\n")) {
$this->indentationStack[$this->indentationStackPos] = substr_count(
$tokenContent, ' ', $newlinePos + 1
);
$result .= str_replace("\n" . str_repeat(' ', $baseIndentation), "\n", $tokenContent);
continue;
}
$prevToken = $this->tokens->get($i - 1);
if ($prevToken->hasType(T_COMMENT) && $prevToken->endsWith("\n")) {
$n = $this->indentationStack[$this->indentationStackPos] = substr_count($tokenContent, ' ');
$result .= substr($tokenContent, $n * 4);
continue;
}
}
$result .= $tokenContent;
}
return $result;
}
}
<?php
interface Token {
public function hasType($type);
public function getContent();
public function endsWith($string);
}
abstract class TokenAbstract implements Token {
public function endsWith($string) {
$length = strlen($string);
if (0 === $length) return true;
return $string === substr($this->getContent(), -$length);
}
}
class DummyTokenException extends Exception {}
class DummyToken extends TokenAbstract {
public function hasType($type) {
return false;
}
public function getContent() {
throw new DummyTokenException;
}
}
class CharToken extends TokenAbstract {
protected $char;
public function __construct($char) {
$this->char = $char;
}
public function hasType($type) {
return $type === $this->char;
}
public function getContent() {
return $this->char;
}
}
class ArrayToken extends TokenAbstract {
protected $type;
protected $content;
protected $startLine;
public function __construct($token) {
list($this->type, $this->content, $this->startLine) = $token;
}
public function hasType($type) {
return $type === $this->type;
}
public function getContent() {
return $this->content;
}
}
class TokenArray {
protected $tokens;
public function __construct(array $tokens) {
$this->setTokens($tokens);
}
protected function setTokens(array $tokens) {
$this->tokens = array();
foreach ($tokens as $token) {
if (is_array($token)) {
$this->tokens[] = new ArrayToken($token);
} else {
$this->tokens[] = new CharToken($token);
}
}
}
public function get($offset) {
return isset($this->tokens[$offset]) ? $this->tokens[$offset] : new DummyToken;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment