parse_tree.py 18 KB

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