Add expression generator
This commit is contained in:
parent
5228af041e
commit
fb9dac3d95
@ -9,7 +9,8 @@
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7 | ^8 | ^9",
|
||||
"amphp/php-cs-fixer-config": "dev-master",
|
||||
"composer/composer": "^1|^2"
|
||||
"composer/composer": "^1|^2",
|
||||
"haydenpierce/class-finder": "^0.4.2"
|
||||
},
|
||||
"license": "MIT",
|
||||
"authors": [{
|
||||
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Php7to5\NodeVisitors;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class YieldReturnDetector extends NodeVisitorAbstract
|
||||
{
|
||||
protected $hasYield = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\FunctionLike) {
|
||||
$this->hasYield []= $node;
|
||||
}
|
||||
if ($node instanceof Node\Expr\Yield_ ||
|
||||
$node instanceof Node\Expr\YieldFrom
|
||||
) {
|
||||
\end($this->hasYield)->hasYield = true;
|
||||
}
|
||||
}
|
||||
public function leaveNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\FunctionLike) {
|
||||
\array_pop($this->hasYield);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Php7to5\NodeVisitors;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class YieldReturnReplacer extends NodeVisitorAbstract
|
||||
{
|
||||
protected $functions = [];
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\FunctionLike) {
|
||||
$this->functions[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function leaveNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\FunctionLike) {
|
||||
\array_pop($this->functions);
|
||||
return;
|
||||
}
|
||||
if (!$node instanceof Node\Stmt\Return_) {
|
||||
return;
|
||||
}
|
||||
if ($node->expr === null) {
|
||||
return new Node\Stmt\Return_();
|
||||
}
|
||||
|
||||
if (!(\end($this->functions)->hasYield ?? false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $node->expr;
|
||||
|
||||
$newReturn = new Node\Expr\Yield_(
|
||||
new Node\Expr\New_(
|
||||
new Node\Expr\ConstFetch(
|
||||
new Node\Name('\YieldReturnValue')
|
||||
),
|
||||
[$value]
|
||||
)
|
||||
);
|
||||
|
||||
$stmts = [$newReturn, new Node\Stmt\Return_()];
|
||||
return $stmts;
|
||||
}
|
||||
}
|
@ -3,17 +3,27 @@
|
||||
namespace Phabel\Plugin;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use Phabel\Traverser;
|
||||
use PhpParser\Builder\FunctionLike;
|
||||
|
||||
class ReGenerator extends Plugin
|
||||
{
|
||||
const SHOULD_ATTRIBUTE = 'shouldRegenerate';
|
||||
|
||||
/**
|
||||
* Custom traverser.
|
||||
*/
|
||||
private Traverser $traverser;
|
||||
public function __construct()
|
||||
{
|
||||
$this->traverser = Traverser::fromPlugin(new ReGeneratorInternal);
|
||||
}
|
||||
public function enter(FunctionLike $function)
|
||||
{
|
||||
if (!$function->getAttribute(self::SHOULD_ATTRIBUTE, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->traverser->traverseAst($function);
|
||||
}
|
||||
public function runAfter(): array
|
||||
{
|
||||
|
@ -12,62 +12,55 @@ class ReGenerator implements \Iterator
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $variables = [];
|
||||
public $variables = [];
|
||||
/**
|
||||
* Return value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $returnValue;
|
||||
public $returnValue;
|
||||
/**
|
||||
* Yield key.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $yieldKey;
|
||||
public $yieldKey;
|
||||
/**
|
||||
* Yield value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $yieldValue;
|
||||
public $yieldValue;
|
||||
|
||||
/**
|
||||
* Value sent from the outside.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $sentValue;
|
||||
public $sentValue;
|
||||
/**
|
||||
* Exception sent from the outside.
|
||||
*/
|
||||
private ?\Throwable $sentException = null;
|
||||
public ?\Throwable $sentException = null;
|
||||
|
||||
/**
|
||||
* Current state of state machine.
|
||||
*/
|
||||
private int $state = 0;
|
||||
public int $state = 0;
|
||||
|
||||
/**
|
||||
* Whether the generator has returned.
|
||||
*/
|
||||
private bool $returned = false;
|
||||
public bool $returned = false;
|
||||
/**
|
||||
* Whether the generator was started.
|
||||
*/
|
||||
private bool $started = false;
|
||||
public bool $started = false;
|
||||
|
||||
/**
|
||||
* Actual generator function.
|
||||
*/
|
||||
private \Closure $generator;
|
||||
|
||||
/**
|
||||
* Yielded from (re)generator.
|
||||
*
|
||||
* @var \Generator|self|null
|
||||
*/
|
||||
private $yieldedFrom;
|
||||
public \Closure $generator;
|
||||
|
||||
/**
|
||||
* Construct regenerator.
|
||||
@ -97,35 +90,26 @@ class ReGenerator implements \Iterator
|
||||
private function start(): void
|
||||
{
|
||||
if (!$this->started) {
|
||||
($this->generator)($this->state, $this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned, $this->yieldedFrom);
|
||||
($this->generator)($this->state, $this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned);
|
||||
$this->started = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send value into generator
|
||||
*
|
||||
* @param mixed $value Value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function send($value)
|
||||
{
|
||||
$this->start();
|
||||
if ($this->yieldedFrom) {
|
||||
try {
|
||||
$result = $this->yieldedFrom->send($value);
|
||||
} catch (\Throwable $exception) {
|
||||
$e = $exception;
|
||||
}
|
||||
if (!$this->yieldedFrom->valid()) { // Returned from yield from
|
||||
$returnValue = \method_exists($this->yieldedFrom, 'getReturn') ? $this->yieldedFrom->getReturn() : null;
|
||||
$this->yieldedFrom = null;
|
||||
if (isset($e)) {
|
||||
return $this->throw($e);
|
||||
}
|
||||
return $this->send($returnValue);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
$value = $this->yieldValue;
|
||||
if (!$this->returned) {
|
||||
$this->sentValue = $value;
|
||||
try {
|
||||
($this->generator)($this->state, $this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned, $this->yieldedFrom);
|
||||
($this->generator)($this->state, $this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned);
|
||||
} catch (\Throwable $e) {
|
||||
$this->returned = true;
|
||||
throw $e;
|
||||
@ -135,30 +119,21 @@ class ReGenerator implements \Iterator
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
/**
|
||||
* Throw value into generator
|
||||
*
|
||||
* @param \Throwable $throwable Excpeption
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function throw(\Throwable $throwable)
|
||||
{
|
||||
$this->start();
|
||||
if ($this->yieldedFrom) {
|
||||
try {
|
||||
$result = $this->yieldedFrom->throw($throwable);
|
||||
} catch (\Throwable $exception) {
|
||||
$e = $exception;
|
||||
}
|
||||
if (!$this->yieldedFrom->valid()) { // Returned from yield from
|
||||
$returnValue = \method_exists($this->yieldedFrom, 'getReturn') ? $this->yieldedFrom->getReturn() : null;
|
||||
$this->yieldedFrom = null;
|
||||
if (isset($e)) {
|
||||
return $this->throw($e);
|
||||
}
|
||||
return $this->send($returnValue);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
$value = $this->yieldValue;
|
||||
if (!$this->returned) {
|
||||
$this->sentException = $value;
|
||||
try {
|
||||
($this->generator)($this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned, $this->yieldedFrom);
|
||||
($this->generator)($this->state, $this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned);
|
||||
} catch (\Throwable $e) {
|
||||
$this->returned = true;
|
||||
throw $e;
|
||||
@ -169,31 +144,46 @@ class ReGenerator implements \Iterator
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
if ($this->yieldedFrom) {
|
||||
return $this->yieldedFrom->current();
|
||||
}
|
||||
$this->start();
|
||||
return $this->yieldValue;
|
||||
}
|
||||
/**
|
||||
* Get current key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
if ($this->yieldedFrom) {
|
||||
return $this->yieldedFrom->key();
|
||||
}
|
||||
$this->start();
|
||||
return $this->yieldKey;
|
||||
}
|
||||
/**
|
||||
* Advance generator
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
$this->send(null);
|
||||
}
|
||||
/**
|
||||
* Rewind generator
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
if ($this->started && !$this->returned) {
|
||||
throw new \Exception('Cannot rewind a generator that was already run');
|
||||
}
|
||||
$this->state = 0;
|
||||
$this->started = false;
|
||||
$this->returned = false;
|
||||
$this->returnValue = null;
|
||||
@ -201,10 +191,14 @@ class ReGenerator implements \Iterator
|
||||
$this->yieldValue = null;
|
||||
$this->sentValue = null;
|
||||
$this->sentException = null;
|
||||
$this->yieldedFrom = null;
|
||||
$this->variables = [];
|
||||
$this->start();
|
||||
}
|
||||
/**
|
||||
* Check if generator is valid
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return !$this->returned;
|
||||
|
46
src/Plugin/ReGeneratorInternal.php
Normal file
46
src/Plugin/ReGeneratorInternal.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Phabel\Plugin;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use SplQueue;
|
||||
|
||||
/**
|
||||
* Internal regenerator traversor.
|
||||
*/
|
||||
class ReGeneratorInternal extends Plugin
|
||||
{
|
||||
/**
|
||||
* List of nodes for each case.
|
||||
*
|
||||
* @var SplQueue<SplQueue<Node>>
|
||||
*/
|
||||
private SplQueue $states;
|
||||
public function __construct()
|
||||
{
|
||||
$this->states = new SplQueue;
|
||||
$this->states->enqueue(new SplQueue);
|
||||
}
|
||||
/**
|
||||
* Push node to current case.
|
||||
*
|
||||
* @param Node $node Node
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function pushNode(Node $node): void
|
||||
{
|
||||
$this->states->top()->enqueue($node);
|
||||
}
|
||||
private function pushState(): int
|
||||
{
|
||||
$this->states->enqueue(new SplQueue);
|
||||
return $this->states->count()-1;
|
||||
}
|
||||
public function enterRoot(FunctionLike $func)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -57,12 +57,14 @@ class TypeHintStripper extends Plugin
|
||||
* @var SplStack<T>
|
||||
*/
|
||||
private SplStack $stack;
|
||||
private ArrowClosure $converter;
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->stack = new SplStack;
|
||||
$this->converter = new ArrowClosure;
|
||||
}
|
||||
/**
|
||||
* Convert a function to a closure.
|
||||
@ -70,7 +72,7 @@ class TypeHintStripper extends Plugin
|
||||
private function toClosure(FunctionLike &$func): void
|
||||
{
|
||||
if ($func instanceof ArrowFunction) {
|
||||
$func = ArrowClosure::enterClosure($func);
|
||||
$func = $this->converter->enterClosure($func);
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -4,10 +4,13 @@ namespace Phabel\Target\Php70;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\ArrayDimFetch;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\Isset_;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Expr\Yield_;
|
||||
|
||||
/**
|
||||
@ -25,13 +28,11 @@ class CompoundAccess extends Plugin
|
||||
public function enterIsset(Isset_ $node): void
|
||||
{
|
||||
foreach ($node->vars as &$var) {
|
||||
if (!$var instanceof ArrayDimFetch) {
|
||||
continue;
|
||||
if ($var instanceof ArrayDimFetch
|
||||
&& $var->var instanceof Expr
|
||||
&& !($var->var instanceof Variable || $var->var instanceof FuncCall) {
|
||||
$var->var = self::callPoly('returnMe', $var->var);
|
||||
}
|
||||
if (!$var->var instanceof ClassConstFetch) {
|
||||
continue;
|
||||
}
|
||||
$var->var = self::callPoly('returnMe', $var->var);
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Phabel\Target\Php71;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\List_;
|
||||
@ -81,7 +82,7 @@ class ListKey extends Plugin
|
||||
}
|
||||
}
|
||||
/** @var Array_ */
|
||||
$keys = self::toLiteral($keys);
|
||||
$keys = BuilderHelpers::normalizeValue($keys);
|
||||
return [new List_($newList), $keys];
|
||||
}
|
||||
/**
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Phabel\Target;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use Phabel\Target\Php72\ObjectTypeHintReplacer;
|
||||
|
||||
/**
|
||||
* Makes changes necessary to polyfill PHP 7.2 and run on PHP 7.1 and below.
|
||||
@ -16,4 +17,11 @@ class Php72 extends Plugin
|
||||
{
|
||||
return ['symfony/polyfill-php72' => '*'];
|
||||
}
|
||||
|
||||
public static function runWithAfter(): array
|
||||
{
|
||||
return [
|
||||
ObjectTypeHintReplacer::class
|
||||
];
|
||||
}
|
||||
}
|
||||
|
23
src/Target/Php72/ObjectTypeHintReplacer.php
Normal file
23
src/Target/Php72/ObjectTypeHintReplacer.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Phabel\Target\Php72;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use Phabel\Plugin\TypeHintStripper;
|
||||
|
||||
class ObjectTypeHintReplacer extends Plugin
|
||||
{
|
||||
/**
|
||||
* Alias.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function runAfter(): array
|
||||
{
|
||||
return [
|
||||
TypeHintStripper::class => [
|
||||
'types' => ['object']
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
43
src/Target/Php74/ArrayUnpack.php
Normal file
43
src/Target/Php74/ArrayUnpack.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Phabel\Target\Php74;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
|
||||
class ArrayUnpack extends Plugin
|
||||
{
|
||||
public function enter(Array_ $array): ?FuncCall
|
||||
{
|
||||
$hasUnpack = false;
|
||||
foreach ($array->items as $item) {
|
||||
if ($item->unpack) {
|
||||
$hasUnpack = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$hasUnpack) {
|
||||
return;
|
||||
}
|
||||
$args = [];
|
||||
$array = new Array_();
|
||||
foreach ($array->items as $item) {
|
||||
if ($item->unpack) {
|
||||
if ($array->items) {
|
||||
$args []= new Arg($array);
|
||||
$array = new Array_();
|
||||
}
|
||||
$args []= new Arg($item->value);
|
||||
} else {
|
||||
$array->items []= $item;
|
||||
}
|
||||
}
|
||||
if ($array->items) {
|
||||
$args []= new Arg($array);
|
||||
}
|
||||
return Plugin::call("array_merge", ...$args);
|
||||
}
|
||||
}
|
@ -2,14 +2,47 @@
|
||||
|
||||
namespace Phabel\Target\Php74;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use Phabel\Traverser;
|
||||
use PhpParser\Node\Expr\ArrowFunction;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Expr\ClosureUse;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Param;
|
||||
|
||||
/**
|
||||
* Turn an arrow function into a closure.
|
||||
*/
|
||||
class ArrowClosure
|
||||
{
|
||||
/**
|
||||
* Traverser.
|
||||
*/
|
||||
private Traverser $traverser;
|
||||
/**
|
||||
* Finder plugin.
|
||||
*/
|
||||
private Plugin $finderPlugin;
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->finderPlugin = new class extends Plugin {
|
||||
private array $found = [];
|
||||
public function enter(Variable $var)
|
||||
{
|
||||
$this->found[$var->name]= new ClosureUse($var, true);
|
||||
}
|
||||
public function getFound(): array
|
||||
{
|
||||
$found = $this->found;
|
||||
$this->found = [];
|
||||
return $found;
|
||||
}
|
||||
};
|
||||
$this->traverser = Traverser::fromPlugin($this->finderPlugin);
|
||||
}
|
||||
/**
|
||||
* Enter arrow function.
|
||||
*
|
||||
@ -17,12 +50,24 @@ class ArrowClosure
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
public static function enterClosure(ArrowFunction $func): Closure
|
||||
public function enterClosure(ArrowFunction $func): Closure
|
||||
{
|
||||
$nodes = [];
|
||||
foreach ($func->getSubNodeNames() as $node) {
|
||||
$nodes[$node] = $func->{$node};
|
||||
}
|
||||
$params = [];
|
||||
foreach ($nodes['params'] ?? [] as $param) {
|
||||
$params[$param->var->name] = true;
|
||||
}
|
||||
$this->traverser->traverseAst($func);
|
||||
$nodes['uses'] = \array_merge(
|
||||
\array_values(\array_diff_key(
|
||||
$this->finderPlugin->getFound(),
|
||||
$params
|
||||
)),
|
||||
$nodes['use'] ?? []
|
||||
);
|
||||
return new Closure($nodes, $func->getAttributes());
|
||||
}
|
||||
}
|
||||
|
16
src/Target/Php74/NullCoalesceAssignment.php
Normal file
16
src/Target/Php74/NullCoalesceAssignment.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Phabel\Target\Php74;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\AssignOp\Coalesce;
|
||||
use PhpParser\Node\Expr\BinaryOp\Coalesce as BinaryOpCoalesce;
|
||||
|
||||
class NullCoalesceAssignment extends Plugin
|
||||
{
|
||||
public function enter(Coalesce $coalesce): Assign
|
||||
{
|
||||
return new Assign($coalesce->var, new BinaryOpCoalesce($coalesce->var, $coalesce->expr), $coalesce->getAttributes());
|
||||
}
|
||||
}
|
13
src/Target/Php74/TypedProperty.php
Normal file
13
src/Target/Php74/TypedProperty.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Phabel\Target\Php74;
|
||||
|
||||
use Phabel\Plugin;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class TypedProperty extends Plugin
|
||||
{
|
||||
|
||||
}
|
@ -28,6 +28,23 @@ class Traverser
|
||||
* @return SplQueue<SplQueue<Plugin>>
|
||||
*/
|
||||
private SplQueue $packageQueue;
|
||||
/**
|
||||
* Generate traverser from basic plugin instances.
|
||||
*
|
||||
* @param Plugin ...$plugin Plugins
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function fromPlugin(Plugin ...$plugin): self
|
||||
{
|
||||
$queue = new SplQueue;
|
||||
foreach ($plugin as $p) {
|
||||
$queue->enqueue($p);
|
||||
}
|
||||
$final = new SplQueue;
|
||||
$final->enqueue($queue);
|
||||
return new self($final);
|
||||
}
|
||||
/**
|
||||
* AST traverser.
|
||||
*
|
||||
@ -68,7 +85,7 @@ class Traverser
|
||||
/**
|
||||
* Traverse AST of file.
|
||||
*
|
||||
* @param string $file File
|
||||
* @param string $file File
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@ -95,9 +112,21 @@ class Traverser
|
||||
return;
|
||||
}
|
||||
$ast = new RootNode($this->parser->parse(\file_get_contents($file)));
|
||||
$this->traverseAst($ast, $reducedQueue);
|
||||
}
|
||||
/**
|
||||
* Traverse AST.
|
||||
*
|
||||
* @param Node $node Initial node
|
||||
* @param SplQueue $pluginQueue Plugin queue (optional)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function traverseAst(Node &$node, SplQueue $pluginQueue = null): void
|
||||
{
|
||||
$context = new Context;
|
||||
$context->parents->push($ast);
|
||||
foreach ($reducedQueue as $queue) {
|
||||
$context->parents->push($node);
|
||||
foreach ($pluginQueue ?? $this->packageQueue ?? $this->queue as $queue) {
|
||||
$this->traverseNode($ast, $queue, $context);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\PrettyPrinter\Standard;
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
@ -10,5 +11,7 @@ if ($argc < 2) {
|
||||
}
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
//$parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP5);
|
||||
|
||||
\var_dump($parser->parse(\file_get_contents($argv[1])));
|
||||
\var_dump($a = $parser->parse(\file_get_contents($argv[1])));
|
||||
var_dumP((new Standard())->prettyPrint($a));
|
||||
|
285
test/exprGen.php
Normal file
285
test/exprGen.php
Normal file
@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
use HaydenPierce\ClassFinder\ClassFinder;
|
||||
use PhpParser\Builder\Class_;
|
||||
use PhpParser\Builder\Method;
|
||||
use PhpParser\Internal\PrintableNewAnonClassNode;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Expr\ArrowFunction;
|
||||
use PhpParser\Node\Expr\Error;
|
||||
use PhpParser\Node\Expr\Exit_;
|
||||
use PhpParser\Node\Expr\Isset_;
|
||||
use PhpParser\Node\Expr\List_;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\EncapsedStringPart;
|
||||
use PhpParser\Node\Stmt\Class_ as StmtClass_;
|
||||
use PhpParser\Node\VarLikeIdentifier;
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
function format(Node $code): string
|
||||
{
|
||||
static $count = 0;
|
||||
$count++;
|
||||
$code = (new Class_("lmao$count"))->addStmt(
|
||||
(new Method("te"))
|
||||
->addStmt($code)
|
||||
->getNode()
|
||||
)->getNode();
|
||||
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
|
||||
return $prettyPrinter->prettyPrintFile([$code]);
|
||||
}
|
||||
function readUntilPrompt($resource): string
|
||||
{
|
||||
$data = '';
|
||||
while (\substr($data, -6) !== 'php > ') {
|
||||
$data .= \fread($resource, 1);
|
||||
}
|
||||
return \substr($data, 0, -6);
|
||||
}
|
||||
function checkSyntaxVersion(int $version, string $code): bool
|
||||
{
|
||||
$hasPrompt = $version < 80;
|
||||
$code = \str_replace(["\n", '<?php'], '', $code)."\n";
|
||||
static $processes = [];
|
||||
static $pipes = [];
|
||||
if (!isset($processes[$version])) {
|
||||
$cmd = "php$version -a 2>&1";
|
||||
$processes[$version] = \proc_open($cmd, [0 => ['pipe', 'r'], 1 => ['pipe', 'w']], $pipes[$version]);
|
||||
if ($hasPrompt) {
|
||||
readUntilPrompt($pipes[$version][1]);
|
||||
} else {
|
||||
\fgets($pipes[$version][1]);
|
||||
\fgets($pipes[$version][1]);
|
||||
}
|
||||
}
|
||||
if (!$hasPrompt) {
|
||||
$code .= "echo 'php > ';\n";
|
||||
}
|
||||
\fputs($pipes[$version][0], $code);
|
||||
if ($hasPrompt) {
|
||||
$result = \str_replace(['{', '}'], '', \substr(\preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', readUntilPrompt($pipes[$version][1])), \strlen($code)));
|
||||
//var_dump($code, "REsult for $version is: " .trim($result));
|
||||
} else {
|
||||
$result = readUntilPrompt($pipes[$version][1]);
|
||||
//var_dump($code, trim($result));
|
||||
}
|
||||
$result = \trim($result);
|
||||
return \strlen($result) === 0;
|
||||
}
|
||||
function checkSyntax(string $code, int $startFrom = 56): int
|
||||
{
|
||||
if (!$startFrom) {
|
||||
return $startFrom;
|
||||
}
|
||||
|
||||
foreach ([56, 70, 73, 74, 80] as $version) {
|
||||
if ($version < $startFrom) {
|
||||
continue;
|
||||
}
|
||||
if (checkSyntaxVersion($version, $code)) {
|
||||
return $version;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @var ReflectionClass[] */
|
||||
$expressions = [];
|
||||
foreach ($allClasses = ClassFinder::getClassesInNamespace('PhpParser', ClassFinder::RECURSIVE_MODE) as $class) {
|
||||
$class = new ReflectionClass($class);
|
||||
if ($class->isSubclassOf(Expr::class) && !$class->isAbstract()
|
||||
&& $class->getName() !== PrintableNewAnonClassNode::class
|
||||
&& $class->getName() !== ArrowFunction::class
|
||||
&& $class->getName() !== Error::class
|
||||
&& $class->getName() !== List_::class
|
||||
&& $class->getName() !== ArrayItem::class
|
||||
&& $class->getName() !== EncapsedStringPart::class
|
||||
&& $class->getName() !== Exit_::class
|
||||
&& $class->getName() !== List_::class) {
|
||||
$expressions []= $class;
|
||||
}
|
||||
}
|
||||
|
||||
$instanceArgs = [];
|
||||
$instanceArgNames = [];
|
||||
$instanceArgTypes = [];
|
||||
|
||||
$exprInstances = [];
|
||||
foreach ($expressions as $expr) {
|
||||
$class = $expr->getName();
|
||||
$method = $expr->getMethod('__construct');
|
||||
if ($method->getNumberOfParameters() === 1) {
|
||||
$exprInstances[$class] = $expr->newInstance();
|
||||
continue; // Is a magic constant or such
|
||||
}
|
||||
\preg_match_all('/@param (?<type>\S+) +\$(?<name>\S+)/', $method->getDocComment(), $matches);
|
||||
$types = \array_combine($matches['name'], $matches['type']);
|
||||
foreach ($types as &$type) {
|
||||
$type = \explode("|", $type);
|
||||
foreach ($type as $key => &$subtype) {
|
||||
if (str_starts_with($subtype, 'Node')) {
|
||||
$subtype = 'PhpParser\\'.$subtype;
|
||||
} elseif ($subtype === 'Error') {
|
||||
unset($type[$key]);
|
||||
} elseif ($subtype === 'Identifier') {
|
||||
$subtype = Identifier::class;
|
||||
} elseif ($subtype === 'Name') {
|
||||
$subtype = Name::class;
|
||||
} elseif ($subtype === 'Expr') {
|
||||
$subtype = Expr::class;
|
||||
} elseif ($subtype === 'VarLikeIdentifier') {
|
||||
$subtype = VarLikeIdentifier::class;
|
||||
}
|
||||
}
|
||||
}
|
||||
$params = $method->getParameters();
|
||||
$hasExpr = false;
|
||||
$arguments = [];
|
||||
$argNames = [];
|
||||
$argTypes = [];
|
||||
foreach ($params as $key => $param) {
|
||||
$paramStr = (string) $param->getType();
|
||||
$argNames[] = $param->getName();
|
||||
switch ($paramStr) {
|
||||
case Expr::class:
|
||||
$argTypes[$key] = [false, [Expr::class]];
|
||||
$arguments[] = new Variable("test");
|
||||
break;
|
||||
case Name::class:
|
||||
$arguments[] = new Name('self');
|
||||
break;
|
||||
case Variable::class:
|
||||
$arguments[] = new Variable("test");
|
||||
break;
|
||||
case 'array':
|
||||
if (\in_array('Expr[]', $types[$param->getName()] ?? [])) {
|
||||
$argTypes[$key] = [true, [Expr::class]];
|
||||
$arguments[] = [new Variable('test')];
|
||||
} else {
|
||||
$arguments[] = [];
|
||||
}
|
||||
break;
|
||||
case 'bool':
|
||||
$arguments[] = false;
|
||||
break;
|
||||
case 'float':
|
||||
case 'int':
|
||||
$arguments[] = 0;
|
||||
break;
|
||||
case 'string':
|
||||
$arguments[] = 'test';
|
||||
break;
|
||||
default:
|
||||
$argTypes[$key] = [false, $types[$param->getName()]];
|
||||
$arguments[] = new Variable("test");
|
||||
break;
|
||||
}
|
||||
}
|
||||
$exprInstances[$class] = $expr->newInstanceArgs($arguments);
|
||||
if (\count($argTypes)) {
|
||||
$instanceArgs[$class] = $arguments;
|
||||
$instanceArgNames[$class] = $argNames;
|
||||
$instanceArgTypes[$class] = $argTypes;
|
||||
}
|
||||
}
|
||||
|
||||
$versionMap = [];
|
||||
|
||||
$result = [
|
||||
'main' => [], // Needs adaptation for nested expressions
|
||||
'isset' => [], // Needs adaptation for nested expressions in isset
|
||||
];
|
||||
|
||||
$newInstances = [];
|
||||
foreach ($exprInstances as $class => $instance) {
|
||||
$version = checkSyntax(format($prev = $instance));
|
||||
$versionMap[$class] = $version ?: 1000;
|
||||
}
|
||||
|
||||
foreach ($instanceArgTypes as $class => $argTypes) {
|
||||
$baseArgs = $instanceArgs[$class];
|
||||
foreach ($argTypes as $key => [$isArray, $types]) {
|
||||
$name = $instanceArgNames[$class][$key];
|
||||
$possibleValues = [];
|
||||
foreach ($types as $type) {
|
||||
switch ($type) {
|
||||
case Expr::class:
|
||||
$possibleValues = array_merge($possibleValues, $exprInstances);
|
||||
break;
|
||||
case Name::class:
|
||||
$possibleValues[] = new Name('self');
|
||||
break;
|
||||
case Variable::class:
|
||||
$possibleValues[] = new Variable("test");
|
||||
break;
|
||||
case Identifier::class;
|
||||
$possibleValues[] = new Identifier('test');
|
||||
break;
|
||||
case StmtClass_::class:
|
||||
// Avoid using anonymous classes
|
||||
//$possibleValues[] = new StmtClass_(null);
|
||||
break;
|
||||
case 'string':
|
||||
$possibleValues[] = 'test';
|
||||
break;
|
||||
default:
|
||||
throw new Exception($type);
|
||||
}
|
||||
}
|
||||
foreach ($possibleValues as $arg) {
|
||||
$subVersion = \max(is_object($arg) ? $versionMap[\get_class($arg)] : 0, $versionMap[$class]);
|
||||
|
||||
$arguments = $baseArgs;
|
||||
$arguments[$key] = $isArray ? [$arg] : $arg;
|
||||
|
||||
$code = format($prev = new $class(...$arguments));
|
||||
$curVersion = checkSyntax($code, $subVersion);
|
||||
if ($curVersion && $curVersion !== $subVersion) {
|
||||
$result['main'][$curVersion][$class][$name][$arg] = true;
|
||||
echo "Min $curVersion for $code\n";
|
||||
}
|
||||
|
||||
$code = format(new Isset_([$prev]));
|
||||
$curVersion = checkSyntax($code, $subVersion);
|
||||
if ($curVersion && $curVersion !== $subVersion) {
|
||||
$result['isset'][$curVersion][$class][$name][\get_class($expr)] = true;
|
||||
echo "Min $curVersion for $code\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($instanceArgs as $class => $argumentsOld) {
|
||||
foreach ($argumentsOld as $key => $argument) {
|
||||
$name = $instanceArgNames[$class][$key];
|
||||
if ($argument instanceof Expr || (\is_array($argument) && \count($argument))) {
|
||||
foreach ($exprInstances as $expr) {
|
||||
$subVersion = \max($versionMap[\get_class($expr)], $versionMap[$class]);
|
||||
$arguments = $argumentsOld;
|
||||
$arguments[$key] = \is_array($argument) ? [$expr] : $expr;
|
||||
|
||||
$code = format($prev = new $class(...$arguments));
|
||||
$curVersion = checkSyntax($code, $subVersion);
|
||||
if ($curVersion && $curVersion !== $subVersion) {
|
||||
$result['main'][$curVersion][$class][$name][\get_class($expr)] = true;
|
||||
echo "Min $curVersion for $code\n";
|
||||
}
|
||||
|
||||
$code = format(new Isset_([$prev]));
|
||||
$curVersion = checkSyntax($code, $subVersion);
|
||||
if ($curVersion && $curVersion !== $subVersion) {
|
||||
$result['isset'][$curVersion][$class][$name][\get_class($expr)] = true;
|
||||
echo "Min $curVersion for $code\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$allClassesKeys = \array_fill_keys($allClasses, true);
|
||||
|
||||
\file_put_contents('result.php', '<?php $result = '.\var_export($result, true).";");
|
Loading…
Reference in New Issue
Block a user