Implement expression extraction
This commit is contained in:
parent
9d6c66361e
commit
8dda97c514
172
src/Context.php
172
src/Context.php
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ use PhpParser\ParserFactory;
|
|||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Plugin
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
abstract class Plugin implements PluginInterface
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace Phabel;
|
|||
use Phabel\PluginGraph\PackageContext;
|
||||
|
||||
/**
|
||||
* Plugin interface
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
interface PluginInterface
|
||||
|
|
|
@ -7,6 +7,8 @@ use PhpParser\NodeAbstract;
|
|||
|
||||
/**
|
||||
* Root node.
|
||||
*
|
||||
* @author Daniil Gentili <email@email.com>
|
||||
*/
|
||||
class RootNode extends NodeAbstract
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ use PhpParser\ParserFactory;
|
|||
use SplQueue;
|
||||
|
||||
/**
|
||||
* AST traverser
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class Traverser
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* Dump AST of file.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\PrettyPrinter\Standard;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue