├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src └── AstReverter │ └── AstReverter.php └── tests ├── AstReverterTest.php └── AstReverterTests ├── classes.test ├── closures.test ├── functions.test ├── generators.test ├── languageConstructs.test ├── loops.test ├── methodDefinitions.test ├── misc.test ├── namespaces.test ├── php71.test ├── propertyDefinitions.test ├── strings.test ├── traits.test ├── tryCatch.test └── variableSyntax.test /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | phpunit.xml 3 | composer.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - 7.1 6 | - nightly 7 | 8 | matrix: 9 | allow_failures: 10 | - php: nightly 11 | fast_finish: true 12 | 13 | install: 14 | - composer install 15 | - pecl install ast 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Thomas Punt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-ast-reverter 2 | A tool that reverts an abstract syntax tree (AST) produced by the 3 | [php-ast](https://github.com/nikic/php-ast) extension back into (somewhat) 4 | PSR-compliant code. This enables for code preprocessing to be done. 5 | 6 | Requirements: 7 | - PHP 7.* 8 | - [php-ast](https://github.com/nikic/php-ast) extension (compatible with 9 | versions 30, 35, 40, 45, and 50) 10 | 11 | ## Installation 12 | 13 | ### Composer 14 | ``` 15 | composer require tpunt/php-ast-reverter 16 | ``` 17 | 18 | ## Example 19 | 20 | Running the following code snippet: 21 | ```php 22 | prop = $arg; 50 | } 51 | } 52 | end; 53 | 54 | $ast = ast\parse_code($code, $version=40); 55 | 56 | echo (new AstReverter\AstReverter)->getCode($ast); 57 | ``` 58 | 59 | Will output: 60 | ```php 61 | prop = $arg; 83 | } 84 | } 85 | 86 | ``` 87 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tpunt/php-ast-reverter", 3 | "description": "Reverts an AST back into (somewhat) PSR-compliant code", 4 | "keywords": ["abstract syntax tree", "AST", "parse tree"], 5 | "homepage": "https://github.com/tpunt/php-ast-reverter", 6 | "require": { 7 | "php": ">=7.0", 8 | "ext-ast": "*" 9 | }, 10 | "require-dev": { 11 | "phpunit/phpunit": "^6" 12 | }, 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Thomas Punt" 17 | } 18 | ], 19 | "autoload": { 20 | "psr-4": { 21 | "AstReverter\\": "src/AstReverter" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | 21 | ./src/ 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/AstReverter/AstReverter.php: -------------------------------------------------------------------------------- 1 | sanitiseString($node) . '"'; 33 | default: 34 | // an array, null, etc, should never come through here 35 | assert(false, 'Unknown type ('. gettype($node) . ') found.'); 36 | } 37 | } 38 | 39 | switch ($node->kind) { 40 | case \ast\AST_ARG_LIST: 41 | return $this->argList($node); 42 | case \ast\AST_ARRAY: 43 | return $this->array($node); 44 | case \ast\AST_ARRAY_ELEM: 45 | return $this->arrayElem($node); 46 | case \ast\AST_ASSIGN: 47 | return $this->assign($node); 48 | case \ast\AST_ASSIGN_OP: 49 | return $this->assignOp($node); 50 | case \ast\AST_ASSIGN_REF: 51 | return $this->assignRef($node); 52 | case \ast\AST_BINARY_OP: 53 | return $this->binaryOp($node); 54 | case \ast\AST_BREAK: 55 | return $this->break($node); 56 | case \ast\AST_CALL: 57 | return $this->call($node); 58 | case \ast\AST_CAST: 59 | return $this->cast($node); 60 | case \ast\AST_CATCH: 61 | return $this->catch($node); 62 | case \ast\AST_CATCH_LIST: 63 | return $this->catchList($node); 64 | case \ast\AST_CLASS: 65 | return $this->class($node); 66 | case \ast\AST_CLASS_CONST: 67 | return $this->classConst($node); 68 | case \ast\AST_CLASS_CONST_DECL: 69 | return $this->classConstDecl($node); 70 | case \ast\AST_CLONE: 71 | return $this->clone($node); 72 | case \ast\AST_CLOSURE: 73 | return $this->closure($node); 74 | case \ast\AST_CLOSURE_VAR: 75 | return $this->closureVar($node); 76 | case \ast\AST_COALESCE: 77 | return $this->coalesce($node); 78 | case \ast\AST_CONDITIONAL: 79 | return $this->conditional($node); 80 | case \ast\AST_CONST: 81 | return $this->const($node); 82 | case \ast\AST_CONST_DECL: 83 | return $this->constDecl($node); 84 | case \ast\AST_CONST_ELEM: 85 | return $this->constElem($node); 86 | case \ast\AST_CONTINUE: 87 | return $this->continue($node); 88 | case \ast\AST_DECLARE: 89 | return $this->declare($node); 90 | case \ast\AST_DIM: 91 | return $this->dim($node); 92 | case \ast\AST_DO_WHILE: 93 | return $this->doWhile($node); 94 | case \ast\AST_ECHO: 95 | return $this->echo($node); 96 | case \ast\AST_EMPTY: 97 | return $this->empty($node); 98 | case \ast\AST_ENCAPS_LIST: 99 | return $this->encapsList($node); 100 | case \ast\AST_EXIT: 101 | return $this->exit($node); 102 | case \ast\AST_EXPR_LIST: 103 | return $this->exprList($node); 104 | case \ast\AST_FOR: 105 | return $this->for($node); 106 | case \ast\AST_FOREACH: 107 | return $this->foreach($node); 108 | case \ast\AST_FUNC_DECL: 109 | return $this->funcDecl($node); 110 | case \ast\AST_GLOBAL: 111 | return $this->global($node); 112 | case \ast\AST_GOTO: 113 | return $this->goto($node); 114 | case \ast\AST_GROUP_USE: 115 | return $this->groupUse($node); 116 | case \ast\AST_HALT_COMPILER: 117 | return $this->haltCompiler($node); 118 | case \ast\AST_IF: 119 | return $this->if($node); 120 | case \ast\AST_IF_ELEM: 121 | return $this->ifElem($node); 122 | case \ast\AST_INCLUDE_OR_EVAL: 123 | return $this->includeOrEval($node); 124 | case \ast\AST_INSTANCEOF: 125 | return $this->instanceof($node); 126 | case \ast\AST_ISSET: 127 | return $this->isset($node); 128 | case \ast\AST_LABEL: 129 | return $this->label($node); 130 | case \ast\AST_LIST: 131 | return $this->list($node); 132 | case \ast\AST_MAGIC_CONST: 133 | return $this->magicConst($node); 134 | case \ast\AST_METHOD: 135 | return $this->method($node); 136 | case \ast\AST_METHOD_CALL: 137 | return $this->methodCall($node); 138 | case \ast\AST_METHOD_REFERENCE: 139 | return $this->methodReference($node); 140 | case \ast\AST_NAME: 141 | return $this->name($node); 142 | case \ast\AST_NAMESPACE: 143 | return $this->namespace($node); 144 | case \ast\AST_NAME_LIST: 145 | return $this->nameList($node); 146 | case \ast\AST_NEW: 147 | return $this->new($node); 148 | case \ast\AST_NULLABLE_TYPE: 149 | return $this->nullableType($node); 150 | case \ast\AST_PARAM: 151 | return $this->param($node); 152 | case \ast\AST_PARAM_LIST: 153 | return $this->paramList($node); 154 | case \ast\AST_POST_DEC: 155 | return $this->postDec($node); 156 | case \ast\AST_POST_INC: 157 | return $this->postInc($node); 158 | case \ast\AST_PRE_DEC: 159 | return $this->preDec($node); 160 | case \ast\AST_PRE_INC: 161 | return $this->preInc($node); 162 | case \ast\AST_PRINT: 163 | return $this->print($node); 164 | case \ast\AST_PROP: 165 | return $this->prop($node); 166 | case \ast\AST_PROP_DECL: 167 | return $this->propDecl($node); 168 | case \ast\AST_PROP_ELEM: 169 | return $this->propElem($node); 170 | case \ast\AST_REF: 171 | return $this->ref($node); 172 | case \ast\AST_RETURN: 173 | return $this->return($node); 174 | case \ast\AST_SHELL_EXEC: 175 | return $this->shellExec($node); 176 | case \ast\AST_STATIC: 177 | return $this->static($node); 178 | case \ast\AST_STATIC_CALL: 179 | return $this->staticCall($node); 180 | case \ast\AST_STATIC_PROP: 181 | return $this->staticProp($node); 182 | case \ast\AST_STMT_LIST: 183 | return $this->stmtList($node); 184 | case \ast\AST_SWITCH: 185 | return $this->switch($node); 186 | case \ast\AST_SWITCH_CASE: 187 | return $this->switchCase($node); 188 | case \ast\AST_SWITCH_LIST: 189 | return $this->switchList($node); 190 | case \ast\AST_THROW: 191 | return $this->throw($node); 192 | case \ast\AST_TRAIT_ADAPTATIONS: 193 | return $this->traitAdaptations($node); 194 | case \ast\AST_TRAIT_ALIAS: 195 | return $this->traitAlias($node); 196 | case \ast\AST_TRAIT_PRECEDENCE: 197 | return $this->traitPrecedence($node); 198 | case \ast\AST_TRY: 199 | return $this->try($node); 200 | case \ast\AST_TYPE: 201 | return $this->type($node); 202 | case \ast\AST_UNARY_OP: 203 | return $this->unaryOp($node); 204 | case \ast\AST_UNPACK: 205 | return $this->unpack($node); 206 | case \ast\AST_UNSET: 207 | return $this->unset($node); 208 | case \ast\AST_USE: 209 | return $this->use($node); 210 | case \ast\AST_USE_ELEM: 211 | return $this->useElem($node); 212 | case \ast\AST_USE_TRAIT: 213 | return $this->useTrait($node); 214 | case \ast\AST_VAR: 215 | return $this->var($node); 216 | case \ast\AST_WHILE: 217 | return $this->while($node); 218 | case \ast\AST_YIELD: 219 | return $this->yield($node); 220 | case \ast\AST_YIELD_FROM: 221 | return $this->yieldFrom($node); 222 | default: 223 | assert(false, 'Unknown AST kind (' . \ast\get_kind_name($node->kind) . ') found.'); 224 | } 225 | } 226 | 227 | /** 228 | * Custom method not node-related to encapsulate common logic of 229 | * methodCall() and staticCall() methods. 230 | */ 231 | private function abstractCall(Node $lhs, $rhs, Node $args, string $op) : string 232 | { 233 | $code = ''; 234 | 235 | if ($lhs->kind === \ast\AST_NEW) { 236 | $code .= "({$this->revertAST($lhs)})"; 237 | } else { 238 | $code .= $this->revertAST($lhs); 239 | } 240 | 241 | $code .= $op; 242 | 243 | if ($rhs instanceof Node) { 244 | $code .= $this->revertAST($rhs); 245 | } else { 246 | $code .= $rhs; 247 | } 248 | 249 | $code .= $this->revertAST($args); 250 | 251 | return $code; 252 | } 253 | 254 | /** 255 | * Custom method not node-related to encapsulate common logic of 256 | * prop() and staticProp() methods. 257 | */ 258 | private function abstractProp(Node $lhs, $rhs, string $op) : string 259 | { 260 | $code = ''; 261 | $openBrace = ''; 262 | $closeBrace = ''; 263 | 264 | // Maintain the right associativity 265 | if (isset($rhs->kind) && $rhs->kind !== \ast\AST_VAR) { 266 | $openBrace = '{'; 267 | $closeBrace = '}'; 268 | } 269 | 270 | $code .= $this->revertAST($lhs) 271 | . $op 272 | . $openBrace; 273 | 274 | if ($rhs instanceof Node) { 275 | $code .= $this->revertAST($rhs); 276 | } else { 277 | $code .= $rhs; 278 | } 279 | 280 | $code .= $closeBrace; 281 | 282 | return $code; 283 | } 284 | 285 | private function argList(Node $node) : string 286 | { 287 | return '(' 288 | . $this->commaSeparatedValues($node, ', ') 289 | . ')'; 290 | } 291 | 292 | private function array(Node $node) : string 293 | { 294 | switch ($node->flags) { 295 | case \ast\flags\ARRAY_SYNTAX_LIST: 296 | $code = 'list('; 297 | $closing = ')'; 298 | break; 299 | case \ast\flags\ARRAY_SYNTAX_LONG: 300 | $code = 'array('; 301 | $closing = ')'; 302 | break; 303 | case \ast\flags\ARRAY_SYNTAX_SHORT: 304 | $code = '['; 305 | $closing = ']'; 306 | break; 307 | case 0: 308 | // nothing to do - required for PHP 7.0 309 | $code = '['; 310 | $closing = ']'; 311 | break; 312 | default: 313 | assert(false, "Unknown flag ({$node->flags}) for AST_ARRAY found."); 314 | } 315 | 316 | if ($node->children !== []) { 317 | $code .= $this->commaSeparatedValues($node, ', '); 318 | } 319 | 320 | $code .= $closing; 321 | 322 | return $code; 323 | } 324 | 325 | private function arrayElem(Node $node) : string 326 | { 327 | $code = ''; 328 | 329 | if ($node->children['key'] === null) { 330 | $code .= $this->revertAST($node->children['value']); 331 | } else { 332 | $code .= "{$this->revertAST($node->children['key'])} => "; 333 | 334 | if ($node->flags === \ast\flags\PARAM_REF) { 335 | $code .= '&'; 336 | } 337 | 338 | $code .= $this->revertAST($node->children['value']); 339 | } 340 | 341 | return $code; 342 | } 343 | 344 | private function assign(Node $node) : string 345 | { 346 | return $this->revertAST($node->children['var']) 347 | . ' = ' 348 | . $this->revertAST($node->children['expr']); 349 | } 350 | 351 | private function assignOp(Node $node) : string 352 | { 353 | $op = ''; 354 | 355 | switch ($node->flags) { 356 | case \ast\flags\BINARY_BITWISE_OR: 357 | $op = '|='; 358 | break; 359 | case \ast\flags\BINARY_BITWISE_AND: 360 | $op = '&='; 361 | break; 362 | case \ast\flags\BINARY_BITWISE_XOR: 363 | $op = '^='; 364 | break; 365 | case \ast\flags\BINARY_CONCAT: 366 | $op = '.='; 367 | break; 368 | case \ast\flags\BINARY_ADD: 369 | $op = '+='; 370 | break; 371 | case \ast\flags\BINARY_SUB: 372 | $op = '-='; 373 | break; 374 | case \ast\flags\BINARY_MUL: 375 | $op = '*='; 376 | break; 377 | case \ast\flags\BINARY_DIV: 378 | $op = '/='; 379 | break; 380 | case \ast\flags\BINARY_MOD: 381 | $op = '%='; 382 | break; 383 | case \ast\flags\BINARY_POW: 384 | $op = '**='; 385 | break; 386 | case \ast\flags\BINARY_SHIFT_LEFT: 387 | $op = '<<='; 388 | break; 389 | case \ast\flags\BINARY_SHIFT_RIGHT: 390 | $op = '>>='; 391 | break; 392 | default: 393 | assert(false, "Unknown flag ({$node->flags}) for AST_ASSIGN_OP found."); 394 | } 395 | 396 | return $this->revertAST($node->children['var']) 397 | . ' ' 398 | . $op 399 | . ' ' 400 | . $this->revertAST($node->children['expr']); 401 | } 402 | 403 | private function assignRef(Node $node) : string 404 | { 405 | return $this->revertAST($node->children['var']) 406 | . ' = &' 407 | . $this->revertAST($node->children['expr']); 408 | } 409 | 410 | private function binaryOp(Node $node) : string 411 | { 412 | $code = ''; 413 | $op = ''; 414 | $enforcePrecendence = true; 415 | 416 | switch ($node->flags) { 417 | case \ast\flags\BINARY_BITWISE_OR: 418 | $op = '|'; 419 | break; 420 | case \ast\flags\BINARY_BITWISE_AND: 421 | $op = '&'; 422 | break; 423 | case \ast\flags\BINARY_BITWISE_XOR: 424 | $op = '^'; 425 | break; 426 | case \ast\flags\BINARY_CONCAT: 427 | $enforcePrecendence = false; 428 | $op = '.'; 429 | break; 430 | case \ast\flags\BINARY_ADD: 431 | $op = '+'; 432 | break; 433 | case \ast\flags\BINARY_SUB: 434 | $op = '-'; 435 | break; 436 | case \ast\flags\BINARY_MUL: 437 | $op = '*'; 438 | break; 439 | case \ast\flags\BINARY_DIV: 440 | $op = '/'; 441 | break; 442 | case \ast\flags\BINARY_MOD: 443 | $op = '%'; 444 | break; 445 | case \ast\flags\BINARY_POW: 446 | $op = '**'; 447 | break; 448 | case \ast\flags\BINARY_SHIFT_LEFT: 449 | $op = '<<'; 450 | break; 451 | case \ast\flags\BINARY_SHIFT_RIGHT: 452 | $op = '>>'; 453 | break; 454 | case \ast\flags\BINARY_BOOL_XOR: 455 | $op = 'xor'; 456 | break; 457 | case \ast\flags\BINARY_BOOL_OR: 458 | $op = '||'; 459 | break; 460 | case \ast\flags\BINARY_BOOL_AND: 461 | $op = '&&'; 462 | break; 463 | case \ast\flags\BINARY_IS_IDENTICAL: 464 | $op = '==='; 465 | break; 466 | case \ast\flags\BINARY_IS_NOT_IDENTICAL: 467 | $op = '!=='; 468 | break; 469 | case \ast\flags\BINARY_IS_EQUAL: 470 | $op = '=='; 471 | break; 472 | case \ast\flags\BINARY_IS_NOT_EQUAL: 473 | $op = '!='; 474 | break; 475 | case \ast\flags\BINARY_IS_SMALLER: 476 | $op = '<'; 477 | break; 478 | case \ast\flags\BINARY_IS_SMALLER_OR_EQUAL: 479 | $op = '<='; 480 | break; 481 | case \ast\flags\BINARY_IS_GREATER: 482 | $op = '>'; 483 | break; 484 | case \ast\flags\BINARY_IS_GREATER_OR_EQUAL: 485 | $op = '>='; 486 | break; 487 | case \ast\flags\BINARY_SPACESHIP: 488 | $op = '<=>'; 489 | break; 490 | default: 491 | assert(false, "Unknown flag ({$node->flags}) for AST_BINARY_OP found."); 492 | } 493 | 494 | $buffer = []; 495 | 496 | foreach (['left', 'right'] as $child) { 497 | if ( 498 | $enforcePrecendence 499 | && $node->children[$child] instanceof Node 500 | && $node->children[$child]->kind === \ast\AST_BINARY_OP 501 | ) { 502 | $buffer[] = '(' . $this->revertAST($node->children[$child]) . ')'; 503 | continue; 504 | } 505 | 506 | $buffer[] = $this->revertAST($node->children[$child]); 507 | } 508 | 509 | return implode(' ' . $op . ' ', $buffer); 510 | } 511 | 512 | private function break(Node $node) : string 513 | { 514 | $code = 'break'; 515 | 516 | if ($node->children['depth'] !== null) { 517 | $code .= " {$this->revertAST($node->children['depth'])}"; 518 | } 519 | 520 | return $code; 521 | } 522 | 523 | private function call(Node $node) : string 524 | { 525 | return $this->revertAST($node->children['expr']) 526 | . $this->revertAST($node->children['args']); 527 | } 528 | 529 | private function cast(Node $node) : string 530 | { 531 | $code = '('; 532 | 533 | switch ($node->flags) { 534 | case \ast\flags\TYPE_NULL: 535 | $code .= 'unset'; 536 | break; 537 | case \ast\flags\TYPE_BOOL: 538 | $code .= 'bool'; 539 | break; 540 | case \ast\flags\TYPE_LONG: 541 | $code .= 'int'; 542 | break; 543 | case \ast\flags\TYPE_DOUBLE: 544 | $code .= 'float'; 545 | break; 546 | case \ast\flags\TYPE_STRING: 547 | $code .= 'string'; 548 | break; 549 | case \ast\flags\TYPE_ARRAY: 550 | $code .= 'array'; 551 | break; 552 | case \ast\flags\TYPE_OBJECT: 553 | $code .= 'object'; 554 | break; 555 | default: 556 | assert(false, "Unknown cast type ({$node->flags}) for AST_CAST found."); 557 | } 558 | 559 | $code .= ') ' . $this->revertAST($node->children['expr']); 560 | 561 | return $code; 562 | } 563 | 564 | private function catch(Node $node) : string 565 | { 566 | $code = ' catch (' . $this->revertAST($node->children['class']) . ' '; 567 | $code .= $this->revertAST($node->children['var']); 568 | $code .= ') {' . PHP_EOL; 569 | 570 | ++$this->indentationLevel; 571 | 572 | $code .= $this->revertAST($node->children['stmts']); 573 | 574 | --$this->indentationLevel; 575 | 576 | $code .= $this->indent() . '}'; 577 | 578 | return $code; 579 | } 580 | 581 | private function catchList(Node $node) : string 582 | { 583 | $code = ''; 584 | 585 | $this->inCatchBlock = true; 586 | 587 | foreach ($node->children as $catch) { 588 | $code .= $this->revertAST($catch); 589 | } 590 | 591 | $this->inCatchBlock = false; 592 | 593 | return $code; 594 | } 595 | 596 | /** 597 | * The second argument is for anonymous classes. 598 | */ 599 | private function class(Node $node, Node $args = null) : string 600 | { 601 | $code = ''; 602 | 603 | if (isset($node->docComment)) { 604 | $code .= $node->docComment . PHP_EOL; 605 | } else if (isset($node->children['docComment'])) { // php-ast v50+ 606 | $code .= $node->children['docComment'] . PHP_EOL; 607 | } 608 | 609 | $modifier = ''; 610 | $type = 'class'; 611 | $extends = ''; 612 | $implements = ''; 613 | 614 | switch ($node->flags) { 615 | case \ast\flags\CLASS_ABSTRACT: 616 | $modifier = 'abstract '; 617 | break; 618 | case \ast\flags\CLASS_FINAL: 619 | $modifier = 'final '; 620 | break; 621 | case \ast\flags\CLASS_TRAIT: 622 | $type = 'trait'; 623 | break; 624 | case \ast\flags\CLASS_INTERFACE: 625 | $type = 'interface'; 626 | break; 627 | case 0: 628 | // no flag used 629 | break; 630 | case \ast\flags\CLASS_ANONYMOUS: 631 | // anonymous class 632 | break; 633 | default: 634 | assert(false, "Unknown flag ({$node->flags}) for AST_CLASS found."); 635 | } 636 | 637 | if ($node->children['extends'] !== null) { 638 | $extends .= ' extends ' . $this->revertAST($node->children['extends']); 639 | } 640 | 641 | if ($node->children['implements'] !== null) { 642 | if ($type === 'interface') { 643 | $implements .= ' extends '; 644 | } else { 645 | $implements .= ' implements '; 646 | } 647 | 648 | $implements .= $this->revertAST($node->children['implements']); 649 | } 650 | 651 | $code .= $modifier . $type . ' ' . $this->getNodeName($node); 652 | 653 | if ($args !== null) { 654 | $code .= $this->revertAST($args); 655 | } 656 | 657 | $code .= $extends . $implements; 658 | 659 | if ($args === null) { 660 | $code .= PHP_EOL . $this->indent(); 661 | } else { 662 | $code .= ' '; 663 | } 664 | 665 | $code .= '{' . PHP_EOL; 666 | 667 | ++$this->indentationLevel; 668 | 669 | $code .= $this->revertAST($node->children['stmts']); 670 | 671 | --$this->indentationLevel; 672 | 673 | $code .= $this->indent() . '}'; 674 | 675 | return $code; 676 | } 677 | 678 | private function getNodeName(Node $node) : string 679 | { 680 | if ($node instanceof Decl) { 681 | return $node->name ?? ''; 682 | } 683 | 684 | return $node->children['name'] ?? ''; 685 | } 686 | 687 | private function classConst(Node $node) : string 688 | { 689 | $code = $this->revertAST($node->children['class']) . '::'; 690 | 691 | if ($node->children['const'] instanceof Node) { 692 | $code .= $this->revertAST($node->children['const']); 693 | } else { 694 | $code .= $node->children['const']; 695 | } 696 | 697 | return $code; 698 | } 699 | 700 | private function classConstDecl(Node $node) : string 701 | { 702 | return $this->constDecl($node); 703 | } 704 | 705 | private function clone(Node $node) : string 706 | { 707 | return 'clone ' . $this->revertAST($node->children['expr']); 708 | } 709 | 710 | private function closure(Node $node) : string 711 | { 712 | $code = 'function '; 713 | 714 | if ($node->flags === \ast\flags\RETURNS_REF) { 715 | $code .= '&'; 716 | } 717 | 718 | $code .= $this->revertAST($node->children['params']); 719 | 720 | if ($node->children['uses'] !== null) { 721 | $code .= ' use (' 722 | . $this->commaSeparatedValues($node->children['uses'], ', ') 723 | . ')'; 724 | } 725 | 726 | if ($node->children['returnType'] !== null) { 727 | $code .= ' : ' . $this->revertAST($node->children['returnType']); 728 | } 729 | 730 | $code .= ' {' . PHP_EOL; 731 | 732 | ++$this->indentationLevel; 733 | 734 | $code .= $this->revertAST($node->children['stmts']); 735 | 736 | --$this->indentationLevel; 737 | 738 | $code .= $this->indent() . '}'; 739 | 740 | return $code; 741 | } 742 | 743 | private function closureVar(Node $node) : string 744 | { 745 | $code = ''; 746 | 747 | if ($node->flags === \ast\flags\PARAM_REF) { 748 | $code .= '&'; 749 | } 750 | 751 | $code .= '$' . $node->children['name']; 752 | 753 | return $code; 754 | } 755 | 756 | private function coalesce(Node $node) : string 757 | { 758 | return '(' 759 | . $this->revertAST($node->children['left']) 760 | . ' ?? ' 761 | . $this->revertAST($node->children['right']) 762 | . ')'; 763 | } 764 | 765 | /** 766 | * Custom method for building comma-deliniated lists. 767 | * 768 | * Sometimes, a space is not wanted after a comma because it causes 769 | * trailing white space in code, like after multiple property declarations. 770 | * 771 | * The NULL check is required for list(,,, $a). 772 | */ 773 | private function commaSeparatedValues(Node $node, string $separator) : string 774 | { 775 | $aggregator = []; 776 | 777 | foreach ($node->children as $child) { 778 | $aggregator[] = ($child === null) ? null : $this->revertAST($child); 779 | } 780 | 781 | return implode($separator, $aggregator); 782 | } 783 | 784 | private function conditional(Node $node) : string 785 | { 786 | return '(' 787 | . $this->revertAST($node->children['cond']) 788 | . ' ? ' 789 | . $this->revertAST($node->children['true']) 790 | . ' : ' 791 | . $this->revertAST($node->children['false']) 792 | . ')'; 793 | } 794 | 795 | private function const(Node $node) : string 796 | { 797 | return $this->revertAST($node->children['name']); 798 | } 799 | 800 | private function constDecl(Node $node, $setConst = true) : string 801 | { 802 | $code = ''; 803 | 804 | switch ($node->flags) { 805 | case \ast\flags\MODIFIER_PUBLIC: 806 | $code = 'public '; 807 | break; 808 | case \ast\flags\MODIFIER_PROTECTED: 809 | $code = 'protected '; 810 | break; 811 | case \ast\flags\MODIFIER_PRIVATE: 812 | $code = 'private '; 813 | break; 814 | case 0: 815 | // nothing to do - for PHP 7.0 816 | break; 817 | default: 818 | assert(false, "Unknown flag ({$node->flags}) for AST_CONST_DECL found."); 819 | } 820 | 821 | if ($setConst) { 822 | $code .= 'const '; 823 | } 824 | 825 | $code .= $this->commaSeparatedValues($node, ', '); 826 | 827 | return $code; 828 | } 829 | 830 | private function constElem(Node $node) : string 831 | { 832 | return $node->children['name'] 833 | . ' = ' 834 | . $this->revertAST($node->children['value']); 835 | } 836 | 837 | private function continue(Node $node) : string 838 | { 839 | $code = 'continue'; 840 | 841 | if ($node->children['depth'] !== null) { 842 | $code .= ' ' . $this->revertAST($node->children['depth']); 843 | } 844 | 845 | return $code; 846 | } 847 | 848 | /** 849 | * Custom method used to wrap single statement bodies into a AST_STMT_LIST. 850 | * 851 | * Used when braces are omitted for single statement bodies, like: 852 | * while (1) 853 | * doSomething(); 854 | */ 855 | private function createStmtList($node) : Node 856 | { 857 | $node2 = new Node; 858 | $node2->kind = \ast\AST_STMT_LIST; 859 | $node2->children = [$node]; 860 | 861 | return $node2; 862 | } 863 | 864 | private function declare(Node $node) : string 865 | { 866 | $code = 'declare(' . $this->constDecl($node->children['declares'], false) . ')'; 867 | 868 | if ($node->children['stmts'] !== null) { 869 | $code .= ' {' . PHP_EOL; 870 | ++$this->indentationLevel; 871 | $code .= $this->revertAST($node->children['stmts']); 872 | --$this->indentationLevel; 873 | $code .= $this->indent() . '}'; 874 | } 875 | 876 | return $code; 877 | } 878 | 879 | private function dim(Node $node) : string 880 | { 881 | $code = $this->revertAST($node->children['expr']) . '['; 882 | 883 | if ($node->children['dim'] !== null) { 884 | $code .= $this->revertAST($node->children['dim']); 885 | } 886 | 887 | $code .= ']'; 888 | 889 | return $code; 890 | } 891 | 892 | private function doWhile(Node $node) : string 893 | { 894 | $code = 'do {' . PHP_EOL; 895 | 896 | ++$this->indentationLevel; 897 | 898 | $code .= $this->revertAST($node->children['stmts']); 899 | 900 | --$this->indentationLevel; 901 | 902 | $code .= $this->indent() 903 | . '} while (' 904 | . $this->revertAST($node->children['cond']) 905 | . ')'; 906 | 907 | return $code; 908 | } 909 | 910 | private function echo(Node $node) : string 911 | { 912 | return 'echo ' . $this->revertAST($node->children['expr']); 913 | } 914 | 915 | private function empty(Node $node) : string 916 | { 917 | return 'empty(' . $this->revertAST($node->children['expr']) . ')'; 918 | } 919 | 920 | private function encapsList(Node $node) : string 921 | { 922 | $code = '"'; 923 | 924 | foreach ($node->children as $child) { 925 | if ($child instanceof Node) { 926 | $code .= '{' . $this->revertAST($child) . '}'; 927 | } else { 928 | $code .= $this->sanitiseString($child); 929 | } 930 | } 931 | 932 | $code .= '"'; 933 | 934 | return $code; 935 | } 936 | 937 | private function exit(Node $node) : string 938 | { 939 | $code = 'die'; 940 | 941 | if ($node->children['expr'] !== null) { 942 | $code .= '(' . $this->revertAST($node->children['expr']) .')'; 943 | } 944 | 945 | return $code; 946 | } 947 | 948 | private function exprList(Node $node) : string 949 | { 950 | return $this->commaSeparatedValues($node, ', '); 951 | } 952 | 953 | private function for(Node $node) : string 954 | { 955 | $code = 'for ('; 956 | 957 | if ($node->children['init'] !== null) { 958 | $code .= $this->revertAST($node->children['init']); 959 | } 960 | 961 | $code .= ';'; 962 | 963 | if ($node->children['cond'] !== null) { 964 | $code .= ' ' . $this->revertAST($node->children['cond']); 965 | } 966 | 967 | $code .= ';'; 968 | 969 | if ($node->children['loop'] !== null) { 970 | $code .= ' ' . $this->revertAST($node->children['loop']); 971 | } 972 | 973 | $code .= ')'; 974 | 975 | $bodyNode = $node->children['stmts']; 976 | 977 | if ($bodyNode !== null && $bodyNode->children !== []) { 978 | if ( 979 | !$bodyNode instanceof Node 980 | || $bodyNode->kind !== \ast\AST_STMT_LIST 981 | ) { 982 | $bodyNode = $this->createStmtList($bodyNode); 983 | } 984 | 985 | $code .=' {' . PHP_EOL; 986 | 987 | ++$this->indentationLevel; 988 | 989 | $code .= $this->revertAST($bodyNode); 990 | 991 | --$this->indentationLevel; 992 | 993 | $code .= $this->indent() . '}'; 994 | } 995 | 996 | return $code; 997 | } 998 | 999 | /** 1000 | * Custom method that terminates a statement irregardless. 1001 | */ 1002 | private function forceTerminateStatement(string $buffer) : string 1003 | { 1004 | $lastChar = substr($buffer, -1); 1005 | 1006 | if ($lastChar === ';') { 1007 | return PHP_EOL; 1008 | } 1009 | 1010 | if ($lastChar === PHP_EOL) { 1011 | return ''; 1012 | } 1013 | 1014 | return ';' . PHP_EOL; 1015 | } 1016 | 1017 | private function foreach(Node $node) : string 1018 | { 1019 | $code = 'foreach (' . $this->revertAST($node->children['expr']) . ' as '; 1020 | 1021 | if (isset($node->children['key'])) { 1022 | $code .= $this->revertAST($node->children['key']) . ' => '; 1023 | } 1024 | 1025 | $code .= $this->revertAST($node->children['value']) . ')'; 1026 | 1027 | $bodyNode = $node->children['stmts']; 1028 | 1029 | if ( 1030 | !$bodyNode instanceof Node 1031 | || $bodyNode->kind !== \ast\AST_STMT_LIST 1032 | ) { 1033 | $bodyNode = $this->createStmtList($bodyNode); 1034 | } 1035 | 1036 | $code .= ' {' . PHP_EOL; 1037 | 1038 | ++$this->indentationLevel; 1039 | 1040 | $code .= $this->revertAST($bodyNode); 1041 | 1042 | --$this->indentationLevel; 1043 | 1044 | $code .= $this->indent() . '}'; 1045 | 1046 | return $code; 1047 | } 1048 | 1049 | private function funcDecl(Node $node) : string 1050 | { 1051 | $code = ''; 1052 | 1053 | if (isset($node->docComment)) { 1054 | $code .= $node->docComment . PHP_EOL; 1055 | } 1056 | 1057 | $code .= 'function '; 1058 | 1059 | if ($node->flags === \ast\flags\RETURNS_REF) { 1060 | $code .= '&'; 1061 | } 1062 | 1063 | $code .= $this->getNodeName($node) . $this->revertAST($node->children['params']); 1064 | 1065 | if ($node->children['returnType'] !== null) { 1066 | $code .= ' : ' . $this->revertAST($node->children['returnType']); 1067 | } 1068 | 1069 | $code .= PHP_EOL . $this->indent() . '{' . PHP_EOL; 1070 | 1071 | ++$this->indentationLevel; 1072 | 1073 | $code .= $this->revertAST($node->children['stmts']); 1074 | 1075 | --$this->indentationLevel; 1076 | 1077 | $code .= $this->indent() . '}' . PHP_EOL; 1078 | 1079 | return $code; 1080 | } 1081 | 1082 | public function getCode(Node $node, bool $fromFile = true) : string 1083 | { 1084 | $code = ''; 1085 | 1086 | if ($fromFile) { 1087 | $code .= 'revertAST($node) . PHP_EOL; 1091 | 1092 | return $code; 1093 | } 1094 | 1095 | public function global(Node $node) : string 1096 | { 1097 | return 'global ' . $this->revertAST($node->children['var']); 1098 | } 1099 | 1100 | private function goto(Node $node) : string 1101 | { 1102 | return 'goto ' . $node->children['label']; 1103 | } 1104 | 1105 | private function groupUse(Node $node) : string 1106 | { 1107 | $code = $this->useAbstract($node) . $node->children['prefix'] . '\{'; 1108 | 1109 | $code .= $this->use($node->children['uses'], false); // a hack to not show 'use ' in block 1110 | 1111 | $code .= '}'; 1112 | 1113 | return $code; 1114 | } 1115 | 1116 | private function haltCompiler(Node $node) : string 1117 | { 1118 | return '__halt_compiler()'; 1119 | } 1120 | 1121 | private function if(Node $node) : string 1122 | { 1123 | $code = $this->ifElem($node->children[0], 'if '); 1124 | 1125 | $childCount = count($node->children); 1126 | 1127 | for ($i = 1; $i < $childCount; ++$i) { 1128 | $type = ($node->children[$i]->children['cond'] !== null) 1129 | ? ' elseif ' 1130 | : ' else'; 1131 | 1132 | $code .= $this->ifElem($node->children[$i], $type); 1133 | } 1134 | 1135 | return $code; 1136 | } 1137 | 1138 | private function ifElem(Node $node, string $type) : string 1139 | { 1140 | $code = $type; 1141 | 1142 | if ($node->children['cond'] !== null) { 1143 | $code .= '(' . $this->revertAST($node->children['cond']) . ')'; 1144 | } 1145 | 1146 | $code .= ' {' . PHP_EOL; 1147 | 1148 | ++$this->indentationLevel; 1149 | 1150 | $bodyNode = $node->children['stmts']; 1151 | 1152 | if ( 1153 | !$bodyNode instanceof Node 1154 | || $bodyNode->kind !== \ast\AST_STMT_LIST 1155 | ) { 1156 | $bodyNode = $this->createStmtList($bodyNode); 1157 | } 1158 | 1159 | $code .= $this->revertAST($bodyNode); 1160 | 1161 | --$this->indentationLevel; 1162 | 1163 | $code .= $this->indent() . '}'; 1164 | 1165 | return $code; 1166 | } 1167 | 1168 | private function includeOrEval(Node $node) : string 1169 | { 1170 | $code = ''; 1171 | $arg = $this->revertAST($node->children['expr']); 1172 | 1173 | switch ($node->flags) { 1174 | case \ast\flags\EXEC_INCLUDE: 1175 | $code .= "include {$arg}"; 1176 | break; 1177 | case \ast\flags\EXEC_INCLUDE_ONCE: 1178 | $code .= "include_once {$arg}"; 1179 | break; 1180 | case \ast\flags\EXEC_REQUIRE: 1181 | $code .= "require {$arg}"; 1182 | break; 1183 | case \ast\flags\EXEC_REQUIRE_ONCE: 1184 | $code .= "require_once {$arg}"; 1185 | break; 1186 | case \ast\flags\EXEC_EVAL: 1187 | $code .= "eval({$arg})"; 1188 | break; 1189 | default: 1190 | assert(false, "Unknown flag ({$node->flags}) for AST_INCLUDE_OR_EVAL found."); 1191 | } 1192 | 1193 | return $code; 1194 | } 1195 | 1196 | private function instanceof(Node $node) : string 1197 | { 1198 | return $this->revertAST($node->children['expr']) 1199 | . ' instanceof ' 1200 | . $this->revertAST($node->children['class']); 1201 | } 1202 | 1203 | /** 1204 | * Custom method to indent statements 1205 | */ 1206 | private function indent() : string 1207 | { 1208 | return str_repeat( 1209 | self::INDENTATION_CHAR, 1210 | $this->indentationLevel * self::INDENTATION_SIZE 1211 | ); 1212 | } 1213 | 1214 | private function isset(Node $node) : string 1215 | { 1216 | return 'isset(' . $this->revertAST($node->children['var']) . ')'; 1217 | } 1218 | 1219 | private function label(Node $node) : string 1220 | { 1221 | return $node->children['name'] . ':' . PHP_EOL; 1222 | } 1223 | 1224 | private function list(Node $node) : string 1225 | { 1226 | return 'list(' . $this->commaSeparatedValues($node, ', ') . ')'; 1227 | } 1228 | 1229 | private function magicConst(Node $node) 1230 | { 1231 | switch ($node->flags) { 1232 | case \ast\flags\MAGIC_LINE: 1233 | return '__LINE__'; 1234 | case \ast\flags\MAGIC_FILE: 1235 | return '__FILE__'; 1236 | case \ast\flags\MAGIC_DIR: 1237 | return '__DIR__'; 1238 | case \ast\flags\MAGIC_TRAIT: 1239 | return '__TRAIT__'; 1240 | case \ast\flags\MAGIC_METHOD: 1241 | return '__METHOD__'; 1242 | case \ast\flags\MAGIC_FUNCTION: 1243 | return '__FUNCTION__'; 1244 | case \ast\flags\MAGIC_NAMESPACE: 1245 | return '__NAMESPACE__'; 1246 | case \ast\flags\MAGIC_CLASS: 1247 | return '__CLASS__'; 1248 | default: 1249 | assert(false, "Unknown flag ({$node->flags}) for T_MAGIC_CONST found."); 1250 | } 1251 | } 1252 | 1253 | private function method(Node $node) : string 1254 | { 1255 | $code = ''; 1256 | $scope = ''; 1257 | $returnsRef = ''; 1258 | 1259 | if (isset($node->docComment)) { 1260 | $code .= $node->docComment . PHP_EOL . $this->indent(); 1261 | } else if (isset($node->children['docComment'])) { // php-ast v50+ 1262 | $code .= $node->children['docComment'] . PHP_EOL . $this->indent(); 1263 | } 1264 | 1265 | // (abstract|final)?(public|protected|private)(static)?&? 1266 | switch ($node->flags) { 1267 | case \ast\flags\MODIFIER_PUBLIC: 1268 | $scope = 'public'; 1269 | break; 1270 | case \ast\flags\MODIFIER_PUBLIC | \ast\flags\RETURNS_REF: 1271 | $scope = 'public'; 1272 | $returnsRef = '&'; 1273 | break; 1274 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PUBLIC: 1275 | $scope = 'abstract public'; 1276 | break; 1277 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PUBLIC | \ast\flags\RETURNS_REF: 1278 | $scope = 'abstract public'; 1279 | $returnsRef = '&'; 1280 | break; 1281 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PUBLIC: 1282 | $scope = 'final public'; 1283 | break; 1284 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PUBLIC | \ast\flags\RETURNS_REF: 1285 | $scope = 'final public'; 1286 | $returnsRef = '&'; 1287 | break; 1288 | case \ast\flags\MODIFIER_PUBLIC | \ast\flags\MODIFIER_STATIC: 1289 | $scope = 'public static'; 1290 | break; 1291 | case \ast\flags\MODIFIER_PUBLIC | \ast\flags\MODIFIER_STATIC | \ast\flags\RETURNS_REF: 1292 | $scope = 'public static'; 1293 | $returnsRef = '&'; 1294 | break; 1295 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PUBLIC | \ast\flags\MODIFIER_STATIC: 1296 | $scope = 'abstract public static'; 1297 | break; 1298 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PUBLIC | \ast\flags\MODIFIER_STATIC | \ast\flags\RETURNS_REF: 1299 | $scope = 'abstract public static'; 1300 | $returnsRef = '&'; 1301 | break; 1302 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PUBLIC | \ast\flags\MODIFIER_STATIC: 1303 | $scope = 'final public static'; 1304 | break; 1305 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PUBLIC | \ast\flags\MODIFIER_STATIC | \ast\flags\RETURNS_REF: 1306 | $scope = 'final public static'; 1307 | $returnsRef = '&'; 1308 | break; 1309 | case \ast\flags\MODIFIER_PROTECTED: 1310 | $scope = 'protected'; 1311 | break; 1312 | case \ast\flags\MODIFIER_PROTECTED | \ast\flags\RETURNS_REF: 1313 | $scope = 'protected'; 1314 | $returnsRef = '&'; 1315 | break; 1316 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PROTECTED: 1317 | $scope = 'abstract protected'; 1318 | break; 1319 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PROTECTED | \ast\flags\RETURNS_REF: 1320 | $scope = 'abstract protected'; 1321 | $returnsRef = '&'; 1322 | break; 1323 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PROTECTED: 1324 | $scope = 'final protected'; 1325 | break; 1326 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PROTECTED | \ast\flags\RETURNS_REF: 1327 | $scope = 'final protected'; 1328 | $returnsRef = '&'; 1329 | break; 1330 | case \ast\flags\MODIFIER_PROTECTED | \ast\flags\MODIFIER_STATIC: 1331 | $scope = 'protected static'; 1332 | break; 1333 | case \ast\flags\MODIFIER_PROTECTED | \ast\flags\MODIFIER_STATIC | \ast\flags\RETURNS_REF: 1334 | $scope = 'protected static'; 1335 | $returnsRef = '&'; 1336 | break; 1337 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PROTECTED | \ast\flags\MODIFIER_STATIC: 1338 | $scope = 'abstract protected static'; 1339 | break; 1340 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PROTECTED | \ast\flags\MODIFIER_STATIC | \ast\flags\RETURNS_REF: 1341 | $scope = 'abstract protected static'; 1342 | $returnsRef = '&'; 1343 | break; 1344 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PROTECTED | \ast\flags\MODIFIER_STATIC: 1345 | $scope = 'final protected static'; 1346 | break; 1347 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PROTECTED | \ast\flags\MODIFIER_STATIC | \ast\flags\RETURNS_REF: 1348 | $scope = 'final protected static'; 1349 | $returnsRef = '&'; 1350 | break; 1351 | case \ast\flags\MODIFIER_PRIVATE: 1352 | $scope = 'private'; 1353 | break; 1354 | case \ast\flags\MODIFIER_PRIVATE | \ast\flags\RETURNS_REF: 1355 | $scope = 'private'; 1356 | $returnsRef = '&'; 1357 | break; 1358 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PRIVATE: 1359 | $scope = 'abstract private'; 1360 | break; 1361 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PRIVATE | \ast\flags\RETURNS_REF: 1362 | $scope = 'abstract private'; 1363 | $returnsRef = '&'; 1364 | break; 1365 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PRIVATE: 1366 | $scope = 'final private'; 1367 | break; 1368 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PRIVATE | \ast\flags\RETURNS_REF: 1369 | $scope = 'final private'; 1370 | $returnsRef = '&'; 1371 | break; 1372 | case \ast\flags\MODIFIER_PRIVATE | \ast\flags\MODIFIER_STATIC: 1373 | $scope = 'private static'; 1374 | break; 1375 | case \ast\flags\MODIFIER_PRIVATE | \ast\flags\MODIFIER_STATIC | \ast\flags\RETURNS_REF: 1376 | $scope = 'private static'; 1377 | $returnsRef = '&'; 1378 | break; 1379 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PRIVATE | \ast\flags\MODIFIER_STATIC: 1380 | $scope = 'abstract private static'; 1381 | break; 1382 | case \ast\flags\MODIFIER_ABSTRACT | \ast\flags\MODIFIER_PRIVATE | \ast\flags\MODIFIER_STATIC | \ast\flags\RETURNS_REF: 1383 | $scope = 'abstract private static'; 1384 | $returnsRef = '&'; 1385 | break; 1386 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PRIVATE | \ast\flags\MODIFIER_STATIC: 1387 | $scope = 'final private static'; 1388 | break; 1389 | case \ast\flags\MODIFIER_FINAL | \ast\flags\MODIFIER_PRIVATE | \ast\flags\MODIFIER_STATIC | \ast\flags\RETURNS_REF: 1390 | $scope = 'final private static'; 1391 | $returnsRef = '&'; 1392 | break; 1393 | default: 1394 | assert(false, "Unknown flag(s) ({$node->flags}) for AST_METHOD found."); 1395 | } 1396 | 1397 | $code .= $scope 1398 | . ' function ' 1399 | . $returnsRef 1400 | . $this->getNodeName($node) 1401 | . $this->revertAST($node->children['params']); 1402 | 1403 | if ($node->children['returnType'] !== null) { 1404 | $code .= ' : ' . $this->revertAST($node->children['returnType']); 1405 | } 1406 | 1407 | if ($node->children['stmts'] !== null) { 1408 | $code .= PHP_EOL 1409 | . $this->indent() 1410 | . '{' 1411 | . PHP_EOL; 1412 | 1413 | ++$this->indentationLevel; 1414 | 1415 | $code .= $this->revertAST($node->children['stmts']); 1416 | 1417 | --$this->indentationLevel; 1418 | 1419 | $code .= $this->indent() 1420 | . '}' 1421 | . PHP_EOL; 1422 | } 1423 | 1424 | return $code; 1425 | } 1426 | 1427 | private function methodCall(Node $node) : string 1428 | { 1429 | return $this->abstractCall( 1430 | $node->children['expr'], 1431 | $node->children['method'], 1432 | $node->children['args'], 1433 | '->'); 1434 | } 1435 | 1436 | private function methodReference(Node $node) : string 1437 | { 1438 | return $this->revertAST($node->children['class']) 1439 | . '::' 1440 | . $node->children['method']; 1441 | } 1442 | 1443 | private function name(Node $node) : string 1444 | { 1445 | $code = ''; 1446 | 1447 | switch ($node->flags) { 1448 | case \ast\flags\NAME_FQ: 1449 | if ($node->children['name'][0] != '\\') { // @version php-ast v30 and v35 1450 | $code .= '\\'; 1451 | } 1452 | break; 1453 | case \ast\flags\NAME_NOT_FQ: 1454 | // nothing to do 1455 | break; 1456 | case \ast\flags\NAME_RELATIVE: 1457 | // nothing to do 1458 | break; 1459 | default: 1460 | assert(false, "Unknown flag ({$node->flags}) for AST_PARAM found."); 1461 | } 1462 | 1463 | $code .= $node->children['name']; 1464 | 1465 | return $code; 1466 | } 1467 | 1468 | private function nameList(Node $node) : string 1469 | { 1470 | return $this->commaSeparatedValues($node, $this->inCatchBlock ? ' | ' : ', '); 1471 | } 1472 | 1473 | private function namespace(Node $node) : string 1474 | { 1475 | $code = 'namespace'; 1476 | 1477 | if ($node->children['name'] !== null) { 1478 | $code .= ' ' . $node->children['name']; 1479 | } 1480 | 1481 | if ($node->children['stmts'] !== null) { 1482 | $code .= ' {' . PHP_EOL; 1483 | 1484 | ++$this->indentationLevel; 1485 | 1486 | $code .= $this->revertAST($node->children['stmts']); 1487 | 1488 | --$this->indentationLevel; 1489 | 1490 | $code .= $this->indent() . '}' . PHP_EOL; 1491 | } 1492 | 1493 | return $code; 1494 | } 1495 | 1496 | private function new(Node $node) : string 1497 | { 1498 | $code = 'new '; 1499 | 1500 | if ($node->children['class']->kind === \ast\AST_CLASS) { 1501 | $code .= $this->class($node->children['class'], $node->children['args']) 1502 | . ';'; 1503 | } else { 1504 | $code .= $this->revertAST($node->children['class']); 1505 | 1506 | if ($node->children['args'] !== null) { 1507 | $code .= $this->revertAST($node->children['args']); 1508 | } 1509 | } 1510 | 1511 | return $code; 1512 | } 1513 | 1514 | private function nullableType(Node $node) : string 1515 | { 1516 | return '?' . $this->revertAST($node->children['type']); 1517 | } 1518 | 1519 | private function param(Node $node) : string 1520 | { 1521 | $code = ''; 1522 | 1523 | if ($node->children['type'] !== null) { 1524 | $code .= $this->revertAST($node->children['type']) . ' '; 1525 | } 1526 | 1527 | if ($node->flags & \ast\flags\PARAM_REF) { 1528 | $code .= '&'; 1529 | } 1530 | if ($node->flags & \ast\flags\PARAM_VARIADIC) { 1531 | $code .= '...'; 1532 | } 1533 | 1534 | $code .= '$' . $node->children['name']; 1535 | 1536 | if ($node->children['default'] !== null) { 1537 | $code .= ' = ' . $this->revertAST($node->children['default']); 1538 | } 1539 | 1540 | return $code; 1541 | } 1542 | 1543 | private function paramList(Node $node) : string 1544 | { 1545 | return '(' 1546 | . $this->commaSeparatedValues($node, ', ') 1547 | . ')'; 1548 | } 1549 | 1550 | private function postDec(Node $node) : string 1551 | { 1552 | return $this->revertAst($node->children['var']) . '--'; 1553 | } 1554 | 1555 | private function postInc(Node $node) : string 1556 | { 1557 | return $this->revertAst($node->children['var']) . '++'; 1558 | } 1559 | 1560 | private function preDec(Node $node) : string 1561 | { 1562 | return '--' . $this->revertAst($node->children['var']); 1563 | } 1564 | 1565 | private function preInc(Node $node) : string 1566 | { 1567 | return '++' . $this->revertAst($node->children['var']); 1568 | } 1569 | 1570 | private function print(Node $node) : string 1571 | { 1572 | return 'print ' . $this->revertAST($node->children['expr']); 1573 | } 1574 | 1575 | private function prop(Node $node) : string 1576 | { 1577 | return $this->abstractProp($node->children['expr'], $node->children['prop'], '->'); 1578 | } 1579 | 1580 | private function propDecl(Node $node) : string 1581 | { 1582 | $code = ''; 1583 | $scope = ''; 1584 | 1585 | // (public|protected|private)(static)? 1586 | switch ($node->flags) { 1587 | case \ast\flags\MODIFIER_STATIC: 1588 | $scope = 'static'; 1589 | break; 1590 | case \ast\flags\MODIFIER_PUBLIC: 1591 | $scope = 'public'; 1592 | break; 1593 | case \ast\flags\MODIFIER_PUBLIC | \ast\flags\MODIFIER_STATIC: 1594 | $scope = 'public static'; 1595 | break; 1596 | case \ast\flags\MODIFIER_PROTECTED: 1597 | $scope = 'protected'; 1598 | break; 1599 | case \ast\flags\MODIFIER_PROTECTED | \ast\flags\MODIFIER_STATIC: 1600 | $scope = 'protected static'; 1601 | break; 1602 | case \ast\flags\MODIFIER_PRIVATE: 1603 | $scope = 'private'; 1604 | break; 1605 | case \ast\flags\MODIFIER_PRIVATE | \ast\flags\MODIFIER_STATIC: 1606 | $scope = 'private static'; 1607 | break; 1608 | default: 1609 | assert(false, "Unknown flag(s) ({$node->flags}) for AST_PROP_DECL found."); 1610 | } 1611 | 1612 | $code .= $scope . $this->commaSeparatedValues($node, ','); 1613 | 1614 | return $code; 1615 | } 1616 | 1617 | private function propElem(Node $node) : string 1618 | { 1619 | $code = ''; 1620 | 1621 | if (isset($node->docComment)) { 1622 | $code .= PHP_EOL . $this->indent() . $node->docComment . PHP_EOL . $this->indent(); 1623 | } else if (isset($node->children['docComment'])) { // php-ast v50+ 1624 | $code .= PHP_EOL . $this->indent() . $node->children['docComment'] . PHP_EOL . $this->indent(); 1625 | } else { 1626 | $code .= ' '; 1627 | } 1628 | 1629 | $code .= '$' . $node->children['name']; 1630 | 1631 | if ($node->children['default'] !== null) { 1632 | $code .= ' = ' . $this->revertAST($node->children['default']); 1633 | } 1634 | 1635 | return $code; 1636 | } 1637 | 1638 | private function ref(Node $node) : string 1639 | { 1640 | return '&' . $this->revertAST($node->children['var']); 1641 | } 1642 | 1643 | private function return(Node $node) : string 1644 | { 1645 | $code = 'return'; 1646 | 1647 | if ($node->children['expr'] !== null) { 1648 | $code .= ' ' . $this->revertAST($node->children['expr']); 1649 | } 1650 | 1651 | return $code; 1652 | } 1653 | 1654 | private function shellExec(Node $node) : string 1655 | { 1656 | // ugly hack to remove double quotes 1657 | $expr = substr($this->revertAST($node->children['expr']), 1, -1); 1658 | $code = '`' . $expr . '`'; 1659 | 1660 | return $code; 1661 | } 1662 | 1663 | private function sanitiseString(string $string) : string 1664 | { 1665 | return strtr( 1666 | $string, 1667 | ['$' => '\\$', '\\' => '\\\\', "\n" => '\n', '"' => '\"'] 1668 | ); 1669 | } 1670 | 1671 | private function static(Node $node) : string 1672 | { 1673 | $code = 'static '; 1674 | $code .= $this->revertAST($node->children['var']); 1675 | 1676 | if ($node->children['default'] !== null) { 1677 | $code .= ' = ' . $this->revertAST($node->children['default']); 1678 | } 1679 | 1680 | return $code; 1681 | } 1682 | 1683 | private function staticCall(Node $node) : string 1684 | { 1685 | return $this->abstractCall( 1686 | $node->children['class'], 1687 | $node->children['method'], 1688 | $node->children['args'], 1689 | '::'); 1690 | } 1691 | 1692 | private function staticProp(Node $node) : string 1693 | { 1694 | return $this->abstractProp($node->children['class'], $node->children['prop'], '::$'); 1695 | } 1696 | 1697 | private function stmtList(Node $node) : string 1698 | { 1699 | $code = ''; 1700 | 1701 | foreach ($node->children as $child) { 1702 | if ($child === null) { 1703 | continue; 1704 | } 1705 | 1706 | if (!$child instanceof Node || $child->kind !== \ast\AST_STMT_LIST) { 1707 | $code .= $this->indent(); 1708 | } 1709 | 1710 | $code .= $this->revertAST($child); 1711 | 1712 | if ( 1713 | $child instanceof Node 1714 | && ( 1715 | $child->kind === \ast\AST_VAR 1716 | || $child->kind === \ast\AST_PROP 1717 | || $child->kind === \ast\AST_STATIC_PROP 1718 | || $child->kind === \ast\AST_GLOBAL 1719 | || $child->kind === \ast\AST_ASSIGN 1720 | || $child->kind === \ast\AST_ASSIGN_OP 1721 | || $child->kind === \ast\AST_CLOSURE 1722 | || $child->kind === \ast\AST_GROUP_USE 1723 | ) 1724 | ) { 1725 | $code .= $this->forceTerminateStatement($code); 1726 | } else { 1727 | $code .= $this->terminateStatement($code); 1728 | } 1729 | } 1730 | 1731 | return $code; 1732 | } 1733 | 1734 | private function switch(Node $node) : string 1735 | { 1736 | $code = 'switch (' 1737 | . $this->revertAST($node->children['cond']) 1738 | . ') {' 1739 | . PHP_EOL; 1740 | 1741 | ++$this->indentationLevel; 1742 | 1743 | $code .= $this->revertAST($node->children['stmts']); 1744 | 1745 | --$this->indentationLevel; 1746 | 1747 | $code .= $this->indent() . '}'; 1748 | 1749 | return $code; 1750 | } 1751 | 1752 | private function switchCase(Node $node) : string 1753 | { 1754 | $code = $this->indent(); 1755 | 1756 | if ($node->children['cond'] === null) { 1757 | $code .= 'default:'; 1758 | } else { 1759 | $code .= 'case ' . $this->revertAST($node->children['cond']) . ':'; 1760 | } 1761 | 1762 | $code .= PHP_EOL; 1763 | 1764 | ++$this->indentationLevel; 1765 | 1766 | $code .= $this->revertAST($node->children['stmts']); 1767 | 1768 | --$this->indentationLevel; 1769 | 1770 | return $code; 1771 | } 1772 | 1773 | private function switchList(Node $node) : string 1774 | { 1775 | $code = ''; 1776 | 1777 | foreach ($node->children as $child) { 1778 | $code .= $this->revertAST($child); 1779 | } 1780 | 1781 | return $code; 1782 | } 1783 | 1784 | /** 1785 | * Custom method that ensures statements are terminated. 1786 | */ 1787 | private function terminateStatement(string $buffer) : string 1788 | { 1789 | $lastChar = substr($buffer, -1); 1790 | 1791 | if ($lastChar !== '}') { 1792 | return $this->forceTerminateStatement($buffer); 1793 | } 1794 | 1795 | return PHP_EOL; 1796 | } 1797 | 1798 | private function throw(Node $node) : string 1799 | { 1800 | return 'throw ' . $this->revertAST($node->children['expr']); 1801 | } 1802 | 1803 | private function traitAdaptations(Node $node) : string 1804 | { 1805 | $code = ''; 1806 | 1807 | foreach ($node->children as $child) { 1808 | $code .= $this->indent() 1809 | . $this->revertAST($child) 1810 | . ';' 1811 | . PHP_EOL; 1812 | } 1813 | 1814 | return $code; 1815 | } 1816 | 1817 | private function traitAlias(Node $node) : string 1818 | { 1819 | $code = $this->revertAST($node->children['method']) . ' as '; 1820 | 1821 | switch ($node->flags) { 1822 | case \ast\flags\MODIFIER_PUBLIC: 1823 | $code .= 'public '; 1824 | break; 1825 | case \ast\flags\MODIFIER_PROTECTED: 1826 | $code .= 'protected '; 1827 | break; 1828 | case \ast\flags\MODIFIER_PRIVATE: 1829 | $code .= 'private '; 1830 | break; 1831 | default: 1832 | // if there's no flag, then the value will be 0 1833 | // assert(false, "Unknown flag ({$node->flags}) for AST_TRAIT_ALIAS found."); 1834 | } 1835 | 1836 | $code .= $node->children['alias']; 1837 | 1838 | return $code; 1839 | } 1840 | 1841 | private function traitPrecedence(Node $node) : string 1842 | { 1843 | return $this->revertAST($node->children['method']) 1844 | . ' insteadof ' 1845 | . $this->revertAST($node->children['insteadof']); 1846 | } 1847 | 1848 | private function try(Node $node) : string 1849 | { 1850 | $code = 'try {' . PHP_EOL; 1851 | 1852 | ++$this->indentationLevel; 1853 | 1854 | $code .= $this->revertAST($node->children['try']); 1855 | 1856 | --$this->indentationLevel; 1857 | 1858 | $code .= $this->indent() . '}'; 1859 | 1860 | $code .= $this->revertAST($node->children['catches']); 1861 | 1862 | if ($node->children['finally'] !== null) { 1863 | $code .= ' finally {' . PHP_EOL; 1864 | 1865 | ++$this->indentationLevel; 1866 | 1867 | $code .= $this->revertAST($node->children['finally']); 1868 | 1869 | -- $this->indentationLevel; 1870 | 1871 | $code .= $this->indent() . '}'; 1872 | } 1873 | 1874 | return $code; 1875 | } 1876 | 1877 | private function type(Node $node) : string 1878 | { 1879 | switch ($node->flags) { 1880 | case \ast\flags\TYPE_ARRAY: 1881 | return 'array'; 1882 | case \ast\flags\TYPE_CALLABLE: 1883 | return 'callable'; 1884 | case \ast\flags\TYPE_LONG: 1885 | return 'int'; 1886 | case \ast\flags\TYPE_STRING: 1887 | return 'string'; 1888 | case \ast\flags\TYPE_BOOL: 1889 | return 'bool'; 1890 | case \ast\flags\TYPE_ITERABLE: 1891 | return 'iterable'; 1892 | case \ast\flags\TYPE_DOUBLE: 1893 | return 'float'; 1894 | case \ast\flags\TYPE_VOID: 1895 | return 'void'; 1896 | default: 1897 | assert(false, "Unknown flag ({$node->flags}) for AST_TYPE found."); 1898 | } 1899 | } 1900 | 1901 | private function unaryOp(Node $node) : string 1902 | { 1903 | $code = ''; 1904 | 1905 | switch ($node->flags) { 1906 | case \ast\flags\UNARY_BOOL_NOT: 1907 | $code .= '!'; 1908 | break; 1909 | case \ast\flags\UNARY_BITWISE_NOT: 1910 | $code .= '~'; 1911 | break; 1912 | case \ast\flags\UNARY_SILENCE: 1913 | $code .= '@'; 1914 | break; 1915 | case \ast\flags\UNARY_PLUS: 1916 | $code .= '+'; 1917 | break; 1918 | case \ast\flags\UNARY_MINUS: 1919 | $code .= '-'; 1920 | break; 1921 | default: 1922 | assert(false, "Unknown flag ({$node->flags}) for AST_UNARY_OP found."); 1923 | } 1924 | 1925 | $code .= $this->revertAST($node->children['expr']); 1926 | 1927 | return $code; 1928 | } 1929 | 1930 | private function unpack(Node $node) : string 1931 | { 1932 | return '...' . $this->revertAST($node->children['expr']); 1933 | } 1934 | 1935 | private function unset(Node $node) : string 1936 | { 1937 | return 'unset('. $this->revertAST($node->children['var']) . ')'; 1938 | } 1939 | 1940 | private function use(Node $node, $setUse = true) : string 1941 | { 1942 | return $this->useAbstract($node, $setUse) 1943 | . $this->commaSeparatedValues($node, ', '); 1944 | } 1945 | 1946 | /** 1947 | * Custom method to encapsulate common logic between use() and groupUse(). 1948 | */ 1949 | private function useAbstract(Node $node, $setUse = true) : string 1950 | { 1951 | $code = ''; 1952 | 1953 | if ($setUse) { 1954 | $code .= 'use '; 1955 | } 1956 | 1957 | switch ($node->flags) { 1958 | case \ast\flags\USE_CONST: 1959 | $code .= 'const '; 1960 | break; 1961 | case \ast\flags\USE_FUNCTION: 1962 | $code .= 'function '; 1963 | break; 1964 | case \ast\flags\USE_NORMAL: 1965 | // nothing to do 1966 | break; 1967 | case 0: 1968 | // denotes no flags are set 1969 | break; 1970 | default: 1971 | assert(false, "Unknown flag ({$node->flags}) for AST_USE or AST_GROUP_USE found."); 1972 | } 1973 | 1974 | return $code; 1975 | } 1976 | 1977 | private function useElem(Node $node) : string 1978 | { 1979 | $code = ''; 1980 | 1981 | switch ($node->flags) { 1982 | case \ast\flags\USE_CONST: 1983 | $code .= 'const '; 1984 | break; 1985 | case \ast\flags\USE_FUNCTION: 1986 | $code .= 'function '; 1987 | break; 1988 | case \ast\flags\USE_NORMAL: 1989 | // nothing to do 1990 | break; 1991 | case 0: 1992 | // denotes no flags are set 1993 | break; 1994 | default: 1995 | assert(false, "Unknown flag ({$node->flags}) for AST_USE_ELEM found."); 1996 | } 1997 | 1998 | $code .= $node->children['name']; 1999 | 2000 | if ($node->children['alias'] !== null) { 2001 | $code .= ' as ' . $node->children['alias']; 2002 | } 2003 | 2004 | return $code; 2005 | } 2006 | 2007 | private function useTrait(Node $node) : string 2008 | { 2009 | $code = 'use '; 2010 | 2011 | $code .= $this->commaSeparatedValues($node->children['traits'], ', '); 2012 | 2013 | if ($node->children['adaptations'] !== null) { 2014 | $code .= ' {' . PHP_EOL; 2015 | 2016 | ++$this->indentationLevel; 2017 | 2018 | $code .= $this->revertAST($node->children['adaptations']); 2019 | 2020 | --$this->indentationLevel; 2021 | 2022 | $code .= $this->indent() . '}' . PHP_EOL; 2023 | } 2024 | 2025 | return $code; 2026 | } 2027 | 2028 | private function var(Node $node) : string 2029 | { 2030 | $code = '$'; 2031 | 2032 | if (!$node->children['name'] instanceof Node) { 2033 | $varName = $node->children['name']; 2034 | 2035 | if (preg_match('~^[_a-z][_a-z0-9]*$~i', $varName)) { 2036 | return $code . $varName; 2037 | } 2038 | 2039 | return $code . "{'" . $varName . "'}"; 2040 | } 2041 | 2042 | $code .= '{' . $this->revertAST($node->children['name']) . '}'; 2043 | 2044 | // special case check for something like `${$a = $b};` 2045 | if ($node->children['name']->kind === \ast\AST_ASSIGN) { 2046 | $code .= $this->forceTerminateStatement($code); 2047 | } 2048 | 2049 | return $code; 2050 | } 2051 | 2052 | private function while(Node $node) : string 2053 | { 2054 | $code = 'while (' . $this->revertAST($node->children['cond']) . ')'; 2055 | 2056 | $bodyNode = $node->children['stmts']; 2057 | 2058 | if ($bodyNode !== null && (!$bodyNode instanceof Node || $bodyNode->children !== [])) { 2059 | if ( 2060 | !$bodyNode instanceof Node 2061 | || $bodyNode->kind !== \ast\AST_STMT_LIST 2062 | ) { 2063 | $bodyNode = $this->createStmtList($bodyNode); 2064 | } 2065 | 2066 | $code .= ' {' . PHP_EOL; 2067 | 2068 | ++$this->indentationLevel; 2069 | 2070 | $code .= $this->revertAST($bodyNode); 2071 | 2072 | --$this->indentationLevel; 2073 | 2074 | $code .= $this->indent() . '}'; 2075 | } 2076 | 2077 | return $code; 2078 | } 2079 | 2080 | private function yield(Node $node) : string 2081 | { 2082 | $code = 'yield'; 2083 | if ($node->children['value'] !== null) { 2084 | $code .= ' '; 2085 | if ($node->children['key'] !== null) { 2086 | $code .= $this->revertAST($node->children['key']) . ' => '; 2087 | } 2088 | $code .= $this->revertAST($node->children['value']); 2089 | } 2090 | return $code; 2091 | } 2092 | 2093 | private function yieldFrom(Node $node) : string 2094 | { 2095 | return 'yield from ' . $this->revertAST($node->children['expr']); 2096 | } 2097 | } 2098 | -------------------------------------------------------------------------------- /tests/AstReverterTest.php: -------------------------------------------------------------------------------- 1 | abstractTestAstReverter(30); 15 | } 16 | 17 | public function testAstReverterV35() 18 | { 19 | echo "\nphp-ast v35 tests:\n"; 20 | @$this->abstractTestAstReverter(35); 21 | } 22 | 23 | public function testAstReverterV40() 24 | { 25 | echo "\nphp-ast v40 tests:\n"; 26 | $this->abstractTestAstReverter(40); 27 | } 28 | 29 | public function testAstReverterV45() 30 | { 31 | echo "\nphp-ast v45 tests:\n"; 32 | $this->abstractTestAstReverter(45); 33 | } 34 | 35 | public function testAstReverterV50() 36 | { 37 | echo "\nphp-ast v50 tests:\n"; 38 | $this->abstractTestAstReverter(50); 39 | } 40 | 41 | private function abstractTestAstReverter($version) 42 | { 43 | $testsDir = __DIR__ . '/AstReverterTests/'; 44 | $dirIter = new \DirectoryIterator($testsDir); 45 | $this->astReverter = new AstReverter(); 46 | 47 | foreach ($dirIter as $file) { 48 | $fileName = $file->getFileName(); 49 | $fileParts = explode('.', $fileName); 50 | $extension = end($fileParts); 51 | 52 | if ($fileParts[1] !== 'test') { 53 | continue; 54 | } 55 | 56 | $file = file_get_contents("{$testsDir}{$fileName}"); 57 | $test = explode('<=======>', $file); 58 | $name = trim($test[0]); 59 | $minVersion = trim($test[1]); 60 | $input = trim($test[2]); 61 | $expected = ltrim($test[3]) . PHP_EOL; 62 | 63 | if (version_compare(PHP_VERSION, $minVersion) < 0) { 64 | continue; 65 | } 66 | 67 | echo "Running {$fileParts[0]} tests\n"; 68 | $this->assertEquals($expected, $this->astReverter->getCode(parse_code($input, $version)), "{$name} ({$fileParts[0]})"); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/AstReverterTests/classes.test: -------------------------------------------------------------------------------- 1 | Class Definitions Test 2 | <=======> 3 | 7.0 4 | <=======> 5 | num = $num; 24 | } 25 | 26 | use SomeTrait; 27 | }; 28 | interface SomeInterface {} 29 | interface ArrayProxyAccess extends ArrayAccess{} 30 | <=======> 31 | num = $num; 66 | } 67 | use SomeTrait; 68 | }; 69 | interface SomeInterface 70 | { 71 | } 72 | interface ArrayProxyAccess extends ArrayAccess 73 | { 74 | } 75 | -------------------------------------------------------------------------------- /tests/AstReverterTests/closures.test: -------------------------------------------------------------------------------- 1 | Closures Test 2 | <=======> 3 | 7.0 4 | <=======> 5 | 11 | 3 | 7.0 4 | <=======> 5 | 15 | 3 | 7.0 4 | <=======> 5 | 'bar'; 11 | } 12 | function b($b) {yield from a();} 13 | <=======> 14 | "bar"; 21 | } 22 | function b($b) 23 | { 24 | yield from a(); 25 | } 26 | -------------------------------------------------------------------------------- /tests/AstReverterTests/languageConstructs.test: -------------------------------------------------------------------------------- 1 | Language Constructs Test 2 | <=======> 3 | 7.0 4 | <=======> 5 | b(); 34 | global $a, $b; 35 | global $$c; 36 | declare(strict_types=1); 37 | declare(a=1,b=2); 38 | declare(ticks=1) {$a;} 39 | const afunction=3,ause=4; 40 | if (1) {$a;} 41 | if (1) {$a;} elseif (2) {$b;} 42 | if (1) {$a;} elseif (2) {$b;} else {$c;} 43 | if (1) { 44 | $a; 45 | } else if (2) { 46 | $b; 47 | } elseif (3) { 48 | $c; 49 | } else { 50 | $d; 51 | } 52 | if (0) { 53 | $a; 54 | } else { 55 | if(1) 56 | if (2) { 57 | 58 | } 59 | } 60 | if (1); 61 | list(,,$a) = []; 62 | <=======> 63 | b(); 93 | global $a; 94 | global $b; 95 | global ${$c}; 96 | declare(strict_types = 1); 97 | declare(a = 1, b = 2); 98 | declare(ticks = 1) { 99 | $a; 100 | } 101 | const afunction = 3, ause = 4; 102 | if (1) { 103 | $a; 104 | } 105 | if (1) { 106 | $a; 107 | } elseif (2) { 108 | $b; 109 | } 110 | if (1) { 111 | $a; 112 | } elseif (2) { 113 | $b; 114 | } else { 115 | $c; 116 | } 117 | if (1) { 118 | $a; 119 | } else { 120 | if (2) { 121 | $b; 122 | } elseif (3) { 123 | $c; 124 | } else { 125 | $d; 126 | } 127 | } 128 | if (0) { 129 | $a; 130 | } else { 131 | if (1) { 132 | if (2) { 133 | } 134 | } 135 | } 136 | if (1) { 137 | } 138 | list(, , $a) = []; 139 | -------------------------------------------------------------------------------- /tests/AstReverterTests/loops.test: -------------------------------------------------------------------------------- 1 | Loops Test 2 | <=======> 3 | 7.0 4 | <=======> 5 | $c){} 14 | foreach($arr as $key => &$val){} 15 | for ($a = 0, $b = 0; $a > 10, $b >= 1; ++$a, $b--) { 16 | 'a'; 17 | } 18 | for(;;){} 19 | for(;;); 20 | for (;;) $a; 21 | while(0); 22 | while (1) 1; 23 | foreach($a as $b); 24 | foreach ($a as $b) true; 25 | <=======> 26 | $c) { 35 | } 36 | foreach ($arr as $key => &$val) { 37 | } 38 | for ($a = 0, $b = 0; $a > 10, $b >= 1; ++$a, $b--) { 39 | "a"; 40 | } 41 | for (;;); 42 | for (;;); 43 | for (;;) { 44 | $a; 45 | } 46 | while (0); 47 | while (1) { 48 | 1; 49 | } 50 | foreach ($a as $b) { 51 | } 52 | foreach ($a as $b) { 53 | true; 54 | } 55 | -------------------------------------------------------------------------------- /tests/AstReverterTests/methodDefinitions.test: -------------------------------------------------------------------------------- 1 | Method Definitions Test 2 | <=======> 3 | 7.0 4 | <=======> 5 | 54 | 3 | 7.0 4 | <=======> 5 | 2, 3=>4, 5, 6, a(), b() => c]; 12 | (string) $a; 13 | (bool) $a; 14 | (float) $a; 15 | (int) $a; 16 | (array) $a; 17 | (object) $a; 18 | (unset) $a; 19 | $a = &$b; 20 | function &a(&$a){} 21 | __LINE__; 22 | __FILE__; 23 | __DIR__; 24 | __TRAIT__; 25 | __METHOD__; 26 | __FUNCTION__; 27 | __NAMESPACE__; 28 | __CLASS__; 29 | (new SomeClass)->invokeMethod(); 30 | `a$a`; 31 | $a .= "a"; 32 | $a += 1; 33 | $a -= 1; 34 | $a /= 1; 35 | $a *= 1; 36 | $a ^= 1; 37 | $a |= 1; 38 | $a &= 1; 39 | $a %= 1; 40 | $a **= 1; 41 | $a <<= 1; 42 | $a >>= 1; 43 | $b = ${$a}; 44 | $a += ${$b}; 45 | $a{$a+=$b}; 46 | ($a <=> $b) === 1; 47 | ($a === 1) === 2; 48 | $a === (1 === 2); 49 | ($a === 3) !== true; 50 | 1 << ($a === 1); 51 | <=======> 52 | 2, 3 => 4, 5, 6, a(), b() => c]; 59 | (string) $a; 60 | (bool) $a; 61 | (float) $a; 62 | (int) $a; 63 | (array) $a; 64 | (object) $a; 65 | (unset) $a; 66 | $a = &$b; 67 | function &a(&$a) 68 | { 69 | } 70 | __LINE__; 71 | __FILE__; 72 | __DIR__; 73 | __TRAIT__; 74 | __METHOD__; 75 | __FUNCTION__; 76 | __NAMESPACE__; 77 | __CLASS__; 78 | (new SomeClass())->invokeMethod(); 79 | `a{$a}`; 80 | $a .= "a"; 81 | $a += 1; 82 | $a -= 1; 83 | $a /= 1; 84 | $a *= 1; 85 | $a ^= 1; 86 | $a |= 1; 87 | $a &= 1; 88 | $a %= 1; 89 | $a **= 1; 90 | $a <<= 1; 91 | $a >>= 1; 92 | $b = ${$a}; 93 | $a += ${$b}; 94 | $a[$a += $b]; 95 | ($a <=> $b) === 1; 96 | ($a === 1) === 2; 97 | $a === (1 === 2); 98 | ($a === 3) !== true; 99 | 1 << ($a === 1); 100 | -------------------------------------------------------------------------------- /tests/AstReverterTests/namespaces.test: -------------------------------------------------------------------------------- 1 | Namespaces Test 2 | <=======> 3 | 7.0 4 | <=======> 5 | 28 | 3 | 7.1 4 | <=======> 5 | 16 | 3 | 7.0 4 | <=======> 5 | 25 | 3 | 7.0 4 | <=======> 5 | {$a->b()}}"; 22 | <=======> 23 | {$a->b()}}"; 40 | -------------------------------------------------------------------------------- /tests/AstReverterTests/traits.test: -------------------------------------------------------------------------------- 1 | Traits Test 2 | <=======> 3 | 7.0 4 | <=======> 5 | 20 | 3 | 7.0 4 | <=======> 5 | 28 | 3 | 7.0 4 | <=======> 5 | {$b->$c}; 11 | $a::${$b->$c}; 12 | $a->{$$b()->$$c->b()}::${$a->$b()->c}; 13 | ${$a = $b}; 14 | <=======> 15 | {$b->$c}; 21 | $a::${$b->$c}; 22 | $a->{${$b}()->${$c}->b()}::${$a->$b()->c}; 23 | ${$a = $b}; 24 | --------------------------------------------------------------------------------