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