php-parser/lib/PhpParser/PrettyPrinterAbstract.php
Nikita Popov 574665b45b PHP 7.1: list() with keys
Expr\List will now contain ArrayItems instead of plain variables.
I'm reusing ArrayItem, because code handling list() must also handle
arrays, and this allows both to go through the same code path.

This also renames Expr\List->vars to ->items.

TODO: Should Expr\List be dropped in favor of Expr\Array with an
extra flag?
2016-07-09 21:55:55 +02:00

317 lines
12 KiB
PHP

<?php
namespace PhpParser;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
abstract class PrettyPrinterAbstract
{
protected $precedenceMap = array(
// [precedence, associativity] where for the latter -1 is %left, 0 is %nonassoc and 1 is %right
'Expr_BinaryOp_Pow' => array( 0, 1),
'Expr_BitwiseNot' => array( 10, 1),
'Expr_PreInc' => array( 10, 1),
'Expr_PreDec' => array( 10, 1),
'Expr_PostInc' => array( 10, -1),
'Expr_PostDec' => array( 10, -1),
'Expr_UnaryPlus' => array( 10, 1),
'Expr_UnaryMinus' => array( 10, 1),
'Expr_Cast_Int' => array( 10, 1),
'Expr_Cast_Double' => array( 10, 1),
'Expr_Cast_String' => array( 10, 1),
'Expr_Cast_Array' => array( 10, 1),
'Expr_Cast_Object' => array( 10, 1),
'Expr_Cast_Bool' => array( 10, 1),
'Expr_Cast_Unset' => array( 10, 1),
'Expr_ErrorSuppress' => array( 10, 1),
'Expr_Instanceof' => array( 20, 0),
'Expr_BooleanNot' => array( 30, 1),
'Expr_BinaryOp_Mul' => array( 40, -1),
'Expr_BinaryOp_Div' => array( 40, -1),
'Expr_BinaryOp_Mod' => array( 40, -1),
'Expr_BinaryOp_Plus' => array( 50, -1),
'Expr_BinaryOp_Minus' => array( 50, -1),
'Expr_BinaryOp_Concat' => array( 50, -1),
'Expr_BinaryOp_ShiftLeft' => array( 60, -1),
'Expr_BinaryOp_ShiftRight' => array( 60, -1),
'Expr_BinaryOp_Smaller' => array( 70, 0),
'Expr_BinaryOp_SmallerOrEqual' => array( 70, 0),
'Expr_BinaryOp_Greater' => array( 70, 0),
'Expr_BinaryOp_GreaterOrEqual' => array( 70, 0),
'Expr_BinaryOp_Equal' => array( 80, 0),
'Expr_BinaryOp_NotEqual' => array( 80, 0),
'Expr_BinaryOp_Identical' => array( 80, 0),
'Expr_BinaryOp_NotIdentical' => array( 80, 0),
'Expr_BinaryOp_Spaceship' => array( 80, 0),
'Expr_BinaryOp_BitwiseAnd' => array( 90, -1),
'Expr_BinaryOp_BitwiseXor' => array(100, -1),
'Expr_BinaryOp_BitwiseOr' => array(110, -1),
'Expr_BinaryOp_BooleanAnd' => array(120, -1),
'Expr_BinaryOp_BooleanOr' => array(130, -1),
'Expr_BinaryOp_Coalesce' => array(140, 1),
'Expr_Ternary' => array(150, -1),
// parser uses %left for assignments, but they really behave as %right
'Expr_Assign' => array(160, 1),
'Expr_AssignRef' => array(160, 1),
'Expr_AssignOp_Plus' => array(160, 1),
'Expr_AssignOp_Minus' => array(160, 1),
'Expr_AssignOp_Mul' => array(160, 1),
'Expr_AssignOp_Div' => array(160, 1),
'Expr_AssignOp_Concat' => array(160, 1),
'Expr_AssignOp_Mod' => array(160, 1),
'Expr_AssignOp_BitwiseAnd' => array(160, 1),
'Expr_AssignOp_BitwiseOr' => array(160, 1),
'Expr_AssignOp_BitwiseXor' => array(160, 1),
'Expr_AssignOp_ShiftLeft' => array(160, 1),
'Expr_AssignOp_ShiftRight' => array(160, 1),
'Expr_AssignOp_Pow' => array(160, 1),
'Expr_YieldFrom' => array(165, 1),
'Expr_Print' => array(168, 1),
'Expr_BinaryOp_LogicalAnd' => array(170, -1),
'Expr_BinaryOp_LogicalXor' => array(180, -1),
'Expr_BinaryOp_LogicalOr' => array(190, -1),
'Expr_Include' => array(200, -1),
);
protected $noIndentToken;
protected $docStringEndToken;
protected $canUseSemicolonNamespaces;
protected $options;
/**
* Creates a pretty printer instance using the given options.
*
* Supported options:
* * bool $shortArraySyntax = false: Whether to use [] instead of array() as the default array
* syntax, if the node does not specify a format.
*
* @param array $options Dictionary of formatting options
*/
public function __construct(array $options = []) {
$this->noIndentToken = '_NO_INDENT_' . mt_rand();
$this->docStringEndToken = '_DOC_STRING_END_' . mt_rand();
$defaultOptions = ['shortArraySyntax' => false];
$this->options = $options + $defaultOptions;
}
/**
* Pretty prints an array of statements.
*
* @param Node[] $stmts Array of statements
*
* @return string Pretty printed statements
*/
public function prettyPrint(array $stmts) {
$this->preprocessNodes($stmts);
return ltrim($this->handleMagicTokens($this->pStmts($stmts, false)));
}
/**
* Pretty prints an expression.
*
* @param Expr $node Expression node
*
* @return string Pretty printed node
*/
public function prettyPrintExpr(Expr $node) {
return $this->handleMagicTokens($this->p($node));
}
/**
* Pretty prints a file of statements (includes the opening <?php tag if it is required).
*
* @param Node[] $stmts Array of statements
*
* @return string Pretty printed statements
*/
public function prettyPrintFile(array $stmts) {
if (!$stmts) {
return "<?php\n\n";
}
$p = "<?php\n\n" . $this->prettyPrint($stmts);
if ($stmts[0] instanceof Stmt\InlineHTML) {
$p = preg_replace('/^<\?php\s+\?>\n?/', '', $p);
}
if ($stmts[count($stmts) - 1] instanceof Stmt\InlineHTML) {
$p = preg_replace('/<\?php$/', '', rtrim($p));
}
return $p;
}
/**
* Preprocesses the top-level nodes to initialize pretty printer state.
*
* @param Node[] $nodes Array of nodes
*/
protected function preprocessNodes(array $nodes) {
/* We can use semicolon-namespaces unless there is a global namespace declaration */
$this->canUseSemicolonNamespaces = true;
foreach ($nodes as $node) {
if ($node instanceof Stmt\Namespace_ && null === $node->name) {
$this->canUseSemicolonNamespaces = false;
}
}
}
protected function handleMagicTokens($str) {
// Drop no-indent tokens
$str = str_replace($this->noIndentToken, '', $str);
// Replace doc-string-end tokens with nothing or a newline
$str = str_replace($this->docStringEndToken . ";\n", ";\n", $str);
$str = str_replace($this->docStringEndToken, "\n", $str);
return $str;
}
/**
* Pretty prints an array of nodes (statements) and indents them optionally.
*
* @param Node[] $nodes Array of nodes
* @param bool $indent Whether to indent the printed nodes
*
* @return string Pretty printed statements
*/
protected function pStmts(array $nodes, $indent = true) {
$result = '';
foreach ($nodes as $node) {
$comments = $node->getAttribute('comments', array());
if ($comments) {
$result .= "\n" . $this->pComments($comments);
if ($node instanceof Stmt\Nop) {
continue;
}
}
$result .= "\n" . $this->p($node) . ($node instanceof Expr ? ';' : '');
}
if ($indent) {
return preg_replace('~\n(?!$|' . $this->noIndentToken . ')~', "\n ", $result);
} else {
return $result;
}
}
/**
* Pretty prints a node.
*
* @param Node $node Node to be pretty printed
*
* @return string Pretty printed node
*/
protected function p(Node $node) {
return $this->{'p' . $node->getType()}($node);
}
protected function pInfixOp($type, Node $leftNode, $operatorString, Node $rightNode) {
list($precedence, $associativity) = $this->precedenceMap[$type];
return $this->pPrec($leftNode, $precedence, $associativity, -1)
. $operatorString
. $this->pPrec($rightNode, $precedence, $associativity, 1);
}
protected function pPrefixOp($type, $operatorString, Node $node) {
list($precedence, $associativity) = $this->precedenceMap[$type];
return $operatorString . $this->pPrec($node, $precedence, $associativity, 1);
}
protected function pPostfixOp($type, Node $node, $operatorString) {
list($precedence, $associativity) = $this->precedenceMap[$type];
return $this->pPrec($node, $precedence, $associativity, -1) . $operatorString;
}
/**
* Prints an expression node with the least amount of parentheses necessary to preserve the meaning.
*
* @param Node $node Node to pretty print
* @param int $parentPrecedence Precedence of the parent operator
* @param int $parentAssociativity Associativity of parent operator
* (-1 is left, 0 is nonassoc, 1 is right)
* @param int $childPosition Position of the node relative to the operator
* (-1 is left, 1 is right)
*
* @return string The pretty printed node
*/
protected function pPrec(Node $node, $parentPrecedence, $parentAssociativity, $childPosition) {
$type = $node->getType();
if (isset($this->precedenceMap[$type])) {
$childPrecedence = $this->precedenceMap[$type][0];
if ($childPrecedence > $parentPrecedence
|| ($parentPrecedence == $childPrecedence && $parentAssociativity != $childPosition)
) {
return '(' . $this->{'p' . $type}($node) . ')';
}
}
return $this->{'p' . $type}($node);
}
/**
* Pretty prints an array of nodes and implodes the printed values.
*
* @param Node[] $nodes Array of Nodes to be printed
* @param string $glue Character to implode with
*
* @return string Imploded pretty printed nodes
*/
protected function pImplode(array $nodes, $glue = '') {
$pNodes = array();
foreach ($nodes as $node) {
if (null === $node) {
$pNodes[] = '';
} else {
$pNodes[] = $this->p($node);
}
}
return implode($glue, $pNodes);
}
/**
* Pretty prints an array of nodes and implodes the printed values with commas.
*
* @param Node[] $nodes Array of Nodes to be printed
*
* @return string Comma separated pretty printed nodes
*/
protected function pCommaSeparated(array $nodes) {
return $this->pImplode($nodes, ', ');
}
/**
* Signals the pretty printer that a string shall not be indented.
*
* @param string $string Not to be indented string
*
* @return string String marked with $this->noIndentToken's.
*/
protected function pNoIndent($string) {
return str_replace("\n", "\n" . $this->noIndentToken, $string);
}
/**
* Prints reformatted text of the passed comments.
*
* @param Comment[] $comments List of comments
*
* @return string Reformatted text of comments
*/
protected function pComments(array $comments) {
$formattedComments = [];
foreach ($comments as $comment) {
$formattedComments[] = $comment->getReformattedText();
}
return implode("\n", $formattedComments);
}
}