Implement expression extraction

This commit is contained in:
Daniil Gentili 2020-09-05 20:52:54 +02:00
parent 9d6c66361e
commit 8dda97c514
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
10 changed files with 188 additions and 29 deletions

View File

@ -2,22 +2,32 @@
namespace Phabel;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignRef;
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use PhpParser\Node\Expr\BinaryOp\Coalesce;
use PhpParser\Node\Expr\BooleanNot;
use PhpParser\Node\Expr\Cast\Bool_;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\If_;
use SplStack;
/**
* AST Context
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
@ -117,10 +127,27 @@ class Context
return new Variable($this->parents->top()->getVar());
}
/**
* Insert nodes before node.
* Get child currently being iterated on.
*
* @param Node $node
* @param Node ...$nodes
* @return Node
*/
public static function getCurrentChild(Node $node): Node
{
if (!$subNode = $node->getAttribute('currentNode')) {
throw new \RuntimeException('Node is not a part of the current AST stack!');
}
$child = $node->{$subNode};
if ($index = $node->getAttribute('currentNodeIndex')) {
return $child[$index];
}
return $child;
}
/**
* Insert nodes before node.
*
* @param Node $node Node before which to insert nodes
* @param Node ...$nodes Nodes to insert
* @return void
*/
public function insertBefore(Node $node, Node ...$nodes): void
@ -128,25 +155,142 @@ class Context
if (empty($nodes)) {
return;
}
$subNode = $node->getAttribute('currentNode');
$subNodeIndex = $node->getAttribute('currentNodeIndex');
\array_splice($node->{$subNode}, $subNodeIndex, 0, $nodes);
$skips = $node->getAttribute('skipNodes', []);
$skips []= $subNodeIndex+\count($nodes);
$node->setAttribute('skipNodes', $skips);
$node->setAttribute('currentNodeIndex', $subNodeIndex - 1);
$found = false;
foreach ($this->parents as $parent) {
if ($this->getCurrentChild($parent) === $node) {
$found = true;
break;
}
}
if (!$found) {
throw new \RuntimeException('Node is not a part of the current AST stack!');
}
$this->insertBeforeParent($parent, $nodes);
}
/**
* Insert nodes before node.
*
* @param Node $node Node before which to insert nodes
* @param Node[] $nodes Nodes to insert
* @return void
*/
private function insertBeforeParent(Node $parent, array $nodes): void
{
$subNode = $parent->getAttribute('currentNode');
if ($subNode === 'stmts') {
$subNodeIndex = $parent->getAttribute('currentNodeIndex');
\array_splice($parent->{$subNode}, $subNodeIndex, 0, $nodes);
$skips = $parent->getAttribute('skipNodes', []);
$skips []= $subNodeIndex+\count($nodes);
$parent->setAttribute('skipNodes', $skips);
$parent->setAttribute('currentNodeIndex', $subNodeIndex - 1);
} else { // Cannot insert, is not in a statement
$curNode = &$parent->{$subNode};
if ($curNode instanceof BooleanOr && $subNode === 'right') {
$result = $this->getVariable();
$nodes = new If_(
$curNode->left,
[
'stmts' => [
new Assign($result, BuilderHelpers::normalizeValue(true))
],
'else' => [
...$nodes,
new Assign($result, new Bool_($curNode->right))
]
]
);
$curNode = $result;
} elseif ($curNode instanceof BooleanAnd && $subNode === 'right') {
$result = $this->getVariable();
$nodes = new If_(
$curNode->left,
[
'stmts' => [
...$nodes,
new Assign($result, new Bool_($curNode->right))
],
'else' => [
new Assign($result, BuilderHelpers::normalizeValue(false))
]
]
);
$curNode = $result;
} elseif ($curNode instanceof Ternary && $subNode !== 'cond') {
$result = $this->getVariable();
if (!$curNode->if) { // ?:
$nodes = new If_(
new BooleanNot(
new Assign($result, $curNode->cond)
),
[
'stmts' => [
...$nodes,
new Assign($result, $curNode->else)
]
]
);
} else {
$nodes = new If_(
$curNode->cond,
[
'stmts' => [
...$subNode === 'if' ? $nodes : [],
new Assign($result, $curNode->if)
],
'else' => [
...$subNode === 'else' ? $nodes : [],
new Assign($result, $curNode->else)
]
]
);
}
$curNode = $result;
} elseif ($subNode instanceof Coalesce && $subNode === 'right') {
$result = $this->getVariable();
$nodes = new If_(
Plugin::call(
'is_null',
new Assign($result, $curNode->left)
),
[
'stmts' => [
...$nodes,
new Assign($result, $curNode->right)
]
]
);
$curNode = $result;
} else if ($subNode instanceof FuncCall) {
}
$this->insertBefore($parent, $nodes);
}
}
/**
* Insert nodes after node.
*
* @param Node $node
* @param Node ...$nodes
* @param Node $node Node ater which to insert nodes
* @param Node ...$nodes Nodes to insert
* @return void
*/
public function insertAfter(Node $node, Node ...$nodes): void
{
$subNode = $node->getAttribute('currentNode');
$subNodeIndex = $node->getAttribute('currentNodeIndex');
\array_splice($node->{$subNode}, $subNodeIndex+1, 0, $nodes);
if (empty($nodes)) {
return;
}
$found = false;
foreach ($this->parents as $parent) {
if ($this->getCurrentChild($parent) === $node) {
$found = true;
break;
}
}
if (!$found) {
throw new \RuntimeException('Node is not a part of the current AST stack!');
}
$subNode = $parent->getAttribute('currentNode');
$subNodeIndex = $parent->getAttribute('currentNodeIndex');
\array_splice($parent->{$subNode}, $subNodeIndex+1, 0, $nodes);
}
}

