Files
BusyRabbit/Tools/ue_header_parser.py
2025-09-24 13:57:17 +08:00

403 lines
15 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
UE头文件解析工具
用于扫描UE头文件并生成slua插件的emmy-lua注解
"""
import os
import re
import argparse
from pathlib import Path
from typing import List, Dict, Set, Optional
class UEHeaderParser:
def __init__(self):
self.classes = []
self.structs = []
self.enums = []
self.delegates = []
def parse_header_file(self, file_path: str) -> Dict:
"""解析单个头文件"""
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
result = {
'classes': [],
'structs': [],
'enums': [],
'delegates': [],
'file_path': file_path
}
# 解析UCLASS
class_pattern = r'UCLASS\s*\([^)]*\)\s*\n\s*class\s+[A-Z_]+\s+(\w+)\s*:\s*public\s+(\w+)'
classes = re.findall(class_pattern, content)
for class_name, parent_class in classes:
result['classes'].append({
'name': class_name,
'parent': parent_class,
'functions': self._parse_functions(content, class_name),
'properties': self._parse_properties(content, class_name)
})
# 解析USTRUCT
struct_pattern = r'USTRUCT\s*\([^)]*\)\s*\n\s*struct\s+[A-Z_]+\s+(\w+)'
structs = re.findall(struct_pattern, content)
for struct_name in structs:
result['structs'].append({
'name': struct_name,
'properties': self._parse_struct_properties(content, struct_name)
})
# 解析USTRUCT (BlueprintType变体)
struct_pattern2 = r'USTRUCT\s*\([^)]*\)\s*\n\s*struct\s+F(\w+)'
structs2 = re.findall(struct_pattern2, content)
for struct_name in structs2:
result['structs'].append({
'name': f'F{struct_name}',
'properties': self._parse_struct_properties(content, f'F{struct_name}')
})
# 解析UENUM
enum_pattern = r'UENUM\s*\([^)]*\)\s*\n\s*enum\s+class\s+(\w+)'
enums = re.findall(enum_pattern, content)
for enum_name in enums:
result['enums'].append({
'name': enum_name,
'values': self._parse_enum_values(content, enum_name)
})
# 解析委托
delegate_pattern = r'DECLARE_DYNAMIC_(MULTICAST_)?DELEGATE(?:_\w+)?\s*\(\s*(\w+)\s*'
delegates = re.findall(delegate_pattern, content)
for is_multicast, delegate_name in delegates:
result['delegates'].append({
'name': delegate_name,
'is_multicast': bool(is_multicast),
'params': self._parse_delegate_params(content, delegate_name)
})
return result
def _parse_functions(self, content: str, class_name: str) -> List[Dict]:
"""解析UFUNCTION"""
functions = []
# 匹配UFUNCTION声明
func_pattern = r'UFUNCTION\s*\([^)]*\)\s*\n\s*(\w+)\s+(\w+)\s*\(([^)]*)\)'
matches = re.findall(func_pattern, content)
for return_type, func_name, params in matches:
# 解析参数
param_list = []
if params.strip():
for param in params.split(','):
param = param.strip()
if param:
# 简单的参数解析
parts = param.split()
if len(parts) >= 2:
param_type = parts[-2] if len(parts) > 2 else parts[0]
param_name = parts[-1]
param_list.append({
'type': param_type,
'name': param_name
})
functions.append({
'name': func_name,
'return_type': return_type,
'params': param_list
})
return functions
def _parse_properties(self, content: str, class_name: str) -> List[Dict]:
"""解析UPROPERTY"""
properties = []
# 匹配UPROPERTY声明
prop_pattern = r'UPROPERTY\s*\([^)]*\)\s*\n\s*(\w+(?:<[^>]*>)?)\s+(\w+);'
matches = re.findall(prop_pattern, content)
for prop_type, prop_name in matches:
properties.append({
'name': prop_name,
'type': prop_type
})
return properties
def _parse_struct_properties(self, content: str, struct_name: str) -> List[Dict]:
"""解析结构体属性"""
properties = []
# 在结构体定义范围内查找属性
struct_start = content.find(f'struct {struct_name}')
if struct_start == -1:
return properties
# 找到结构体结束位置
brace_count = 0
struct_content = ""
for i in range(struct_start, len(content)):
char = content[i]
if char == '{':
brace_count += 1
elif char == '}':
brace_count -= 1
if brace_count == 0:
struct_content = content[struct_start:i+1]
break
if not struct_content:
return properties
# 在结构体内容中查找UPROPERTY
prop_pattern = r'UPROPERTY\s*\([^)]*\)\s*\n\s*(\w+(?:<[^>]*>)?)\s+(\w+);'
matches = re.findall(prop_pattern, struct_content)
for prop_type, prop_name in matches:
properties.append({
'name': prop_name,
'type': prop_type
})
return properties
def _parse_enum_values(self, content: str, enum_name: str) -> List[Dict]:
"""解析枚举值"""
values = []
# 找到枚举定义
enum_start = content.find(f'enum class {enum_name}')
if enum_start == -1:
return values
# 找到枚举内容
brace_start = content.find('{', enum_start)
if brace_start == -1:
return values
brace_end = content.find('}', brace_start)
if brace_end == -1:
return values
enum_content = content[brace_start+1:brace_end]
# 解析枚举值
value_pattern = r'(\w+)\s*(?:=\s*(\d+))?'
matches = re.findall(value_pattern, enum_content)
current_value = 0
for name, explicit_value in matches:
if explicit_value:
current_value = int(explicit_value)
values.append({
'name': name,
'value': current_value
})
current_value += 1
return values
def _parse_delegate_params(self, content: str, delegate_name: str) -> List[Dict]:
"""解析委托参数"""
params = []
# 找到委托声明
delegate_pattern = f'DECLARE_DYNAMIC_(MULTICAST_)?DELEGATE(?:_\\w+)?\\s*\\(\\s*{delegate_name}'
match = re.search(delegate_pattern, content)
if not match:
return params
# 查找参数列表
param_start = content.find('(', match.end())
if param_start == -1:
return params
param_end = content.find(')', param_start)
if param_end == -1:
return params
param_content = content[param_start+1:param_end]
# 解析参数
param_parts = [p.strip() for p in param_content.split(',') if p.strip()]
for i, param in enumerate(param_parts):
parts = param.split()
if len(parts) >= 2:
param_type = ' '.join(parts[:-1])
param_name = parts[-1]
params.append({
'type': param_type,
'name': param_name
})
return params
def generate_emmy_lua_annotations(self, parsed_data: Dict) -> str:
"""生成emmy-lua注解"""
output = []
# 文件头注释
output.append(f'-- 自动生成的emmy-lua注解文件')
output.append(f'-- 源文件: {parsed_data["file_path"]}')
output.append('')
# 生成枚举注解
for enum in parsed_data['enums']:
output.append(f'---@enum {enum["name"]}')
output.append(f'local {enum["name"]} = {{')
for value in enum['values']:
output.append(f' {value["name"]} = {value["value"]},')
output.append('}')
output.append('')
# 生成结构体注解
for struct in parsed_data['structs']:
output.append(f'---@class {struct["name"]}')
for prop in struct['properties']:
lua_type = self._cpp_to_lua_type(prop['type'])
output.append(f'---@field {prop["name"]} {lua_type}')
output.append(f'local {struct["name"]} = {{}}')
output.append('')
# 生成类注解
for cls in parsed_data['classes']:
output.append(f'---@class {cls["name"]} : {cls["parent"]}')
# 添加属性
for prop in cls['properties']:
lua_type = self._cpp_to_lua_type(prop['type'])
output.append(f'---@field {prop["name"]} {lua_type}')
# 添加方法
for func in cls['functions']:
lua_return_type = self._cpp_to_lua_type(func['return_type'])
param_annotations = []
for param in func['params']:
lua_param_type = self._cpp_to_lua_type(param['type'])
param_annotations.append(f'---@param {param["name"]} {lua_param_type}')
if param_annotations:
output.extend(param_annotations)
output.append(f'---@return {lua_return_type}')
output.append(f'function {cls["name"]}:{func["name"]}({", ".join(p["name"] for p in func["params"])}) end')
output.append('')
output.append('')
# 生成委托注解
for delegate in parsed_data['delegates']:
output.append(f'---@class {delegate["name"]}')
param_types = []
for param in delegate['params']:
lua_type = self._cpp_to_lua_type(param['type'])
param_types.append(f'{param["name"]}: {lua_type}')
if param_types:
output.append(f'---@field Call fun({", ".join(param_types)})')
else:
output.append('---@field Call fun()')
output.append(f'local {delegate["name"]} = {{}}')
output.append('')
return '\n'.join(output)
def _cpp_to_lua_type(self, cpp_type: str) -> str:
"""将C++类型转换为Lua类型"""
type_mapping = {
'int32': 'integer',
'int64': 'integer',
'float': 'number',
'double': 'number',
'bool': 'boolean',
'FString': 'string',
'FText': 'string',
'FName': 'string',
'void': 'nil',
'TArray': 'table',
'TMap': 'table',
'TSet': 'table'
}
# 处理模板类型
if '<' in cpp_type and '>' in cpp_type:
base_type = cpp_type.split('<')[0]
inner_type = cpp_type.split('<')[1].split('>')[0]
lua_inner_type = self._cpp_to_lua_type(inner_type)
return f'{type_mapping.get(base_type, "any")}<{lua_inner_type}>'
return type_mapping.get(cpp_type, 'any')
def scan_directory(self, directory: str, output_dir: str = None):
"""扫描目录或单个文件并生成注解文件"""
header_files = []
if os.path.isfile(directory) and directory.endswith('.h'):
# 单个文件
header_files = [directory]
elif os.path.isdir(directory):
# 目录
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.h'):
header_files.append(os.path.join(root, file))
else:
print(f'错误: {directory} 不是有效的文件或目录')
return
print(f'找到 {len(header_files)} 个头文件')
for header_file in header_files:
try:
print(f'正在解析: {header_file}')
parsed_data = self.parse_header_file(header_file)
if any([parsed_data['classes'], parsed_data['structs'], parsed_data['enums'], parsed_data['delegates']]):
annotations = self.generate_emmy_lua_annotations(parsed_data)
# 确定输出文件路径
if output_dir:
if os.path.isfile(directory):
# 单个文件的情况
output_file = os.path.join(output_dir, os.path.basename(header_file).replace('.h', '.d.lua'))
else:
# 目录的情况
relative_path = os.path.relpath(header_file, directory)
output_file = os.path.join(output_dir, relative_path.replace('.h', '.d.lua'))
os.makedirs(os.path.dirname(output_file), exist_ok=True)
else:
output_file = header_file.replace('.h', '.d.lua')
with open(output_file, 'w', encoding='utf-8') as f:
f.write(annotations)
print(f'已生成: {output_file}')
except Exception as e:
print(f'解析文件 {header_file} 时出错: {e}')
def main():
parser = argparse.ArgumentParser(description='UE头文件解析工具 - 生成slua插件的emmy-lua注解')
parser.add_argument('directory', help='要扫描的目录路径')
parser.add_argument('-o', '--output', help='输出目录路径(默认为源文件同目录)')
parser.add_argument('--recursive', action='store_true', help='递归扫描子目录')
args = parser.parse_args()
if not os.path.exists(args.directory):
print(f'错误: 目录 {args.directory} 不存在')
return
parser = UEHeaderParser()
parser.scan_directory(args.directory, args.output)
if __name__ == '__main__':
main()