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