403 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			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() |