支持目录处理

pull/1/head
韩天峰 8 months ago
parent c9c558103c
commit 7c5c716b36
  1. 495
      ast.py
  2. 55
      bin/compiler.php
  3. 157
      bin/extractor.php
  4. 614
      src/Php/Extractor.php
  5. 63
      src/Php/FileScanner.php
  6. 62
      src/Php/Translator.php
  7. 8
      src/Php/Unsupported.php
  8. 14
      src/functions.php

495
ast.py

@ -0,0 +1,495 @@
#!/usr/bin/env python3
"""
使用 libclang 解析 C++ 代码并添加头文件路径
"""
import clang.cindex
from clang.cindex import Index, CursorKind, TypeKind, StorageClass
import json
import sys
import os
from pathlib import Path
import subprocess
import argparse
class PHPConfigHelper:
"""PHP 配置辅助类"""
def __init__(self, php_config_path='php-config'):
self.php_config = php_config_path
self._check_availability()
def _check_availability(self):
"""检查 php-config 是否可用"""
try:
result = subprocess.run(
[self.php_config, '--version'],
capture_output=True,
text=True,
check=True
)
print(f"✓ 找到 PHP {result.stdout.strip()}")
except (FileNotFoundError, subprocess.CalledProcessError) as e:
print(f"警告: php-config 不可用: {e}")
# 尝试使用常见路径
common_paths = [
'/usr/bin/php-config',
'/usr/local/bin/php-config',
'/opt/php/bin/php-config'
]
for path in common_paths:
if os.path.exists(path):
self.php_config = path
try:
result = subprocess.run(
[self.php_config, '--version'],
capture_output=True,
text=True,
check=True
)
print(f"✓ 找到 PHP {result.stdout.strip()}{path}")
return
except (FileNotFoundError, subprocess.CalledProcessError):
continue
print("警告: php-config 在任何常见路径都不可用,使用默认路径")
def get_includes(self):
"""
获取 include 路径列表
Returns:
list: 头文件路径列表不带 -I 前缀
"""
try:
result = subprocess.run(
[self.php_config, '--includes'],
capture_output=True,
text=True,
check=True
)
# 解析输出: "-I/path1 -I/path2" -> ['/path1', '/path2']
includes = []
for flag in result.stdout.strip().split():
if flag.startswith('-I'):
includes.append(flag[2:])
return includes
except (subprocess.CalledProcessError, FileNotFoundError):
print("警告: 无法获取 PHP includes,使用默认路径")
return ['/usr/include/php', '/usr/include/php/20210902'] # 默认路径
def get_include_dir(self):
"""获取主 include 目录"""
try:
result = subprocess.run(
[self.php_config, '--include-dir'],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return '/usr/include/php'
def get_extension_dir(self):
"""获取扩展目录"""
try:
result = subprocess.run(
[self.php_config, '--extension-dir'],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return '/usr/lib/php'
def get_version(self):
"""获取 PHP 版本"""
try:
result = subprocess.run(
[self.php_config, '--version'],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return 'unknown'
def get_php_binary(self):
"""获取 PHP 二进制路径"""
try:
result = subprocess.run(
[self.php_config, '--php-binary'],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return 'php'
def get_configure_options(self):
"""获取配置选项"""
try:
result = subprocess.run(
[self.php_config, '--configure-options'],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return ''
def get_all_info(self):
"""获取所有配置信息"""
return {
'version': self.get_version(),
'includes': self.get_includes(),
'include_dir': self.get_include_dir(),
'extension_dir': self.get_extension_dir(),
'php_binary': self.get_php_binary(),
'configure_options': self.get_configure_options(),
}
class ClangParser:
def __init__(self, libclang_path=None):
"""
初始化 Clang 解析器
Args:
libclang_path: libclang 库的路径可选
"""
if libclang_path:
try:
clang.cindex.Config.set_library_file(libclang_path)
except Exception as e:
print(f"警告: 无法设置 libclang 路径 {libclang_path}: {e}")
print("尝试使用默认路径...")
try:
self.index = Index.create()
except Exception as e:
print(f"错误: 无法创建 Clang 索引: {e}")
print("请确保已安装 python3-clang 和 clang 库")
raise
def parse_file(self, filename, include_paths=None, defines=None,
compiler_args=None, language='c++'):
"""
解析 C++ 文件
Args:
filename: 要解析的文件路径
include_paths: 头文件搜索路径列表
defines: 宏定义列表 ['MACRO=value', 'DEBUG']
compiler_args: 额外的编译器参数
language: 语言类型 ('c', 'c++', 'objective-c')
Returns:
TranslationUnit 对象
"""
if not os.path.exists(filename):
raise FileNotFoundError(f"文件不存在: {filename}")
args = []
# 1. 设置语言标准
if language == 'c++':
args.extend([
'-x', 'c++',
'-std=c++14', # 更标准的 C++ 版本
])
elif language == 'c':
args.extend(['-x', 'c', '-std=c11'])
# 2. 添加头文件搜索路径
if include_paths:
for path in include_paths:
if os.path.exists(path): # 检查路径是否存在
args.append(f'-I{path}')
else:
print(f"警告: 包含路径不存在: {path}")
# 3. 添加宏定义
if defines:
for define in defines:
args.append(f'-D{define}')
# 4. 添加额外的编译器参数
if compiler_args:
args.extend(compiler_args)
# 5. 常用的编译选项
args.extend([
'-Wno-pragma-once-outside-header', # 忽略警告
'-ferror-limit=0', # 不限制错误数量
'-fno-delayed-template-parsing', # 避免某些 C++ 模板解析问题
'-w', # 禁用所有警告以减少输出
])
print(f"编译参数: {' '.join(args)}")
# 解析文件
try:
tu = self.index.parse(
filename,
args=args,
options=clang.cindex.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD
)
except Exception as e:
print(f"解析文件时出错: {e}")
print("尝试使用最小参数集...")
# 尝试使用最小参数集
minimal_args = ['-x', 'c++', '-std=c++14', '-w']
if include_paths:
for path in include_paths:
if os.path.exists(path):
minimal_args.append(f'-I{path}')
try:
tu = self.index.parse(
filename,
args=minimal_args,
options=clang.cindex.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD
)
print("使用最小参数集成功解析")
except Exception as e2:
print(f"使用最小参数集也失败: {e2}")
print("尝试解析不包含头文件的简化版本...")
# 创建一个临时文件,移除头文件包含行
temp_filename = filename + ".tmp"
with open(filename, 'r') as original:
lines = original.readlines()
# 移除 #include 行
filtered_lines = [line for line in lines if not line.strip().startswith('#include')]
with open(temp_filename, 'w') as temp:
temp.writelines(filtered_lines)
try:
tu = self.index.parse(
temp_filename,
args=minimal_args,
options=clang.cindex.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD
)
print("解析简化版本成功")
# 清理临时文件
os.remove(temp_filename)
except Exception as e3:
print(f"简化版本也失败: {e3}")
# 清理临时文件
if os.path.exists(temp_filename):
os.remove(temp_filename)
raise
# 检查诊断信息
if tu.diagnostics:
print(f"\n诊断信息 ({len(tu.diagnostics)} 个):")
error_count = 0
warning_count = 0
for diag in tu.diagnostics:
if diag.severity >= 3: # 错误级别
error_count += 1
else: # 警告级别
warning_count += 1
print(f"错误: {error_count}, 警告: {warning_count}")
# 只显示前几个诊断信息,避免输出过多
for i, diag in enumerate(tu.diagnostics):
if i >= 5: # 只显示前5个
print("... 还有更多诊断信息")
break
print(f" [{diag.severity}] {diag.spelling}")
if diag.location.file:
print(f" at {diag.location.file.name}:{diag.location.line}")
return tu
def extract_functions(self, tu, name_prefixes=None):
"""
提取函数定义
Args:
tu: TranslationUnit 对象
name_prefixes: 函数名前缀过滤列表
Returns:
函数信息列表
"""
functions = []
def visit_node(node, depth=0):
# 只处理函数声明/定义
if node.kind == CursorKind.FUNCTION_DECL:
try:
func_info = self.parse_function(node)
# 过滤函数名
if name_prefixes:
if any(func_info['name'].startswith(prefix)
for prefix in name_prefixes):
functions.append(func_info)
else:
functions.append(func_info)
except Exception as e:
print(f"解析函数时出错: {e}")
# 递归访问子节点
for child in node.get_children():
visit_node(child, depth + 1)
visit_node(tu.cursor)
return functions
def parse_function(self, cursor):
"""
解析函数详细信息
"""
# 检查方法是否存在
def safe_call(method, default_value=None):
try:
return method()
except AttributeError:
return default_value
# 基本信息
func_info = {
'name': cursor.spelling,
'displayName': cursor.displayname,
'mangledName': cursor.mangled_name,
'returnType': cursor.result_type.spelling,
'isStatic': cursor.storage_class == StorageClass.STATIC,
'isInline': safe_call(lambda: cursor.is_inline_function(), False),
'isVirtual': safe_call(lambda: cursor.is_virtual_method(), False),
'isConst': safe_call(lambda: cursor.is_const_method(), False),
'location': {
'file': str(cursor.location.file) if cursor.location.file else None,
'line': cursor.location.line,
'column': cursor.location.column,
},
'parameters': [],
'namespaces': self.get_namespaces(cursor),
}
# 解析参数
for arg in cursor.get_arguments():
param_info = {
'name': arg.spelling or f'arg{len(func_info["parameters"])}',
'type': arg.type.spelling,
'canonicalType': arg.type.get_canonical().spelling,
}
# 检查是否有默认值
try:
for token in arg.get_tokens():
if token.spelling == '=':
# 有默认值
param_info['hasDefault'] = True
break
except:
# 如果无法获取 tokens,跳过默认值检查
pass
func_info['parameters'].append(param_info)
return func_info
def get_namespaces(self, cursor):
"""
获取函数所在的命名空间
"""
namespaces = []
parent = cursor.semantic_parent
while parent and parent.kind != CursorKind.TRANSLATION_UNIT:
if parent.kind == CursorKind.NAMESPACE:
namespaces.insert(0, parent.spelling)
parent = parent.semantic_parent
return namespaces
def main():
parser = argparse.ArgumentParser(description='使用 libclang 解析 C++ 代码并提取函数信息')
parser.add_argument('filename', help='要解析的 C++ 文件路径')
parser.add_argument('--libclang-path', help='libclang 库路径')
parser.add_argument('--include-paths', nargs='*', help='额外的包含路径')
parser.add_argument('--function-prefixes', nargs='*', help='函数名前缀过滤器')
args = parser.parse_args()
if not os.path.exists(args.filename):
print(f"错误: 文件不存在: {args.filename}")
sys.exit(1)
try:
# 创建解析器
parser_obj = ClangParser(libclang_path=args.libclang_path)
# 配置头文件路径
include_paths = args.include_paths or [
"/usr/include/linux",
"/home/swoole/workspace/projects/phpx/include"
]
# 尝试获取 PHP 配置的头文件路径
try:
php_config = PHPConfigHelper()
php_includes = php_config.get_includes()
include_paths.extend(php_includes)
except Exception as e:
print(f"警告: 无法获取 PHP 配置: {e}")
print("继续使用默认路径...")
# 配置宏定义
defines = [
'HAVE_CONFIG_H',
'ZEND_ENABLE_STATIC_TSRMLS_CACHE=1',
]
# 额外的编译器参数
compiler_args = [
'-fparse-all-comments', # 解析所有注释
'-Wno-unknown-pragmas',
]
# 解析文件
tu = parser_obj.parse_file(
args.filename,
include_paths=include_paths,
defines=defines,
compiler_args=compiler_args,
language='c++'
)
# 提取函数
name_prefixes = args.function_prefixes or None
functions = parser_obj.extract_functions(tu, name_prefixes=name_prefixes)
# 输出结果
output = {
'file': args.filename,
'functions': functions,
'total': len(functions),
}
print(json.dumps(output, indent=2, ensure_ascii=False))
except clang.cindex.TranslationUnitLoadError as e:
print(f"翻译单元加载错误: {e}")
print("这通常意味着 C++ 代码包含语法错误或缺少必要的头文件")
sys.exit(1)
except Exception as e:
print(f"错误: {e}")
sys.exit(1)
if __name__ == '__main__':
main()

@ -2,24 +2,59 @@
<?php
require __DIR__ . '/bootstrap.php';
use PhpAot\Php\FileScanner;
use PhpAot\Php\Translator;
use PhpAot\Php\Unsupported;
if (empty($argv[1])) {
die("php compiler.php [file]\n");
}
$file = $argv[1];
$path = $argv[1];
$translator = new Translator(ROOT_PATH);
$translator->setIndent(' ');
$code = $translator->convert($file);
$info = pathinfo($file);
$cppFile = $info['dirname'] . '/' . $info['filename'] . '.cc';
$translator->save($code, $cppFile);
if (is_dir($path)) {
$scanner = new FileScanner($path);
$list = $scanner->scan();
$targetFile = basename($path);
} else {
$list = [$path];
$targetFile = basename($path, '.php');
}
$sourceFiles = [];
$objectFiles = [];
// 分析 PHP 文件,生成 C++ 文件
foreach ($list as $file) {
try {
if (str_ends_with($file, '.php')) {
$code = $translator->convert($file);
$info = pathinfo($file);
$cppFile = $info['dirname'] . '/' . $info['filename'] . '.cc';
$translator->save($code, $cppFile);
} else {
$cppFile = $file;
}
$sourceFiles[] = $cppFile;
} catch (Unsupported $e) {
echo " skip: " . $file . "\n";
}
}
// 生成所有函数声明
$translator->genFunctionDeclaration("./php_func_decl.h");
$translator->compileFile($cppFile);
$objectFile = $info['dirname'] . '/' . $info['filename'] . '.cc.o';
if (!is_file($objectFile)) {
throw new Exception("compile error");
// 编译所有 C++ 文件
foreach ($sourceFiles as $cppFile) {
$translator->compileFile($cppFile);
$objectFile = $cppFile . '.o';
if (!is_file($objectFile)) {
throw new Exception("compile error");
}
$objectFiles[] = $objectFile;
}
$translator->compileBinary($info['filename'], $objectFile);
// 连接所有目标文件,生成可执行文件
$translator->compileBinary($targetFile, $objectFiles);

@ -0,0 +1,157 @@
<?php
require __DIR__ . '/bootstrap.php';
// ============================================================================
// 命令行接口
// ============================================================================
function showUsage(): void
{
echo <<<USAGE
用法: php extract_functions.php <file.cpp> [prefix] [output.json]
参数:
file.cpp 要分析的 C/C++ 文件(必需)
prefix 函数名前缀,多个前缀用逗号分隔(默认: php_)
output.json 输出文件路径(默认: stdout)
选项:
--help, -h 显示此帮助信息
--pretty 美化 JSON 输出
--batch 批量处理模式(从 stdin 读取文件列表)
示例:
# 基本用法
php extract_functions.php myfile.cpp
# 指定前缀
php extract_functions.php myfile.cpp php_
# 多个前缀
php extract_functions.php myfile.cpp php_,swoole_,zend_
# 保存到文件
php extract_functions.php myfile.cpp php_ output.json
# 美化输出
php extract_functions.php myfile.cpp php_ output.json --pretty
# 批量处理
find . -name "*.cpp" | php extract_functions.php --batch php_
特点:
✓ 无需头文件
✓ 速度快
✓ 支持 C/C++
✓ 输出 JSON 格式
✓ 支持复杂的参数类型
USAGE;
}
function main(array $argv): void
{
// 解析命令行参数
$options = [
'help' => false,
'pretty' => false,
'batch' => false,
];
$args = [];
for ($i = 1; $i < count($argv); $i++) {
$arg = $argv[$i];
if ($arg === '--help' || $arg === '-h') {
$options['help'] = true;
} elseif ($arg === '--pretty') {
$options['pretty'] = true;
} elseif ($arg === '--batch') {
$options['batch'] = true;
} else {
$args[] = $arg;
}
}
// 显示帮助
if ($options['help'] || (empty($args) && !$options['batch'])) {
showUsage();
exit(0);
}
// 创建提取器
$extractor = new PhpAot\Php\Extractor();
// 批量模式
if ($options['batch']) {
$prefixes = !empty($args[0]) ? explode(',', $args[0]) : ['php_'];
$files = [];
while ($line = fgets(STDIN)) {
$file = trim($line);
if (!empty($file) && file_exists($file)) {
$files[] = $file;
}
}
if (empty($files)) {
fprintf(STDERR, "错误: 未找到有效的文件\n");
exit(1);
}
$functions = $extractor->extractFromFiles($files, $prefixes);
// 输出结果
$jsonFlags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
if ($options['pretty']) {
$jsonFlags |= JSON_PRETTY_PRINT;
}
echo json_encode($functions, $jsonFlags) . "\n";
exit(0);
}
// 单文件模式
$filename = $args[0] ?? null;
$prefixesStr = $args[1] ?? 'php_';
$output = $args[2] ?? null;
if (!$filename) {
fprintf(STDERR, "错误: 未指定文件\n");
showUsage();
exit(1);
}
// 解析前缀
$prefixes = array_map('trim', explode(',', $prefixesStr));
try {
// 提取函数
$functions = $extractor->extractFunctions($filename, $prefixes);
// 准备 JSON 输出
$jsonFlags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
if ($options['pretty']) {
$jsonFlags |= JSON_PRETTY_PRINT;
}
$json = json_encode($functions, $jsonFlags);
// 输出
if ($output) {
file_put_contents($output, $json . "\n");
fprintf(STDERR, "\033[0;32m已保存到: %s\033[0m\n", $output);
} else {
echo $json . "\n";
}
} catch (Exception $e) {
fprintf(STDERR, "\033[0;31m错误: %s\033[0m\n", $e->getMessage());
exit(1);
}
}
// 运行主函数
if (php_sapi_name() === 'cli') {
main($argv);
}

@ -0,0 +1,614 @@
<?php
namespace PhpAot\Php;
class Extractor
{
private string $ctagsPath = 'ctags';
private bool $isUniversalCtags = false;
public function __construct()
{
$this->checkCtags();
}
/**
* 检查 ctags 是否可用
*/
private function checkCtags(): void
{
$output = shell_exec("{$this->ctagsPath} --version 2>&1");
if ($output === null) {
$this->error("未找到 ctags 命令\n安装: sudo apt install universal-ctags");
}
$this->isUniversalCtags = stripos($output, 'Universal Ctags') !== false;
if (!$this->isUniversalCtags) {
$this->warn("建议使用 Universal Ctags 以获得更好的支持");
}
}
/**
* 提取函数定义
*
* @param string $filename 文件路径
* @param array $prefixes 函数名前缀列表
* @return array 函数列表
*/
public function extractFunctions(string $filename, array $prefixes = ['php_']): array
{
if (!file_exists($filename)) {
throw new RuntimeException("文件不存在: {$filename}");
}
$this->info("分析文件: {$filename}");
$this->info("函数前缀: " . implode(', ', $prefixes));
// 运行 ctags
$tags = $this->runCtags($filename);
// 过滤和解析函数
$functions = [];
foreach ($tags as $tag) {
if ($tag['kind'] !== 'function') {
continue;
}
$funcName = $tag['name'] ?? '';
// 检查前缀
$matched = false;
foreach ($prefixes as $prefix) {
if (str_starts_with($funcName, $prefix)) {
$matched = true;
break;
}
}
if (!$matched) {
continue;
}
// 解析函数详细信息
$funcInfo = $this->parseFunction($filename, $tag);
if ($funcInfo) {
$functions[] = $funcInfo;
}
}
$this->info("找到 " . count($functions) . " 个函数");
return $functions;
}
/**
* 运行 ctags 命令
*/
private function runCtags(string $filename): array
{
$cmd = sprintf(
'%s --output-format=json --fields=+nKSzZt --kinds-c++=f --extras=+q -f - %s 2>&1',
escapeshellcmd($this->ctagsPath),
escapeshellarg($filename)
);
$output = shell_exec($cmd);
if ($output === null) {
throw new RuntimeException("ctags 执行失败");
}
// 解析 JSON 输出
$tags = [];
$lines = explode("\n", trim($output));
foreach ($lines as $line) {
if (empty($line)) {
continue;
}
$tag = json_decode($line, true);
if ($tag === null) {
continue;
}
$tags[] = $tag;
}
return $tags;
}
/**
* 解析单个函数的详细信息
*/
private function parseFunction(string $filename, array $tag): ?array
{
$funcName = $tag['name'] ?? '';
$lineNum = $tag['line'] ?? 0;
if (empty($funcName) || $lineNum < 1) {
return null;
}
// 提取完整的函数签名
$signature = $this->extractSignature($filename, $lineNum, $funcName);
if (empty($signature)) {
return null;
}
// 解析返回类型
$returnType = $this->parseReturnType($signature, $funcName);
// 解析参数
$parameters = $this->parseParameters($signature, $funcName);
return [
'name' => $funcName,
'returnType' => $returnType,
'signature' => $signature,
'parameters' => $parameters,
'location' => [
'file' => $filename,
'line' => $lineNum
],
'scope' => $tag['scope'] ?? null,
'scopeKind' => $tag['scopeKind'] ?? null
];
}
/**
* 从源文件中提取完整的函数签名
*/
private function extractSignature(string $filename, int $lineNum, string $funcName): string
{
$lines = file($filename, FILE_IGNORE_NEW_LINES);
if ($lines === false || $lineNum > count($lines)) {
return '';
}
// 从函数声明行开始收集,直到遇到 { 或 ;
$signatureLines = [];
$maxLines = min($lineNum + 20, count($lines));
for ($i = $lineNum - 1; $i < $maxLines; $i++) {
$line = $lines[$i];
$signatureLines[] = $line;
// 检查是否到达函数体或声明结束
if (strpos($line, '{') !== false || strpos($line, ';') !== false) {
break;
}
}
// 合并并清理
$signature = implode(' ', $signatureLines);
// 移除 { 或 ; 之后的内容
$signature = preg_replace('/[{;].*$/', '', $signature);
// 合并多个空白字符
$signature = preg_replace('/\s+/', ' ', $signature);
// 清理首尾空白
$signature = trim($signature);
return $signature;
}
/**
* 解析返回类型
*/
private function parseReturnType(string $signature, string $funcName): string
{
// 匹配: <返回类型> <函数名>(
$pattern = '/^(.+?)\s+' . preg_quote($funcName, '/') . '\s*\(/';
if (preg_match($pattern, $signature, $matches)) {
$returnType = trim($matches[1]);
// 移除可能的修饰符
$returnType = preg_replace('/\b(static|inline|extern|virtual|explicit)\b/', '', $returnType);
$returnType = preg_replace('/\s+/', ' ', $returnType);
$returnType = trim($returnType);
return $returnType ?: 'void';
}
return 'unknown';
}
/**
* 解析参数列表
*/
private function parseParameters(string $signature, string $funcName): array
{
// 提取括号内的参数
$pattern = '/' . preg_quote($funcName, '/') . '\s*\((.*?)\)/s';
if (!preg_match($pattern, $signature, $matches)) {
return [];
}
$paramsStr = trim($matches[1]);
// 空参数或 void
if (empty($paramsStr) || $paramsStr === 'void') {
return [];
}
// 分割参数(处理嵌套的模板和括号)
$params = $this->splitParameters($paramsStr);
$parameters = [];
foreach ($params as $param) {
$param = trim($param);
if (empty($param)) {
continue;
}
$paramInfo = $this->parseParameter($param);
if ($paramInfo) {
$parameters[] = $paramInfo;
}
}
return $parameters;
}
/**
* 智能分割参数(处理嵌套的模板和括号)
*/
private function splitParameters(string $paramsStr): array
{
$params = [];
$current = '';
$depth = 0;
$length = strlen($paramsStr);
for ($i = 0; $i < $length; $i++) {
$char = $paramsStr[$i];
if ($char === '<' || $char === '(' || $char === '[') {
$depth++;
$current .= $char;
} elseif ($char === '>' || $char === ')' || $char === ']') {
$depth--;
$current .= $char;
} elseif ($char === ',' && $depth === 0) {
$params[] = $current;
$current = '';
} else {
$current .= $char;
}
}
if (!empty($current)) {
$params[] = $current;
}
return $params;
}
/**
* 解析单个参数
*/
private function parseParameter(string $param): ?array
{
$param = trim($param);
// 移除默认值
$param = preg_replace('/\s*=\s*.*$/', '', $param);
// 尝试匹配: <类型> <名称>
// 支持复杂类型如: const char*, std::string&, int**, etc.
if (preg_match('/^(.+?)\s+(\w+)\s*$/', $param, $matches)) {
return [
'type' => trim($matches[1]),
'name' => trim($matches[2])
];
}
// 只有类型,没有名称
return [
'type' => $param,
'name' => ''
];
}
/**
* 批量提取多个文件
*/
public function extractFromFiles(array $files, array $prefixes = ['php_']): array
{
$allFunctions = [];
foreach ($files as $file) {
try {
$functions = $this->extractFunctions($file, $prefixes);
$allFunctions = array_merge($allFunctions, $functions);
} catch (Exception $e) {
$this->error("处理文件 {$file} 失败: " . $e->getMessage());
}
}
return $allFunctions;
}
/**
* 输出信息
*/
private function info(string $message): void
{
fprintf(STDERR, "\033[0;32m%s\033[0m\n", $message);
}
/**
* 输出警告
*/
private function warn(string $message): void
{
fprintf(STDERR, "\033[1;33m警告: %s\033[0m\n", $message);
}
/**
* 输出错误并退出
*/
private function error(string $message): void
{
fprintf(STDERR, "\033[0;31m错误: %s\033[0m\n", $message);
exit(1);
}
/**
* 提取函数并添加额外信息
*/
public function extractWithMetadata(string $filename, array $prefixes = ['php_']): array
{
$functions = $this->extractFunctions($filename, $prefixes);
// 添加额外的元数据
foreach ($functions as &$func) {
$func['metadata'] = $this->extractMetadata($filename, $func);
}
return $functions;
}
/**
* 提取函数的元数据(注释、属性等)
*/
private function extractMetadata(string $filename, array $func): array
{
$lineNum = $func['location']['line'];
$lines = file($filename, FILE_IGNORE_NEW_LINES);
$metadata = [
'comments' => [],
'attributes' => [],
'isPHPFunction' => false,
'isStatic' => false,
'isInline' => false,
];
// 提取函数前的注释
$comments = $this->extractComments($lines, $lineNum);
$metadata['comments'] = $comments;
// 检测 PHP 函数宏
$metadata['isPHPFunction'] = $this->isPHPFunction($func['signature']);
// 检测修饰符
$signature = $func['signature'];
$metadata['isStatic'] = strpos($signature, 'static') !== false;
$metadata['isInline'] = strpos($signature, 'inline') !== false;
// 提取文档注释中的标签
$metadata['docTags'] = $this->parseDocTags($comments);
return $metadata;
}
/**
* 提取函数前的注释
*/
private function extractComments(array $lines, int $lineNum): array
{
$comments = [];
$i = $lineNum - 2; // 从函数声明的前一行开始
// 向上查找注释
while ($i >= 0) {
$line = trim($lines[$i]);
// 空行
if (empty($line)) {
$i--;
continue;
}
// C++ 风格注释
if (str_starts_with($line, '//')) {
array_unshift($comments, substr($line, 2));
$i--;
continue;
}
// C 风格注释结束
if (str_ends_with($line, '*/')) {
$commentLines = [$line];
$i--;
// 继续向上查找注释开始
while ($i >= 0) {
$commentLine = trim($lines[$i]);
array_unshift($commentLines, $commentLine);
if (str_starts_with($commentLine, '/*')) {
break;
}
$i--;
}
// 解析多行注释
$comment = implode("\n", $commentLines);
$comment = preg_replace('#^/\*+\s*#', '', $comment);
$comment = preg_replace('#\s*\*+/$#', '', $comment);
$comment = preg_replace('#^\s*\*\s?#m', '', $comment);
array_unshift($comments, trim($comment));
$i--;
continue;
}
// 遇到非注释行,停止
break;
}
return $comments;
}
/**
* 检测是否是 PHP 函数宏定义
*/
private function isPHPFunction(string $signature): bool
{
$phpMacros = [
'PHP_FUNCTION',
'PHP_METHOD',
'ZEND_FUNCTION',
'ZEND_METHOD',
];
foreach ($phpMacros as $macro) {
if (strpos($signature, $macro) !== false) {
return true;
}
}
return false;
}
/**
* 解析文档注释标签
*/
private function parseDocTags(array $comments): array
{
$tags = [];
foreach ($comments as $comment) {
// 匹配 @tag 格式
if (preg_match_all('/@(\w+)\s+(.*)$/m', $comment, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$tagName = $match[1];
$tagValue = trim($match[2]);
if (!isset($tags[$tagName])) {
$tags[$tagName] = [];
}
$tags[$tagName][] = $tagValue;
}
}
}
return $tags;
}
/**
* 生成函数统计信息
*/
public function generateStatistics(array $functions): array
{
$stats = [
'total' => count($functions),
'byReturnType' => [],
'byParameterCount' => [],
'byPrefix' => [],
'withComments' => 0,
'isPHPFunction' => 0,
];
foreach ($functions as $func) {
// 按返回类型统计
$returnType = $func['returnType'];
$stats['byReturnType'][$returnType] =
($stats['byReturnType'][$returnType] ?? 0) + 1;
// 按参数数量统计
$paramCount = count($func['parameters']);
$stats['byParameterCount'][$paramCount] =
($stats['byParameterCount'][$paramCount] ?? 0) + 1;
// 按前缀统计
$name = $func['name'];
$prefix = preg_match('/^([a-z_]+_)/i', $name, $m) ? $m[1] : 'other';
$stats['byPrefix'][$prefix] =
($stats['byPrefix'][$prefix] ?? 0) + 1;
// 有注释的函数
if (!empty($func['metadata']['comments'])) {
$stats['withComments']++;
}
// PHP 函数宏
if ($func['metadata']['isPHPFunction'] ?? false) {
$stats['isPHPFunction']++;
}
}
return $stats;
}
/**
* 导出为 Markdown 文档
*/
public function exportToMarkdown(array $functions, string $title = 'API 文档'): string
{
$md = "# {$title}\n\n";
$md .= "生成时间: " . date('Y-m-d H:i:s') . "\n\n";
$md .= "总计: " . count($functions) . " 个函数\n\n";
$md .= "---\n\n";
foreach ($functions as $func) {
$md .= "## {$func['name']}\n\n";
// 签名
$md .= "```c\n{$func['signature']}\n```\n\n";
// 返回类型
$md .= "**返回类型**: `{$func['returnType']}`\n\n";
// 参数
if (!empty($func['parameters'])) {
$md .= "**参数**:\n\n";
foreach ($func['parameters'] as $param) {
$name = $param['name'] ?: '(unnamed)';
$md .= "- `{$param['type']}` **{$name}**\n";
}
$md .= "\n";
} else {
$md .= "**参数**: 无\n\n";
}
// 注释
if (!empty($func['metadata']['comments'])) {
$md .= "**说明**:\n\n";
foreach ($func['metadata']['comments'] as $comment) {
$md .= $comment . "\n\n";
}
}
// 位置
$md .= "**位置**: {$func['location']['file']}:{$func['location']['line']}\n\n";
$md .= "---\n\n";
}
return $md;
}
}

@ -0,0 +1,63 @@
<?php
namespace PhpAot\Php;
use FilesystemIterator;
class FileScanner
{
private string $directory;
private array $extensions;
private array $excludePatterns;
public function __construct(string $directory, array $extensions = ['.php', '.cc', '.cpp', '.cxx', '.c', '.h', '.hpp'])
{
if (!is_dir($directory)) {
throw new \InvalidArgumentException("Directory does not exist: $directory");
}
$this->directory = rtrim($directory, DIRECTORY_SEPARATOR);
$this->extensions = $extensions;
$this->excludePatterns = [];
}
public function addExcludePattern(string $pattern): self
{
$this->excludePatterns[] = $pattern;
return $this;
}
public function scan(): array
{
$files = [];
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($this->directory, FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$extension = '.' . $file->getExtension();
if (in_array($extension, $this->extensions)) {
$filePath = $file->getPathname();
$excluded = false;
foreach ($this->excludePatterns as $pattern) {
if ($this->matchPattern($pattern, $filePath)) {
$excluded = true;
break;
}
}
if (!$excluded) {
$files[] = $filePath;
}
}
}
}
sort($files);
return $files;
}
private function matchPattern(string $pattern, string $path): bool
{
return fnmatch($pattern, $path, FNM_PATHNAME);
}
}

@ -8,6 +8,7 @@ use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Error;
use PhpParser\Node\NullableType;
use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter;
@ -256,9 +257,9 @@ class Translator extends \PhpAot\Core\Translator
$this->phpxDir = $dir;
}
private function doConvert(string $phpCode)
private function doConvert(string $phpCode): string
{
$this->climate->info('do convert');
$this->climate->info('do convert: ' . $this->file);
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$ast = $parser->parse($phpCode);
@ -317,7 +318,6 @@ class Translator extends \PhpAot\Core\Translator
$this->formatCppCode($file);
}
public function getLine($node): int
{
return $node->getLine();
@ -389,9 +389,13 @@ class Translator extends \PhpAot\Core\Translator
}
}
$this->indentLevel++;
$stmts = $this->parseStmts($v->stmts);
$this->indentLevel--;
if ($v->stmts) {
$this->indentLevel++;
$stmts = $this->parseStmts($v->stmts);
$this->indentLevel--;
} else {
$stmts = '';
}
$functionDeclCode = $this->getReturnType() . ' ' . self::PREFIX . $name . '(' . $this->functionDef->params . ')';
@ -418,9 +422,24 @@ class Translator extends \PhpAot\Core\Translator
}
}
private function parseScalarValue($expr): string
private function parseScalar(Node $expr)
{
return $expr->getAttribute('rawValue');
$type = $expr->getType();
switch ($type) {
case 'Scalar_Int':
return $expr->value . 'L';
case 'Scalar_Float':
return $this->parseScalarFloat($expr);
case 'Scalar_String':
if ($this->noLiteralStrings) {
return '"' . $this->escapeString($expr->value) . '"';
} else {
$index = $this->literalStrings[$expr->value] ?? $this->addLiteralString($expr->value);
return self::LITERAL_STRINGS . '[' . $index . ']';
}
default:
abort($expr);
}
}
private function parseIdentifier(Node $expr)
@ -433,16 +452,9 @@ class Translator extends \PhpAot\Core\Translator
case 'Identifier':
return $expr->name;
case 'Scalar_Int':
return $expr->value . 'L';
case 'Scalar_Float':
return $this->parseScalarFloat($expr);
case 'Scalar_String':
if ($this->noLiteralStrings) {
return '"' . $this->escapeString($expr->value) . '"';
} else {
$index = $this->literalStrings[$expr->value] ?? $this->addLiteralString($expr->value);
return self::LITERAL_STRINGS . '[' . $index . ']';
}
return $this->parseScalar($expr);
case 'Expr_ConstFetch':
return $this->parseConstFetch($expr);
default:
@ -463,7 +475,7 @@ class Translator extends \PhpAot\Core\Translator
$argInfo->type = $type;
if (isset($param->default)) {
$this->functionDef->argCountRequired = count($list) - 1;
$argInfo->default = $param->default->getAttribute('rawValue');
$argInfo->default = $this->parseScalar($param->default);
}
$this->functionDef->argInfoList[] = $argInfo;
}
@ -981,6 +993,9 @@ class Translator extends \PhpAot\Core\Translator
if ($type == null) {
return self::TYPE_VAR;
}
if ($type instanceof NullableType) {
return self::TYPE_VAR;
}
$name = $type->name;
switch ($name) {
case 'int':
@ -1054,12 +1069,13 @@ class Translator extends \PhpAot\Core\Translator
shell_exec($cmd);
}
public function compileBinary($targetFile, $objectFile): void
public function compileBinary(string $targetFile, array $objectFiles): void
{
$this->genGlobalVars();
if ($this->climate->arguments->defined('output')) {
$targetFile = $this->climate->arguments->get('output');
}
$objectFile = implode(' ', $objectFiles);
$cmd = $this->cppCompiler . ' main.cc global_vars.cc ' . $objectFile . ' -o ' . $targetFile . ' ' . $this->parseLdflags() . $this->parseLibs();
$this->addCompilationOption($cmd);
$this->climate->comment($cmd);
@ -1874,7 +1890,7 @@ class Translator extends \PhpAot\Core\Translator
$this->localVars = $localVars;
goto _fail;
}
$code .= $this->getIndent() . 'case ' . $this->parseScalarValue($case->cond) . ': {' . PHP_EOL;
$code .= $this->getIndent() . 'case ' . $this->parseScalar($case->cond) . ': {' . PHP_EOL;
}
$this->indentLevel++;
$code .= $this->parseStmts($case->stmts);
@ -1909,8 +1925,12 @@ class Translator extends \PhpAot\Core\Translator
{
$list = [];
foreach ($v->vars as $var) {
$type = $this->detectExprType($var->default);
$list[] = 'static ' . $type . ' ' . $this->parseIdentifier($var->var) . ' = ' . $this->parseIdentifier($var->default) . ';';
if ($var->default) {
$type = $this->detectExprType($var->default);
$list[] = 'static ' . $type . ' ' . $this->parseIdentifier($var->var) . ' = ' . $this->parseIdentifier($var->default) . ';';
} else {
$list[] = 'static ' . self::TYPE_VAR . ' ' . $this->parseIdentifier($var->var) . ';';
}
}
return implode(PHP_EOL . $this->getIndent(), $list);
}

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

@ -1,6 +1,7 @@
<?php
use PhpAot\Core\Translator;
use PhpAot\Php\Unsupported;
function abort($v)
{
@ -9,18 +10,17 @@ function abort($v)
*/
global $translator;
$lang = $translator->getLang();
$msg = 'Error: Unsupported ' . $lang . ' Syntax,';
$msg .= ' Line: ' . $translator->getLine($v) . ', Type: ' . $translator->getType($v) . PHP_EOL;
if ($translator->mode == 'cli') {
echo 'Error: Unsupported ' . $lang . ' Syntax,';
echo ' Line: ' . $translator->getLine($v) . ', Type: ' . $translator->getType($v) . PHP_EOL;
if (DEBUG) {
debug_print_backtrace();
var_dump($v);
}
// if (DEBUG) {
// var_dump($v);
// }
} else {
header('Content-Type: application/json');
echo json_encode($v, JSON_PRETTY_PRINT);
}
die;
throw new Unsupported($msg);
}
function if_empty_debug($if_expr, $v)

Loading…
Cancel
Save