动态函数调用语法

pull/1/head
韩天峰 8 months ago
parent 739152ba14
commit fb10132b6c
  1. 3
      main.cc
  2. 88
      src/Php/Translator.php
  3. 9
      tests/aot/static-call.phpt
  4. 15
      tests/zend/dynamic_call/dynamic_call_002.phpt
  5. 16
      tests/zend/dynamic_call/dynamic_call_003.phpt
  6. 17
      tests/zend/dynamic_call/dynamic_call_004.phpt

@ -37,6 +37,9 @@ int main(int cpp_argc, char **cpp_argv) {
} }
zend_catch { zend_catch {
rc = EG(exit_status); rc = EG(exit_status);
if (EG(exception)) {
zend_exception_error(EG(exception), E_ERROR);
}
} }
zend_end_try(); zend_end_try();
#if PPROF_ON #if PPROF_ON

@ -91,6 +91,11 @@ class Translator extends \PhpAot\Core\Translator
'pipe', 'pipe',
]; ];
private array $unsupportedFunctions = [
'compact',
'extract'
];
private array $nativeFunctions = []; private array $nativeFunctions = [];
private array $internalFunctions = []; private array $internalFunctions = [];
private int $optimizeLevel = 0; private int $optimizeLevel = 0;
@ -1315,28 +1320,33 @@ class Translator extends \PhpAot\Core\Translator
private function parseFuncCall(mixed $expr): string private function parseFuncCall(mixed $expr): string
{ {
$name = $this->parseIdentifier($expr->name); if ($expr->name->getType() === self::EXPR_VARIABLE) {
if ($this->isNativeFunction($name)) { $fn = $this->parseIdentifier($expr->name);
return self::PREFIX . $name . '(' . $this->parseCallArgs($expr->args, $name) . ')'; $name = '';
}
if ($this->isInternalFunction($name)) {
$fn = 'php::' . $name;
} else { } else {
$fn = '"' . $name . '"'; $name = $this->parseIdentifier($expr->name);
} if ($this->isNativeFunction($name)) {
if ($name === 'strlen' or $name === 'sizeof' or $name === 'count') { return self::PREFIX . $name . '(' . $this->parseCallArgs($expr->args, $name) . ')';
return 'php::len(' . $this->parseIdentifier($expr->args[0]->value) . ')'; }
} if ($this->isInternalFunction($name)) {
if (count($expr->args) == 1) { $fn = 'php::' . $name;
switch ($name) { } else {
case 'intval': $fn = '"' . $name . '"';
return $this->convertIntExpr($this->parseExpr($expr->args[0]->value)); }
case 'floatval': if ($name === 'strlen' or $name === 'sizeof' or $name === 'count') {
return $this->convertFloatExpr($this->parseExpr($expr->args[0]->value)); return 'php::len(' . $this->parseIdentifier($expr->args[0]->value) . ')';
case 'boolval': }
return $this->convertBoolExpr($this->parseExpr($expr->args[0]->value)); if (count($expr->args) == 1) {
default: switch ($name) {
break; case 'intval':
return $this->convertIntExpr($this->parseExpr($expr->args[0]->value));
case 'floatval':
return $this->convertFloatExpr($this->parseExpr($expr->args[0]->value));
case 'boolval':
return $this->convertBoolExpr($this->parseExpr($expr->args[0]->value));
default:
break;
}
} }
} }
if (empty($expr->args)) { if (empty($expr->args)) {
@ -2254,15 +2264,39 @@ class Translator extends \PhpAot\Core\Translator
} }
} }
private function identifierToStr(Node $node, bool $require = true): string
{
$id = $this->parseIdentifier($node);
if ($node->getType() === self::EXPR_VARIABLE) {
if ($require) {
$this->requireVar($node, $id);
}
return $id;
} else {
return '"'. $id . '"';
}
}
private function requireVar($node, string $var): void
{
if (!$this->hasVar($var)) {
$this->fatalError($node, 'The variable `' . $var . '` is not defined');
}
}
private function parseStaticCall(mixed $expr): string private function parseStaticCall(mixed $expr): string
{ {
$class = $this->parseIdentifier($expr->class); if ($expr->class->getType() === self::EXPR_VARIABLE or $expr->name->getType() === self::EXPR_VARIABLE) {
$method = $this->parseIdentifier($expr->name); $fn = 'php::concat({' . $this->identifierToStr($expr->class). ', "::", ' . $this->identifierToStr($expr->name) . '})';
$fn = $class . '::' . $method; } else {
$class = $this->parseIdentifier($expr->class);
$method = $this->parseIdentifier($expr->name);
$fn = '"'. $class . '::' . $method . '"';
}
if (empty($expr->args)) { if (empty($expr->args)) {
return 'php::call("' . $fn . '")'; return 'php::call(' . $fn . ')';
} else { } else {
return 'php::call("' . $fn . '", {' . $this->parseCallArgs($expr->args) . '})'; return 'php::call(' . $fn . ', {' . $this->parseCallArgs($expr->args) . '})';
} }
} }
@ -2320,7 +2354,7 @@ class Translator extends \PhpAot\Core\Translator
} }
$code = $this->getIndent() . $var . ' = ' . $exVar . ';' . PHP_EOL; $code = $this->getIndent() . $var . ' = ' . $exVar . ';' . PHP_EOL;
$code .= $this->getIndent() . 'if ('; $code .= $this->getIndent() . 'if (' . $var . ' && ';
foreach ($types as $type) { foreach ($types as $type) {
$code .= 'php::instanceOf(' . $var . ', "' . $this->parseIdentifier($type) . '")'; $code .= 'php::instanceOf(' . $var . ', "' . $this->parseIdentifier($type) . '")';
} }

@ -0,0 +1,9 @@
--TEST--
static calls
--FILE--
<?php
$cls = 'DateTime';
$method = 'createFromFormat';
$now = $cls::$method('U', time());
?>
--EXPECTF--

@ -0,0 +1,15 @@
--TEST--
Testing dynamic call with invalid value for method name
--FILE--
<?php
$a = new stdClass;
$a::$a();
?>
--EXPECTF--
Fatal error: Uncaught Error: %s
Stack trace:
#0 {main}
thrown in %s on line %d

@ -0,0 +1,16 @@
--TEST--
Testing dynamic call with invalid method name
--FILE--
<?php
$a = new stdClass;
$b = 1;
$a::$b();
?>
--EXPECTF--
Fatal error: Uncaught Error: %s
Stack trace:
#0 {main}
thrown in %s on line %d

@ -0,0 +1,17 @@
--TEST--
Testing dynamic call with undefined variables
--SKIPIF--
<?php die("skip");?>
--FILE--
<?php
$a::$b();
?>
--EXPECTF--
Warning: Undefined variable $a in %s on line %d
Fatal error: Uncaught Error: Class name must be a valid object or a string in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
Loading…
Cancel
Save