1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4# Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd. 5# 6# HDF is dual licensed: you can use it either under the terms of 7# the GPL, or the BSD license, at your option. 8# See the LICENSE file in the root of this repository for complete details. 9 10import argparse 11import os 12import re 13import stat 14 15import pip 16 17 18def lost_module(module_name): 19 print(""" 20need %s module, try install first: 21 22 pip install %s""" % (module_name, module_name)) 23 exit() 24 25 26class IDLGenerator: 27 def __init__(self): 28 self._idl = [] 29 self._output_path = "" 30 self._file_list = [] 31 self._key_list = {} 32 self._typedef_list = {} 33 self._parse_results = {} 34 35 def generate(self): 36 try: 37 self._parse_option() 38 self._parse_header() 39 for i in self._parse_results: 40 self._generate_type(self._parse_results[i]) 41 self._generate_interface(self._parse_results[i]) 42 except KeyError: 43 pass 44 finally: 45 pass 46 47 def _parse_option(self): 48 parser = argparse.ArgumentParser(description="Compile C/C++ header files and generate .idl files.") 49 parser.add_argument("-v", "--version", help="Display version information", action="store_true") 50 parser.add_argument("-f", "--file", metavar="<*.h>", required=True, action='append', 51 help="Compile the C/C++ header file") 52 parser.add_argument("-o", "--out", metavar="<directory>", default=".", required=True, 53 help="Place generated .idl files into the <directory>") 54 args = parser.parse_args() 55 56 self._file_list = args.file 57 self._output_path = args.out 58 59 def _search_file(self, root_path, file_name): 60 file_list = os.listdir(root_path) 61 for file in file_list: 62 path = os.path.join(root_path, file) 63 if os.path.isdir(path): 64 file_path = self._search_file(path, file_name) 65 if file_path is not None: 66 return file_path 67 if os.path.isfile(path): 68 if os.path.split(path)[1] == file_name: 69 return path 70 71 def _parse_include_file(self, file_list, root_path): 72 include_file = [] 73 for header_file in file_list: 74 result = HeaderParser().parse(header_file) 75 if result is None: 76 continue 77 self._parse_results[result.get("name")] = result 78 for file_name in result.get("import", None): # 把include的文件加入列表 79 if file_name in self._parse_results and file_name is not None: # 解析过的不重复解析 80 continue 81 file_path = self._search_file(root_path, file_name) 82 if file_path is not None: 83 include_file.append(file_path) 84 return include_file 85 86 def _parse_header(self): 87 try: 88 for file in self._file_list: 89 root_path, _ = os.path.split(file) # 输入文件所在目录 90 file_list = [file] # 需要解析的头文件列表,包括include包含的头文件 91 while len(file_list) > 0: 92 file_list = self._parse_include_file(file_list, root_path) 93 94 for i in self._parse_results: 95 for enum in self._parse_results[i]["enum"]: 96 self._key_list[enum["name"]] = "enum" 97 for union in self._parse_results[i]["union"]: 98 self._key_list[union["name"]] = "union" 99 for struct in self._parse_results[i]["struct"]: 100 self._key_list[struct["name"]] = "struct" 101 for clas in self._parse_results[i]["interface"]: 102 self._key_list[clas["name"]] = "" 103 for cb in self._parse_results[i]["callback"]: 104 self._key_list[cb["name"]] = "" 105 for td in self._parse_results[i]["typedef"]: 106 self._typedef_list[td["name"]] = td["type"] 107 except KeyError: 108 pass 109 finally: 110 pass 111 112 def _generate_type(self, header): 113 if self._has_user_define_type(header): 114 self._install_package(header["path"]) 115 self._install_import(header) 116 self._install_enum(header["enum"]) 117 self._install_stack(header["union"]) 118 self._install_stack(header["struct"]) 119 120 self._write_file(header["path"], "Types.idl") 121 122 def _generate_interface(self, header): 123 for iface in header["interface"]: 124 self._install_package(header["path"]) 125 self._install_import(header) 126 self._install_interface(iface) 127 128 self._write_file(header["path"], iface["name"] + ".idl") 129 130 def _install_package(self, file_path): 131 self._idl += "package " + self._get_package(file_path) + ";\n\n" 132 133 def _install_import(self, header): 134 try: 135 original_idl = self._idl 136 for file_name in header["import"]: 137 if file_name not in self._parse_results: 138 self._idl.append("// can't import %s\n" % file_name) 139 print("[IDLGenerator]: %s[line ?] can't find %s" 140 % (os.path.normpath(header["path"] + "/" + header["name"]), file_name)) 141 continue 142 include_file = self._parse_results[file_name] 143 if self._has_user_define_type(include_file): 144 tt = re.search("import\\s+\\S+.Types", "".join(self._idl)) 145 if tt is None: 146 self._idl.append("import %s.Types;\n" % self._get_package(include_file['path'])) 147 for iface in include_file["interface"]: 148 self._idl.append("import %s.%s;\n" % (self._get_package(include_file['path']), iface["name"])) 149 # self._idl += 150 for cb in include_file["callback"]: 151 self._idl.append("import %s.%s;\n" % (self._get_package(include_file['path']), cb["name"])) 152 for cb in header['callback']: 153 self._idl.append("import %s.%s;\n" % (self._get_package(header['path']), cb["name"])) 154 155 if original_idl != "".join(self._idl): 156 self._idl.append("\n") 157 except KeyError: 158 pass 159 finally: 160 pass 161 162 def _install_enum(self, enums): 163 for enum in enums: 164 self._idl.append("enum %s {\n" % enum["name"]) 165 for member in enum["members"]: 166 self._idl.append(" %s = %s,\n" % (member["name"], member["value"])) 167 self._idl.append("};\n") 168 169 def _install_stack(self, stacks): 170 for stack in stacks: 171 self._idl.append(stack["type"] + " %s {\n" % stack["name"]) 172 for member in stack["members"]: 173 param_type = self._swap_type_c2idl(member["type"]) 174 if "unknown type" in param_type: 175 print("[IDLGenerator]: %s[line %d] %s" % ( 176 os.path.normpath(member["file_name"]), member["line_number"], param_type)) 177 self._idl.append(" %s %s;\n" % (param_type, member["name"])) 178 self._idl.append("};\n") 179 180 def _install_interface(self, iface): 181 if re.search("[Cc]all[Bb]ack", iface["name"]): 182 self._idl.append("[callback] ") 183 self._idl.append("interface %s {\n" % iface["name"]) 184 for member in iface["members"]: 185 self._install_function(iface["name"], member) 186 self._idl.append("}\n") 187 188 def _install_function(self, iface_name, member): 189 self._idl.append(" %s(" % member["name"]) 190 for i, param in enumerate(member["params"]): 191 tt = re.fullmatch(r"(enum)*(union)*(struct)* *%s *\** * *\**" % iface_name, param["type"]) 192 if tt: 193 continue 194 param_type = self._swap_type_c2idl(param["type"]) 195 if "unknown type" in param_type: 196 print("[IDLGenerator]: %s[line %d] %s" % ( 197 os.path.normpath(member["file_name"]), member["line_number"], param_type)) 198 self._idl.append("%s %s %s," % ( 199 '* *' in param["type"] and "[out]" or "[in]", 200 param_type, 201 param["name"])) 202 if "".join(self._idl).endswith(','): 203 self._idl = ["".join(self._idl)[:-1], ");\n"] 204 else: 205 self._idl.append(");\n") 206 207 @staticmethod 208 def _convert_basic_type(c_type): 209 type_c2idl = [ 210 [["bool"], "boolean"], 211 [["int8_t", "char"], "byte"], 212 [["int16_t"], "short"], 213 [["int32_t", "int"], "int"], 214 [["int64_t"], "long"], 215 [["float"], "float"], 216 [["double"], "double"], 217 [["std::string", "string", "cstring"], "String"], 218 [["uint8_t", "unsigned char"], "unsigned char"], 219 [["uint16_t", "unsigned short"], "unsigned short"], 220 [["uint32_t", "unsigned int"], "unsigned int"], 221 [["uint64_t", "unsigned long"], "unsigned long"], 222 [["void"], "void"] 223 ] 224 for cti in type_c2idl: 225 for ct in cti[0]: 226 tt = re.match(r"(const )* *%s *(\**) *" % ct, c_type) 227 if not tt: 228 continue 229 idl_type = cti[1] 230 if c_type.count('*') == 1: 231 idl_type += '[]' 232 return idl_type 233 return "" 234 235 def _convert_structure(self, c_type): 236 for type_name in self._key_list: 237 if "_ENUM_POINTER" in c_type: 238 c_type = c_type.replace("_ENUM_POINTER", " * ") 239 tt = re.fullmatch(r"(const )* *(enum)*(union)*(struct)* *%s *[*&]* * *[*&]*" % type_name, c_type) 240 if tt: 241 if len(self._key_list.get(type_name)) > 0: 242 idl_type = self._key_list.get(type_name) + ' ' + type_name 243 else: 244 idl_type = type_name 245 if c_type.count('*') == 1: 246 idl_type += '[]' 247 return idl_type 248 return "" 249 250 def _convert_typedef(self, c_type): 251 for type_name in self._typedef_list: 252 tt = re.match(r"(const )* *%s *\** *" % type_name, c_type) 253 if tt: 254 if self._typedef_list.get(type_name).count('*') == 1: 255 idl_type = self._typedef_list.get(type_name).split(' ')[0] + '[]' 256 else: 257 idl_type = self._typedef_list.get(type_name) 258 return idl_type 259 return "" 260 261 def _convert_container_type(self, c_type): 262 type_pattern = " *(std::)*(struct *)*((unsigned *)*\\w+) *" 263 tt = re.match("(const *)* *(std::)*map *<%s, %s>" % (type_pattern, type_pattern), c_type) 264 if tt: 265 key_type = self._convert_basic_type(tt[5]) 266 value_type = self._convert_basic_type(tt[9]) 267 return "Map<%s, %s>" % (key_type == "" and tt[5] or key_type, value_type == "" and tt[9] or value_type) 268 269 tt = re.match("(const *)* *(std::)*vector *<%s>" % type_pattern, c_type) 270 if tt: 271 v_type = self._convert_basic_type(tt[5]) 272 return "List<%s>" % (v_type == "" and tt[5] or v_type) 273 return "" 274 275 def _swap_type_c2idl(self, c_type): 276 idl_type = self._convert_basic_type(c_type) 277 if idl_type != "": 278 return idl_type 279 280 idl_type = self._convert_structure(c_type) 281 if idl_type != "": 282 return idl_type 283 284 idl_type = self._convert_typedef(c_type) 285 if idl_type != "": 286 return idl_type 287 288 idl_type = self._convert_container_type(c_type) 289 if idl_type != "": 290 return idl_type 291 292 idl_type = "/* unknown type: [%s] */" % c_type 293 return idl_type 294 295 def _write_file(self, file_path, file_name): 296 file = os.path.join(self._make_output_dir(file_path), file_name).replace('\\', '/') 297 flags = os.O_RDWR | os.O_CREAT 298 modes = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH 299 with os.fdopen(os.open(file, flags, modes), "w", encoding="utf-8") as fp: 300 fp.write("".join(self._idl)) 301 print("Generate: --------------------- %s ---------------------\n" % os.path.normpath(file)) 302 self._idl = [] 303 304 @staticmethod 305 def _split_path(file_path): 306 normpath = os.path.normpath(file_path) 307 drive, out_path = os.path.splitdrive(normpath) 308 out_path = out_path.replace('\\', '/') 309 new_path = out_path.strip('.').strip('/') 310 while new_path != out_path: 311 out_path = new_path 312 new_path = out_path.strip('.').strip('/') 313 314 return new_path 315 316 def _make_output_dir(self, file_path): 317 output_dir = self._output_path + '/' + self._split_path(file_path) 318 if not os.path.exists(output_dir): 319 os.makedirs(output_dir) 320 return output_dir 321 322 @staticmethod 323 def _has_user_define_type(h): 324 if len(h["enum"]) > 0 or len(h["union"]) > 0 or len(h["struct"]) > 0: 325 return True 326 return False 327 328 def _get_package(self, file_path): 329 path_name = re.split(r'[/\\]', self._split_path(file_path)) 330 out_path_temp = [] 331 for name in path_name: 332 out_path_temp.append(name) 333 return ".".join(out_path_temp) 334 335 336if __name__ == "__main__": 337 try: 338 import CppHeaderParser 339 from _header_parser import HeaderParser 340 except ImportError: 341 pip.main(["--disable-pip-version-check", "install", "robotpy-cppheaderparser"]) 342 try: 343 import CppHeaderParser 344 from _header_parser import HeaderParser 345 except ImportError: 346 HeaderParser = None 347 lost_module("robotpy-cppheaderparser") 348 finally: 349 pass 350 finally: 351 pass 352 generator = IDLGenerator() 353 generator.generate()