feat(compiler): 支持函数别名和命名空间功能

- 实现了函数导入别名功能,解决命名冲突问题
- 添加了命名空间支持,包括嵌套命名空间解析
- 更新了函数定义实体类以支持命名空间属性
- 修改了编译器基础类中的函数解析逻辑
- 调整了扩展模板中函数注册的命名空间处理
- 优化了预处理器中的命名空间定义逻辑
- 完善了翻译器中函数包装器的命名空间支持
- 新增了函数别名查找和解析功能
- 添加了相关测试用例验证功能正确性
pull/1/head
韩天峰 7 months ago
parent ca4a34204c
commit 9118fc4e33
  1. 40
      src/Php/CompilerBase.php
  2. 9
      src/Php/Entity/FunctionDef.php
  3. 2
      src/Php/Preprocessor.php
  4. 22
      src/Php/Translator.php
  5. 5
      src/template/extension.cc.php
  6. 38
      tests/zend/str_offset_001.phpt
  7. 24
      tests/zend/try/try_finally_001.phpt
  8. 33
      tests/zend/use_function/alias.phpt

@ -106,7 +106,6 @@ class CompilerBase extends \PhpAot\Core\Translator
protected int $floatPrecision = 17;
protected bool $debugInfo = true;
protected bool $noLiteralStrings = false;
protected bool $useCppNamespace = false;
protected string $file;
protected string $dir;
@ -644,7 +643,7 @@ class CompilerBase extends \PhpAot\Core\Translator
}
$fnName = $this->parseIdentifier($v->name);
$returnType = $v->returnType ? $this->getTypeFromZendType($this->parseIdentifier($v->returnType)) : self::TYPE_VOID;
$functionDef = new FunctionDef($fnName, $returnType);
$functionDef = new FunctionDef($fnName, $returnType, $this->namespace);
$this->functionDef = $functionDef;
$this->parseParams($v->params, $functionDef);
@ -1796,6 +1795,9 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function findNativeFunction(string $fname): string|false
{
$possibleFunctionNames = [$this->escapeName($fname)];
if (isset($this->useAliases[$fname])) {
$possibleFunctionNames[] = $this->escapeName($this->escapeNamespace($this->useAliases[$fname]));
}
if ($this->namespace) {
$possibleFunctionNames[] = $this->escapeNamespace($this->namespace) . self::NAMESPACE_SEPARATOR . $fname;
}
@ -3102,8 +3104,8 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->addLocalVar($exVar, self::TYPE_OBJECT);
$code .= 'catch(zend_object *_ex) {' . PHP_EOL;
$code .= $this->getIndent() . $exVar . ' = php::catchException();' . PHP_EOL;
if ($catches) {
$code .= $this->getIndent() . $exVar . ' = php::catchException();' . PHP_EOL;
$this->indentLevel++;
foreach ($catches as $catch) {
$code .= $this->parseCatch($catch, $exVar);
@ -3111,6 +3113,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->indentLevel--;
}
$code .= '}' . PHP_EOL;
if ($finally) {
$code .= $this->parseStmts($finally->stmts);
$code .= PHP_EOL;
@ -3267,28 +3270,21 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseUse(mixed $v2): string
{
$code = '';
if ($this->useCppNamespace) {
foreach ($v2->uses as $use) {
$code .= 'using ' . str_replace('\\', '::', $use->name->toString()) . ';' . PHP_EOL;
}
} else {
foreach ($v2->uses as $use) {
$id = $this->parseIdentifier($use->name);
if ($use->type === Node\Stmt\Use_::TYPE_FUNCTION) {
$rpos = strrpos($id, '\\');
$fn = substr($id, $rpos + 1);
$ns = substr($id, 0, $rpos);
// fn => namespace
$this->useFunctions[$fn] = $ns;
} else {
$this->useNamespaces[] = $id;
if ($use->alias) {
$this->useAliases[$use->alias->toString()] = $id;
}
foreach ($v2->uses as $use) {
$id = $this->parseIdentifier($use->name);
if ($use->type === Node\Stmt\Use_::TYPE_FUNCTION) {
$rpos = strrpos($id, '\\');
$fn = substr($id, $rpos + 1);
$ns = substr($id, 0, $rpos);
// fn => namespace
$this->useFunctions[$fn] = $ns;
} else {
$this->useNamespaces[] = $id;
if ($use->alias) {
$this->useAliases[$use->alias->toString()] = $id;
}
}
}
return $code;
}

@ -21,11 +21,18 @@ class FunctionDef
public array $argInfoList = [];
public int $argCountRequired = 0;
public string $params = '';
public string $namespace = '';
public bool $method = false;
public function __construct(string $name, string $returnType)
public function __construct(string $name, string $returnType, string $namespace = '')
{
$this->name = $name;
$this->returnType = $returnType;
$this->namespace = $namespace;
}
public function getNamespacedName(): string
{
return $this->namespace ? $this->namespace . '\\' . $this->name : $this->name;
}
}

@ -130,7 +130,7 @@ class Preprocessor extends CompilerBase
protected function prepareNamespaceDef(Node\Stmt\Namespace_ $node): void
{
$this->resetNamespace();
$this->namespace = $this->parseIdentifier($node->name);
$this->namespace = $node->name ? $this->parseIdentifier($node->name) : '';
foreach ($node->stmts as $v2) {
$type2 = $v2->getType();
switch ($type2) {

@ -587,25 +587,13 @@ class Translator extends Preprocessor
protected function parseNamespace(Node\Stmt\Namespace_ $node): string
{
$ns = $this->parseIdentifier($node->name);
$ns = $node->name ? $this->parseIdentifier($node->name) : '';
$code = '';
$this->resetNamespace();
if ($this->useCppNamespace) {
$ns = explode('\\', $ns);
$ns = array_filter($ns, function ($v) {
return $v !== '';
});
foreach ($ns as $name) {
$code .= 'namespace ' . $name . ' {' . PHP_EOL;
}
$ns_end = str_repeat('}', count($ns));
$this->namespace = implode('::', $ns);
} else {
$this->namespace = $ns;
$ns_end = '';
}
$this->namespace = $ns;
$ns_end = '';
foreach ($node->stmts as $v2) {
$type2 = $v2->getType();
@ -984,9 +972,9 @@ class Translator extends Preprocessor
private function genFunctionWrapper(FunctionDef $functionDef): string
{
$name = $functionDef->name;
$name = $functionDef->namespace ? $functionDef->namespace . '_' . $functionDef->name : $functionDef->name;
$cppCode = 'ZEND_FUNCTION(' . $name . '){' . PHP_EOL;
$fn = self::PREFIX . $this->getNativeName($functionDef->name);
$fn = self::PREFIX . $this->getNativeName($functionDef->name, $functionDef->namespace);
$cppCode .= $this->genWrapperFunctionArgs($fn, $functionDef);
return $cppCode;

@ -76,7 +76,12 @@ foreach ($this->functions as $functionDef):
continue;
}
?>
<?php if ($functionDef->namespace): ?>
<?php $zif_name = $functionDef->namespace . '_' . $functionDef->name; ?>
ZEND_NAMED_FE("<?=$this->escapeString($functionDef->getNamespacedName())?>", ZEND_FN(<?= $zif_name ?>), arginfo_<?=$zif_name?>)
<?php else: ?>
ZEND_FE(<?=$functionDef->name?>, arginfo_<?=$functionDef->name?>)
<?php endif; ?>
<?php endforeach;?>
ZEND_FE_END
};

@ -0,0 +1,38 @@
--TEST--
string offset 001
--FILE--
<?php
// Test positive or null string offsets
function foo($x) {
var_dump($x);
}
function main() {
$str = "abc";
var_dump($str[0]);
var_dump($str[1]);
var_dump($str[2]);
var_dump($str[3]);
var_dump($str[1][0]);
foo($str[0]);
foo($str[1]);
foo($str[2]);
foo($str[3]);
foo($str[1][0]);
}
?>
--EXPECTF--
string(1) "a"
string(1) "b"
string(1) "c"
string(0) ""
string(1) "b"
string(1) "a"
string(1) "b"
string(1) "c"
string(0) ""
string(1) "b"

@ -0,0 +1,24 @@
--TEST--
Try finally (basic test)
--FILE--
<?php
function foo ($a) {
try {
throw new Exception("ex");
} finally {
var_dump($a);
}
}
function main() {
foo("finally");
}
?>
--EXPECTF--
string(7) "finally"
Fatal error: Uncaught Exception: ex %s
Stack trace:
#0 %stry_finally_001.php(%d): foo('finally')
#1 {main}
thrown in %stry_finally_001.php on line %d

@ -0,0 +1,33 @@
--TEST--
aliasing imported functions to resolve naming conflicts
--FILE--
<?php
namespace foo {
function baz() {
return 'foo.baz';
}
}
namespace bar {
function baz() {
return 'bar.baz';
}
}
namespace {
use function foo\baz as foo_baz,
bar\baz as bar_baz;
function main() {
var_dump(foo_baz());
var_dump(bar_baz());
echo "Done\n";
}
}
?>
--EXPECT--
string(7) "foo.baz"
string(7) "bar.baz"
Done
Loading…
Cancel
Save