diff --git a/src/Context.php b/src/Context.php index a2d9bd6..0ebc4c8 100644 --- a/src/Context.php +++ b/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 * @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); } } diff --git a/src/Plugin.php b/src/Plugin.php index 974be55..58be8d3 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -17,6 +17,8 @@ use PhpParser\ParserFactory; use ReflectionClass; /** + * Plugin + * * @author Daniil Gentili */ abstract class Plugin implements PluginInterface diff --git a/src/Plugin/TypeHintStripper.php b/src/Plugin/TypeHintStripper.php index a922e03..88a2595 100644 --- a/src/Plugin/TypeHintStripper.php +++ b/src/Plugin/TypeHintStripper.php @@ -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. diff --git a/src/PluginInterface.php b/src/PluginInterface.php index ddb96f0..87e9681 100644 --- a/src/PluginInterface.php +++ b/src/PluginInterface.php @@ -5,6 +5,8 @@ namespace Phabel; use Phabel\PluginGraph\PackageContext; /** + * Plugin interface + * * @author Daniil Gentili */ interface PluginInterface diff --git a/src/RootNode.php b/src/RootNode.php index f1e3ac3..15d24a2 100644 --- a/src/RootNode.php +++ b/src/RootNode.php @@ -7,6 +7,8 @@ use PhpParser\NodeAbstract; /** * Root node. + * + * @author Daniil Gentili */ class RootNode extends NodeAbstract { diff --git a/src/Target/Php70/AnonymousClassReplacer.php b/src/Target/Php70/AnonymousClassReplacer.php index df5ed75..5286cb9 100644 --- a/src/Target/Php70/AnonymousClassReplacer.php +++ b/src/Target/Php70/AnonymousClassReplacer.php @@ -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; } } diff --git a/src/Traverser.php b/src/Traverser.php index dbcc899..5aa01d4 100644 --- a/src/Traverser.php +++ b/src/Traverser.php @@ -8,6 +8,8 @@ use PhpParser\ParserFactory; use SplQueue; /** + * AST traverser + * * @author Daniil Gentili */ class Traverser diff --git a/src/VariableContext.php b/src/VariableContext.php index b7c2c01..3311ee1 100644 --- a/src/VariableContext.php +++ b/src/VariableContext.php @@ -4,6 +4,8 @@ namespace Phabel; /** * Represent variables currently in scope. + * + * @author Daniil Gentili */ class VariableContext { @@ -13,10 +15,6 @@ class VariableContext * @var array */ 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; diff --git a/test/dump.php b/test/dump.php index 2de752b..445751f 100644 --- a/test/dump.php +++ b/test/dump.php @@ -1,4 +1,10 @@ + * @license MIT + */ use PhpParser\ParserFactory; use PhpParser\PrettyPrinter\Standard; diff --git a/test/exprGen.php b/test/exprGen.php index 9ab5743..6d403b6 100644 --- a/test/exprGen.php +++ b/test/exprGen.php @@ -1,8 +1,12 @@ + * @license MIT + */ use HaydenPierce\ClassFinder\ClassFinder; use Phabel\Plugin\IssetExpressionFixer;