#!/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()