-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Request: dumper that create php to create AST #566
Comments
Probably something like this can be made based on reflection. Constructor argument names should match subnodes names and it should be possible to generate code based on that. |
@nikic maybe reflection can help for the subNodes. However the |
@flip111 |
@nikic when i look here |
@flip111 By subnodes I mean anything returned here: https://github.com/nikic/PHP-Parser/blob/master/lib/PhpParser/Node/Stmt/Class_.php#L47 All subnodes are public properties of the class. |
@nikic ok i can try to make this. Is PR welcome? Where should this code live? |
Found a class that is irregular.. the constructor can not be created with the same name as the subNodes. https://github.com/nikic/PHP-Parser/blob/master/lib/PhpParser/Node/Name.php#L20-L29 There needs to be a translation from |
I have this now .. it works for some code, but not fully tested. Maybe i can finish this and send PR ? <?php
use PhpParser\Node as N;
use PhpParser\Node\Expr as E;
use PhpParser\Node\Scalar as SV;
// assumes values have already been transformed into AST nodes
function wrapArray($array) {
$wrapped = [];
$i = 0;
$nonAssoc = true;
foreach ($array as $key => $value) {
if (is_array($value)) {
$value = wrapArray($value);
}
if (is_string($key)) {
$wrapped[] = new E\ArrayItem($value, new SV\String_($key));
} elseif (is_int($key)) {
if ($key !== $i) {
$nonAssoc = false;
}
// don't set an explicit key when we can use php auto indexing
if ($nonAssoc) {
$wrapped[] = new E\ArrayItem($value);
} else {
$wrapped[] = new E\ArrayItem($value, new SV\LNumber($key));
}
}
$i++;
}
return new E\Array_($wrapped);
};
function astToAstBuild($node) {
if (is_array($node)) {
array_walk($node, function(&$value, $key) { $value = astToAstBuild($value); });
return $node;
} elseif (is_object($node)) {
$args = array_map(function($refl_par) {
return [ 'name' => $refl_par->name
// empty array means "no value"
// filled array means "a value"
, 'default' => $refl_par->isOptional() ? [$refl_par->getDefaultValue()] : []
];
}, (new \ReflectionClass($node))->getConstructor()->getParameters());
$nonConstructorParameterSubnodes = array_diff($node->getSubNodeNames(), array_column($args, 'name'));
foreach ($args as $key => ['name' => $name, 'default' => $default]) {
if ($name === 'subNodes') {
$subNodes = [];
// only process array items that were listed as being a subNode
// but not an explicit constructor parameter
foreach ($nonConstructorParameterSubnodes as $k) {
$v = $node->{$k};
if ($k === 'stmts') {
if ($v !== []) {
$subNodes[$k] = $v;
}
} else {
// can put some custom rules per class here
if (get_class($node) === 'PhpParser\Node\Stmt\Class_') {
if ( ($k === 'flags' && $v !== 0)
|| ($k === 'extends' && $v !== null)
|| ($k === 'implements' && $v !== [])
) {
$subNodes[$k] = $v;
}
// when not specifying rules per class it can lead to more noise in the output
} else {
$subNodes[$k] = $v;
}
}
}
$args[$key]['value'] = $subNodes;
} elseif ($name === 'attributes') {
$args[$key]['value'] = []; // ignoring attributes for the moment
} elseif (($node instanceof \PhpParser\Node\Name) && $name === 'name') {
$args[$key]['value'] = $node->toString();
} else {
$args[$key]['value'] = $node->{$name};
}
unset($args[$key]['name']); // don't need this information anymore
}
// find out which constructor parameters can use the default value
$args = array_reverse($args, true);
$can_use_default = true;
foreach ($args as $k => $v) {
if (count($v['default']) === 0 || $v['value'] !== $v['default'][0]) {
$can_use_default = false;
}
$args[$k]['can_use_default'] = $can_use_default;
unset($args[$k]['default']); // don't need this information anymore
}
$args = array_reverse($args, true);
$argsBuild = [];
foreach ($args as $arg) {
if ($arg['can_use_default']) {
break;
}
$ret = astToAstBuild($arg['value']);
if (is_array($ret)) {
$ret = wrapArray($ret);
}
$argsBuild[] = new N\Arg($ret);
}
return new E\New_(new N\Name(get_class($node)), $argsBuild);
} elseif (is_string($node)) {
return new SV\String_($node);
} elseif (is_int($node)) {
return new SV\LNumber($node);
} elseif ($node === null) {
return new E\ConstFetch(new Node\Name('null'));
} elseif (is_float($node)) {
return new SV\DNumber($node);
} elseif (is_bool($node)) {
return new E\ConstFetch(new Node\Name($node ? 'true' : 'false'));
} else {
printf("Not handled:\n");
var_dump($node);
die();
}
} |
Nice idea! |
FYI: I've just wrapped up the initial version: https://github.com/matthiasnoback/php-parser-instantiation-printer I've created this in a test-driven way and have added several examples to show that certain edge cases are handled (thanks @flip111 for the inspiration, great suggestions regarding default constructor arguments, constructor arguments that are also subnodes, and subnodes that have a default value. |
Thanks for the library looks pretty nice. I would add the following things to it:
|
Interesting suggestions, @flip111 ! I'll see what I can do. |
Input php code
Now when i look at the AST and ignore the wrapping array (just to clear additional clutter in the following code snippets), i get these outputs:
var_export($ast)
dumper
What i like to have is a dumper that prints PHP code that can construct the AST
For this the dumper should have some knowledge about the node constructor argument defaults, so that no unneeded arguments are given. For example if the same class had some statements in it, it would need to be:
This would be a very handy tool to create PHP from AST nodes with the help of a "static written" php file used for prototyping dynamic php code creation. While the export of var_export is probably runnable php code .. it's very hard to create code based on this because it's very verbose and __set_state is probably not the best thing to use here either.
The text was updated successfully, but these errors were encountered: