Add memoization

This commit is contained in:
Daniil Gentili 2020-08-09 15:14:32 +02:00
parent 0fb5cf8d88
commit 6ca8ebe0c1
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
6 changed files with 182 additions and 4 deletions

View File

@ -5,8 +5,10 @@ namespace Phabel;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Expression;
use PhpParser\ParserFactory;
@ -74,6 +76,23 @@ abstract class Plugin implements PluginInterface
{
$node = self::replaceType($node, $class, $propertyMap);
}
/**
* Create variable assignment
*
* @param Variable $name Variable
* @param Expr $expression Expression
*
* @return Expression
*/
public static function assign(Variable $name, Expr $expression): Expression
{
return new Expression(
new Assign(
$name,
$expression
)
);
}
/**
* Call function.
*

141
src/Plugin/Memoization.php Normal file
View File

@ -0,0 +1,141 @@
<?php
namespace Phabel\Plugin;
use Phabel\Plugin;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Identifier;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\Static_;
use PhpParser\Node\Stmt\StaticVar;
use SplStack;
/**
* Enable memoization of results based on a parameter.
*/
class Memoization
{
/**
* Stack of cache objects for current function.
*
* @var SplStack<null|ArrayDimFetch>
*/
private SplStack $cache;
/**
* Stack of stmts to prepend for current function.
*
* @var SplStack<Node[]>
*/
private SplStack $stmts;
/**
* Constructor function.
*/
public function __construct()
{
$this->cache = new SplStack;
$this->stmts = new SplStack;
}
/**
* Enter functions.
*
* @param FunctionLike $node Function
*
* @return void
*/
public function enterFunctionLike(FunctionLike $node): void
{
if (!\preg_match_all('/@memoize \$([\w\d_]+)/', (string) ($node->getDocComment() ?? ''), $matches)) {
$this->cache->push(null);
$this->stmts->push([]);
return;
}
if ($node->getReturnType() instanceof Identifier && $node->getReturnType()->name === 'void') {
throw new \RuntimeException('Cannot memoize void function');
}
/** @var Node[] */
$toPrepend = [];
/** @var string[] */
$memoizeParams = $matches[1];
/** @var array<string, Param> */
$params = [];
foreach ($node->getParams() as $param) {
if (!$param->var instanceof Variable) {
continue;
}
$params[$param->var->name] = $param;
}
/** @var Variable[] */
$memoizeVars = [];
foreach ($memoizeParams as $memoizeVar) {
if (!isset($params[$memoizeVar])) {
throw new \RuntimeException('Cannot find memoization parameter $'.$memoizeVar);
}
$memoizeParam = $params[$memoizeVar];
if ($memoizeParam->type === null) {
throw new \RuntimeException('Cannot memoize by untyped parameter $'.$memoizeVar);
}
if ($memoizeParam->type instanceof Identifier) {
if ($memoizeParam->type->name === 'array') {
throw new \RuntimeException('Cannot memoize by array parameter $'.$memoizeVar);
}
if (\in_array($memoizeParam->type->name, ['string', 'int', 'float', 'bool'])) {
$memoizeVars[] = $memoizeParam->var;
continue;
}
}
$toPrepend []= Plugin::assign(
$variable = new Variable($memoizeParam->var->name.'___memo'),
Plugin::call('spl_object_hash', $memoizeParam->var)
);
$memoizeVars []= $variable;
}
$toPrepend []= new Static_([new StaticVar($cache = new Variable('memoizeCache'), new Array_())]);
foreach ($memoizeVars as $var) {
$cache = new ArrayDimFetch($cache, $var);
}
$toPrepend []= new If_(new Isset_([$cache]), [new Return_($cache)]);
$this->cache->push($cache);
$this->stmts->push($toPrepend);
}
/**
* Leave function.
*
* @param FunctionLike $fun Function
*
* @return void
*/
public function leaveFunctionLike(FunctionLike $fun): void
{
$this->cache->pop();
\array_unshift($fun->stmts, $this->stmts->pop());
}
/**
* Enter return expression.
*
* @param Return_ $return Return expression
*
* @return void
*/
public function enterReturn(Return_ $return)
{
if ($this->cache->top()) {
$return->expr = new Assign($this->cache->top(), $return->expr);
}
}
}

View File

@ -15,9 +15,9 @@ class Node
/**
* Plugins and configs.
*
* @var Plugin
* @var Plugins
*/
private Plugin $plugin;
private Plugins $plugin;
/**
* Original plugin name.
@ -103,7 +103,7 @@ class Node
public function init(string $plugin, array $config, PackageContext $ctx): self
{
$this->name = $plugin;
$this->plugin = new Plugin($plugin, $config);
$this->plugin = new Plugins($plugin, $config);
$this->packageContexts->attach($ctx);
$this->canBeRequired = PluginCache::canBeRequired($plugin);

View File

@ -8,7 +8,7 @@ use SplQueue;
/**
* Representation of multiple plugins+configs.
*/
class Plugin
class Plugins
{
/**
* Plugin configs, indexed by plugin name.

4
test.php Normal file
View File

@ -0,0 +1,4 @@
<?php
function a(): void {
}

14
test/dump.php Normal file
View File

@ -0,0 +1,14 @@
<?php
use PhpParser\ParserFactory;
require 'vendor/autoload.php';
if ($argc < 2) {
echo("Usage: {$argv[0]} file.php\n");
die(1);
}
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
var_dump($parser->parse(file_get_contents($argv[1])));