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; namespace Phabel;
use PhpParser\BuilderHelpers;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch; use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\ArrowFunction; use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\AssignOp; use PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignRef; 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\Closure;
use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Expr\Variable; use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike; use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param; use PhpParser\Node\Param;
use PhpParser\Node\Stmt\If_;
use SplStack; use SplStack;
/** /**
* AST Context
*
* @author Daniil Gentili <daniil@daniil.it> * @author Daniil Gentili <daniil@daniil.it>
* @license MIT * @license MIT
*/ */
@ -117,10 +127,27 @@ class Context
return new Variable($this->parents->top()->getVar()); return new Variable($this->parents->top()->getVar());
} }
/** /**
* Insert nodes before node. * Get child currently being iterated on.
* *
* @param Node $node * @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 * @return void
*/ */
public function insertBefore(Node $node, Node ...$nodes): void public function insertBefore(Node $node, Node ...$nodes): void
@ -128,25 +155,142 @@ class Context
if (empty($nodes)) { if (empty($nodes)) {
return; return;
} }
$subNode = $node->getAttribute('currentNode'); $found = false;
$subNodeIndex = $node->getAttribute('currentNodeIndex'); foreach ($this->parents as $parent) {
\array_splice($node->{$subNode}, $subNodeIndex, 0, $nodes); if ($this->getCurrentChild($parent) === $node) {
$skips = $node->getAttribute('skipNodes', []); $found = true;
$skips []= $subNodeIndex+\count($nodes); break;
$node->setAttribute('skipNodes', $skips); }
$node->setAttribute('currentNodeIndex', $subNodeIndex - 1); }
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. * Insert nodes after node.
* *
* @param Node $node * @param Node $node Node ater which to insert nodes
* @param Node ...$nodes * @param Node ...$nodes Nodes to insert
* @return void * @return void
*/ */
public function insertAfter(Node $node, Node ...$nodes): void public function insertAfter(Node $node, Node ...$nodes): void
{ {
$subNode = $node->getAttribute('currentNode'); if (empty($nodes)) {
$subNodeIndex = $node->getAttribute('currentNodeIndex'); return;
\array_splice($node->{$subNode}, $subNodeIndex+1, 0, $nodes); }
$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; use ReflectionClass;
/** /**
* Plugin
*
* @author Daniil Gentili <daniil@daniil.it> * @author Daniil Gentili <daniil@daniil.it>
*/ */
abstract class Plugin implements PluginInterface abstract class Plugin implements PluginInterface

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,12 @@
<?php <?php
/**
// This file generates an array containing all possible expression nodes, generated using default parameters. * 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, * 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. * 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 HaydenPierce\ClassFinder\ClassFinder;
use Phabel\Plugin\IssetExpressionFixer; use Phabel\Plugin\IssetExpressionFixer;