Better prededence and associativity handling in pretty printer

Previously the pretty printer added unnecessary and odd-looking parentheses
when several operators with the same precedence were chained:

    'a' . 'b' . 'c' . 'd' . 'e'
    // was printed as
    'a' . ('b' . ('c' . ('d' . 'e')))

Another issue reported as part of #39 was that assignments inside closures
were wrapped in parentheses:

    function() {
        $a = $b;
    }
    // was printed as
    function() {
        ($a = $b);
    }

This was caused by the automatic precedence handling, which just regarded
the closure as an ordinal nested expression.

With the new system the $predenceMap of PrettyPrinterAbstract contains both
precedence and associativity and there is a new method pPrec() which prints
a node taking precedence and associativity into account.

For simpler usage there are additional function pInfixOp(), pPrefixOp() and
pPostfixOp().

Prints not going through pPrec() do not have any precedence handling (fixing
the closure issue).
This commit is contained in:
nikic 2012-10-31 17:34:06 +01:00
parent 759c04db9b
commit ac6f221c50
4 changed files with 220 additions and 130 deletions

View File

@ -91,225 +91,225 @@ class PHPParser_PrettyPrinter_Zend extends PHPParser_PrettyPrinterAbstract
// Assignments
public function pExpr_Assign(PHPParser_Node_Expr_Assign $node) {
return $this->p($node->var) . ' = ' . $this->p($node->expr);
return $this->pInfixOp('Expr_Assign', $node->var, ' = ', $node->expr);
}
public function pExpr_AssignRef(PHPParser_Node_Expr_AssignRef $node) {
return $this->p($node->var) . ' =& ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignRef', $node->var, ' =& ', $node->expr);
}
public function pExpr_AssignPlus(PHPParser_Node_Expr_AssignPlus $node) {
return $this->p($node->var) . ' += ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignPlus', $node->var, ' += ', $node->expr);
}
public function pExpr_AssignMinus(PHPParser_Node_Expr_AssignMinus $node) {
return $this->p($node->var) . ' -= ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignMinus', $node->var, ' -= ', $node->expr);
}
public function pExpr_AssignMul(PHPParser_Node_Expr_AssignMul $node) {
return $this->p($node->var) . ' *= ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignMul', $node->var, ' *= ', $node->expr);
}
public function pExpr_AssignDiv(PHPParser_Node_Expr_AssignDiv $node) {
return $this->p($node->var) . ' /= ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignDiv', $node->var, ' /= ', $node->expr);
}
public function pExpr_AssignConcat(PHPParser_Node_Expr_AssignConcat $node) {
return $this->p($node->var) . ' .= ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignConcat', $node->var, ' .= ', $node->expr);
}
public function pExpr_AssignMod(PHPParser_Node_Expr_AssignMod $node) {
return $this->p($node->var) . ' %= ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignMod', $node->var, ' %= ', $node->expr);
}
public function pExpr_AssignBitwiseAnd(PHPParser_Node_Expr_AssignBitwiseAnd $node) {
return $this->p($node->var) . ' &= ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignBitwiseAnd', $node->var, ' &= ', $node->expr);
}
public function pExpr_AssignBitwiseOr(PHPParser_Node_Expr_AssignBitwiseOr $node) {
return $this->p($node->var) . ' |= ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignBitwiseOr', $node->var, ' |= ', $node->expr);
}
public function pExpr_AssignBitwiseXor(PHPParser_Node_Expr_AssignBitwiseXor $node) {
return $this->p($node->var) . ' ^= ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignBitwiseXor', $node->var, ' ^= ', $node->expr);
}
public function pExpr_AssignShiftLeft(PHPParser_Node_Expr_AssignShiftLeft $node) {
return $this->p($node->var) . ' <<= ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignShiftLeft', $node->var, ' <<= ', $node->expr);
}
public function pExpr_AssignShiftRight(PHPParser_Node_Expr_AssignShiftRight $node) {
return $this->p($node->var) . ' >>= ' . $this->p($node->expr);
return $this->pInfixOp('Expr_AssignShiftRight', $node->var, ' >>= ', $node->expr);
}
// Binary expressions
public function pExpr_Plus(PHPParser_Node_Expr_Plus $node) {
return $this->p($node->left) . ' + ' . $this->p($node->right);
return $this->pInfixOp('Expr_Plus', $node->left, ' + ', $node->right);
}
public function pExpr_Minus(PHPParser_Node_Expr_Minus $node) {
return $this->p($node->left) . ' - ' . $this->p($node->right);
return $this->pInfixOp('Expr_Minus', $node->left, ' - ', $node->right);
}
public function pExpr_Mul(PHPParser_Node_Expr_Mul $node) {
return $this->p($node->left) . ' * ' . $this->p($node->right);
return $this->pInfixOp('Expr_Mul', $node->left, ' * ', $node->right);
}
public function pExpr_Div(PHPParser_Node_Expr_Div $node) {
return $this->p($node->left) . ' / ' . $this->p($node->right);
return $this->pInfixOp('Expr_Div', $node->left, ' / ', $node->right);
}
public function pExpr_Concat(PHPParser_Node_Expr_Concat $node) {
return $this->p($node->left) . ' . ' . $this->p($node->right);
return $this->pInfixOp('Expr_Concat', $node->left, ' . ', $node->right);
}
public function pExpr_Mod(PHPParser_Node_Expr_Mod $node) {
return $this->p($node->left) . ' % ' . $this->p($node->right);
return $this->pInfixOp('Expr_Mod', $node->left, ' % ', $node->right);
}
public function pExpr_BooleanAnd(PHPParser_Node_Expr_BooleanAnd $node) {
return $this->p($node->left) . ' && ' . $this->p($node->right);
return $this->pInfixOp('Expr_BooleanAnd', $node->left, ' && ', $node->right);
}
public function pExpr_BooleanOr(PHPParser_Node_Expr_BooleanOr $node) {
return $this->p($node->left) . ' || ' . $this->p($node->right);
return $this->pInfixOp('Expr_BooleanOr', $node->left, ' || ', $node->right);
}
public function pExpr_BitwiseAnd(PHPParser_Node_Expr_BitwiseAnd $node) {
return $this->p($node->left) . ' & ' . $this->p($node->right);
return $this->pInfixOp('Expr_BitwiseAnd', $node->left, ' & ', $node->right);
}
public function pExpr_BitwiseOr(PHPParser_Node_Expr_BitwiseOr $node) {
return $this->p($node->left) . ' | ' . $this->p($node->right);
return $this->pInfixOp('Expr_BitwiseOr', $node->left, ' | ', $node->right);
}
public function pExpr_BitwiseXor(PHPParser_Node_Expr_BitwiseXor $node) {
return $this->p($node->left) . ' ^ ' . $this->p($node->right);
return $this->pInfixOp('Expr_BitwiseXor', $node->left, ' ^ ', $node->right);
}
public function pExpr_ShiftLeft(PHPParser_Node_Expr_ShiftLeft $node) {
return $this->p($node->left) . ' << ' . $this->p($node->right);
return $this->pInfixOp('Expr_ShiftLeft', $node->left, ' << ', $node->right);
}
public function pExpr_ShiftRight(PHPParser_Node_Expr_ShiftRight $node) {
return $this->p($node->left) . ' >> ' . $this->p($node->right);
return $this->pInfixOp('Expr_ShiftRight', $node->left, ' >> ', $node->right);
}
public function pExpr_LogicalAnd(PHPParser_Node_Expr_LogicalAnd $node) {
return $this->p($node->left) . ' and ' . $this->p($node->right);
return $this->pInfixOp('Expr_LogicalAnd', $node->left, ' and ', $node->right);
}
public function pExpr_LogicalOr(PHPParser_Node_Expr_LogicalOr $node) {
return $this->p($node->left) . ' or ' . $this->p($node->right);
return $this->pInfixOp('Expr_LogicalOr', $node->left, ' or ', $node->right);
}
public function pExpr_LogicalXor(PHPParser_Node_Expr_LogicalXor $node) {
return $this->p($node->left) . ' xor ' . $this->p($node->right);
return $this->pInfixOp('Expr_LogicalXor', $node->left, ' xor ', $node->right);
}
public function pExpr_Equal(PHPParser_Node_Expr_Equal $node) {
return $this->p($node->left) . ' == ' . $this->p($node->right);
return $this->pInfixOp('Expr_Equal', $node->left, ' == ', $node->right);
}
public function pExpr_NotEqual(PHPParser_Node_Expr_NotEqual $node) {
return $this->p($node->left) . ' != ' . $this->p($node->right);
return $this->pInfixOp('Expr_NotEqual', $node->left, ' != ', $node->right);
}
public function pExpr_Identical(PHPParser_Node_Expr_Identical $node) {
return $this->p($node->left) . ' === ' . $this->p($node->right);
return $this->pInfixOp('Expr_Identical', $node->left, ' === ', $node->right);
}
public function pExpr_NotIdentical(PHPParser_Node_Expr_NotIdentical $node) {
return $this->p($node->left) . ' !== ' . $this->p($node->right);
return $this->pInfixOp('Expr_NotIdentical', $node->left, ' !== ', $node->right);
}
public function pExpr_Greater(PHPParser_Node_Expr_Greater $node) {
return $this->p($node->left) . ' > ' . $this->p($node->right);
return $this->pInfixOp('Expr_Greater', $node->left, ' > ', $node->right);
}
public function pExpr_GreaterOrEqual(PHPParser_Node_Expr_GreaterOrEqual $node) {
return $this->p($node->left) . ' >= ' . $this->p($node->right);
return $this->pInfixOp('Expr_GreaterOrEqual', $node->left, ' >= ', $node->right);
}
public function pExpr_Smaller(PHPParser_Node_Expr_Smaller $node) {
return $this->p($node->left) . ' < ' . $this->p($node->right);
return $this->pInfixOp('Expr_Smaller', $node->left, ' < ', $node->right);
}
public function pExpr_SmallerOrEqual(PHPParser_Node_Expr_SmallerOrEqual $node) {
return $this->p($node->left) . ' <= ' . $this->p($node->right);
return $this->pInfixOp('Expr_SmallerOrEqual', $node->left, ' <= ', $node->right);
}
public function pExpr_Instanceof(PHPParser_Node_Expr_Instanceof $node) {
return $this->p($node->expr) . ' instanceof ' . $this->p($node->class);
return $this->pInfixOp('Expr_Instanceof', $node->expr, ' instanceof ', $node->class);
}
// Unary expressions
public function pExpr_BooleanNot(PHPParser_Node_Expr_BooleanNot $node) {
return '!' . $this->p($node->expr);
return $this->pPrefixOp('Expr_BooleanNot', '!', $node->expr);
}
public function pExpr_BitwiseNot(PHPParser_Node_Expr_BitwiseNot $node) {
return '~' . $this->p($node->expr);
return $this->pPrefixOp('Expr_BitwiseNot', '~', $node->expr);
}
public function pExpr_UnaryMinus(PHPParser_Node_Expr_UnaryMinus $node) {
return '-' . $this->p($node->expr);
return $this->pPrefixOp('Expr_UnaryMinus', '-', $node->expr);
}
public function pExpr_UnaryPlus(PHPParser_Node_Expr_UnaryPlus $node) {
return '+' . $this->p($node->expr);
return $this->pPrefixOp('Expr_UnaryPlus', '+', $node->expr);
}
public function pExpr_PreInc(PHPParser_Node_Expr_PreInc $node) {
return '++' . $this->p($node->var);
return $this->pPrefixOp('Expr_PreInc', '++', $node->var);
}
public function pExpr_PreDec(PHPParser_Node_Expr_PreDec $node) {
return '--' . $this->p($node->var);
return $this->pPrefixOp('Expr_PreDec', '--', $node->var);
}
public function pExpr_PostInc(PHPParser_Node_Expr_PostInc $node) {
return $this->p($node->var) . '++';
return $this->pPostfixOp('Expr_PostInc', $node->var, '++');
}
public function pExpr_PostDec(PHPParser_Node_Expr_PostDec $node) {
return $this->p($node->var) . '--';
return $this->pPostfixOp('Expr_PostDec', $node->var, '--');
}
public function pExpr_ErrorSuppress(PHPParser_Node_Expr_ErrorSuppress $node) {
return '@' . $this->p($node->expr);
return $this->pPrefixOp('Expr_ErrorSuppress', '@', $node->expr);
}
// Casts
public function pExpr_Cast_Int(PHPParser_Node_Expr_Cast_Int $node) {
return '(int) ' . $this->p($node->expr);
return $this->pPrefixOp('Expr_Cast_Int', '(int) ', $node->expr);
}
public function pExpr_Cast_Double(PHPParser_Node_Expr_Cast_Double $node) {
return '(double) ' . $this->p($node->expr);
return $this->pPrefixOp('Expr_Cast_Double', '(double) ', $node->expr);
}
public function pExpr_Cast_String(PHPParser_Node_Expr_Cast_String $node) {
return '(string) ' . $this->p($node->expr);
return $this->pPrefixOp('Expr_Cast_String', '(string) ', $node->expr);
}
public function pExpr_Cast_Array(PHPParser_Node_Expr_Cast_Array $node) {
return '(array) ' . $this->p($node->expr);
return $this->pPrefixOp('Expr_Cast_Array', '(array) ', $node->expr);
}
public function pExpr_Cast_Object(PHPParser_Node_Expr_Cast_Object $node) {
return '(object) ' . $this->p($node->expr);
return $this->pPrefixOp('Expr_Cast_Object', '(object) ', $node->expr);
}
public function pExpr_Cast_Bool(PHPParser_Node_Expr_Cast_Bool $node) {
return '(bool) ' . $this->p($node->expr);
return $this->pPrefixOp('Expr_Cast_Bool', '(bool) ', $node->expr);
}
public function pExpr_Cast_Unset(PHPParser_Node_Expr_Cast_Unset $node) {
return '(unset) ' . $this->p($node->expr);
return $this->pPrefixOp('Expr_Cast_Unset', '(unset) ', $node->expr);
}
// Function calls and similar constructs
@ -439,9 +439,11 @@ class PHPParser_PrettyPrinter_Zend extends PHPParser_PrettyPrinterAbstract
}
public function pExpr_Ternary(PHPParser_Node_Expr_Ternary $node) {
return $this->p($node->cond) . ' ?'
. (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '')
. ': ' . $this->p($node->else);
// a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator.
// this is okay because the part between ? and : never needs parentheses.
return $this->pInfixOp('Expr_Ternary',
$node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else
);
}
public function pExpr_Exit(PHPParser_Node_Expr_Exit $node) {

View File

@ -3,69 +3,68 @@
abstract class PHPParser_PrettyPrinterAbstract
{
protected $precedenceMap = array(
'Expr_BitwiseNot' => 1,
'Expr_PreInc' => 1,
'Expr_PreDec' => 1,
'Expr_PostInc' => 1,
'Expr_PostDec' => 1,
'Expr_UnaryPlus' => 1,
'Expr_UnaryMinus' => 1,
'Expr_Cast_Int' => 1,
'Expr_Cast_Double' => 1,
'Expr_Cast_String' => 1,
'Expr_Cast_Array' => 1,
'Expr_Cast_Object' => 1,
'Expr_Cast_Bool' => 1,
'Expr_Cast_Unset' => 1,
'Expr_ErrorSuppress' => 1,
'Expr_Instanceof' => 2,
'Expr_BooleanNot' => 3,
'Expr_Mul' => 4,
'Expr_Div' => 4,
'Expr_Mod' => 4,
'Expr_Plus' => 5,
'Expr_Minus' => 5,
'Expr_Concat' => 5,
'Expr_ShiftLeft' => 6,
'Expr_ShiftRight' => 6,
'Expr_Smaller' => 7,
'Expr_SmallerOrEqual' => 7,
'Expr_Greater' => 7,
'Expr_GreaterOrEqual' => 7,
'Expr_Equal' => 8,
'Expr_NotEqual' => 8,
'Expr_Identical' => 8,
'Expr_NotIdentical' => 8,
'Expr_BitwiseAnd' => 9,
'Expr_BitwiseXor' => 10,
'Expr_BitwiseOr' => 11,
'Expr_BooleanAnd' => 12,
'Expr_BooleanOr' => 13,
'Expr_Ternary' => 14,
'Expr_Assign' => 15,
'Expr_AssignPlus' => 15,
'Expr_AssignMinus' => 15,
'Expr_AssignMul' => 15,
'Expr_AssignDiv' => 15,
'Expr_AssignConcat' => 15,
'Expr_AssignMod' => 15,
'Expr_AssignBitwiseAnd' => 15,
'Expr_AssignBitwiseOr' => 15,
'Expr_AssignBitwiseXor' => 15,
'Expr_AssignShiftLeft' => 15,
'Expr_AssignShiftRight' => 15,
'Expr_AssignList' => 15,
'Expr_LogicalAnd' => 16,
'Expr_LogicalXor' => 17,
'Expr_LogicalOr' => 18,
// [precedence, associativity] where for the latter -1 is %left, 0 is %nonassoc and 1 is %right
'Expr_BitwiseNot' => array( 1, 1),
'Expr_PreInc' => array( 1, 1),
'Expr_PreDec' => array( 1, 1),
'Expr_PostInc' => array( 1, -1),
'Expr_PostDec' => array( 1, -1),
'Expr_UnaryPlus' => array( 1, 1),
'Expr_UnaryMinus' => array( 1, 1),
'Expr_Cast_Int' => array( 1, 1),
'Expr_Cast_Double' => array( 1, 1),
'Expr_Cast_String' => array( 1, 1),
'Expr_Cast_Array' => array( 1, 1),
'Expr_Cast_Object' => array( 1, 1),
'Expr_Cast_Bool' => array( 1, 1),
'Expr_Cast_Unset' => array( 1, 1),
'Expr_ErrorSuppress' => array( 1, 1),
'Expr_Instanceof' => array( 2, 0),
'Expr_BooleanNot' => array( 3, 1),
'Expr_Mul' => array( 4, -1),
'Expr_Div' => array( 4, -1),
'Expr_Mod' => array( 4, -1),
'Expr_Plus' => array( 5, -1),
'Expr_Minus' => array( 5, -1),
'Expr_Concat' => array( 5, -1),
'Expr_ShiftLeft' => array( 6, -1),
'Expr_ShiftRight' => array( 6, -1),
'Expr_Smaller' => array( 7, 0),
'Expr_SmallerOrEqual' => array( 7, 0),
'Expr_Greater' => array( 7, 0),
'Expr_GreaterOrEqual' => array( 7, 0),
'Expr_Equal' => array( 8, 0),
'Expr_NotEqual' => array( 8, 0),
'Expr_Identical' => array( 8, 0),
'Expr_NotIdentical' => array( 8, 0),
'Expr_BitwiseAnd' => array( 9, -1),
'Expr_BitwiseXor' => array(10, -1),
'Expr_BitwiseOr' => array(11, -1),
'Expr_BooleanAnd' => array(12, -1),
'Expr_BooleanOr' => array(13, -1),
'Expr_Ternary' => array(14, -1),
// parser uses %left for assignments, but they really behave as %right
'Expr_Assign' => array(15, 1),
'Expr_AssignRef' => array(15, 1),
'Expr_AssignPlus' => array(15, 1),
'Expr_AssignMinus' => array(15, 1),
'Expr_AssignMul' => array(15, 1),
'Expr_AssignDiv' => array(15, 1),
'Expr_AssignConcat' => array(15, 1),
'Expr_AssignMod' => array(15, 1),
'Expr_AssignBitwiseAnd' => array(15, 1),
'Expr_AssignBitwiseOr' => array(15, 1),
'Expr_AssignBitwiseXor' => array(15, 1),
'Expr_AssignShiftLeft' => array(15, 1),
'Expr_AssignShiftRight' => array(15, 1),
'Expr_LogicalAnd' => array(16, -1),
'Expr_LogicalXor' => array(17, -1),
'Expr_LogicalOr' => array(18, -1),
);
protected $precedenceStack;
protected $precedenceStackPos;
protected $noIndentToken;
public function __construct() {
$this->precedenceStack = array($this->precedenceStackPos = 0 => 19);
$this->noIndentToken = uniqid('_NO_INDENT_');
}
@ -126,26 +125,52 @@ abstract class PHPParser_PrettyPrinterAbstract
* @return string Pretty printed node
*/
protected function p(PHPParser_Node $node) {
return $this->{'p' . $node->getType()}($node);
}
protected function pInfixOp($type, PHPParser_Node $leftNode, $operatorString, PHPParser_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, PHPParser_Node $node) {
list($precedence, $associativity) = $this->precedenceMap[$type];
return $operatorString . $this->pPrec($node, $precedence, $associativity, 1);
}
protected function pPostfixOp($type, PHPParser_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 PHPParser_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(PHPParser_Node $node, $parentPrecedence, $parentAssociativity, $childPosition) {
$type = $node->getType();
if (isset($this->precedenceMap[$type])) {
$precedence = $this->precedenceMap[$type];
if ($precedence >= $this->precedenceStack[$this->precedenceStackPos]) {
$this->precedenceStack[++$this->precedenceStackPos] = $precedence;
$return = '(' . $this->{'p' . $type}($node) . ')';
--$this->precedenceStackPos;
} else {
$this->precedenceStack[++$this->precedenceStackPos] = $precedence;
$return = $this->{'p' . $type}($node);
--$this->precedenceStackPos;
$childPrecedence = $this->precedenceMap[$type][0];
if ($childPrecedence > $parentPrecedence
|| ($parentPrecedence == $childPrecedence && $parentAssociativity != $childPosition)
) {
return '(' . $this->{'p' . $type}($node) . ')';
}
}
return $return;
} else {
return $this->{'p' . $type}($node);
}
}
/**
* Pretty prints an array of nodes and implodes the printed values.

View File

@ -0,0 +1,18 @@
Closures
-----
<?php
$closureWithArgs = function ($arg1, $arg2) {
$comment = 'closure body';
};
$closureWithArgsAndVars = function ($arg1, $arg2) use($var1, $var2) {
$comment = 'closure body';
};
-----
$closureWithArgs = function ($arg1, $arg2) {
$comment = 'closure body';
};
$closureWithArgsAndVars = function ($arg1, $arg2) use($var1, $var2) {
$comment = 'closure body';
};

View File

@ -0,0 +1,45 @@
Pretty printer generates least-parentheses output
-----
<?php
echo 'abc' . 'cde' . 'fgh';
echo 'abc' . ('cde' . 'fgh');
echo 'abc' . 1 + 2 . 'fgh';
echo 'abc' . (1 + 2) . 'fgh';
echo 1 * 2 + 3 / 4 % 5 . 6;
echo 1 * (2 + 3) / (4 % (5 . 6));
$a = $b = $c = $d = $f && true;
($a = $b = $c = $d = $f) && true;
$a = $b = $c = $d = $f and true;
$a = $b = $c = $d = ($f and true);
$a ? $b : $c ? $d : $e ? $f : $g;
$a ? $b : ($c ? $d : ($e ? $f : $g));
$a ? $b ? $c : $d : $f;
(1 > 0) > (1 < 0);
// this will currently unnecessarily print !($a = $b). This is correct as far as operator precedence is concerned, but
// the parentheses are not really necessary, because = takes only variables as the left hand side operator.
!$a = $b;
-----
echo 'abc' . 'cde' . 'fgh';
echo 'abc' . ('cde' . 'fgh');
echo 'abc' . 1 + 2 . 'fgh';
echo 'abc' . (1 + 2) . 'fgh';
echo 1 * 2 + 3 / 4 % 5 . 6;
echo 1 * (2 + 3) / (4 % (5 . 6));
$a = $b = $c = $d = $f && true;
($a = $b = $c = $d = $f) && true;
$a = $b = $c = $d = $f and true;
$a = $b = $c = $d = ($f and true);
$a ? $b : $c ? $d : $e ? $f : $g;
$a ? $b : ($c ? $d : ($e ? $f : $g));
$a ? $b ? $c : $d : $f;
(1 > 0) > (1 < 0);
// this will currently unnecessarily print !($a = $b). This is correct as far as operator precedence is concerned, but
// the parentheses are not really necessary, because = takes only variables as the left hand side operator.
!($a = $b);