1<?php declare(strict_types=1); 2 3namespace PhpParser\PrettyPrinter; 4 5use PhpParser\Node; 6use PhpParser\Node\Expr; 7use PhpParser\Node\Expr\AssignOp; 8use PhpParser\Node\Expr\BinaryOp; 9use PhpParser\Node\Expr\Cast; 10use PhpParser\Node\Name; 11use PhpParser\Node\Scalar; 12use PhpParser\Node\Scalar\MagicConst; 13use PhpParser\Node\Stmt; 14use PhpParser\PrettyPrinterAbstract; 15 16class Standard extends PrettyPrinterAbstract { 17 // Special nodes 18 19 protected function pParam(Node\Param $node): string { 20 return $this->pAttrGroups($node->attrGroups, true) 21 . $this->pModifiers($node->flags) 22 . ($node->type ? $this->p($node->type) . ' ' : '') 23 . ($node->byRef ? '&' : '') 24 . ($node->variadic ? '...' : '') 25 . $this->p($node->var) 26 . ($node->default ? ' = ' . $this->p($node->default) : '') 27 . ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : ''); 28 } 29 30 protected function pArg(Node\Arg $node): string { 31 return ($node->name ? $node->name->toString() . ': ' : '') 32 . ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') 33 . $this->p($node->value); 34 } 35 36 protected function pVariadicPlaceholder(Node\VariadicPlaceholder $node): string { 37 return '...'; 38 } 39 40 protected function pConst(Node\Const_ $node): string { 41 return $node->name . ' = ' . $this->p($node->value); 42 } 43 44 protected function pNullableType(Node\NullableType $node): string { 45 return '?' . $this->p($node->type); 46 } 47 48 protected function pUnionType(Node\UnionType $node): string { 49 $types = []; 50 foreach ($node->types as $typeNode) { 51 if ($typeNode instanceof Node\IntersectionType) { 52 $types[] = '('. $this->p($typeNode) . ')'; 53 continue; 54 } 55 $types[] = $this->p($typeNode); 56 } 57 return implode('|', $types); 58 } 59 60 protected function pIntersectionType(Node\IntersectionType $node): string { 61 return $this->pImplode($node->types, '&'); 62 } 63 64 protected function pIdentifier(Node\Identifier $node): string { 65 return $node->name; 66 } 67 68 protected function pVarLikeIdentifier(Node\VarLikeIdentifier $node): string { 69 return '$' . $node->name; 70 } 71 72 protected function pAttribute(Node\Attribute $node): string { 73 return $this->p($node->name) 74 . ($node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : ''); 75 } 76 77 protected function pAttributeGroup(Node\AttributeGroup $node): string { 78 return '#[' . $this->pCommaSeparated($node->attrs) . ']'; 79 } 80 81 // Names 82 83 protected function pName(Name $node): string { 84 return $node->name; 85 } 86 87 protected function pName_FullyQualified(Name\FullyQualified $node): string { 88 return '\\' . $node->name; 89 } 90 91 protected function pName_Relative(Name\Relative $node): string { 92 return 'namespace\\' . $node->name; 93 } 94 95 // Magic Constants 96 97 protected function pScalar_MagicConst_Class(MagicConst\Class_ $node): string { 98 return '__CLASS__'; 99 } 100 101 protected function pScalar_MagicConst_Dir(MagicConst\Dir $node): string { 102 return '__DIR__'; 103 } 104 105 protected function pScalar_MagicConst_File(MagicConst\File $node): string { 106 return '__FILE__'; 107 } 108 109 protected function pScalar_MagicConst_Function(MagicConst\Function_ $node): string { 110 return '__FUNCTION__'; 111 } 112 113 protected function pScalar_MagicConst_Line(MagicConst\Line $node): string { 114 return '__LINE__'; 115 } 116 117 protected function pScalar_MagicConst_Method(MagicConst\Method $node): string { 118 return '__METHOD__'; 119 } 120 121 protected function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node): string { 122 return '__NAMESPACE__'; 123 } 124 125 protected function pScalar_MagicConst_Trait(MagicConst\Trait_ $node): string { 126 return '__TRAIT__'; 127 } 128 129 protected function pScalar_MagicConst_Property(MagicConst\Property $node): string { 130 return '__PROPERTY__'; 131 } 132 133 // Scalars 134 135 private function indentString(string $str): string { 136 return str_replace("\n", $this->nl, $str); 137 } 138 139 protected function pScalar_String(Scalar\String_ $node): string { 140 $kind = $node->getAttribute('kind', Scalar\String_::KIND_SINGLE_QUOTED); 141 switch ($kind) { 142 case Scalar\String_::KIND_NOWDOC: 143 $label = $node->getAttribute('docLabel'); 144 if ($label && !$this->containsEndLabel($node->value, $label)) { 145 $shouldIdent = $this->phpVersion->supportsFlexibleHeredoc(); 146 $nl = $shouldIdent ? $this->nl : $this->newline; 147 if ($node->value === '') { 148 return "<<<'$label'$nl$label{$this->docStringEndToken}"; 149 } 150 151 // Make sure trailing \r is not combined with following \n into CRLF. 152 if ($node->value[strlen($node->value) - 1] !== "\r") { 153 $value = $shouldIdent ? $this->indentString($node->value) : $node->value; 154 return "<<<'$label'$nl$value$nl$label{$this->docStringEndToken}"; 155 } 156 } 157 /* break missing intentionally */ 158 // no break 159 case Scalar\String_::KIND_SINGLE_QUOTED: 160 return $this->pSingleQuotedString($node->value); 161 case Scalar\String_::KIND_HEREDOC: 162 $label = $node->getAttribute('docLabel'); 163 $escaped = $this->escapeString($node->value, null); 164 if ($label && !$this->containsEndLabel($escaped, $label)) { 165 $nl = $this->phpVersion->supportsFlexibleHeredoc() ? $this->nl : $this->newline; 166 if ($escaped === '') { 167 return "<<<$label$nl$label{$this->docStringEndToken}"; 168 } 169 170 return "<<<$label$nl$escaped$nl$label{$this->docStringEndToken}"; 171 } 172 /* break missing intentionally */ 173 // no break 174 case Scalar\String_::KIND_DOUBLE_QUOTED: 175 return '"' . $this->escapeString($node->value, '"') . '"'; 176 } 177 throw new \Exception('Invalid string kind'); 178 } 179 180 protected function pScalar_InterpolatedString(Scalar\InterpolatedString $node): string { 181 if ($node->getAttribute('kind') === Scalar\String_::KIND_HEREDOC) { 182 $label = $node->getAttribute('docLabel'); 183 if ($label && !$this->encapsedContainsEndLabel($node->parts, $label)) { 184 $nl = $this->phpVersion->supportsFlexibleHeredoc() ? $this->nl : $this->newline; 185 if (count($node->parts) === 1 186 && $node->parts[0] instanceof Node\InterpolatedStringPart 187 && $node->parts[0]->value === '' 188 ) { 189 return "<<<$label$nl$label{$this->docStringEndToken}"; 190 } 191 192 return "<<<$label$nl" . $this->pEncapsList($node->parts, null) 193 . "$nl$label{$this->docStringEndToken}"; 194 } 195 } 196 return '"' . $this->pEncapsList($node->parts, '"') . '"'; 197 } 198 199 protected function pScalar_Int(Scalar\Int_ $node): string { 200 if ($node->value === -\PHP_INT_MAX - 1) { 201 // PHP_INT_MIN cannot be represented as a literal, 202 // because the sign is not part of the literal 203 return '(-' . \PHP_INT_MAX . '-1)'; 204 } 205 206 $kind = $node->getAttribute('kind', Scalar\Int_::KIND_DEC); 207 if (Scalar\Int_::KIND_DEC === $kind) { 208 return (string) $node->value; 209 } 210 211 if ($node->value < 0) { 212 $sign = '-'; 213 $str = (string) -$node->value; 214 } else { 215 $sign = ''; 216 $str = (string) $node->value; 217 } 218 switch ($kind) { 219 case Scalar\Int_::KIND_BIN: 220 return $sign . '0b' . base_convert($str, 10, 2); 221 case Scalar\Int_::KIND_OCT: 222 return $sign . '0' . base_convert($str, 10, 8); 223 case Scalar\Int_::KIND_HEX: 224 return $sign . '0x' . base_convert($str, 10, 16); 225 } 226 throw new \Exception('Invalid number kind'); 227 } 228 229 protected function pScalar_Float(Scalar\Float_ $node): string { 230 if (!is_finite($node->value)) { 231 if ($node->value === \INF) { 232 return '1.0E+1000'; 233 } 234 if ($node->value === -\INF) { 235 return '-1.0E+1000'; 236 } else { 237 return '\NAN'; 238 } 239 } 240 241 // Try to find a short full-precision representation 242 $stringValue = sprintf('%.16G', $node->value); 243 if ($node->value !== (float) $stringValue) { 244 $stringValue = sprintf('%.17G', $node->value); 245 } 246 247 // %G is locale dependent and there exists no locale-independent alternative. We don't want 248 // mess with switching locales here, so let's assume that a comma is the only non-standard 249 // decimal separator we may encounter... 250 $stringValue = str_replace(',', '.', $stringValue); 251 252 // ensure that number is really printed as float 253 return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue; 254 } 255 256 // Assignments 257 258 protected function pExpr_Assign(Expr\Assign $node, int $precedence, int $lhsPrecedence): string { 259 return $this->pPrefixOp(Expr\Assign::class, $this->p($node->var) . ' = ', $node->expr, $precedence, $lhsPrecedence); 260 } 261 262 protected function pExpr_AssignRef(Expr\AssignRef $node, int $precedence, int $lhsPrecedence): string { 263 return $this->pPrefixOp(Expr\AssignRef::class, $this->p($node->var) . ' =& ', $node->expr, $precedence, $lhsPrecedence); 264 } 265 266 protected function pExpr_AssignOp_Plus(AssignOp\Plus $node, int $precedence, int $lhsPrecedence): string { 267 return $this->pPrefixOp(AssignOp\Plus::class, $this->p($node->var) . ' += ', $node->expr, $precedence, $lhsPrecedence); 268 } 269 270 protected function pExpr_AssignOp_Minus(AssignOp\Minus $node, int $precedence, int $lhsPrecedence): string { 271 return $this->pPrefixOp(AssignOp\Minus::class, $this->p($node->var) . ' -= ', $node->expr, $precedence, $lhsPrecedence); 272 } 273 274 protected function pExpr_AssignOp_Mul(AssignOp\Mul $node, int $precedence, int $lhsPrecedence): string { 275 return $this->pPrefixOp(AssignOp\Mul::class, $this->p($node->var) . ' *= ', $node->expr, $precedence, $lhsPrecedence); 276 } 277 278 protected function pExpr_AssignOp_Div(AssignOp\Div $node, int $precedence, int $lhsPrecedence): string { 279 return $this->pPrefixOp(AssignOp\Div::class, $this->p($node->var) . ' /= ', $node->expr, $precedence, $lhsPrecedence); 280 } 281 282 protected function pExpr_AssignOp_Concat(AssignOp\Concat $node, int $precedence, int $lhsPrecedence): string { 283 return $this->pPrefixOp(AssignOp\Concat::class, $this->p($node->var) . ' .= ', $node->expr, $precedence, $lhsPrecedence); 284 } 285 286 protected function pExpr_AssignOp_Mod(AssignOp\Mod $node, int $precedence, int $lhsPrecedence): string { 287 return $this->pPrefixOp(AssignOp\Mod::class, $this->p($node->var) . ' %= ', $node->expr, $precedence, $lhsPrecedence); 288 } 289 290 protected function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node, int $precedence, int $lhsPrecedence): string { 291 return $this->pPrefixOp(AssignOp\BitwiseAnd::class, $this->p($node->var) . ' &= ', $node->expr, $precedence, $lhsPrecedence); 292 } 293 294 protected function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node, int $precedence, int $lhsPrecedence): string { 295 return $this->pPrefixOp(AssignOp\BitwiseOr::class, $this->p($node->var) . ' |= ', $node->expr, $precedence, $lhsPrecedence); 296 } 297 298 protected function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node, int $precedence, int $lhsPrecedence): string { 299 return $this->pPrefixOp(AssignOp\BitwiseXor::class, $this->p($node->var) . ' ^= ', $node->expr, $precedence, $lhsPrecedence); 300 } 301 302 protected function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node, int $precedence, int $lhsPrecedence): string { 303 return $this->pPrefixOp(AssignOp\ShiftLeft::class, $this->p($node->var) . ' <<= ', $node->expr, $precedence, $lhsPrecedence); 304 } 305 306 protected function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node, int $precedence, int $lhsPrecedence): string { 307 return $this->pPrefixOp(AssignOp\ShiftRight::class, $this->p($node->var) . ' >>= ', $node->expr, $precedence, $lhsPrecedence); 308 } 309 310 protected function pExpr_AssignOp_Pow(AssignOp\Pow $node, int $precedence, int $lhsPrecedence): string { 311 return $this->pPrefixOp(AssignOp\Pow::class, $this->p($node->var) . ' **= ', $node->expr, $precedence, $lhsPrecedence); 312 } 313 314 protected function pExpr_AssignOp_Coalesce(AssignOp\Coalesce $node, int $precedence, int $lhsPrecedence): string { 315 return $this->pPrefixOp(AssignOp\Coalesce::class, $this->p($node->var) . ' ??= ', $node->expr, $precedence, $lhsPrecedence); 316 } 317 318 // Binary expressions 319 320 protected function pExpr_BinaryOp_Plus(BinaryOp\Plus $node, int $precedence, int $lhsPrecedence): string { 321 return $this->pInfixOp(BinaryOp\Plus::class, $node->left, ' + ', $node->right, $precedence, $lhsPrecedence); 322 } 323 324 protected function pExpr_BinaryOp_Minus(BinaryOp\Minus $node, int $precedence, int $lhsPrecedence): string { 325 return $this->pInfixOp(BinaryOp\Minus::class, $node->left, ' - ', $node->right, $precedence, $lhsPrecedence); 326 } 327 328 protected function pExpr_BinaryOp_Mul(BinaryOp\Mul $node, int $precedence, int $lhsPrecedence): string { 329 return $this->pInfixOp(BinaryOp\Mul::class, $node->left, ' * ', $node->right, $precedence, $lhsPrecedence); 330 } 331 332 protected function pExpr_BinaryOp_Div(BinaryOp\Div $node, int $precedence, int $lhsPrecedence): string { 333 return $this->pInfixOp(BinaryOp\Div::class, $node->left, ' / ', $node->right, $precedence, $lhsPrecedence); 334 } 335 336 protected function pExpr_BinaryOp_Concat(BinaryOp\Concat $node, int $precedence, int $lhsPrecedence): string { 337 return $this->pInfixOp(BinaryOp\Concat::class, $node->left, ' . ', $node->right, $precedence, $lhsPrecedence); 338 } 339 340 protected function pExpr_BinaryOp_Mod(BinaryOp\Mod $node, int $precedence, int $lhsPrecedence): string { 341 return $this->pInfixOp(BinaryOp\Mod::class, $node->left, ' % ', $node->right, $precedence, $lhsPrecedence); 342 } 343 344 protected function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node, int $precedence, int $lhsPrecedence): string { 345 return $this->pInfixOp(BinaryOp\BooleanAnd::class, $node->left, ' && ', $node->right, $precedence, $lhsPrecedence); 346 } 347 348 protected function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node, int $precedence, int $lhsPrecedence): string { 349 return $this->pInfixOp(BinaryOp\BooleanOr::class, $node->left, ' || ', $node->right, $precedence, $lhsPrecedence); 350 } 351 352 protected function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node, int $precedence, int $lhsPrecedence): string { 353 return $this->pInfixOp(BinaryOp\BitwiseAnd::class, $node->left, ' & ', $node->right, $precedence, $lhsPrecedence); 354 } 355 356 protected function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node, int $precedence, int $lhsPrecedence): string { 357 return $this->pInfixOp(BinaryOp\BitwiseOr::class, $node->left, ' | ', $node->right, $precedence, $lhsPrecedence); 358 } 359 360 protected function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node, int $precedence, int $lhsPrecedence): string { 361 return $this->pInfixOp(BinaryOp\BitwiseXor::class, $node->left, ' ^ ', $node->right, $precedence, $lhsPrecedence); 362 } 363 364 protected function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node, int $precedence, int $lhsPrecedence): string { 365 return $this->pInfixOp(BinaryOp\ShiftLeft::class, $node->left, ' << ', $node->right, $precedence, $lhsPrecedence); 366 } 367 368 protected function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node, int $precedence, int $lhsPrecedence): string { 369 return $this->pInfixOp(BinaryOp\ShiftRight::class, $node->left, ' >> ', $node->right, $precedence, $lhsPrecedence); 370 } 371 372 protected function pExpr_BinaryOp_Pow(BinaryOp\Pow $node, int $precedence, int $lhsPrecedence): string { 373 return $this->pInfixOp(BinaryOp\Pow::class, $node->left, ' ** ', $node->right, $precedence, $lhsPrecedence); 374 } 375 376 protected function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node, int $precedence, int $lhsPrecedence): string { 377 return $this->pInfixOp(BinaryOp\LogicalAnd::class, $node->left, ' and ', $node->right, $precedence, $lhsPrecedence); 378 } 379 380 protected function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node, int $precedence, int $lhsPrecedence): string { 381 return $this->pInfixOp(BinaryOp\LogicalOr::class, $node->left, ' or ', $node->right, $precedence, $lhsPrecedence); 382 } 383 384 protected function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node, int $precedence, int $lhsPrecedence): string { 385 return $this->pInfixOp(BinaryOp\LogicalXor::class, $node->left, ' xor ', $node->right, $precedence, $lhsPrecedence); 386 } 387 388 protected function pExpr_BinaryOp_Equal(BinaryOp\Equal $node, int $precedence, int $lhsPrecedence): string { 389 return $this->pInfixOp(BinaryOp\Equal::class, $node->left, ' == ', $node->right, $precedence, $lhsPrecedence); 390 } 391 392 protected function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node, int $precedence, int $lhsPrecedence): string { 393 return $this->pInfixOp(BinaryOp\NotEqual::class, $node->left, ' != ', $node->right, $precedence, $lhsPrecedence); 394 } 395 396 protected function pExpr_BinaryOp_Identical(BinaryOp\Identical $node, int $precedence, int $lhsPrecedence): string { 397 return $this->pInfixOp(BinaryOp\Identical::class, $node->left, ' === ', $node->right, $precedence, $lhsPrecedence); 398 } 399 400 protected function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node, int $precedence, int $lhsPrecedence): string { 401 return $this->pInfixOp(BinaryOp\NotIdentical::class, $node->left, ' !== ', $node->right, $precedence, $lhsPrecedence); 402 } 403 404 protected function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node, int $precedence, int $lhsPrecedence): string { 405 return $this->pInfixOp(BinaryOp\Spaceship::class, $node->left, ' <=> ', $node->right, $precedence, $lhsPrecedence); 406 } 407 408 protected function pExpr_BinaryOp_Greater(BinaryOp\Greater $node, int $precedence, int $lhsPrecedence): string { 409 return $this->pInfixOp(BinaryOp\Greater::class, $node->left, ' > ', $node->right, $precedence, $lhsPrecedence); 410 } 411 412 protected function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node, int $precedence, int $lhsPrecedence): string { 413 return $this->pInfixOp(BinaryOp\GreaterOrEqual::class, $node->left, ' >= ', $node->right, $precedence, $lhsPrecedence); 414 } 415 416 protected function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node, int $precedence, int $lhsPrecedence): string { 417 return $this->pInfixOp(BinaryOp\Smaller::class, $node->left, ' < ', $node->right, $precedence, $lhsPrecedence); 418 } 419 420 protected function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node, int $precedence, int $lhsPrecedence): string { 421 return $this->pInfixOp(BinaryOp\SmallerOrEqual::class, $node->left, ' <= ', $node->right, $precedence, $lhsPrecedence); 422 } 423 424 protected function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node, int $precedence, int $lhsPrecedence): string { 425 return $this->pInfixOp(BinaryOp\Coalesce::class, $node->left, ' ?? ', $node->right, $precedence, $lhsPrecedence); 426 } 427 428 protected function pExpr_Instanceof(Expr\Instanceof_ $node, int $precedence, int $lhsPrecedence): string { 429 return $this->pPostfixOp( 430 Expr\Instanceof_::class, $node->expr, 431 ' instanceof ' . $this->pNewOperand($node->class), 432 $precedence, $lhsPrecedence); 433 } 434 435 // Unary expressions 436 437 protected function pExpr_BooleanNot(Expr\BooleanNot $node, int $precedence, int $lhsPrecedence): string { 438 return $this->pPrefixOp(Expr\BooleanNot::class, '!', $node->expr, $precedence, $lhsPrecedence); 439 } 440 441 protected function pExpr_BitwiseNot(Expr\BitwiseNot $node, int $precedence, int $lhsPrecedence): string { 442 return $this->pPrefixOp(Expr\BitwiseNot::class, '~', $node->expr, $precedence, $lhsPrecedence); 443 } 444 445 protected function pExpr_UnaryMinus(Expr\UnaryMinus $node, int $precedence, int $lhsPrecedence): string { 446 return $this->pPrefixOp(Expr\UnaryMinus::class, '-', $node->expr, $precedence, $lhsPrecedence); 447 } 448 449 protected function pExpr_UnaryPlus(Expr\UnaryPlus $node, int $precedence, int $lhsPrecedence): string { 450 return $this->pPrefixOp(Expr\UnaryPlus::class, '+', $node->expr, $precedence, $lhsPrecedence); 451 } 452 453 protected function pExpr_PreInc(Expr\PreInc $node): string { 454 return '++' . $this->p($node->var); 455 } 456 457 protected function pExpr_PreDec(Expr\PreDec $node): string { 458 return '--' . $this->p($node->var); 459 } 460 461 protected function pExpr_PostInc(Expr\PostInc $node): string { 462 return $this->p($node->var) . '++'; 463 } 464 465 protected function pExpr_PostDec(Expr\PostDec $node): string { 466 return $this->p($node->var) . '--'; 467 } 468 469 protected function pExpr_ErrorSuppress(Expr\ErrorSuppress $node, int $precedence, int $lhsPrecedence): string { 470 return $this->pPrefixOp(Expr\ErrorSuppress::class, '@', $node->expr, $precedence, $lhsPrecedence); 471 } 472 473 protected function pExpr_YieldFrom(Expr\YieldFrom $node, int $precedence, int $lhsPrecedence): string { 474 return $this->pPrefixOp(Expr\YieldFrom::class, 'yield from ', $node->expr, $precedence, $lhsPrecedence); 475 } 476 477 protected function pExpr_Print(Expr\Print_ $node, int $precedence, int $lhsPrecedence): string { 478 return $this->pPrefixOp(Expr\Print_::class, 'print ', $node->expr, $precedence, $lhsPrecedence); 479 } 480 481 // Casts 482 483 protected function pExpr_Cast_Int(Cast\Int_ $node, int $precedence, int $lhsPrecedence): string { 484 return $this->pPrefixOp(Cast\Int_::class, '(int) ', $node->expr, $precedence, $lhsPrecedence); 485 } 486 487 protected function pExpr_Cast_Double(Cast\Double $node, int $precedence, int $lhsPrecedence): string { 488 $kind = $node->getAttribute('kind', Cast\Double::KIND_DOUBLE); 489 if ($kind === Cast\Double::KIND_DOUBLE) { 490 $cast = '(double)'; 491 } elseif ($kind === Cast\Double::KIND_FLOAT) { 492 $cast = '(float)'; 493 } else { 494 assert($kind === Cast\Double::KIND_REAL); 495 $cast = '(real)'; 496 } 497 return $this->pPrefixOp(Cast\Double::class, $cast . ' ', $node->expr, $precedence, $lhsPrecedence); 498 } 499 500 protected function pExpr_Cast_String(Cast\String_ $node, int $precedence, int $lhsPrecedence): string { 501 return $this->pPrefixOp(Cast\String_::class, '(string) ', $node->expr, $precedence, $lhsPrecedence); 502 } 503 504 protected function pExpr_Cast_Array(Cast\Array_ $node, int $precedence, int $lhsPrecedence): string { 505 return $this->pPrefixOp(Cast\Array_::class, '(array) ', $node->expr, $precedence, $lhsPrecedence); 506 } 507 508 protected function pExpr_Cast_Object(Cast\Object_ $node, int $precedence, int $lhsPrecedence): string { 509 return $this->pPrefixOp(Cast\Object_::class, '(object) ', $node->expr, $precedence, $lhsPrecedence); 510 } 511 512 protected function pExpr_Cast_Bool(Cast\Bool_ $node, int $precedence, int $lhsPrecedence): string { 513 return $this->pPrefixOp(Cast\Bool_::class, '(bool) ', $node->expr, $precedence, $lhsPrecedence); 514 } 515 516 protected function pExpr_Cast_Unset(Cast\Unset_ $node, int $precedence, int $lhsPrecedence): string { 517 return $this->pPrefixOp(Cast\Unset_::class, '(unset) ', $node->expr, $precedence, $lhsPrecedence); 518 } 519 520 // Function calls and similar constructs 521 522 protected function pExpr_FuncCall(Expr\FuncCall $node): string { 523 return $this->pCallLhs($node->name) 524 . '(' . $this->pMaybeMultiline($node->args) . ')'; 525 } 526 527 protected function pExpr_MethodCall(Expr\MethodCall $node): string { 528 return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name) 529 . '(' . $this->pMaybeMultiline($node->args) . ')'; 530 } 531 532 protected function pExpr_NullsafeMethodCall(Expr\NullsafeMethodCall $node): string { 533 return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name) 534 . '(' . $this->pMaybeMultiline($node->args) . ')'; 535 } 536 537 protected function pExpr_StaticCall(Expr\StaticCall $node): string { 538 return $this->pStaticDereferenceLhs($node->class) . '::' 539 . ($node->name instanceof Expr 540 ? ($node->name instanceof Expr\Variable 541 ? $this->p($node->name) 542 : '{' . $this->p($node->name) . '}') 543 : $node->name) 544 . '(' . $this->pMaybeMultiline($node->args) . ')'; 545 } 546 547 protected function pExpr_Empty(Expr\Empty_ $node): string { 548 return 'empty(' . $this->p($node->expr) . ')'; 549 } 550 551 protected function pExpr_Isset(Expr\Isset_ $node): string { 552 return 'isset(' . $this->pCommaSeparated($node->vars) . ')'; 553 } 554 555 protected function pExpr_Eval(Expr\Eval_ $node): string { 556 return 'eval(' . $this->p($node->expr) . ')'; 557 } 558 559 protected function pExpr_Include(Expr\Include_ $node, int $precedence, int $lhsPrecedence): string { 560 static $map = [ 561 Expr\Include_::TYPE_INCLUDE => 'include', 562 Expr\Include_::TYPE_INCLUDE_ONCE => 'include_once', 563 Expr\Include_::TYPE_REQUIRE => 'require', 564 Expr\Include_::TYPE_REQUIRE_ONCE => 'require_once', 565 ]; 566 567 return $this->pPrefixOp(Expr\Include_::class, $map[$node->type] . ' ', $node->expr, $precedence, $lhsPrecedence); 568 } 569 570 protected function pExpr_List(Expr\List_ $node): string { 571 $syntax = $node->getAttribute('kind', 572 $this->phpVersion->supportsShortArrayDestructuring() ? Expr\List_::KIND_ARRAY : Expr\List_::KIND_LIST); 573 if ($syntax === Expr\List_::KIND_ARRAY) { 574 return '[' . $this->pMaybeMultiline($node->items, true) . ']'; 575 } else { 576 return 'list(' . $this->pMaybeMultiline($node->items, true) . ')'; 577 } 578 } 579 580 // Other 581 582 protected function pExpr_Error(Expr\Error $node): string { 583 throw new \LogicException('Cannot pretty-print AST with Error nodes'); 584 } 585 586 protected function pExpr_Variable(Expr\Variable $node): string { 587 if ($node->name instanceof Expr) { 588 return '${' . $this->p($node->name) . '}'; 589 } else { 590 return '$' . $node->name; 591 } 592 } 593 594 protected function pExpr_Array(Expr\Array_ $node): string { 595 $syntax = $node->getAttribute('kind', 596 $this->shortArraySyntax ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG); 597 if ($syntax === Expr\Array_::KIND_SHORT) { 598 return '[' . $this->pMaybeMultiline($node->items, true) . ']'; 599 } else { 600 return 'array(' . $this->pMaybeMultiline($node->items, true) . ')'; 601 } 602 } 603 604 protected function pKey(?Node $node): string { 605 if ($node === null) { 606 return ''; 607 } 608 609 // => is not really an operator and does not typically participate in precedence resolution. 610 // However, there is an exception if yield expressions with keys are involved: 611 // [yield $a => $b] is interpreted as [(yield $a => $b)], so we need to ensure that 612 // [(yield $a) => $b] is printed with parentheses. We approximate this by lowering the LHS 613 // precedence to that of yield (which will also print unnecessary parentheses for rare low 614 // precedence unary operators like include). 615 $yieldPrecedence = $this->precedenceMap[Expr\Yield_::class][0]; 616 return $this->p($node, self::MAX_PRECEDENCE, $yieldPrecedence) . ' => '; 617 } 618 619 protected function pArrayItem(Node\ArrayItem $node): string { 620 return $this->pKey($node->key) 621 . ($node->byRef ? '&' : '') 622 . ($node->unpack ? '...' : '') 623 . $this->p($node->value); 624 } 625 626 protected function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node): string { 627 return $this->pDereferenceLhs($node->var) 628 . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']'; 629 } 630 631 protected function pExpr_ConstFetch(Expr\ConstFetch $node): string { 632 return $this->p($node->name); 633 } 634 635 protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node): string { 636 return $this->pStaticDereferenceLhs($node->class) . '::' . $this->pObjectProperty($node->name); 637 } 638 639 protected function pExpr_PropertyFetch(Expr\PropertyFetch $node): string { 640 return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name); 641 } 642 643 protected function pExpr_NullsafePropertyFetch(Expr\NullsafePropertyFetch $node): string { 644 return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name); 645 } 646 647 protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node): string { 648 return $this->pStaticDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name); 649 } 650 651 protected function pExpr_ShellExec(Expr\ShellExec $node): string { 652 return '`' . $this->pEncapsList($node->parts, '`') . '`'; 653 } 654 655 protected function pExpr_Closure(Expr\Closure $node): string { 656 return $this->pAttrGroups($node->attrGroups, true) 657 . $this->pStatic($node->static) 658 . 'function ' . ($node->byRef ? '&' : '') 659 . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' 660 . (!empty($node->uses) ? ' use (' . $this->pCommaSeparated($node->uses) . ')' : '') 661 . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') 662 . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; 663 } 664 665 protected function pExpr_Match(Expr\Match_ $node): string { 666 return 'match (' . $this->p($node->cond) . ') {' 667 . $this->pCommaSeparatedMultiline($node->arms, true) 668 . $this->nl 669 . '}'; 670 } 671 672 protected function pMatchArm(Node\MatchArm $node): string { 673 $result = ''; 674 if ($node->conds) { 675 for ($i = 0, $c = \count($node->conds); $i + 1 < $c; $i++) { 676 $result .= $this->p($node->conds[$i]) . ', '; 677 } 678 $result .= $this->pKey($node->conds[$i]); 679 } else { 680 $result = 'default => '; 681 } 682 return $result . $this->p($node->body); 683 } 684 685 protected function pExpr_ArrowFunction(Expr\ArrowFunction $node, int $precedence, int $lhsPrecedence): string { 686 return $this->pPrefixOp( 687 Expr\ArrowFunction::class, 688 $this->pAttrGroups($node->attrGroups, true) 689 . $this->pStatic($node->static) 690 . 'fn' . ($node->byRef ? '&' : '') 691 . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' 692 . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') 693 . ' => ', 694 $node->expr, $precedence, $lhsPrecedence); 695 } 696 697 protected function pClosureUse(Node\ClosureUse $node): string { 698 return ($node->byRef ? '&' : '') . $this->p($node->var); 699 } 700 701 protected function pExpr_New(Expr\New_ $node): string { 702 if ($node->class instanceof Stmt\Class_) { 703 $args = $node->args ? '(' . $this->pMaybeMultiline($node->args) . ')' : ''; 704 return 'new ' . $this->pClassCommon($node->class, $args); 705 } 706 return 'new ' . $this->pNewOperand($node->class) 707 . '(' . $this->pMaybeMultiline($node->args) . ')'; 708 } 709 710 protected function pExpr_Clone(Expr\Clone_ $node, int $precedence, int $lhsPrecedence): string { 711 return $this->pPrefixOp(Expr\Clone_::class, 'clone ', $node->expr, $precedence, $lhsPrecedence); 712 } 713 714 protected function pExpr_Ternary(Expr\Ternary $node, int $precedence, int $lhsPrecedence): string { 715 // a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator. 716 // this is okay because the part between ? and : never needs parentheses. 717 return $this->pInfixOp(Expr\Ternary::class, 718 $node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else, 719 $precedence, $lhsPrecedence 720 ); 721 } 722 723 protected function pExpr_Exit(Expr\Exit_ $node): string { 724 $kind = $node->getAttribute('kind', Expr\Exit_::KIND_DIE); 725 return ($kind === Expr\Exit_::KIND_EXIT ? 'exit' : 'die') 726 . (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : ''); 727 } 728 729 protected function pExpr_Throw(Expr\Throw_ $node, int $precedence, int $lhsPrecedence): string { 730 return $this->pPrefixOp(Expr\Throw_::class, 'throw ', $node->expr, $precedence, $lhsPrecedence); 731 } 732 733 protected function pExpr_Yield(Expr\Yield_ $node, int $precedence, int $lhsPrecedence): string { 734 if ($node->value === null) { 735 $opPrecedence = $this->precedenceMap[Expr\Yield_::class][0]; 736 return $opPrecedence >= $lhsPrecedence ? '(yield)' : 'yield'; 737 } else { 738 if (!$this->phpVersion->supportsYieldWithoutParentheses()) { 739 return '(yield ' . $this->pKey($node->key) . $this->p($node->value) . ')'; 740 } 741 return $this->pPrefixOp( 742 Expr\Yield_::class, 'yield ' . $this->pKey($node->key), 743 $node->value, $precedence, $lhsPrecedence); 744 } 745 } 746 747 // Declarations 748 749 protected function pStmt_Namespace(Stmt\Namespace_ $node): string { 750 if ($this->canUseSemicolonNamespaces) { 751 return 'namespace ' . $this->p($node->name) . ';' 752 . $this->nl . $this->pStmts($node->stmts, false); 753 } else { 754 return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '') 755 . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; 756 } 757 } 758 759 protected function pStmt_Use(Stmt\Use_ $node): string { 760 return 'use ' . $this->pUseType($node->type) 761 . $this->pCommaSeparated($node->uses) . ';'; 762 } 763 764 protected function pStmt_GroupUse(Stmt\GroupUse $node): string { 765 return 'use ' . $this->pUseType($node->type) . $this->pName($node->prefix) 766 . '\{' . $this->pCommaSeparated($node->uses) . '};'; 767 } 768 769 protected function pUseItem(Node\UseItem $node): string { 770 return $this->pUseType($node->type) . $this->p($node->name) 771 . (null !== $node->alias ? ' as ' . $node->alias : ''); 772 } 773 774 protected function pUseType(int $type): string { 775 return $type === Stmt\Use_::TYPE_FUNCTION ? 'function ' 776 : ($type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : ''); 777 } 778 779 protected function pStmt_Interface(Stmt\Interface_ $node): string { 780 return $this->pAttrGroups($node->attrGroups) 781 . 'interface ' . $node->name 782 . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '') 783 . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; 784 } 785 786 protected function pStmt_Enum(Stmt\Enum_ $node): string { 787 return $this->pAttrGroups($node->attrGroups) 788 . 'enum ' . $node->name 789 . ($node->scalarType ? ' : ' . $this->p($node->scalarType) : '') 790 . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') 791 . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; 792 } 793 794 protected function pStmt_Class(Stmt\Class_ $node): string { 795 return $this->pClassCommon($node, ' ' . $node->name); 796 } 797 798 protected function pStmt_Trait(Stmt\Trait_ $node): string { 799 return $this->pAttrGroups($node->attrGroups) 800 . 'trait ' . $node->name 801 . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; 802 } 803 804 protected function pStmt_EnumCase(Stmt\EnumCase $node): string { 805 return $this->pAttrGroups($node->attrGroups) 806 . 'case ' . $node->name 807 . ($node->expr ? ' = ' . $this->p($node->expr) : '') 808 . ';'; 809 } 810 811 protected function pStmt_TraitUse(Stmt\TraitUse $node): string { 812 return 'use ' . $this->pCommaSeparated($node->traits) 813 . (empty($node->adaptations) 814 ? ';' 815 : ' {' . $this->pStmts($node->adaptations) . $this->nl . '}'); 816 } 817 818 protected function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node): string { 819 return $this->p($node->trait) . '::' . $node->method 820 . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';'; 821 } 822 823 protected function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node): string { 824 return (null !== $node->trait ? $this->p($node->trait) . '::' : '') 825 . $node->method . ' as' 826 . (null !== $node->newModifier ? ' ' . rtrim($this->pModifiers($node->newModifier), ' ') : '') 827 . (null !== $node->newName ? ' ' . $node->newName : '') 828 . ';'; 829 } 830 831 protected function pStmt_Property(Stmt\Property $node): string { 832 return $this->pAttrGroups($node->attrGroups) 833 . (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags)) 834 . ($node->type ? $this->p($node->type) . ' ' : '') 835 . $this->pCommaSeparated($node->props) 836 . ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : ';'); 837 } 838 839 protected function pPropertyItem(Node\PropertyItem $node): string { 840 return '$' . $node->name 841 . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); 842 } 843 844 protected function pPropertyHook(Node\PropertyHook $node): string { 845 return $this->pAttrGroups($node->attrGroups) 846 . $this->pModifiers($node->flags) 847 . ($node->byRef ? '&' : '') . $node->name 848 . ($node->params ? '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' : '') 849 . (\is_array($node->body) ? ' {' . $this->pStmts($node->body) . $this->nl . '}' 850 : ($node->body !== null ? ' => ' . $this->p($node->body) : '') . ';'); 851 } 852 853 protected function pStmt_ClassMethod(Stmt\ClassMethod $node): string { 854 return $this->pAttrGroups($node->attrGroups) 855 . $this->pModifiers($node->flags) 856 . 'function ' . ($node->byRef ? '&' : '') . $node->name 857 . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' 858 . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') 859 . (null !== $node->stmts 860 ? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}' 861 : ';'); 862 } 863 864 protected function pStmt_ClassConst(Stmt\ClassConst $node): string { 865 return $this->pAttrGroups($node->attrGroups) 866 . $this->pModifiers($node->flags) 867 . 'const ' 868 . (null !== $node->type ? $this->p($node->type) . ' ' : '') 869 . $this->pCommaSeparated($node->consts) . ';'; 870 } 871 872 protected function pStmt_Function(Stmt\Function_ $node): string { 873 return $this->pAttrGroups($node->attrGroups) 874 . 'function ' . ($node->byRef ? '&' : '') . $node->name 875 . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' 876 . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') 877 . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; 878 } 879 880 protected function pStmt_Const(Stmt\Const_ $node): string { 881 return 'const ' . $this->pCommaSeparated($node->consts) . ';'; 882 } 883 884 protected function pStmt_Declare(Stmt\Declare_ $node): string { 885 return 'declare (' . $this->pCommaSeparated($node->declares) . ')' 886 . (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . $this->nl . '}' : ';'); 887 } 888 889 protected function pDeclareItem(Node\DeclareItem $node): string { 890 return $node->key . '=' . $this->p($node->value); 891 } 892 893 // Control flow 894 895 protected function pStmt_If(Stmt\If_ $node): string { 896 return 'if (' . $this->p($node->cond) . ') {' 897 . $this->pStmts($node->stmts) . $this->nl . '}' 898 . ($node->elseifs ? ' ' . $this->pImplode($node->elseifs, ' ') : '') 899 . (null !== $node->else ? ' ' . $this->p($node->else) : ''); 900 } 901 902 protected function pStmt_ElseIf(Stmt\ElseIf_ $node): string { 903 return 'elseif (' . $this->p($node->cond) . ') {' 904 . $this->pStmts($node->stmts) . $this->nl . '}'; 905 } 906 907 protected function pStmt_Else(Stmt\Else_ $node): string { 908 if (\count($node->stmts) === 1 && $node->stmts[0] instanceof Stmt\If_) { 909 // Print as "else if" rather than "else { if }" 910 return 'else ' . $this->p($node->stmts[0]); 911 } 912 return 'else {' . $this->pStmts($node->stmts) . $this->nl . '}'; 913 } 914 915 protected function pStmt_For(Stmt\For_ $node): string { 916 return 'for (' 917 . $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '') 918 . $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '') 919 . $this->pCommaSeparated($node->loop) 920 . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; 921 } 922 923 protected function pStmt_Foreach(Stmt\Foreach_ $node): string { 924 return 'foreach (' . $this->p($node->expr) . ' as ' 925 . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '') 926 . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {' 927 . $this->pStmts($node->stmts) . $this->nl . '}'; 928 } 929 930 protected function pStmt_While(Stmt\While_ $node): string { 931 return 'while (' . $this->p($node->cond) . ') {' 932 . $this->pStmts($node->stmts) . $this->nl . '}'; 933 } 934 935 protected function pStmt_Do(Stmt\Do_ $node): string { 936 return 'do {' . $this->pStmts($node->stmts) . $this->nl 937 . '} while (' . $this->p($node->cond) . ');'; 938 } 939 940 protected function pStmt_Switch(Stmt\Switch_ $node): string { 941 return 'switch (' . $this->p($node->cond) . ') {' 942 . $this->pStmts($node->cases) . $this->nl . '}'; 943 } 944 945 protected function pStmt_Case(Stmt\Case_ $node): string { 946 return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':' 947 . $this->pStmts($node->stmts); 948 } 949 950 protected function pStmt_TryCatch(Stmt\TryCatch $node): string { 951 return 'try {' . $this->pStmts($node->stmts) . $this->nl . '}' 952 . ($node->catches ? ' ' . $this->pImplode($node->catches, ' ') : '') 953 . ($node->finally !== null ? ' ' . $this->p($node->finally) : ''); 954 } 955 956 protected function pStmt_Catch(Stmt\Catch_ $node): string { 957 return 'catch (' . $this->pImplode($node->types, '|') 958 . ($node->var !== null ? ' ' . $this->p($node->var) : '') 959 . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; 960 } 961 962 protected function pStmt_Finally(Stmt\Finally_ $node): string { 963 return 'finally {' . $this->pStmts($node->stmts) . $this->nl . '}'; 964 } 965 966 protected function pStmt_Break(Stmt\Break_ $node): string { 967 return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; 968 } 969 970 protected function pStmt_Continue(Stmt\Continue_ $node): string { 971 return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; 972 } 973 974 protected function pStmt_Return(Stmt\Return_ $node): string { 975 return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';'; 976 } 977 978 protected function pStmt_Label(Stmt\Label $node): string { 979 return $node->name . ':'; 980 } 981 982 protected function pStmt_Goto(Stmt\Goto_ $node): string { 983 return 'goto ' . $node->name . ';'; 984 } 985 986 // Other 987 988 protected function pStmt_Expression(Stmt\Expression $node): string { 989 return $this->p($node->expr) . ';'; 990 } 991 992 protected function pStmt_Echo(Stmt\Echo_ $node): string { 993 return 'echo ' . $this->pCommaSeparated($node->exprs) . ';'; 994 } 995 996 protected function pStmt_Static(Stmt\Static_ $node): string { 997 return 'static ' . $this->pCommaSeparated($node->vars) . ';'; 998 } 999 1000 protected function pStmt_Global(Stmt\Global_ $node): string { 1001 return 'global ' . $this->pCommaSeparated($node->vars) . ';'; 1002 } 1003 1004 protected function pStaticVar(Node\StaticVar $node): string { 1005 return $this->p($node->var) 1006 . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); 1007 } 1008 1009 protected function pStmt_Unset(Stmt\Unset_ $node): string { 1010 return 'unset(' . $this->pCommaSeparated($node->vars) . ');'; 1011 } 1012 1013 protected function pStmt_InlineHTML(Stmt\InlineHTML $node): string { 1014 $newline = $node->getAttribute('hasLeadingNewline', true) ? $this->newline : ''; 1015 return '?>' . $newline . $node->value . '<?php '; 1016 } 1017 1018 protected function pStmt_HaltCompiler(Stmt\HaltCompiler $node): string { 1019 return '__halt_compiler();' . $node->remaining; 1020 } 1021 1022 protected function pStmt_Nop(Stmt\Nop $node): string { 1023 return ''; 1024 } 1025 1026 protected function pStmt_Block(Stmt\Block $node): string { 1027 return '{' . $this->pStmts($node->stmts) . $this->nl . '}'; 1028 } 1029 1030 // Helpers 1031 1032 protected function pClassCommon(Stmt\Class_ $node, string $afterClassToken): string { 1033 return $this->pAttrGroups($node->attrGroups, $node->name === null) 1034 . $this->pModifiers($node->flags) 1035 . 'class' . $afterClassToken 1036 . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '') 1037 . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') 1038 . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; 1039 } 1040 1041 protected function pObjectProperty(Node $node): string { 1042 if ($node instanceof Expr) { 1043 return '{' . $this->p($node) . '}'; 1044 } else { 1045 assert($node instanceof Node\Identifier); 1046 return $node->name; 1047 } 1048 } 1049 1050 /** @param (Expr|Node\InterpolatedStringPart)[] $encapsList */ 1051 protected function pEncapsList(array $encapsList, ?string $quote): string { 1052 $return = ''; 1053 foreach ($encapsList as $element) { 1054 if ($element instanceof Node\InterpolatedStringPart) { 1055 $return .= $this->escapeString($element->value, $quote); 1056 } else { 1057 $return .= '{' . $this->p($element) . '}'; 1058 } 1059 } 1060 1061 return $return; 1062 } 1063 1064 protected function pSingleQuotedString(string $string): string { 1065 // It is idiomatic to only escape backslashes when necessary, i.e. when followed by ', \ or 1066 // the end of the string ('Foo\Bar' instead of 'Foo\\Bar'). However, we also don't want to 1067 // produce an odd number of backslashes, so '\\\\a' should not get rendered as '\\\a', even 1068 // though that would be legal. 1069 $regex = '/\'|\\\\(?=[\'\\\\]|$)|(?<=\\\\)\\\\/'; 1070 return '\'' . preg_replace($regex, '\\\\$0', $string) . '\''; 1071 } 1072 1073 protected function escapeString(string $string, ?string $quote): string { 1074 if (null === $quote) { 1075 // For doc strings, don't escape newlines 1076 $escaped = addcslashes($string, "\t\f\v$\\"); 1077 // But do escape isolated \r. Combined with the terminating newline, it might get 1078 // interpreted as \r\n and dropped from the string contents. 1079 $escaped = preg_replace('/\r(?!\n)/', '\\r', $escaped); 1080 if ($this->phpVersion->supportsFlexibleHeredoc()) { 1081 $escaped = $this->indentString($escaped); 1082 } 1083 } else { 1084 $escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\"); 1085 } 1086 1087 // Escape control characters and non-UTF-8 characters. 1088 // Regex based on https://stackoverflow.com/a/11709412/385378. 1089 $regex = '/( 1090 [\x00-\x08\x0E-\x1F] # Control characters 1091 | [\xC0-\xC1] # Invalid UTF-8 Bytes 1092 | [\xF5-\xFF] # Invalid UTF-8 Bytes 1093 | \xE0(?=[\x80-\x9F]) # Overlong encoding of prior code point 1094 | \xF0(?=[\x80-\x8F]) # Overlong encoding of prior code point 1095 | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start 1096 | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start 1097 | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start 1098 | (?<=[\x00-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle 1099 | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence 1100 | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence 1101 | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence 1102 | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2) 1103 )/x'; 1104 return preg_replace_callback($regex, function ($matches): string { 1105 assert(strlen($matches[0]) === 1); 1106 $hex = dechex(ord($matches[0])); 1107 return '\\x' . str_pad($hex, 2, '0', \STR_PAD_LEFT); 1108 }, $escaped); 1109 } 1110 1111 protected function containsEndLabel(string $string, string $label, bool $atStart = true): bool { 1112 $start = $atStart ? '(?:^|[\r\n])[ \t]*' : '[\r\n][ \t]*'; 1113 return false !== strpos($string, $label) 1114 && preg_match('/' . $start . $label . '(?:$|[^_A-Za-z0-9\x80-\xff])/', $string); 1115 } 1116 1117 /** @param (Expr|Node\InterpolatedStringPart)[] $parts */ 1118 protected function encapsedContainsEndLabel(array $parts, string $label): bool { 1119 foreach ($parts as $i => $part) { 1120 if ($part instanceof Node\InterpolatedStringPart 1121 && $this->containsEndLabel($this->escapeString($part->value, null), $label, $i === 0) 1122 ) { 1123 return true; 1124 } 1125 } 1126 return false; 1127 } 1128 1129 protected function pDereferenceLhs(Node $node): string { 1130 if (!$this->dereferenceLhsRequiresParens($node)) { 1131 return $this->p($node); 1132 } else { 1133 return '(' . $this->p($node) . ')'; 1134 } 1135 } 1136 1137 protected function pStaticDereferenceLhs(Node $node): string { 1138 if (!$this->staticDereferenceLhsRequiresParens($node)) { 1139 return $this->p($node); 1140 } else { 1141 return '(' . $this->p($node) . ')'; 1142 } 1143 } 1144 1145 protected function pCallLhs(Node $node): string { 1146 if (!$this->callLhsRequiresParens($node)) { 1147 return $this->p($node); 1148 } else { 1149 return '(' . $this->p($node) . ')'; 1150 } 1151 } 1152 1153 protected function pNewOperand(Node $node): string { 1154 if (!$this->newOperandRequiresParens($node)) { 1155 return $this->p($node); 1156 } else { 1157 return '(' . $this->p($node) . ')'; 1158 } 1159 } 1160 1161 /** 1162 * @param Node[] $nodes 1163 */ 1164 protected function hasNodeWithComments(array $nodes): bool { 1165 foreach ($nodes as $node) { 1166 if ($node && $node->getComments()) { 1167 return true; 1168 } 1169 } 1170 return false; 1171 } 1172 1173 /** @param Node[] $nodes */ 1174 protected function pMaybeMultiline(array $nodes, bool $trailingComma = false): string { 1175 if (!$this->hasNodeWithComments($nodes)) { 1176 return $this->pCommaSeparated($nodes); 1177 } else { 1178 return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl; 1179 } 1180 } 1181 1182 /** @param Node\AttributeGroup[] $nodes */ 1183 protected function pAttrGroups(array $nodes, bool $inline = false): string { 1184 $result = ''; 1185 $sep = $inline ? ' ' : $this->nl; 1186 foreach ($nodes as $node) { 1187 $result .= $this->p($node) . $sep; 1188 } 1189 1190 return $result; 1191 } 1192} 1193