feat(closure): 添加闭包生成功能并支持占位符调用

- 实现 ClosureGenerator trait 来统一处理闭包生成逻辑
- 添加 ArrowFunction 和 Closure 表达式的解析方法
- 引入 PlaceHolder 异常用于处理可调用占位符场景
- 添加 PlaceHolderGenerator trait 处理占位符闭包生成
- 实现 genCharPtr 和 genArray 工具函数
- 更新 FuncCall 和 StaticCall 处理以支持占位符语法
- 修复变参占位符的错误处理机制
pull/1/head
韩天峰 6 months ago
parent 43cb0948e1
commit 70eccf59fc
  1. 1
      examples/place_holder.php
  2. 182
      src/Php/CompilerBase.php
  3. 8
      src/Php/Exception/PlaceHolder.php
  4. 94
      src/Php/Generator/ClosureGenerator.php
  5. 16
      src/Php/Generator/PlaceHolderGenerator.php
  6. 18
      src/Php/Generator/Utils.php
  7. 44
      tests/aot/place-holder.phpt
  8. 29
      tests/aot/ref-closure-param.phpt

@ -14,8 +14,12 @@ use PhpAot\Php\Entity\FunctionDef;
use PhpAot\Php\Entity\InterfaceDef; use PhpAot\Php\Entity\InterfaceDef;
use PhpAot\Php\Entity\MethodDef; use PhpAot\Php\Entity\MethodDef;
use PhpAot\Php\Entity\PropertyDef; use PhpAot\Php\Entity\PropertyDef;
use PhpAot\Php\Exception\PlaceHolder;
use PhpAot\Php\Exception\Redo; use PhpAot\Php\Exception\Redo;
use PhpAot\Php\Exception\Skip; use PhpAot\Php\Exception\Skip;
use PhpAot\Php\Generator\ClosureGenerator;
use PhpAot\Php\Generator\PlaceHolderGenerator;
use PhpAot\Php\Generator\Utils;
use PhpParser\Modifiers; use PhpParser\Modifiers;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Expr\Variable; use PhpParser\Node\Expr\Variable;
@ -33,6 +37,9 @@ class CompilerBase extends \PhpAot\Core\Translator
{ {
use AstNodeType; use AstNodeType;
use FuncCallOptimizer; use FuncCallOptimizer;
use ClosureGenerator;
use PlaceHolderGenerator;
use Utils;
public const string TYPE_VAR = 'php::Var'; public const string TYPE_VAR = 'php::Var';
@ -1983,8 +1990,10 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseFuncCall(Node\Expr\FuncCall $expr, bool $silent = false): string protected function parseFuncCall(Node\Expr\FuncCall $expr, bool $silent = false): string
{ {
$call = ''; $call = '';
$placeHolder = '';
if ($this->isVarExpr($expr->name)) { if ($this->isVarExpr($expr->name)) {
$fn = $this->parseIdentifier($expr->name); $fn = $this->parseIdentifier($expr->name);
$placeHolder = $fn;
$name = ''; $name = '';
} elseif ($expr->name->getType() === 'Name') { } elseif ($expr->name->getType() === 'Name') {
$name = $this->parseIdentifier($expr->name); $name = $this->parseIdentifier($expr->name);
@ -1999,6 +2008,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($code) { if ($code) {
return $code; return $code;
} }
$placeHolder = $this->identifierToStr($expr->name);
$fn = $this->getFuncPtr($name); $fn = $this->getFuncPtr($name);
$this->beforeStmtLines[] = '// Func Call: ' . $name; $this->beforeStmtLines[] = '// Func Call: ' . $name;
$call = $silent ? 'CALL_SILENT' : 'CALL'; $call = $silent ? 'CALL_SILENT' : 'CALL';
@ -2006,8 +2016,8 @@ class CompilerBase extends \PhpAot\Core\Translator
$tmpVar = $this->genTmpVarName(); $tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR); $this->addLocalVar($tmpVar, self::TYPE_VAR);
$this->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($expr->name) . ';'; $this->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($expr->name) . ';';
$fn = $tmpVar; $placeHolder = $fn = $tmpVar;
$name = ''; $name = '';
} }
if (!$call) { if (!$call) {
$call = $silent ? 'php::silentCall' : 'php::call'; $call = $silent ? 'php::silentCall' : 'php::call';
@ -2015,17 +2025,24 @@ class CompilerBase extends \PhpAot\Core\Translator
if (empty($expr->args)) { if (empty($expr->args)) {
return $call . '(' . $fn . ')'; return $call . '(' . $fn . ')';
} }
return $call . '(' . $fn . ', ' . $this->parseCallArgs($expr->args, $name) . ')'; try {
return $call . '(' . $fn . ', ' . $this->parseCallArgs($expr->args, $name) . ')';
} catch (PlaceHolder) {
return $this->genPlaceHolder($placeHolder);
}
} }
protected function parseNativeCallArgs(array $callArgs, string $nativeFunc): string protected function parseNativeCallArgs(array $callArgs, string $nativeFunc): string
{ {
$argList = []; $argList = [];
$functionDef = $this->functions[$nativeFunc]; $functionDef = $this->nativeFunctions[$nativeFunc];
$args = []; $args = [];
$hasNamedArg = false; $hasNamedArg = false;
// 对命名参数进行重排 // 对命名参数进行重排
foreach ($callArgs as $i => $arg) { foreach ($callArgs as $i => $arg) {
if ($arg instanceof Node\VariadicPlaceholder) {
throw new PlaceHolder();
}
if ($arg->name) { if ($arg->name) {
foreach($functionDef->argInfoList as $k => $argInfo) { foreach($functionDef->argInfoList as $k => $argInfo) {
if ($argInfo->name === $arg->name->name) { if ($argInfo->name === $arg->name->name) {
@ -2067,7 +2084,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$last = array_key_last($args); $last = array_key_last($args);
foreach ($args as $i => $arg) { foreach ($args as $i => $arg) {
if ($arg instanceof Node\VariadicPlaceholder) { if ($arg instanceof Node\VariadicPlaceholder) {
$this->fatalError($arg, 'Variadic place holder are not supported'); throw new PlaceHolder();
} }
if ($arg->name !== null) { if ($arg->name !== null) {
$this->fatalError($arg, 'Named arguments are not supported'); $this->fatalError($arg, 'Named arguments are not supported');
@ -3288,13 +3305,19 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
if ($class and $funcName) { if ($class and $funcName) {
$method = $this->getMethodPtr($class, $funcName); $methodPtr = $this->getMethodPtr($class, $funcName);
} else {
$methodPtr = $method;
} }
if (empty($expr->args)) { if (empty($expr->args)) {
return $object . '.exec(' . $method . ')'; return $object . '.exec(' . $methodPtr . ')';
}
try {
return $object . '.exec(' . $methodPtr . ', ' . $this->parseCallArgs($expr->args, $funcName, $class) . ')';
} catch (PlaceHolder) {
return $this->genPlaceHolder($this->genArray([$object, $method]));
} }
return $object . '.exec(' . $method . ', ' . $this->parseCallArgs($expr->args, $funcName, $class) . ')';
} }
protected function identifierToStr(NodeAbstract $node, bool $require = true): string protected function identifierToStr(NodeAbstract $node, bool $require = true): string
@ -3310,7 +3333,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$id = $this->class; $id = $this->class;
} }
if ($this->isNameExpr($node) or $this->isIdExpr($node)) { if ($this->isNameExpr($node) or $this->isIdExpr($node)) {
return '"' . $id . '"'; return $this->genCharPtr($id);
} }
return $id; return $id;
} }
@ -3324,6 +3347,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseStaticCall(Node\Expr\StaticCall $expr): string protected function parseStaticCall(Node\Expr\StaticCall $expr): string
{ {
$placeHolder = '';
if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) { if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) {
$var = $this->parseIdentifier($expr->class); $var = $this->parseIdentifier($expr->class);
if ($this->isTypedObject($var)) { if ($this->isTypedObject($var)) {
@ -3334,6 +3358,7 @@ class CompilerBase extends \PhpAot\Core\Translator
} else { } else {
$fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->identifierToStr($expr->name) . '})'; $fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->identifierToStr($expr->name) . '})';
} }
$placeHolder = $fn;
} else { } else {
$class = $this->parseIdentifier($expr->class); $class = $this->parseIdentifier($expr->class);
if ($class === 'self') { if ($class === 'self') {
@ -3349,7 +3374,11 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) { if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) {
$nativeFunc = $this->getNativeStaticMethod($class, $method); $nativeFunc = $this->getNativeStaticMethod($class, $method);
if ($nativeFunc) { if ($nativeFunc) {
$args = $this->parseNativeCallArgs($expr->args, $nativeFunc); try {
$args = $this->parseNativeCallArgs($expr->args, $nativeFunc);
} catch (PlaceHolder) {
return $this->genPlaceHolder($this->genArray([$this->genCharPtr($class), $this->genCharPtr($method)]));
}
if ($args) { if ($args) {
return self::PREFIX . $nativeFunc . '(php::null_object, ' . $args . ')'; return self::PREFIX . $nativeFunc . '(php::null_object, ' . $args . ')';
} else { } else {
@ -3359,12 +3388,17 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
$ce = $this->getClassEntryPtr($class); $ce = $this->getClassEntryPtr($class);
$fn = $ce . ', ' . $this->getFuncPtr($class . '::' . $method, false); $fn = $ce . ', ' . $this->getFuncPtr($class . '::' . $method, false);
$placeHolder = $this->genArray([$this->genCharPtr($class), $this->genCharPtr($method)]);
} }
$call = 'php::call'; $call = 'php::call';
if (empty($expr->args)) { if (empty($expr->args)) {
return $call . '(' . $fn . ')'; return $call . '(' . $fn . ')';
} }
return $call . '(' . $fn . ', ' . $this->parseCallArgs($expr->args) . ')'; try {
return $call . '(' . $fn . ', ' . $this->parseCallArgs($expr->args) . ')';
} catch (PlaceHolder) {
return $this->genPlaceHolder($placeHolder);
}
} }
protected function findNativeStaticProperty(Node\Expr\StaticPropertyFetch $expr, ?string &$class, ?string &$namespace): ?PropertyDef protected function findNativeStaticProperty(Node\Expr\StaticPropertyFetch $expr, ?string &$class, ?string &$namespace): ?PropertyDef
@ -3843,125 +3877,23 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseArrowFunction(Node\Expr\ArrowFunction $expr): string protected function parseArrowFunction(Node\Expr\ArrowFunction $expr): string
{ {
$tmpVar = $this->genTmpVarName(); $cb = function () use ($expr) {
return 'return ' . $this->parseExpr($expr->expr) . ';';
$fnCode = $this->getIndent() . };
'php::ClosureFn ' . $tmpVar . ' = [&](' return $this->genClosure($expr, $expr->params, $cb, [], true);
. 'INTERNAL_FUNCTION_PARAMETERS, '
. self::TYPE_OBJECT . ' &this_, '
. self::TYPE_ARGS . ' &vars_) ' .
'-> ' . self::TYPE_VAR . ' {' . PHP_EOL;
$oriArgs = $this->arguments;
$this->arguments = [];
$oriInClosure = $this->inClosure;
$this->inClosure = true;
$this->indentLevel++;
foreach ($expr->params as $i => $param) {
if ($param->byRef) {
$this->fatalError($expr, 'Closure cannot use reference parameter');
}
$var = $this->parseIdentifier($param->var);
$fnCode .= 'auto ' . $var . ' = php::getCallArg(' . $i . ');' . PHP_EOL;
$this->addArgument($var, self::TYPE_VAR);
}
if ($this->methodDef) {
$this->addArgument('this_', self::TYPE_OBJECT);
}
$fnCode .= 'return ' . $this->parseExpr($expr->expr) . ';';
$this->indentLevel--;
$fnCode .= '};' . PHP_EOL;
$this->beforeStmtLines[] = $fnCode;
$this->arguments = $oriArgs;
$this->inClosure = $oriInClosure;
if ($this->methodDef) {
return 'php::newClosure(' . $tmpVar . ', { }, this_)';
} else {
return 'php::newClosure(' . $tmpVar . ', { })';
}
} }
protected function parseClosure(Node\Expr\Closure $expr): string protected function parseClosure(Node\Expr\Closure $expr): string
{ {
$tmpVar = $this->genTmpVarName(); $cb = function () use ($expr) {
$fnCode = $this->parseStmts($expr->stmts);
$fnCode = $this->getIndent() . $fnCode = $this->genLocalVarDecl() . $fnCode;
'php::ClosureFn ' . $tmpVar . ' = [](' if (!$this->isReturnStmtInLastLine($expr->stmts)) {
. 'INTERNAL_FUNCTION_PARAMETERS, ' $fnCode .= 'return ' . self::VALUE_NULL . ';' . PHP_EOL;
. self::TYPE_OBJECT . ' &this_, '
. self::TYPE_ARGS . ' &vars_) ' .
'-> ' . self::TYPE_VAR . ' {' . PHP_EOL;
$oriLocalVars = $this->localVars;
$this->localVars = [];
$oriArgs = $this->arguments;
$this->arguments = [];
$oriInClosure = $this->inClosure;
$this->inClosure = true;
$this->indentLevel++;
$fnBodyCode = '';
foreach ($expr->params as $i => $param) {
if ($param->byRef) {
$this->fatalError($expr, 'Closure cannot use reference parameter');
}
$var = $this->parseIdentifier($param->var);
$fnBodyCode .= 'auto ' . $var . ' = php::getCallArg(' . $i . ');' . PHP_EOL;
$this->addArgument($var, self::TYPE_VAR);
}
foreach ($expr->uses as $i => $useItem) {
$var = $this->parseIdentifier($useItem->var);
$fnBodyCode .= 'auto ' . $var . ' = vars_.get(' . $i . ');' . PHP_EOL;
$this->addArgument($var, self::TYPE_VAR);
}
if ($this->methodDef) {
$this->addArgument('this_', self::TYPE_OBJECT);
}
$fnBodyCode .= $this->parseStmts($expr->stmts);;
$fnBodyCode = $this->genLocalVarDecl() . $fnBodyCode;
$fnCode .= $fnBodyCode;
if (!$this->isReturnStmtInLastLine($expr->stmts)) {
$fnCode .= 'return ' . self::VALUE_NULL . ';' . PHP_EOL;
}
$this->indentLevel--;
$fnCode .= '};' . PHP_EOL;
$this->beforeStmtLines[] = $fnCode;
$this->localVars = $oriLocalVars;
$this->arguments = $oriArgs;
$this->inClosure = $oriInClosure;
$useVars = [];
foreach ($expr->uses as $useItem) {
$var = $this->parseIdentifier($useItem->var);
if ($this->isVarExpr($useItem->var) and !$this->hasVar($var)) {
$this->errorUndefinedVariable($useItem->var);
} }
if ($useItem->byRef) { return $fnCode;
$useVars [] = $this->convertToRef($useItem->var); };
} else { return $this->genClosure($expr, $expr->params, $cb, $expr->uses);
$useVars [] = $var;
}
}
if ($this->methodDef) {
return 'php::newClosure(' . $tmpVar . ', { ' . implode(', ', $useVars) . ' }, this_)';
} else {
return 'php::newClosure(' . $tmpVar . ', { ' . implode(', ', $useVars) . ' })';
}
} }
protected function parseAssignOpCoalesce(Node\Expr\AssignOp\Coalesce $expr): string protected function parseAssignOpCoalesce(Node\Expr\AssignOp\Coalesce $expr): string

@ -0,0 +1,8 @@
<?php
namespace PhpAot\Php\Exception;
class PlaceHolder extends \RuntimeException
{
}

@ -0,0 +1,94 @@
<?php
namespace PhpAot\Php\Generator;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\NodeAbstract;
trait ClosureGenerator
{
/**
* @param NodeAbstract $expr
* @param array $params
* @param callable $bodyGenCb
* @param array $uses
* @param $useCurrentScope bool 直接使用当前作用域,C++ 函数将使用 & 捕获所有闭包变量
* @return string
*/
protected function genClosure(NodeAbstract $expr, array $params, callable $bodyGenCb, array $uses = [], bool $useCurrentScope = false): string
{
$tmpVar = $this->genTmpVarName();
$capture = $useCurrentScope ? '&' : '';
$code = $this->getIndent() .
'php::ClosureFn ' . $tmpVar . ' = [' . $capture . ']('
. 'INTERNAL_FUNCTION_PARAMETERS, '
. self::TYPE_OBJECT . ' &this_, '
. self::TYPE_ARGS . ' &vars_) ' .
'-> ' . self::TYPE_VAR . ' {' . PHP_EOL;
if (!$useCurrentScope) {
$oriLocalVars = $this->localVars;
$this->localVars = [];
}
$oriArgs = $this->arguments;
$this->arguments = [];
$oriInClosure = $this->inClosure;
$this->inClosure = true;
$this->indentLevel++;
foreach ($params as $i => $param) {
if ($param->byRef) {
$this->fatalError($expr, 'Closure cannot use reference parameter');
}
$var = $this->parseIdentifier($param->var);
$code .= 'auto ' . $var . ' = php::getCallArg(' . $i . ');' . PHP_EOL;
$this->addArgument($var, self::TYPE_VAR);
}
foreach ($uses as $i => $useItem) {
$var = $this->parseIdentifier($useItem->var);
$code .= 'auto ' . $var . ' = vars_.get(' . $i . ');' . PHP_EOL;
$this->addArgument($var, self::TYPE_VAR);
}
if ($this->methodDef) {
$this->addArgument('this_', self::TYPE_OBJECT);
}
$code .= $bodyGenCb();
$this->indentLevel--;
$code .= '};' . PHP_EOL;
$this->beforeStmtLines[] = $code;
if (!$useCurrentScope) {
$this->localVars = $oriLocalVars;
}
$this->arguments = $oriArgs;
$this->inClosure = $oriInClosure;
$useVars = [];
if ($uses) {
foreach ($uses as $useItem) {
$var = $this->parseIdentifier($useItem->var);
if ($this->isVarExpr($useItem->var) and !$this->hasVar($var)) {
$this->errorUndefinedVariable($useItem->var);
}
if ($useItem->byRef) {
$useVars [] = $this->convertToRef($useItem->var);
} else {
$useVars [] = $var;
}
}
}
if ($this->methodDef) {
return 'php::newClosure(' . $tmpVar . ', { ' . implode(', ', $useVars) . ' }, this_)';
} else {
return 'php::newClosure(' . $tmpVar . ', { ' . implode(', ', $useVars) . ' })';
}
}
}

@ -0,0 +1,16 @@
<?php
namespace PhpAot\Php\Generator;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\NodeAbstract;
trait PlaceHolderGenerator
{
protected function genPlaceHolder(string $callable): string
{
$ce = $this->getClassEntryPtr(\Closure::class);
$fn = $ce . ', ' . $this->getFuncPtr('Closure::fromCallable', false);
return 'php::call(' . $fn . ', {' . $callable . '})';
}
}

@ -0,0 +1,18 @@
<?php
namespace PhpAot\Php\Generator;
use PhpAot\Php\CompilerBase;
trait Utils
{
protected function genCharPtr(string $str): string
{
return '"' . $str . '"';
}
protected function genArray(array $elements): string
{
return CompilerBase::TYPE_ARRAY . '{' . implode(', ', $elements) . ' }';
}
}

@ -0,0 +1,44 @@
--TEST--
place-holder
--FILE--
<?php
class TestPlaceHolder {
static function foo()
{
var_dump(__FUNCTION__);
$fn4 = (self::bar(...));
$fn4();
}
static function bar()
{
var_dump(__METHOD__);
}
function test() {
var_dump(__METHOD__);
}
}
function main()
{
$obj = new TestPlaceHolder;
$fn1 = $obj->test(...);
$fn1();
$fn2 = var_dump(...);
$fn2(__LINE__);
$fn3 = TestPlaceHolder::foo(...);
$fn3();
TestPlaceHolder::foo();
}
?>
--EXPECT--
string(21) "TestPlaceHolder::test"
int(27)
string(3) "foo"
string(20) "TestPlaceHolder::bar"
string(3) "foo"
string(20) "TestPlaceHolder::bar"

@ -0,0 +1,29 @@
--TEST--
closure function with ref parameter
--SKIPIF--
<?php die('skip'); ?>
--FILE--
<?php
//function main()
//{
// $s = "foo";
// $testFn = function (&$data) {
// $data .= " bar";
// };
// $testFn($s);
// var_dump($s);
//}
function main()
{
$testFn = function (&$data) {
$data .= " (_)";
};
$sweet = array('a' => 'apple', 'b' => 'banana');
$fruits = array('sweet' => $sweet, 'sour' => 'lemon');
array_walk_recursive($fruits, $testFn);
var_dump($fruits);
}
?>
--EXPECT--
string(7) "foo bar"
Loading…
Cancel
Save