PluginProbe
WPIDE – File Manager & Code Editor / 3.5.9
WPIDE – File Manager & Code Editor v3.5.9
3.5.9 3.5.8 3.5.7 2.0.14 2.0.15 2.0.16 2.0.2 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1 2.2 2.3 2.3.1 2.3.2 2.4.0 2.5 2.6 3.0 3.1 3.2 3.3 All 55 releases
wpide / vendor / nikic / php-parser / lib / PhpParser / PrettyPrinter / Standard.php

Standard.php in WPIDE – File Manager & Code Editor 3.5.9, at vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php

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