phabel/src/Context.php

153 lines
4.5 KiB
PHP
Raw Normal View History

2020-08-03 21:31:32 +02:00
<?php
namespace Phabel;
2020-08-14 20:40:01 +02:00
use PhpParser\Node;
2020-09-01 12:52:44 +02:00
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\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;
2020-08-14 14:43:29 +02:00
use SplStack;
2020-08-13 18:30:12 +02:00
/**
* @author Daniil Gentili <daniil@daniil.it>
2020-08-14 14:43:29 +02:00
* @license MIT
2020-08-13 18:30:12 +02:00
*/
2020-08-03 21:31:32 +02:00
class Context
{
2020-08-14 14:43:29 +02:00
/**
2020-08-14 20:40:01 +02:00
* Parent nodes stack.
*
2020-08-14 14:43:29 +02:00
* @var SplStack<Node>
*/
public SplStack $parents;
2020-08-30 20:27:43 +02:00
/**
2020-09-01 12:52:44 +02:00
* Declared variables stack.
*
* @var SplStack<VariableContext>
2020-08-30 20:27:43 +02:00
*/
2020-09-01 12:52:44 +02:00
public SplStack $variables;
2020-08-30 20:27:43 +02:00
/**
2020-09-01 12:52:44 +02:00
* Constructor.
2020-08-30 20:27:43 +02:00
*/
2020-08-14 14:43:29 +02:00
public function __construct()
{
$this->parents = new SplStack;
2020-09-01 12:52:44 +02:00
$this->variables = new SplStack;
2020-08-14 14:43:29 +02:00
}
2020-08-30 20:27:43 +02:00
/**
2020-09-01 12:52:44 +02:00
* Push node.
2020-08-30 20:27:43 +02:00
*
* @param Node $node Node
2020-09-01 12:52:44 +02:00
*
2020-08-30 20:27:43 +02:00
* @return void
*/
public function push(Node $node): void
{
$this->parents->push($node);
2020-09-01 12:52:44 +02:00
if ($node instanceof RootNode) {
$this->variables->push(new VariableContext);
}
if ($node instanceof FunctionLike) {
$variables = \array_fill_keys(
\array_map(
fn (Param $param): string => $param->var->name,
$node->getParams()
),
true
);
if ($node instanceof Closure) {
foreach ($node->uses as $use) {
$variables[$use->var->name] = true;
if ($use->byRef) {
$this->variables->top()->addVar($use->var->name);
}
}
2020-09-01 17:36:13 +02:00
} elseif ($node instanceof ArrowFunction) {
$variables += $this->variables->top()->getVars();
2020-09-01 12:52:44 +02:00
}
2020-09-01 17:36:13 +02:00
$this->variables->push(new VariableContext($variables));
2020-09-01 12:52:44 +02:00
} elseif ($node instanceof Assign || $node instanceof AssignOp || $node instanceof AssignRef) {
do {
$node = $node->var;
} while ($node instanceof ArrayDimFetch && $node->var instanceof ArrayDimFetch);
if ($node instanceof Variable && \is_string($node->name)) {
$this->variables->top()->addVar($node->name);
}
} elseif ($node instanceof MethodCall || $node instanceof StaticCall || $node instanceof FuncCall) {
// Cover reference parameters
foreach ($node->args as $argument) {
$argument = $argument->value;
do {
$argument = $argument->var;
} while ($argument instanceof ArrayDimFetch && $argument->var instanceof ArrayDimFetch);
if ($argument instanceof Variable && \is_string($argument->name)) {
$this->variables->top()->addVar($argument->name);
2020-09-01 14:31:23 +02:00
}
2020-09-01 12:52:44 +02:00
}
2020-08-30 20:27:43 +02:00
}
}
/**
2020-09-01 12:52:44 +02:00
* Pop node.
2020-08-30 20:27:43 +02:00
*
* @return void
*/
public function pop(): void
{
2020-09-01 12:52:44 +02:00
$popped = $this->parents->pop();
if ($popped instanceof RootNode || ($popped instanceof FunctionLike && !$popped instanceof ArrowFunction)) {
$this->variables->pop();
2020-08-30 20:27:43 +02:00
}
}
2020-09-01 12:52:44 +02:00
/**
* Return new unoccupied variable.
*
* @return Variable
*/
public function getVariable(): Variable
{
return new Variable($this->parents->top()->getVar());
}
2020-08-14 20:40:01 +02:00
/**
* Insert nodes before node.
*
* @param Node $node
* @param Node ...$nodes
* @return void
*/
public function insertBefore(Node $node, Node ...$nodes): void
{
2020-08-23 20:54:56 +02:00
if (empty($nodes)) {
return;
}
2020-08-14 20:40:01 +02:00
$subNode = $node->getAttribute('currentNode');
$subNodeIndex = $node->getAttribute('currentNodeIndex');
\array_splice($node->{$subNode}, $subNodeIndex, 0, $nodes);
2020-08-23 20:54:56 +02:00
$skips = $node->getAttribute('skipNodes', []);
$skips []= $subNodeIndex+\count($nodes);
$node->setAttribute('skipNodes', $skips);
$node->setAttribute('currentNodeIndex', $subNodeIndex - 1);
2020-08-14 20:40:01 +02:00
}
/**
* Insert nodes after node.
*
* @param Node $node
* @param Node ...$nodes
* @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);
}
2020-08-09 13:49:19 +02:00
}