Standard.php 53 KB

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