- Implement parseNativeCallArgs method for native function argument parsing - Add buildNativeVariadicArg method for variadic argument handling - Create parseNamedCallArgs method for named argument processing - Add isReferenceArgument method for checking reference parameters - Implement AOT call argument info retrieval methods - Add method resolution for AOT class and interface methods - Include argument info by index lookup functionality - Add reference named argument checking capability - Implement comprehensive parseCallArgs method for general argument parsing - Add scoped callback argument parsing for static/self/parent scopes - Create utility methods for ensuring call args, array args and named args - Add positional call argument handling withpull/17/head
parent
eeca2e598b
commit
a9e93eda28
5 changed files with 995 additions and 930 deletions
@ -0,0 +1,782 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* This file is part of TypePHP. |
||||||
|
* |
||||||
|
* Call argument lowering shared by native and dynamic call paths. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace TypePhp\Generator; |
||||||
|
|
||||||
|
use PhpParser\Modifiers; |
||||||
|
use PhpParser\Node; |
||||||
|
use PhpParser\Node\ArrayItem; |
||||||
|
use PhpParser\Node\Expr; |
||||||
|
use PhpParser\NodeAbstract; |
||||||
|
use TypePhp\ArgInfo; |
||||||
|
use TypePhp\Entity\FunctionDef; |
||||||
|
use TypePhp\Exception\PlaceHolder; |
||||||
|
use TypePhp\Reflection; |
||||||
|
use TypePhp\Symbol; |
||||||
|
|
||||||
|
trait CallArgumentGenerator |
||||||
|
{ |
||||||
|
protected function parseNativeCallArgs(array $callArgs, string $nativeFunc, int $parameterOffset = 0): string |
||||||
|
{ |
||||||
|
$argList = []; |
||||||
|
$functionDef = $this->getFunction($nativeFunc); |
||||||
|
$args = []; |
||||||
|
$variadicArgs = []; |
||||||
|
$hasNamedArg = false; |
||||||
|
$argNameIndex = $this->getFunctionArgNameIndex($functionDef); |
||||||
|
$variadicArgIndex = $this->getVariadicArgIndex($functionDef); |
||||||
|
// 对命名参数进行重排 |
||||||
|
foreach ($callArgs as $i => $arg) { |
||||||
|
if ($this->isPlaceholderExpr($arg)) { |
||||||
|
throw new PlaceHolder(); |
||||||
|
} |
||||||
|
if ($arg->name) { |
||||||
|
$argName = $arg->name->name; |
||||||
|
$k = $argNameIndex[$argName] ?? null; |
||||||
|
if ($k !== null and ($variadicArgIndex === null or $k < $variadicArgIndex)) { |
||||||
|
if ($k < $parameterOffset) { |
||||||
|
$this->fatalError($arg, 'Named argument cannot target the extension receiver'); |
||||||
|
} |
||||||
|
$args[$k] = $arg; |
||||||
|
} else { |
||||||
|
$variadicArgs[] = [$argName, $arg]; |
||||||
|
} |
||||||
|
$hasNamedArg = true; |
||||||
|
} elseif ($variadicArgIndex !== null and $i + $parameterOffset >= $variadicArgIndex) { |
||||||
|
$variadicArgs[] = [null, $arg]; |
||||||
|
} else { |
||||||
|
$args[$i + $parameterOffset] = $arg; |
||||||
|
} |
||||||
|
} |
||||||
|
// 对 key 进行排序,确保参数顺序正确 |
||||||
|
if ($hasNamedArg) { |
||||||
|
// 命名参数中间存在空洞,需要使用默认参数填充 |
||||||
|
foreach ($functionDef->argInfoList as $k => $argInfo) { |
||||||
|
if ($k < $parameterOffset) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($variadicArgIndex !== null and $k === $variadicArgIndex) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (!isset($args[$k])) { |
||||||
|
if ($argInfo->defaultValue === null) { |
||||||
|
$errorNode = null; |
||||||
|
foreach ($callArgs as $a) { |
||||||
|
if ($a instanceof Node\Arg && $a->name) { |
||||||
|
$errorNode = $a; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
$argName = $argInfo->phpName ?: $this->unescapeVarName($argInfo->name); |
||||||
|
$this->fatalError($errorNode ?? reset($callArgs), 'Named argument `' . $argName . '` is missing default value'); |
||||||
|
} |
||||||
|
$args[$k] = new Node\Arg($argInfo->defaultValue); |
||||||
|
} |
||||||
|
} |
||||||
|
ksort($args); |
||||||
|
} |
||||||
|
|
||||||
|
if ($variadicArgIndex !== null and $variadicArgs) { |
||||||
|
$args[$variadicArgIndex] = $this->buildNativeVariadicArg($variadicArgs, $functionDef->argInfoList[$variadicArgIndex]); |
||||||
|
ksort($args); |
||||||
|
} |
||||||
|
|
||||||
|
// 函数只接受一个变长参数,且调用参数为空,直接传入空数组 |
||||||
|
if (count($args) === 0 |
||||||
|
and count($functionDef->argInfoList) === $parameterOffset + 1 |
||||||
|
and $functionDef->argInfoList[$parameterOffset]->variadic) { |
||||||
|
return '{}'; |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($args as $i => $arg) { |
||||||
|
if (is_string($arg)) { |
||||||
|
$argList[] = $arg; |
||||||
|
continue; |
||||||
|
} |
||||||
|
$argInfo = $this->getArgInfo($arg, $nativeFunc, $i); |
||||||
|
$argList[] = $this->getTypeConvertedArg($arg, $argInfo); |
||||||
|
} |
||||||
|
|
||||||
|
return implode(', ', $argList); |
||||||
|
} |
||||||
|
|
||||||
|
protected function buildNativeVariadicArg(array $variadicArgs, ArgInfo $argInfo): string |
||||||
|
{ |
||||||
|
if (count($variadicArgs) === 1 and $variadicArgs[0][0] === null and $variadicArgs[0][1]->unpack) { |
||||||
|
$arg = $variadicArgs[0][1]; |
||||||
|
if ($this->isVarExpr($arg->value)) { |
||||||
|
$var = $this->parseIdentifier($arg->value); |
||||||
|
if ($this->getVarType($var) === self::TYPE_ARRAY) { |
||||||
|
return $var; |
||||||
|
} |
||||||
|
} |
||||||
|
return $this->convertArrayExpr($this->parseExpr($arg->value)); |
||||||
|
} |
||||||
|
|
||||||
|
$tmpVar = $this->addTmpVar(self::TYPE_ARRAY); |
||||||
|
foreach ($variadicArgs as [$name, $arg]) { |
||||||
|
if ($arg->unpack) { |
||||||
|
$this->context->beforeStmtLines[] = $tmpVar . '.merge(' . $this->parseArrayArg($arg) . ');'; |
||||||
|
} elseif ($name !== null) { |
||||||
|
$this->context->beforeStmtLines[] = $tmpVar . '.set(' . $this->getLiteralString($name) . ', ' . $this->getTypeConvertedArg($arg, $argInfo) . ');'; |
||||||
|
} else { |
||||||
|
$this->context->beforeStmtLines[] = $tmpVar . '.append(' . $this->getTypeConvertedArg($arg, $argInfo) . ');'; |
||||||
|
} |
||||||
|
} |
||||||
|
return $tmpVar; |
||||||
|
} |
||||||
|
|
||||||
|
protected function parseNamedCallArgs(array $args, int $firstIndex, array $listArgs): string |
||||||
|
{ |
||||||
|
$namedArgs = []; |
||||||
|
foreach ($args as $i => $arg) { |
||||||
|
if ($i < $firstIndex) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($arg->name === null) { |
||||||
|
$this->fatalError($arg, 'Named argument must follow positional argument'); |
||||||
|
} |
||||||
|
if (!$this->isIdExpr($arg->name)) { |
||||||
|
$this->fatalError($arg, 'Named argument must be a string'); |
||||||
|
} |
||||||
|
if (array_key_exists($arg->name->name, $namedArgs)) { |
||||||
|
$this->fatalError($arg, "Duplicate named argument `{$arg->name->name}`"); |
||||||
|
} |
||||||
|
$namedArgs[$arg->name->name] = $this->parseCallArgValue($arg); |
||||||
|
} |
||||||
|
|
||||||
|
$tmpVar = $this->genTmpVarName(); |
||||||
|
|
||||||
|
$array = self::TYPE_ARRAY . ' ' . $tmpVar . ';'; |
||||||
|
foreach ($namedArgs as $k => $v) { |
||||||
|
$array .= $tmpVar . '.set(' . $this->getLiteralString($k) . ', ' . $v . ');' . PHP_EOL; |
||||||
|
} |
||||||
|
$this->context->beforeStmtLines[] = $array; |
||||||
|
$this->context->afterStmtLines[] = $tmpVar . '.unset();'; |
||||||
|
|
||||||
|
return Symbol::argList() . '{' . implode(', ', $listArgs) . '}, ' . $tmpVar . '.array()'; |
||||||
|
} |
||||||
|
|
||||||
|
protected function isReferenceArgument($funcName, $className, $argIndex): bool |
||||||
|
{ |
||||||
|
$argInfo = $this->getAotCallArgInfo($funcName, $className, $argIndex); |
||||||
|
if ($argInfo !== null) { |
||||||
|
return $argInfo->byRef; |
||||||
|
} |
||||||
|
|
||||||
|
if ($className) { |
||||||
|
// 动态调用类方法,无法判断参数是否为引用 |
||||||
|
if ($className === self::DYNAMIC_CALLED_CLASS) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
$param = Reflection::getClassMethodParameter($className, $funcName, $argIndex); |
||||||
|
} else { |
||||||
|
$param = Reflection::getFunctionParameter($funcName, $argIndex); |
||||||
|
} |
||||||
|
|
||||||
|
if ($param) { |
||||||
|
return $param->isPassedByReference(); |
||||||
|
} |
||||||
|
|
||||||
|
// 参数索引超出声明范围,检查最后一个参数是否为变长引用参数(如 &...$rest) |
||||||
|
$variadicParam = Reflection::getVariadicParameter($funcName, $className); |
||||||
|
return $variadicParam !== null && $variadicParam->isPassedByReference(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function getAotCallArgInfo(string $funcName, string $className, int $argIndex): ?ArgInfo |
||||||
|
{ |
||||||
|
if ($className !== '') { |
||||||
|
$functionDef = $this->findAotMethodFunctionDef($className, $funcName); |
||||||
|
if ($functionDef === null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return $this->getArgInfoByIndex($functionDef, $argIndex); |
||||||
|
} |
||||||
|
|
||||||
|
if (!$this->hasFunction($funcName)) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return $this->getArgInfoByIndex($this->getFunction($funcName), $argIndex); |
||||||
|
} |
||||||
|
|
||||||
|
protected function getAotCallArgInfoByName(string $funcName, string $className, string $argName): ?ArgInfo |
||||||
|
{ |
||||||
|
$functionDef = null; |
||||||
|
if ($className !== '') { |
||||||
|
$functionDef = $this->findAotMethodFunctionDef($className, $funcName); |
||||||
|
} elseif ($this->hasFunction($funcName)) { |
||||||
|
$functionDef = $this->getFunction($funcName); |
||||||
|
} |
||||||
|
|
||||||
|
if ($functionDef === null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
$variadicArgInfo = null; |
||||||
|
foreach ($functionDef->argInfoList as $argInfo) { |
||||||
|
if ($argInfo->variadic) { |
||||||
|
$variadicArgInfo = $argInfo; |
||||||
|
} |
||||||
|
if (($argInfo->phpName ?: $this->unescapeVarName($argInfo->name)) === $argName) { |
||||||
|
return $argInfo; |
||||||
|
} |
||||||
|
} |
||||||
|
return $variadicArgInfo; |
||||||
|
} |
||||||
|
|
||||||
|
/** Resolve a project class or interface method declaration for AOT call arguments. */ |
||||||
|
protected function findAotMethodFunctionDef(string $className, string $funcName): ?FunctionDef |
||||||
|
{ |
||||||
|
if ($className === self::DYNAMIC_CALLED_CLASS) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
if ($this->hasInterface($className)) { |
||||||
|
return $this->findAotInterfaceMethodFunctionDef($className, $funcName); |
||||||
|
} |
||||||
|
|
||||||
|
if (!$this->hasClass($className)) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
$classDef = $this->getClass($className); |
||||||
|
while (true) { |
||||||
|
if ($classDef->hasMethod($funcName)) { |
||||||
|
return $classDef->getMethod($funcName)->functionDef; |
||||||
|
} |
||||||
|
if ($classDef->hasAbstractMethod($funcName)) { |
||||||
|
return $classDef->getAbstractMethod($funcName)->functionDef; |
||||||
|
} |
||||||
|
foreach ($classDef->implements as $interface) { |
||||||
|
$functionDef = $this->findAotInterfaceMethodFunctionDef($interface, $funcName); |
||||||
|
if ($functionDef !== null) { |
||||||
|
return $functionDef; |
||||||
|
} |
||||||
|
} |
||||||
|
if (!$classDef->extends || !$this->hasClass($classDef->extends)) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
$classDef = $this->getClass($classDef->extends); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** Resolve a method from an interface or one of its parent interfaces. */ |
||||||
|
protected function findAotInterfaceMethodFunctionDef(string $interfaceName, string $funcName): ?FunctionDef |
||||||
|
{ |
||||||
|
$pending = [$interfaceName]; |
||||||
|
$visited = []; |
||||||
|
|
||||||
|
while ($pending) { |
||||||
|
$current = array_pop($pending); |
||||||
|
$key = strtolower($current); |
||||||
|
if (isset($visited[$key]) || !$this->hasInterface($current)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
$visited[$key] = true; |
||||||
|
|
||||||
|
$interfaceDef = $this->getInterface($current); |
||||||
|
if ($interfaceDef->hasMethod($funcName)) { |
||||||
|
return $interfaceDef->methods[strtolower($funcName)]->functionDef; |
||||||
|
} |
||||||
|
foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parent) { |
||||||
|
$pending[] = $parent; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
protected function getArgInfoByIndex(FunctionDef $functionDef, int $argIndex): ?ArgInfo |
||||||
|
{ |
||||||
|
if (array_key_exists($argIndex, $functionDef->argInfoList)) { |
||||||
|
return $functionDef->argInfoList[$argIndex]; |
||||||
|
} |
||||||
|
if ($functionDef->hasVariadicArg()) { |
||||||
|
return $functionDef->argInfoList[array_key_last($functionDef->argInfoList)]; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
protected function isReferenceNamedArgument(string $funcName, string $className, string $argName): bool |
||||||
|
{ |
||||||
|
$argInfo = $this->getAotCallArgInfoByName($funcName, $className, $argName); |
||||||
|
if ($argInfo !== null) { |
||||||
|
return $argInfo->byRef; |
||||||
|
} |
||||||
|
|
||||||
|
if ($className) { |
||||||
|
if ($className === self::DYNAMIC_CALLED_CLASS) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
$ref = Reflection::getClass($className); |
||||||
|
if (!$ref) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
try { |
||||||
|
$params = $ref->getMethod($funcName)->getParameters(); |
||||||
|
} catch (\ReflectionException) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} else { |
||||||
|
$ref = Reflection::getFunction($funcName); |
||||||
|
if (!$ref) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
$params = $ref->getParameters(); |
||||||
|
} |
||||||
|
|
||||||
|
$variadicParam = null; |
||||||
|
foreach ($params as $param) { |
||||||
|
if ($param->isVariadic()) { |
||||||
|
$variadicParam = $param; |
||||||
|
} |
||||||
|
if ($param->getName() === $argName) { |
||||||
|
return $param->isPassedByReference(); |
||||||
|
} |
||||||
|
} |
||||||
|
return $variadicParam !== null && $variadicParam->isPassedByReference(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function parseCallArgs( |
||||||
|
array $args, |
||||||
|
string $funcName = '', |
||||||
|
string $className = '', |
||||||
|
bool $separateNamedArgs = true, |
||||||
|
bool $forceArrayArgs = false |
||||||
|
): string |
||||||
|
{ |
||||||
|
$list_args = []; |
||||||
|
$arrayArgsVar = null; |
||||||
|
$argsVar = null; |
||||||
|
$namedArgsVar = null; |
||||||
|
$namedArgs = []; |
||||||
|
$hasNamedArg = false; |
||||||
|
$hasUnpack = false; |
||||||
|
|
||||||
|
if ($forceArrayArgs) { |
||||||
|
$this->ensureCallArrayArgs($arrayArgsVar, $list_args); |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($args as $i => $arg) { |
||||||
|
if ($this->isPlaceholderExpr($arg)) { |
||||||
|
throw new PlaceHolder(); |
||||||
|
} |
||||||
|
if ($arg->unpack) { |
||||||
|
if ($hasNamedArg) { |
||||||
|
$this->fatalError($arg, 'Cannot use argument unpacking after named arguments'); |
||||||
|
} |
||||||
|
$hasUnpack = true; |
||||||
|
if (!$forceArrayArgs && $separateNamedArgs) { |
||||||
|
$callArgs = $this->ensureCallArgs($argsVar, $list_args); |
||||||
|
$this->context->beforeStmtLines[] = $callArgs . '.appendUnpacked(' . $this->parseArrayArg($arg) . ');'; |
||||||
|
} else { |
||||||
|
$arrayArgs = $this->ensureCallArrayArgs($arrayArgsVar, $list_args); |
||||||
|
$this->context->beforeStmtLines[] = $arrayArgs . '.merge(' . $this->parseArrayArg($arg) . ');'; |
||||||
|
} |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($arg->name !== null) { |
||||||
|
$hasNamedArg = true; |
||||||
|
if (!$this->isIdExpr($arg->name)) { |
||||||
|
$this->fatalError($arg, 'Named argument must be a string'); |
||||||
|
} |
||||||
|
if (array_key_exists($arg->name->name, $namedArgs)) { |
||||||
|
$this->fatalError($arg, "Duplicate named argument `{$arg->name->name}`"); |
||||||
|
} |
||||||
|
$namedArgs[$arg->name->name] = true; |
||||||
|
$byRef = $funcName && $this->isReferenceNamedArgument($funcName, $className, $arg->name->name); |
||||||
|
$value = ($byRef || $this->isRefvalCall($arg->value) || $this->isToRefCall($arg->value)) |
||||||
|
? $this->parseReferenceCallArgValue($arg) |
||||||
|
: $this->parseCallArgValue($arg); |
||||||
|
if ($separateNamedArgs) { |
||||||
|
$namedArgsArray = $this->ensureCallNamedArgs($namedArgsVar); |
||||||
|
$this->context->beforeStmtLines[] = $namedArgsArray . '.set(' . $this->getLiteralString($arg->name->name) . ', ' . $value . ');'; |
||||||
|
} else { |
||||||
|
$arrayArgs = $this->ensureCallArrayArgs($arrayArgsVar, $list_args); |
||||||
|
$this->context->beforeStmtLines[] = $arrayArgs . '.set(' . $this->getLiteralString($arg->name->name) . ', ' . $value . ');'; |
||||||
|
} |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($hasNamedArg) { |
||||||
|
$this->fatalError($arg, 'Cannot use positional argument after named argument'); |
||||||
|
} |
||||||
|
if ($hasUnpack) { |
||||||
|
$this->fatalError($arg, 'Cannot use positional argument after argument unpacking'); |
||||||
|
} |
||||||
|
$byRef = $funcName && $this->isReferenceArgument($funcName, $className, $i); |
||||||
|
if (($funcName === 'call_user_func' || $funcName === 'call_user_func_array') && $i === 0) { |
||||||
|
$callback = $this->parseScopedCallbackArg($arg); |
||||||
|
if ($callback !== null) { |
||||||
|
$this->addPositionalCallArg($callback, $arrayArgsVar, $list_args); |
||||||
|
continue; |
||||||
|
} |
||||||
|
} |
||||||
|
if ($this->isVarExpr($arg->value)) { |
||||||
|
$name = $this->parseIdentifier($arg->value); |
||||||
|
if ($byRef) { |
||||||
|
$this->addPositionalCallArg($this->parseArgRefVar($arg, $name), $arrayArgsVar, $list_args); |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (!$this->hasVar($name)) { |
||||||
|
$this->fatalError($arg, 'Undefined variable `$' . $name . '`'); |
||||||
|
} |
||||||
|
} elseif ($this->isPropertyFetch($arg->value) and $this->isVarExpr($arg->value->var)) { |
||||||
|
if ($byRef) { |
||||||
|
$this->addPositionalCallArg($this->emitDynamicPropertyFetchRef($arg->value, $arg), $arrayArgsVar, $list_args); |
||||||
|
continue; |
||||||
|
} |
||||||
|
$objectExpr = $this->parseIdentifier($arg->value->var); |
||||||
|
if (!$this->hasVar($objectExpr)) { |
||||||
|
$this->fatalError($arg, 'Undefined variable `$' . $objectExpr . '`'); |
||||||
|
} |
||||||
|
} elseif ($this->isArrayDimFetch($arg->value) and $this->isVarExpr($arg->value->var)) { |
||||||
|
$array = $this->parseIdentifier($arg->value->var); |
||||||
|
if ($array === 'GLOBALS') { |
||||||
|
$globalVar = $this->parseGlobalsArrayDimFetch($arg->value); |
||||||
|
// 全局变量作为引用参数 |
||||||
|
if ($byRef) { |
||||||
|
$ref = $this->addTmpVar(self::TYPE_REF); |
||||||
|
$this->context->beforeStmtLines[] = $ref . ' = ' . $globalVar . '.toReference();'; |
||||||
|
$this->addPositionalCallArg('&' . $ref, $arrayArgsVar, $list_args); |
||||||
|
} else { |
||||||
|
$this->addPositionalCallArg($globalVar, $arrayArgsVar, $list_args); |
||||||
|
} |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($this->isVarExpr($arg->value->var) and !$this->hasVar($array)) { |
||||||
|
$this->fatalError($arg, 'Undefined variable `$' . $array . '`'); |
||||||
|
} |
||||||
|
if ($byRef) { |
||||||
|
if ($arg->value->dim === null) { |
||||||
|
$this->fatalError($arg, 'Array dimension must be a constant expression'); |
||||||
|
} |
||||||
|
$this->addPositionalCallArg($array . '.itemRef(' . $this->identifierToStr($arg->value->dim) . ')', $arrayArgsVar, $list_args); |
||||||
|
continue; |
||||||
|
} |
||||||
|
} elseif ($this->isReferenceWrapperCall($arg->value)) { |
||||||
|
$inner = $this->unwrapReferenceWrapperCall($arg->value, $arg); |
||||||
|
if ($this->isVarExpr($inner)) { |
||||||
|
$name = $this->parseVariable($inner); |
||||||
|
$arg->value = $inner; |
||||||
|
$this->addPositionalCallArg($this->parseArgRefVar($arg, $name), $arrayArgsVar, $list_args); |
||||||
|
continue; |
||||||
|
} |
||||||
|
$expr = $this->expandRefvalExpr($inner, $arg); |
||||||
|
if ($expr !== null) { |
||||||
|
$this->addPositionalCallArg($expr, $arrayArgsVar, $list_args); |
||||||
|
continue; |
||||||
|
} |
||||||
|
$this->fatalError($arg, 'The refval function only accepts a variable, array element, or object property'); |
||||||
|
} else { |
||||||
|
if ($byRef) { |
||||||
|
if ($this->isScalar($arg->value)) { |
||||||
|
$this->fatalError($arg, 'The constants cannot be used as an argument for a reference-type parameter'); |
||||||
|
} |
||||||
|
$tmpRef = $this->genTmpVarName(); |
||||||
|
$this->addLocalVar($tmpRef, self::TYPE_REF); |
||||||
|
$this->context->beforeStmtLines[] = $tmpRef . ' = ' . $this->parseChainedExpr($arg->value, self::OP_REFVAL) . ';'; |
||||||
|
$this->addPositionalCallArg('&' . $tmpRef, $arrayArgsVar, $list_args); |
||||||
|
continue; |
||||||
|
} |
||||||
|
} |
||||||
|
$value = $this->parseCallArgValue($arg); |
||||||
|
$this->addPositionalCallArg($value, $arrayArgsVar, $list_args); |
||||||
|
} |
||||||
|
|
||||||
|
if ($argsVar !== null) { |
||||||
|
return $namedArgsVar !== null ? $argsVar . ', ' . $namedArgsVar . '.array()' : $argsVar; |
||||||
|
} |
||||||
|
if ($arrayArgsVar !== null) { |
||||||
|
return $namedArgsVar !== null ? $arrayArgsVar . ', ' . $namedArgsVar . '.array()' : $arrayArgsVar; |
||||||
|
} |
||||||
|
$callArgs = Symbol::argList() . '{' . implode(', ', $list_args) . '}'; |
||||||
|
return $namedArgsVar !== null ? $callArgs . ', ' . $namedArgsVar . '.array()' : $callArgs; |
||||||
|
} |
||||||
|
|
||||||
|
protected function parseScopedCallbackArg(Node\Arg $arg): ?string |
||||||
|
{ |
||||||
|
$value = $arg->value; |
||||||
|
if (!$value instanceof Expr\Array_ || count($value->items) < 2 || !$this->methodDef) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
$first = $value->items[0]; |
||||||
|
if (!$first instanceof ArrayItem || $first->key !== null || $first->unpack) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
if (!$first->value instanceof Node\Scalar\String_) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
$scope = strtolower($first->value->value); |
||||||
|
$classExpr = match ($scope) { |
||||||
|
'static' => ($this->methodDef->flags & Modifiers::STATIC) |
||||||
|
? $this->getLiteralString($this->getFullClassName()) |
||||||
|
: Symbol::getCalledClass(), |
||||||
|
'self' => $this->getLiteralString($this->getFullClassName()), |
||||||
|
'parent' => $this->classDef->extends ? $this->getLiteralString($this->classDef->extends) : null, |
||||||
|
default => null, |
||||||
|
}; |
||||||
|
if ($classExpr === null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
$items = [$classExpr]; |
||||||
|
foreach (array_slice($value->items, 1) as $item) { |
||||||
|
if (!$item instanceof ArrayItem || $item->key !== null || $item->unpack) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
$this->assertExprCanBeUsedAsValue($item->value, 'callback array item'); |
||||||
|
$items[] = $this->parseIdentifier($item->value); |
||||||
|
} |
||||||
|
|
||||||
|
return $this->genArray($items); |
||||||
|
} |
||||||
|
|
||||||
|
protected function ensureCallArgs(?string &$argsVar, array &$listArgs): string |
||||||
|
{ |
||||||
|
if ($argsVar === null) { |
||||||
|
$argsVar = $this->genTmpVarName(); |
||||||
|
$this->context->beforeStmtLines[] = self::TYPE_ARGS . ' ' . $argsVar . '{' . Symbol::argList() . '{' . implode(', ', $listArgs) . '}};'; |
||||||
|
$listArgs = []; |
||||||
|
} |
||||||
|
return $argsVar; |
||||||
|
} |
||||||
|
|
||||||
|
protected function ensureCallArrayArgs(?string &$arrayArgsVar, array &$listArgs): string |
||||||
|
{ |
||||||
|
if ($arrayArgsVar === null) { |
||||||
|
$arrayArgsVar = $this->genTmpVarName(); |
||||||
|
$this->context->beforeStmtLines[] = self::TYPE_ARRAY . ' ' . $arrayArgsVar . '{' . implode(', ', $listArgs) . '};'; |
||||||
|
$listArgs = []; |
||||||
|
} |
||||||
|
return $arrayArgsVar; |
||||||
|
} |
||||||
|
|
||||||
|
protected function ensureCallNamedArgs(?string &$namedArgsVar): string |
||||||
|
{ |
||||||
|
if ($namedArgsVar === null) { |
||||||
|
$namedArgsVar = $this->genTmpVarName(); |
||||||
|
$this->context->beforeStmtLines[] = self::TYPE_ARRAY . ' ' . $namedArgsVar . ';'; |
||||||
|
$this->context->afterStmtLines[] = $namedArgsVar . '.unset();'; |
||||||
|
} |
||||||
|
return $namedArgsVar; |
||||||
|
} |
||||||
|
|
||||||
|
protected function addPositionalCallArg(string $value, ?string $arrayArgsVar, array &$listArgs): void |
||||||
|
{ |
||||||
|
if ($arrayArgsVar !== null) { |
||||||
|
$this->context->beforeStmtLines[] = $arrayArgsVar . '.append(' . $value . ');'; |
||||||
|
} else { |
||||||
|
$listArgs[] = $value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected function parseCallArgValue(Node\Arg $arg): string |
||||||
|
{ |
||||||
|
$this->assertExprCanBeUsedAsValue($arg->value, 'function argument'); |
||||||
|
return $this->materializeCallArgValue($arg->value, $this->parseArg($arg)); |
||||||
|
} |
||||||
|
|
||||||
|
protected function materializeCallArgValue(NodeAbstract $value, string $expr): string |
||||||
|
{ |
||||||
|
if (!$this->shouldMaterializeCallArg($value)) { |
||||||
|
return $expr; |
||||||
|
} |
||||||
|
return 'php_deindirect(' . $expr . ')'; |
||||||
|
} |
||||||
|
|
||||||
|
protected function shouldMaterializeCallArg(NodeAbstract $value): bool |
||||||
|
{ |
||||||
|
if ($value instanceof Expr\ArrayDimFetch) { |
||||||
|
return !$this->isStdContainerExpr($value); |
||||||
|
} |
||||||
|
|
||||||
|
return $value instanceof Expr\PropertyFetch; |
||||||
|
} |
||||||
|
|
||||||
|
protected function parseReferenceCallArgValue(Node\Arg $arg): string |
||||||
|
{ |
||||||
|
if ($this->isReferenceWrapperCall($arg->value)) { |
||||||
|
$arg->value = $this->unwrapReferenceWrapperCall($arg->value, $arg); |
||||||
|
} |
||||||
|
|
||||||
|
if ($this->isVarExpr($arg->value)) { |
||||||
|
return $this->parseArgRefVar($arg, $this->parseIdentifier($arg->value)); |
||||||
|
} |
||||||
|
|
||||||
|
if ($this->isPropertyFetch($arg->value) and $this->isVarExpr($arg->value->var)) { |
||||||
|
return $this->emitDynamicPropertyFetchRef($arg->value, $arg); |
||||||
|
} |
||||||
|
|
||||||
|
if ($this->isArrayDimFetch($arg->value) and $this->isVarExpr($arg->value->var)) { |
||||||
|
$array = $this->parseIdentifier($arg->value->var); |
||||||
|
if ($array === 'GLOBALS') { |
||||||
|
$globalVar = $this->parseGlobalsArrayDimFetch($arg->value); |
||||||
|
$ref = $this->addTmpVar(self::TYPE_REF); |
||||||
|
$this->context->beforeStmtLines[] = $ref . ' = ' . $globalVar . '.toReference();'; |
||||||
|
return '&' . $ref; |
||||||
|
} |
||||||
|
if (!$this->hasVar($array)) { |
||||||
|
$this->fatalError($arg, 'Undefined variable `$' . $array . '`'); |
||||||
|
} |
||||||
|
if ($arg->value->dim === null) { |
||||||
|
$this->fatalError($arg, 'Array dimension must be a constant expression'); |
||||||
|
} |
||||||
|
return $array . '.itemRef(' . $this->identifierToStr($arg->value->dim) . ')'; |
||||||
|
} |
||||||
|
|
||||||
|
if ($this->isScalar($arg->value)) { |
||||||
|
$this->fatalError($arg, 'The constants cannot be used as an argument for a reference-type parameter'); |
||||||
|
} |
||||||
|
|
||||||
|
$tmpRef = $this->genTmpVarName(); |
||||||
|
$this->addLocalVar($tmpRef, self::TYPE_REF); |
||||||
|
$this->context->beforeStmtLines[] = $tmpRef . ' = ' . $this->parseChainedExpr($arg->value, self::OP_REFVAL) . ';'; |
||||||
|
return '&' . $tmpRef; |
||||||
|
} |
||||||
|
|
||||||
|
protected function isToRefCall(NodeAbstract $expr): bool |
||||||
|
{ |
||||||
|
return $this->isMethodCall($expr) |
||||||
|
&& $this->isNamedMethod($expr->name) |
||||||
|
&& $expr->name->toString() === 'toRef'; |
||||||
|
} |
||||||
|
|
||||||
|
protected function isReferenceWrapperCall(NodeAbstract $expr): bool |
||||||
|
{ |
||||||
|
return $this->isRefvalCall($expr) || $this->isToRefCall($expr); |
||||||
|
} |
||||||
|
|
||||||
|
protected function unwrapReferenceWrapperCall(NodeAbstract $expr, NodeAbstract $errorNode): NodeAbstract |
||||||
|
{ |
||||||
|
if ($this->isRefvalCall($expr)) { |
||||||
|
if (count($expr->args) !== 1) { |
||||||
|
$this->fatalError($errorNode, 'The refval function only accepts one parameter'); |
||||||
|
} |
||||||
|
return $expr->args[0]->value; |
||||||
|
} |
||||||
|
|
||||||
|
if ($this->isToRefCall($expr)) { |
||||||
|
if (!empty($expr->args)) { |
||||||
|
$this->fatalError($errorNode, 'The toRef method does not accept parameters'); |
||||||
|
} |
||||||
|
return $expr->var; |
||||||
|
} |
||||||
|
|
||||||
|
$this->fatalError($errorNode, 'Expected a reference wrapper call'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 展开 refval() 调用中的数组元素或对象属性,返回对应的 C++ 引用表达式。 |
||||||
|
* 若为普通变量则返回 null,由调用方自行处理。 |
||||||
|
*/ |
||||||
|
protected function expandRefvalExpr(NodeAbstract $inner, Node\Arg $arg): ?string |
||||||
|
{ |
||||||
|
if ($this->isPropertyFetch($inner) and $this->isVarExpr($inner->var)) { |
||||||
|
return $this->emitDynamicPropertyFetchRef($inner, $arg); |
||||||
|
} |
||||||
|
if ($this->isArrayDimFetch($inner) and $this->isVarExpr($inner->var)) { |
||||||
|
$array = $this->parseIdentifier($inner->var); |
||||||
|
if ($array === 'GLOBALS') { |
||||||
|
$globalVar = $this->parseGlobalsArrayDimFetch($inner); |
||||||
|
$ref = $this->addTmpVar(self::TYPE_REF); |
||||||
|
$this->context->beforeStmtLines[] = $ref . ' = ' . $globalVar . '.toReference();'; |
||||||
|
return '&' . $ref; |
||||||
|
} |
||||||
|
if (!$this->hasVar($array)) { |
||||||
|
$this->fatalError($arg, 'Undefined variable `$' . $array . '`'); |
||||||
|
} |
||||||
|
if ($inner->dim === null) { |
||||||
|
$this->fatalError($arg, 'Array dimension must be a constant expression'); |
||||||
|
} |
||||||
|
return $array . '.itemRef(' . $this->identifierToStr($inner->dim) . ')'; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 仅用于动态调用的参数解析 |
||||||
|
*/ |
||||||
|
protected function parseArgRefVar(Node\Arg $arg, string $name): string |
||||||
|
{ |
||||||
|
if (!$this->hasVar($name)) { |
||||||
|
// 若参数是引用类型,可以传入未定义变量,将立即创建变量作为引用 |
||||||
|
$this->addLocalVar($name, self::TYPE_REF); |
||||||
|
} else { |
||||||
|
// 本地变量,且是原生类型,则转为普通变量 |
||||||
|
if ($this->hasLocalVar($name) and $this->isNativeType($this->getVarType($name))) { |
||||||
|
$this->context->localVars[$name] = self::TYPE_VAR; |
||||||
|
} |
||||||
|
// 需要引用类型的参数,使用临时变量作为引用,并替换掉实际的参数 |
||||||
|
$tmpVar = $this->genTmpVarName(); |
||||||
|
$this->addLocalVar($tmpVar, self::TYPE_REF); |
||||||
|
$this->context->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($arg->value) . '.toReference();'; |
||||||
|
$name = $tmpVar; |
||||||
|
} |
||||||
|
// 动态调用,参数列表是 Variant 类型而不是 Reference,必须使用 & 符号取地址,传递指针,以保持引用传递 |
||||||
|
return '&' . $name; |
||||||
|
} |
||||||
|
|
||||||
|
protected function parseArg(Node\Arg $arg): string |
||||||
|
{ |
||||||
|
if ($this->isArrayDimFetch($arg->value) and $this->isStdContainerExpr($arg->value)) { |
||||||
|
if ($this->isStdArrayExpr($arg->value)) { |
||||||
|
$valueExpr = $this->parseStdArrayDimFetch($arg->value); |
||||||
|
$attr = $arg->value->getAttribute('stdArrayDimFetch'); |
||||||
|
if ($attr['accessLevel'] === $attr['totalLevel']) { |
||||||
|
return $this->convertExprFromType($this->context->stdArrays[$attr['var']]['type'], $valueExpr); |
||||||
|
} else { |
||||||
|
return $this->convertArrayExpr($valueExpr); |
||||||
|
} |
||||||
|
} else { |
||||||
|
$valueExpr = $this->parseStdContainerDimFetch($arg->value); |
||||||
|
$attr = $arg->value->getAttribute('stdContainerDimFetch'); |
||||||
|
return $this->convertExprFromType($this->context->stdContainers[$attr['var']]['type'], $valueExpr); |
||||||
|
} |
||||||
|
} |
||||||
|
$expr = $this->parseIdentifier($arg->value); |
||||||
|
if ($this->isVarExpr($arg->value) and $arg->value->name === 'GLOBALS') { |
||||||
|
return 'php_globals_array()'; |
||||||
|
} |
||||||
|
if ($this->isVarExpr($arg->value) and $this->isStdContainer($arg->value->name)) { |
||||||
|
return $this->convertArrayExpr($expr . '_ref'); |
||||||
|
} |
||||||
|
return $expr; |
||||||
|
} |
||||||
|
|
||||||
|
protected function parseOrderedArg(Node\Arg $arg): string |
||||||
|
{ |
||||||
|
if ($this->isArrayDimFetch($arg->value) and $this->isStdContainerExpr($arg->value)) { |
||||||
|
return $this->parseArg($arg); |
||||||
|
} |
||||||
|
if ($this->isVarExpr($arg->value) and $arg->value->name === 'GLOBALS') { |
||||||
|
return 'php_globals_array()'; |
||||||
|
} |
||||||
|
if ($this->isVarExpr($arg->value) and $this->isStdContainer($arg->value->name)) { |
||||||
|
return $this->convertArrayExpr($this->parseIdentifier($arg->value) . '_ref'); |
||||||
|
} |
||||||
|
return $this->parseOrderedOperand($arg->value, false); |
||||||
|
} |
||||||
|
|
||||||
|
protected function parseArrayArg(Node\Arg $expr): string |
||||||
|
{ |
||||||
|
$value = $expr->value; |
||||||
|
if ($this->isVarExpr($value)) { |
||||||
|
$var = $this->parseIdentifier($value); |
||||||
|
if (!$this->hasVar($var)) { |
||||||
|
$this->errorUndefinedVariable($value); |
||||||
|
} |
||||||
|
if ($this->getVarType($var) === self::TYPE_ARRAY) { |
||||||
|
return $var; |
||||||
|
} |
||||||
|
} |
||||||
|
return $this->parseExpr($value); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,72 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* This file is part of TypePHP. |
||||||
|
* |
||||||
|
* Generates C++ helpers for defaults that require runtime initialization. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace TypePhp\Generator; |
||||||
|
|
||||||
|
use TypePhp\ArgInfo; |
||||||
|
use TypePhp\Entity\ArrayInitPlan; |
||||||
|
use TypePhp\Entity\FunctionDef; |
||||||
|
|
||||||
|
trait DefaultArgumentGenerator |
||||||
|
{ |
||||||
|
protected function getDefaultArgumentType(ArgInfo $argInfo): string |
||||||
|
{ |
||||||
|
$type = $argInfo->type; |
||||||
|
if ($type === self::TYPE_STREAM || $type === self::TYPE_BOX) { |
||||||
|
return self::TYPE_VAR; |
||||||
|
} |
||||||
|
return $type; |
||||||
|
} |
||||||
|
|
||||||
|
protected function getDefaultArgumentHelperName(FunctionDef $func, ArgInfo $argInfo): string |
||||||
|
{ |
||||||
|
return self::PREFIX . 'default_arg_' . $func->name . '_' . $argInfo->name; |
||||||
|
} |
||||||
|
|
||||||
|
protected function genDefaultArgumentExpr(FunctionDef $func, ArgInfo $argInfo): string |
||||||
|
{ |
||||||
|
if (!$argInfo->arrayInitPlan || !$argInfo->arrayInitPlan->requiresRuntimeInit()) { |
||||||
|
return $argInfo->default; |
||||||
|
} |
||||||
|
|
||||||
|
return $this->getDefaultArgumentHelperName($func, $argInfo) . '()'; |
||||||
|
} |
||||||
|
|
||||||
|
protected function wrapArrayInitPlan(ArrayInitPlan $plan, string $body): string |
||||||
|
{ |
||||||
|
return "do {\n" . $plan->init . $body . $plan->clean . "} while (0);\n"; |
||||||
|
} |
||||||
|
|
||||||
|
protected function genDefaultArgumentHelpers(): string |
||||||
|
{ |
||||||
|
$code = ''; |
||||||
|
foreach ($this->functions as $func) { |
||||||
|
foreach ($func->argInfoList as $argInfo) { |
||||||
|
$plan = $argInfo->arrayInitPlan; |
||||||
|
if (!$plan || !$plan->requiresRuntimeInit()) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
$type = $this->getDefaultArgumentType($argInfo); |
||||||
|
$helper = $this->getDefaultArgumentHelperName($func, $argInfo); |
||||||
|
$code .= 'static inline ' . $type . ' ' . $helper . "() {\n"; |
||||||
|
$code .= $plan->init; |
||||||
|
if ($plan->clean) { |
||||||
|
$code .= $type . ' retval = ' . $plan->expr . ';' . PHP_EOL; |
||||||
|
$code .= $plan->clean; |
||||||
|
$code .= 'return retval;' . PHP_EOL; |
||||||
|
} else { |
||||||
|
$code .= 'return ' . $plan->expr . ';' . PHP_EOL; |
||||||
|
} |
||||||
|
$code .= '}' . PHP_EOL; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $code ? $code . PHP_EOL : ''; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,134 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* This file is part of TypePHP. |
||||||
|
* |
||||||
|
* Resolves pipe targets and ordinary function calls. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace TypePhp\Parser; |
||||||
|
|
||||||
|
use PhpParser\Node; |
||||||
|
use PhpParser\Node\Expr; |
||||||
|
use PhpParser\Node\Expr\CallLike; |
||||||
|
use PhpParser\Node\Expr\Variable; |
||||||
|
use PhpParser\NodeAbstract; |
||||||
|
use TypePhp\Constants; |
||||||
|
use TypePhp\Exception\PlaceHolder; |
||||||
|
|
||||||
|
trait FunctionCallTrait |
||||||
|
{ |
||||||
|
protected function parsePipeOperator(Expr\BinaryOp\Pipe $expr): string |
||||||
|
{ |
||||||
|
$this->assertExprCanBeUsedAsValue($expr->left, 'pipe left operand'); |
||||||
|
$this->assertExprCanBeUsedAsValue($expr->right, 'pipe callable'); |
||||||
|
|
||||||
|
[$leftExpr, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr->left); |
||||||
|
$this->appendCapturedStmtLinesToContext($beforeStmts); |
||||||
|
$value = $this->addTmpVar(self::TYPE_VAR); |
||||||
|
$this->context->beforeStmtLines[] = $value . ' = ' . $leftExpr . ';'; |
||||||
|
$this->appendCapturedStmtLinesToContext($afterStmts); |
||||||
|
|
||||||
|
$directCall = $this->parsePipeFirstClassCallable($expr->right, $value); |
||||||
|
if ($directCall !== null) { |
||||||
|
return $directCall; |
||||||
|
} |
||||||
|
|
||||||
|
$callable = $this->parseExprAsValue($expr->right); |
||||||
|
return 'php::call(' . $callable . ', {' . $value . '})'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Lower a first-class callable used as a pipe target to its direct call. |
||||||
|
* |
||||||
|
* `trim(...)`, `ClassName::method(...)`, and `$object->method(...)` do |
||||||
|
* not need a Closure when the pipe immediately invokes them. Reusing the |
||||||
|
* ordinary call parsers preserves native-call optimization, argument |
||||||
|
* validation, visibility checks, and the left-to-right evaluation order. |
||||||
|
*/ |
||||||
|
protected function parsePipeFirstClassCallable(NodeAbstract $callable, string $value): ?string |
||||||
|
{ |
||||||
|
if (!$callable instanceof CallLike || !$callable->isFirstClassCallable()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
$directCall = clone $callable; |
||||||
|
$directCall->args = [new Node\Arg(new Variable($value))]; |
||||||
|
|
||||||
|
if ($directCall instanceof Expr\FuncCall) { |
||||||
|
return $this->parseFuncCall($directCall); |
||||||
|
} |
||||||
|
if ($directCall instanceof Expr\StaticCall) { |
||||||
|
return $this->parseStaticCall($directCall); |
||||||
|
} |
||||||
|
if ($directCall instanceof Expr\MethodCall) { |
||||||
|
return $this->parseMethodCall($directCall); |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
protected function parseFuncCall(Expr\FuncCall $expr): string |
||||||
|
{ |
||||||
|
if ($this->isVarExpr($expr->name)) { |
||||||
|
$fn = $this->parseIdentifier($expr->name); |
||||||
|
$placeHolder = $fn; |
||||||
|
$name = ''; |
||||||
|
} elseif ($expr->name->getType() === 'Name' or $expr->name->getType() === 'Name_FullyQualified') { |
||||||
|
$name = $this->parseIdentifier($expr->name); |
||||||
|
if (in_array($name, Constants::UNSUPPORTED_FUNCTIONS)) { |
||||||
|
$this->fatalError($expr, 'Unsupported function: `' . $name . '`'); |
||||||
|
} |
||||||
|
if ($name === 'any') { |
||||||
|
if (count($expr->args) !== 1 || $expr->args[0]->unpack) { |
||||||
|
$this->fatalError($expr, 'The any function expects exactly one non-unpacked argument'); |
||||||
|
} |
||||||
|
return $this->parseExprAsValue($expr->args[0]->value); |
||||||
|
} |
||||||
|
if ($name === 'objval') { |
||||||
|
return $this->genObjvalCall($expr); |
||||||
|
} |
||||||
|
$nativeFn = $this->findNativeFunction($name); |
||||||
|
if ($nativeFn) { |
||||||
|
$expr->setAttribute('nativeCall', $nativeFn); |
||||||
|
// 函数调用占位符,不是真实的函数调用 |
||||||
|
if (count($expr->args) === 1 and $this->isPlaceholderExpr($expr->args[0])) { |
||||||
|
return $this->genPlaceHolder($this->identifierToStr($expr->name)); |
||||||
|
} |
||||||
|
$this->checkNativeCallArgs($expr, $this->getFunction($nativeFn), $expr->args, $name); |
||||||
|
if ($this->shouldUseDynamicCallForNativeArgs($nativeFn, $expr->args)) { |
||||||
|
$functionDef = $this->getFunction($nativeFn); |
||||||
|
return $this->genRuntimeFunctionCall($this->getFuncPtr($functionDef->getNamespacedName()), $expr->args, $name); |
||||||
|
} |
||||||
|
try { |
||||||
|
return self::PREFIX . $nativeFn . '(' . $this->parseNativeCallArgs($expr->args, $nativeFn) . ')'; |
||||||
|
} catch (PlaceHolder) { |
||||||
|
return $this->genPlaceHolder($this->identifierToStr($expr->name)); |
||||||
|
} |
||||||
|
} |
||||||
|
// 动态调用的函数,转换函数名为带有命名空间的全限定名称 |
||||||
|
$name = $this->getNamespacedFuncName($name); |
||||||
|
$this->checkInternalFunctionArgCount($name, $expr); |
||||||
|
$code = $this->parseFuncCallWithOptimizer($name, $expr); |
||||||
|
if ($code !== false) { |
||||||
|
return $code; |
||||||
|
} |
||||||
|
$placeHolder = $this->identifierToStr($expr->name); |
||||||
|
$fn = $this->getFuncPtr($name); |
||||||
|
$this->context->beforeStmtLines[] = $this->formatCppLineComment('Func Call: ', $name . '()'); |
||||||
|
} else { |
||||||
|
$tmpVar = $this->addTmpVar(self::TYPE_VAR); |
||||||
|
$this->context->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($expr->name) . ';'; |
||||||
|
$placeHolder = $fn = $tmpVar; |
||||||
|
$name = ''; |
||||||
|
} |
||||||
|
if (empty($expr->args)) { |
||||||
|
return 'php::call(' . $fn . ')'; |
||||||
|
} |
||||||
|
try { |
||||||
|
return $this->genRuntimeFunctionCall($fn, $expr->args, $name); |
||||||
|
} catch (PlaceHolder) { |
||||||
|
return $this->genPlaceHolder($placeHolder); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
Loading…
Reference in new issue