Skip to content

Commit

Permalink
Add first shot at format preserving pretty printer
Browse files Browse the repository at this point in the history
  • Loading branch information
nikic committed Dec 26, 2016
1 parent 9b2d35d commit 4d2a4d0
Show file tree
Hide file tree
Showing 18 changed files with 1,895 additions and 782 deletions.
56 changes: 56 additions & 0 deletions formatPreserving.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace Foo;

use PhpParser;
use PhpParser\Node;

require __DIR__ . '/vendor/autoload.php';

$lexer = new PhpParser\Lexer\Emulative([
'usedAttributes' => [
'comments',
'startLine', 'endLine',
'startFilePos', 'endFilePos',
'startTokenPos', 'endTokenPos',
],
]);

$parser = new PhpParser\Parser\Php7($lexer, [
'useIdentifierNodes' => true,
'useConsistentVariableNodes' => true,
]
);

$traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor(new PhpParser\NodeVisitor\CloningVisitor());

$printer = new PhpParser\PrettyPrinter\Standard();

$code = <<<'PHP'
<?php
function foo() {
echo
1
+
2
+
3;
echo "a";
echo "b";
}
PHP;

$oldStmts = $parser->parse($code);
$oldTokens = $lexer->getTokens();

$newStmts = $traverser->traverse($oldStmts);

$newStmts[0]->stmts[0]->exprs[0]->left->right->value = 42;
$newStmts[0]->stmts[1] = new Node\Expr\Assign(new Node\Expr\Variable('a'), new Node\Scalar\LNumber(42));

$newCode = $printer->printFormatPreserving($newStmts, $oldStmts, $code, $oldTokens);
echo $newCode, "\n";
25 changes: 8 additions & 17 deletions grammar/php5.y
Original file line number Diff line number Diff line change
Expand Up @@ -690,21 +690,8 @@ function_call:
{ $$ = Expr\StaticCall[$1, $3, $4]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
{ $$ = Expr\StaticCall[$1, $4, $6]; }
| static_property argument_list {
if ($1 instanceof Node\Expr\StaticPropertyFetch) {
$$ = Expr\StaticCall[$1->class, Expr\Variable[$1->name], $2];
} elseif ($1 instanceof Node\Expr\ArrayDimFetch) {
$tmp = $1;
while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
$tmp = $tmp->var;
}

$$ = Expr\StaticCall[$tmp->var->class, $1, $2];
$tmp->var = Expr\Variable[$tmp->var->name];
} else {
throw new \Exception;
}
}
| static_property argument_list
{ $$ = $this->fixupPhp5StaticPropCall($1, $2, attributes()); }
| variable_without_objects argument_list
{ $$ = Expr\FuncCall[$1, $2]; }
| function_call '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
Expand Down Expand Up @@ -992,14 +979,18 @@ encaps_base_var:
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
;

encaps_str_varname:
T_STRING_VARNAME { $$ = Expr\Variable[$1]; }
;

encaps_var:
encaps_base_var { $$ = $1; }
| encaps_base_var '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| encaps_base_var T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
{ $$ = Expr\ArrayDimFetch[Expr\Variable[$2], $4]; }
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'
{ $$ = Expr\ArrayDimFetch[$2, $4]; }
| T_CURLY_OPEN variable '}' { $$ = $2; }
;

Expand Down
8 changes: 6 additions & 2 deletions grammar/php7.y
Original file line number Diff line number Diff line change
Expand Up @@ -881,14 +881,18 @@ encaps_base_var:
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
;

encaps_str_varname:
T_STRING_VARNAME { $$ = Expr\Variable[$1]; }
;

encaps_var:
encaps_base_var { $$ = $1; }
| encaps_base_var '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| encaps_base_var T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
{ $$ = Expr\ArrayDimFetch[Expr\Variable[$2], $4]; }
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'
{ $$ = Expr\ArrayDimFetch[$2, $4]; }
| T_CURLY_OPEN variable '}' { $$ = $2; }
;

Expand Down
2 changes: 1 addition & 1 deletion grammar/rebuildParsers.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ function($matches) {
assertArgs(2, $args, $name);

return '$startAttributes = ' . $args[1] . ';'
. ' if (isset($startAttributes[\'comments\']))'
. ' if ($this->useNopStatements && isset($startAttributes[\'comments\']))'
. ' { ' . $args[0] . ' = new Stmt\Nop([\'comments\' => $startAttributes[\'comments\']]); }'
. ' else { ' . $args[0] . ' = null; }';
}
Expand Down
6 changes: 3 additions & 3 deletions lib/PhpParser/Node/Stmt/Do_.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

class Do_ extends Node\Stmt
{
/** @var Node\Expr Condition */
public $cond;
/** @var Node[] Statements */
public $stmts;
/** @var Node\Expr Condition */
public $cond;

/**
* Constructs a do while node.
Expand All @@ -25,6 +25,6 @@ public function __construct(Node\Expr $cond, array $stmts = array(), array $attr
}

public function getSubNodeNames() {
return array('cond', 'stmts');
return array('stmts', 'cond');
}
}
4 changes: 4 additions & 0 deletions lib/PhpParser/NodeAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ public function getAttributes() {
return $this->attributes;
}

public function setAttributes(array $attributes) {
$this->attributes = $attributes;
}

public function jsonSerialize() {
return ['nodeType' => $this->getType()] + get_object_vars($this);
}
Expand Down
19 changes: 19 additions & 0 deletions lib/PhpParser/NodeVisitor/CloningVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace PhpParser\NodeVisitor;

use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;

/**
* Visitor cloning all nodes and linking to the original nodes using an attribute.
*
* This visitor is required to perform format-preserving pretty prints.
*/
class CloningVisitor extends NodeVisitorAbstract {
public function enterNode(Node $origNode) {
$node = clone $origNode;
$node->setAttribute('origNode', $origNode);
return $node;
}
}
Loading

0 comments on commit 4d2a4d0

Please sign in to comment.