联机版实现,先备份
This commit is contained in:
		
							
								
								
									
										402
									
								
								Tools/ue_header_parser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										402
									
								
								Tools/ue_header_parser.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,402 @@ | ||||
| #!/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() | ||||
		Reference in New Issue
	
	Block a user