View File

@ -17,6 +17,8 @@ use PhpParser\ParserFactory;
use ReflectionClass;
/**
* Plugin
*
* @author Daniil Gentili <daniil@daniil.it>
*/
abstract class Plugin implements PluginInterface

View File

@ -264,7 +264,7 @@ class TypeHintStripper extends Plugin
$return->expr = $var;
$ctx->insertBefore($ctx->parents->top(), $assign, $if);
$ctx->insertBefore($return, $assign, $if);
}
/**
* Get trace string for errors.

View File

@ -5,6 +5,8 @@ namespace Phabel;
use Phabel\PluginGraph\PackageContext;
/**
* Plugin interface
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface PluginInterface

View File

@ -7,6 +7,8 @@ use PhpParser\NodeAbstract;
/**
* Root node.
*
* @author Daniil Gentili <email@email.com>
*/
class RootNode extends NodeAbstract
{

View File

@ -7,6 +7,7 @@ use Phabel\Plugin;
use Phabel\RootNode;
use PhpParser\Node;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\Namespace_;
class AnonymousClassReplacer extends Plugin
@ -43,14 +44,13 @@ class AnonymousClassReplacer extends Plugin
if (!$classNode instanceof Node\Stmt\Class_) {
return;
}
$classNode->name = new Identifier('PhabelAnonymousClass'.$this->fileName.($this->count++));
$classNode->name = 'PhabelAnonymousClass'.$this->fileName.($this->count++);
$node->class = new Node\Name($classNode->name);
$node->class = new Node\Name($classNode->name->name);
foreach ($ctx->parents as $node) {
if ($node instanceof Namespace_ || $node instanceof RootNode) {
$ctx->insertAfter($node, $classNode);
$ctx->insertBefore($ctx->getCurrentChild($node), $classNode);
return;
}
}

View File

@ -8,6 +8,8 @@ use PhpParser\ParserFactory;
use SplQueue;
/**
* AST traverser
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class Traverser

View File

@ -4,6 +4,8 @@ namespace Phabel;
/**
* Represent variables currently in scope.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class VariableContext
{
@ -13,10 +15,6 @@ class VariableContext
* @var array<string, true>
*/
private array $variables;
/**
* Custom variable counter.
*/
private int $counter = 0;
/**
* Constructor.
*
@ -77,8 +75,7 @@ class VariableContext
public function getVar(): string
{
do {
$var = 'phabel'.$this->counter;
$this->counter++;
$var = 'phabel'.bin2hex(random_bytes(8));
} while (isset($this->variables[$var]));
$this->variables[$var] = true;
return $var;

View File

@ -1,4 +1,10 @@
<?php
/**
* Dump AST of file.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;

View File

@ -1,8 +1,12 @@
<?php
// This file generates an array containing all possible expression nodes, generated using default parameters.
// Then, for each expression that accepts another expression as subnode, it tries to use all the expressions generated in the previous step,
// for each subnode, to test compatibility with various versions of the PHP lexer.
/**
* This file generates an array containing all possible expression nodes, generated using default parameters.
* Then, for each expression that accepts another expression as subnode, it tries to use all the expressions generated in the previous step,
* for each subnode, to test compatibility with various versions of the PHP lexer.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
use HaydenPierce\ClassFinder\ClassFinder;
use Phabel\Plugin\IssetExpressionFixer;