1#!/usr/bin/env python 2 3# This file is part of OpenCV project. 4# It is subject to the license terms in the LICENSE file found in the top-level directory 5# of this distribution and at http://opencv.org/license.html 6# Copyright (C) 2020 by Archit Rungta 7 8 9import hdr_parser, sys, re, os 10from string import Template 11from pprint import pprint 12from collections import namedtuple 13import json 14import os, shutil 15from io import StringIO 16 17 18forbidden_arg_types = ["void*"] 19 20ignored_arg_types = ["RNG*"] 21 22pass_by_val_types = ["Point*", "Point2f*", "Rect*", "String*", "double*", "float*", "int*"] 23 24 25def get_char(c): 26 if c.isalpha(): 27 return c 28 if ord(c)%52 < 26: 29 return chr(ord('a')+ord(c)%26) 30 return chr(ord('A')+ord(c)%26) 31 32 33def get_var(inp): 34 out = '' 35 for c in inp: 36 out = out+get_char(c) 37 return out 38 39def normalize_name(name): 40 return name.replace('.', '::') 41 42def normalize_class_name(name): 43 _, classes, name = split_decl_name(normalize_name(name)) 44 return "_".join(classes+[name]) 45 46def normalize_full_name(name): 47 ns, classes, name = split_decl_name(normalize_name(name)) 48 return "::".join(ns)+'::'+'_'.join(classes+[name]) 49 50 51 52def split_decl_name(name): 53 chunks = name.split('::') 54 namespace = chunks[:-1] 55 classes = [] 56 while namespace and '::'.join(namespace) not in namespaces: 57 classes.insert(0, namespace.pop()) 58 59 ns = '::'.join(namespace) 60 if ns not in namespaces and ns: 61 assert(0) 62 63 return namespace, classes, chunks[-1] 64 65 66def handle_cpp_arg(inp): 67 def handle_vector(match): 68 return handle_cpp_arg("%svector<%s>" % (match.group(1), match.group(2))) 69 def handle_ptr(match): 70 return handle_cpp_arg("%sPtr<%s>" % (match.group(1), match.group(2))) 71 inp = re.sub("(.*)vector_(.*)", handle_vector, inp) 72 inp = re.sub("(.*)Ptr_(.*)", handle_ptr, inp) 73 74 75 return inp.replace("String", "string") 76 77def get_template_arg(inp): 78 inp = inp.replace(' ','').replace('*', '').replace('cv::', '').replace('std::', '') 79 def handle_vector(match): 80 return get_template_arg("%s" % (match.group(1))) 81 def handle_ptr(match): 82 return get_template_arg("%s" % (match.group(1))) 83 inp = re.sub("vector<(.*)>", handle_vector, inp) 84 inp = re.sub("Ptr<(.*)>", handle_ptr, inp) 85 ns, cl, n = split_decl_name(inp) 86 inp = "::".join(cl+[n]) 87 # print(inp) 88 return inp.replace("String", "string") 89 90def registered_tp_search(tp): 91 found = False 92 if not tp: 93 return True 94 for tpx in registered_types: 95 if re.findall(tpx, tp): 96 found = True 97 break 98 return found 99 100namespaces = {} 101type_paths = {} 102enums = {} 103classes = {} 104functions = {} 105registered_types = ["int", "Size.*", "Rect.*", "Scalar", "RotatedRect", "Point.*", "explicit", "string", "bool", "uchar", 106 "Vec.*", "float", "double", "char", "Mat", "size_t", "RNG", "DescriptorExtractor", "FeatureDetector", "TermCriteria"] 107 108class ClassProp(object): 109 """ 110 Helper class to store field information(type, name and flags) of classes and structs 111 """ 112 def __init__(self, decl): 113 self.tp = decl[0] 114 self.name = decl[1] 115 self.readonly = True 116 if "/RW" in decl[3]: 117 self.readonly = False 118 119class ClassInfo(object): 120 def __init__(self, name, decl=None): 121 self.name = name 122 self.mapped_name = normalize_class_name(name) 123 self.ismap = False #CV_EXPORTS_W_MAP 124 self.isalgorithm = False #if class inherits from cv::Algorithm 125 self.methods = {} #Dictionary of methods 126 self.props = [] #Collection of ClassProp associated with this class 127 self.base = None #name of base class if current class inherits another class 128 self.constructors = [] #Array of constructors for this class 129 self.add_decl(decl) 130 classes[name] = self 131 132 def add_decl(self, decl): 133 if decl: 134 # print(decl) 135 bases = decl[1].split(',') 136 if len(bases[0].split()) > 1: 137 bases[0] = bases[0].split()[1] 138 139 bases = [x.replace(' ','') for x in bases] 140 # print(bases) 141 if len(bases) > 1: 142 # Clear the set a bit 143 bases = list(set(bases)) 144 bases.remove('cv::class') 145 bases_clear = [] 146 for bb in bases: 147 if self.name not in bb: 148 bases_clear.append(bb) 149 bases = bases_clear 150 if len(bases) > 1: 151 print("Note: Class %s has more than 1 base class (not supported by CxxWrap)" % (self.name,)) 152 print(" Bases: ", " ".join(bases)) 153 print(" Only the first base class will be used") 154 if len(bases) >= 1: 155 self.base = bases[0].replace('.', '::') 156 if "cv::Algorithm" in bases: 157 self.isalgorithm = True 158 159 for m in decl[2]: 160 if m.startswith("="): 161 self.mapped_name = m[1:] 162 # if m == "/Map": 163 # self.ismap = True 164 self.props = [ClassProp(p) for p in decl[3]] 165 # return code for functions and setters and getters if simple class or functions and map type 166 167 def get_prop_func_cpp(self, mode, propname): 168 return "jlopencv_" + self.mapped_name + "_"+mode+"_"+propname 169 170argumentst = [] 171default_values = [] 172class ArgInfo(object): 173 """ 174 Helper class to parse and contain information about function arguments 175 """ 176 177 def sec(self, arg_tuple): 178 self.isbig = arg_tuple[0] in ["Mat", "vector_Mat", "cuda::GpuMat", "GpuMat", "vector_GpuMat", "UMat", "vector_UMat"] # or self.tp.startswith("vector") 179 180 self.tp = handle_cpp_arg(arg_tuple[0]) #C++ Type of argument 181 argumentst.append(self.tp) 182 self.name = arg_tuple[1] #Name of argument 183 # TODO: Handle default values nicely 184 self.default_value = arg_tuple[2] #Default value 185 self.inputarg = True #Input argument 186 self.outputarg = False #output argument 187 self.ref = False 188 189 for m in arg_tuple[3]: 190 if m == "/O": 191 self.inputarg = False 192 self.outputarg = True 193 elif m == "/IO": 194 self.inputarg = True 195 self.outputarg = True 196 elif m == '/Ref': 197 self.ref = True 198 199 if self.tp in pass_by_val_types: 200 self.outputarg = True 201 202 203 204 def __init__(self, name, tp = None): 205 if not tp: 206 self.sec(name) 207 else: 208 self.name = name 209 self.tp = tp 210 211 212class FuncVariant(object): 213 """ 214 Helper class to parse and contain information about different overloaded versions of same function 215 """ 216 def __init__(self, classname, name, mapped_name, decl, namespace, istatic=False): 217 self.classname = classname 218 self.name = name 219 self.mapped_name = mapped_name 220 221 self.isconstructor = name.split('::')[-1]==classname.split('::')[-1] 222 self.isstatic = istatic 223 self.namespace = namespace 224 225 self.rettype = decl[4] 226 if self.rettype == "void" or not self.rettype: 227 self.rettype = "" 228 else: 229 self.rettype = handle_cpp_arg(self.rettype) 230 231 self.args = [] 232 233 for ainfo in decl[3]: 234 a = ArgInfo(ainfo) 235 if a.default_value and ('(' in a.default_value or ':' in a.default_value): 236 default_values.append(a.default_value) 237 assert not a.tp in forbidden_arg_types, 'Forbidden type "{}" for argument "{}" in "{}" ("{}")'.format(a.tp, a.name, self.name, self.classname) 238 if a.tp in ignored_arg_types: 239 continue 240 241 self.args.append(a) 242 self.init_proto() 243 244 if name not in functions: 245 functions[name]= [] 246 functions[name].append(self) 247 248 if not registered_tp_search(get_template_arg(self.rettype)): 249 namespaces[namespace].register_types.append(get_template_arg(self.rettype)) 250 for arg in self.args: 251 if not registered_tp_search(get_template_arg(arg.tp)): 252 namespaces[namespace].register_types.append(get_template_arg(arg.tp)) 253 254 255 def get_wrapper_name(self): 256 """ 257 Return wrapping function name 258 """ 259 name = self.name.replace('::', '_') 260 if self.classname: 261 classname = self.classname.replace('::', '_') + "_" 262 else: 263 classname = "" 264 return "jlopencv_" + self.namespace.replace('::','_') + '_' + classname + name 265 266 267 def init_proto(self): 268 # string representation of argument list, with '[', ']' symbols denoting optional arguments, e.g. 269 # "src1, src2[, dst[, mask]]" for cv.add 270 prototype = "" 271 272 inlist = [] 273 optlist = [] 274 outlist = [] 275 deflist = [] 276 biglist = [] 277 278# This logic can almost definitely be simplified 279 280 for a in self.args: 281 if a.isbig and not (a.inputarg and not a.default_value): 282 optlist.append(a) 283 if a.outputarg: 284 outlist.append(a) 285 if a.inputarg and not a.default_value: 286 inlist.append(a) 287 elif a.inputarg and a.default_value and not a.isbig: 288 optlist.append(a) 289 elif not (a.isbig and not (a.inputarg and not a.default_value)): 290 deflist.append(a) 291 292 if self.rettype: 293 outlist = [ArgInfo("retval", self.rettype)] + outlist 294 295 if self.isconstructor: 296 assert outlist == [] or outlist[0].tp == "explicit" 297 outlist = [ArgInfo("retval", self.classname)] 298 299 300 self.outlist = outlist 301 self.optlist = optlist 302 self.deflist = deflist 303 304 self.inlist = inlist 305 306 self.prototype = prototype 307 308class NameSpaceInfo(object): 309 def __init__(self, name): 310 self.funcs = {} 311 self.classes = {} #Dictionary of classname : ClassInfo objects 312 self.enums = {} 313 self.consts = {} 314 self.register_types = [] 315 self.name = name 316 317def add_func(decl): 318 """ 319 Creates functions based on declaration and add to appropriate classes and/or namespaces 320 """ 321 decl[0] = decl[0].replace('.', '::') 322 namespace, classes, barename = split_decl_name(decl[0]) 323 name = "::".join(namespace+classes+[barename]) 324 full_classname = "::".join(namespace + classes) 325 classname = "::".join(classes) 326 namespace = '::'.join(namespace) 327 is_static = False 328 isphantom = False 329 mapped_name = '' 330 331 for m in decl[2]: 332 if m == "/S": 333 is_static = True 334 elif m == "/phantom": 335 print("phantom not supported yet ") 336 return 337 elif m.startswith("="): 338 mapped_name = m[1:] 339 elif m.startswith("/mappable="): 340 print("Mappable not supported yet") 341 return 342 # if m == "/V": 343 # print("skipping ", name) 344 # return 345 346 if classname and full_classname not in namespaces[namespace].classes: 347 # print("HH1") 348 # print(namespace, classname) 349 namespaces[namespace].classes[full_classname] = ClassInfo(full_classname) 350 assert(0) 351 352 353 if is_static: 354 # Add it as global function 355 func_map = namespaces[namespace].funcs 356 if name not in func_map: 357 func_map[name] = [] 358 if not mapped_name: 359 mapped_name = "_".join(classes + [barename]) 360 func_map[name].append(FuncVariant("", name, mapped_name, decl, namespace, True)) 361 else: 362 if classname: 363 func = FuncVariant(full_classname, name, barename, decl, namespace, False) 364 if func.isconstructor: 365 namespaces[namespace].classes[full_classname].constructors.append(func) 366 else: 367 func_map = namespaces[namespace].classes[full_classname].methods 368 if name not in func_map: 369 func_map[name] = [] 370 func_map[name].append(func) 371 else: 372 func_map = namespaces[namespace].funcs 373 if name not in func_map: 374 func_map[name] = [] 375 if not mapped_name: 376 mapped_name = barename 377 func_map[name].append(FuncVariant("", name, mapped_name, decl, namespace, False)) 378 379 380def add_class(stype, name, decl): 381 """ 382 Creates class based on name and declaration. Add it to list of classes and to JSON file 383 """ 384 # print("n", name) 385 name = name.replace('.', '::') 386 classinfo = ClassInfo(name, decl) 387 namespace, classes, barename = split_decl_name(name) 388 namespace = '::'.join(namespace) 389 390 if classinfo.name in classes: 391 namespaces[namespace].classes[name].add_decl(decl) 392 else: 393 namespaces[namespace].classes[name] = classinfo 394 395 396 397def add_const(name, decl, tp = ''): 398 name = name.replace('.','::') 399 namespace, classes, barename = split_decl_name(name) 400 namespace = '::'.join(namespace) 401 mapped_name = '_'.join(classes+[barename]) 402 ns = namespaces[namespace] 403 if mapped_name in ns.consts: 404 print("Generator error: constant %s (name=%s) already exists" \ 405 % (name, name)) 406 sys.exit(-1) 407 ns.consts[name] = mapped_name 408 409def add_enum(name, decl): 410 name = name.replace('.', '::') 411 mapped_name = normalize_class_name(name) 412 # print(name) 413 if mapped_name.endswith("<unnamed>"): 414 mapped_name = None 415 else: 416 enums[name.replace(".", "::")] = mapped_name 417 const_decls = decl[3] 418 419 if mapped_name: 420 namespace, classes, name2 = split_decl_name(name) 421 namespace = '::'.join(namespace) 422 mapped_name = '_'.join(classes+[name2]) 423 # print(mapped_name) 424 namespaces[namespace].enums[name] = (name.replace(".", "::"),mapped_name) 425 426 for decl in const_decls: 427 name = decl[0] 428 add_const(name.replace("const ", "", ).strip(), decl, "int") 429 430 431 432def gen_tree(srcfiles): 433 parser = hdr_parser.CppHeaderParser(generate_umat_decls=False, generate_gpumat_decls=False) 434 435 allowed_func_list = [] 436 437 with open("funclist.csv", "r") as f: 438 allowed_func_list = f.readlines() 439 allowed_func_list = [x[:-1] for x in allowed_func_list] 440 441 442 count = 0 443 # step 1: scan the headers and build more descriptive maps of classes, consts, functions 444 for hdr in srcfiles: 445 decls = parser.parse(hdr) 446 for ns in parser.namespaces: 447 ns = ns.replace('.', '::') 448 if ns not in namespaces: 449 namespaces[ns] = NameSpaceInfo(ns) 450 count += len(decls) 451 if len(decls) == 0: 452 continue 453 if hdr.find('opencv2/') >= 0: #Avoid including the shadow files 454 # code_include.write( '#include "{0}"\n'.format(hdr[hdr.rindex('opencv2/'):]) ) 455 pass 456 for decl in decls: 457 name = decl[0] 458 if name.startswith("struct") or name.startswith("class"): 459 # class/struct 460 p = name.find(" ") 461 stype = name[:p] 462 name = name[p+1:].strip() 463 add_class(stype, name, decl) 464 elif name.startswith("const"): 465 # constant 466 assert(0) 467 add_const(name.replace("const ", "").strip(), decl) 468 elif name.startswith("enum"): 469 # enum 470 add_enum(name.rsplit(" ", 1)[1], decl) 471 else: 472 # function 473 if decl[0] in allowed_func_list: 474 add_func(decl) 475 # step 1.5 check if all base classes exist 476 # print(classes) 477 for name, classinfo in classes.items(): 478 if classinfo.base: 479 base = classinfo.base 480 # print(base) 481 if base not in classes: 482 print("Generator error: unable to resolve base %s for %s" 483 % (classinfo.base, classinfo.name)) 484 sys.exit(-1) 485 base_instance = classes[base] 486 classinfo.base = base 487 classinfo.isalgorithm |= base_instance.isalgorithm # wrong processing of 'isalgorithm' flag: 488 # doesn't work for trees(graphs) with depth > 2 489 classes[name] = classinfo 490 491 # tree-based propagation of 'isalgorithm' 492 processed = dict() 493 def process_isalgorithm(classinfo): 494 if classinfo.isalgorithm or classinfo in processed: 495 return classinfo.isalgorithm 496 res = False 497 if classinfo.base: 498 res = process_isalgorithm(classes[classinfo.base]) 499 #assert not (res == True or classinfo.isalgorithm is False), "Internal error: " + classinfo.name + " => " + classinfo.base 500 classinfo.isalgorithm |= res 501 res = classinfo.isalgorithm 502 processed[classinfo] = True 503 return res 504 for name, classinfo in classes.items(): 505 process_isalgorithm(classinfo) 506 507 for name, ns in namespaces.items(): 508 if name.split('.')[-1] == '': 509 continue 510 ns.registered = [] 511 for name, cl in ns.classes.items(): 512 registered_types.append(get_template_arg(name)) 513 ns.registered.append(cl.mapped_name) 514 nss, clss, bs = split_decl_name(name) 515 type_paths[bs] = [name.replace("::", ".")] 516 type_paths["::".join(clss+[bs])] = [name.replace("::", ".")] 517 518 519 for e1,e2 in ns.enums.items(): 520 registered_types.append(get_template_arg(e2[0])) 521 registered_types.append(get_template_arg(e2[0]).replace('::', '_')) #whyyy typedef 522 ns.registered.append(e2[1]) 523 524 ns.register_types = list(set(ns.register_types)) 525 ns.register_types = [tp for tp in ns.register_types if not registered_tp_search(tp) and not tp in ns.registered] 526 for tp in ns.register_types: 527 registered_types.append(get_template_arg(tp)) 528 ns.registered.append(get_template_arg(tp)) 529 default_valuesr = list(set(default_values)) 530 # registered_types = registered_types + ns.register_types 531 return namespaces, default_valuesr 532