You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
531 lines
18 KiB
531 lines
18 KiB
#!/usr/bin/env python |
|
|
|
# This file is part of OpenCV project. |
|
# It is subject to the license terms in the LICENSE file found in the top-level directory |
|
# of this distribution and at http://opencv.org/license.html |
|
# Copyright (C) 2020 by Archit Rungta |
|
|
|
|
|
import hdr_parser, sys, re, os |
|
from string import Template |
|
from pprint import pprint |
|
from collections import namedtuple |
|
import json |
|
import os, shutil |
|
from io import StringIO |
|
|
|
|
|
forbidden_arg_types = ["void*"] |
|
|
|
ignored_arg_types = ["RNG*"] |
|
|
|
pass_by_val_types = ["Point*", "Point2f*", "Rect*", "String*", "double*", "float*", "int*"] |
|
|
|
|
|
def get_char(c): |
|
if c.isalpha(): |
|
return c |
|
if ord(c)%52 < 26: |
|
return chr(ord('a')+ord(c)%26) |
|
return chr(ord('A')+ord(c)%26) |
|
|
|
|
|
def get_var(inp): |
|
out = '' |
|
for c in inp: |
|
out = out+get_char(c) |
|
return out |
|
|
|
def normalize_name(name): |
|
return name.replace('.', '::') |
|
|
|
def normalize_class_name(name): |
|
_, classes, name = split_decl_name(normalize_name(name)) |
|
return "_".join(classes+[name]) |
|
|
|
def normalize_full_name(name): |
|
ns, classes, name = split_decl_name(normalize_name(name)) |
|
return "::".join(ns)+'::'+'_'.join(classes+[name]) |
|
|
|
|
|
|
|
def split_decl_name(name): |
|
chunks = name.split('::') |
|
namespace = chunks[:-1] |
|
classes = [] |
|
while namespace and '::'.join(namespace) not in namespaces: |
|
classes.insert(0, namespace.pop()) |
|
|
|
ns = '::'.join(namespace) |
|
if ns not in namespaces and ns: |
|
assert(0) |
|
|
|
return namespace, classes, chunks[-1] |
|
|
|
|
|
def handle_cpp_arg(inp): |
|
def handle_vector(match): |
|
return handle_cpp_arg("%svector<%s>" % (match.group(1), match.group(2))) |
|
def handle_ptr(match): |
|
return handle_cpp_arg("%sPtr<%s>" % (match.group(1), match.group(2))) |
|
inp = re.sub("(.*)vector_(.*)", handle_vector, inp) |
|
inp = re.sub("(.*)Ptr_(.*)", handle_ptr, inp) |
|
|
|
|
|
return inp.replace("String", "string") |
|
|
|
def get_template_arg(inp): |
|
inp = inp.replace(' ','').replace('*', '').replace('cv::', '').replace('std::', '') |
|
def handle_vector(match): |
|
return get_template_arg("%s" % (match.group(1))) |
|
def handle_ptr(match): |
|
return get_template_arg("%s" % (match.group(1))) |
|
inp = re.sub("vector<(.*)>", handle_vector, inp) |
|
inp = re.sub("Ptr<(.*)>", handle_ptr, inp) |
|
ns, cl, n = split_decl_name(inp) |
|
inp = "::".join(cl+[n]) |
|
# print(inp) |
|
return inp.replace("String", "string") |
|
|
|
def registered_tp_search(tp): |
|
found = False |
|
if not tp: |
|
return True |
|
for tpx in registered_types: |
|
if re.findall(tpx, tp): |
|
found = True |
|
break |
|
return found |
|
|
|
namespaces = {} |
|
type_paths = {} |
|
enums = {} |
|
classes = {} |
|
functions = {} |
|
registered_types = ["int", "Size.*", "Rect.*", "Scalar", "RotatedRect", "Point.*", "explicit", "string", "bool", "uchar", |
|
"Vec.*", "float", "double", "char", "Mat", "size_t", "RNG", "DescriptorExtractor", "FeatureDetector", "TermCriteria"] |
|
|
|
class ClassProp(object): |
|
""" |
|
Helper class to store field information(type, name and flags) of classes and structs |
|
""" |
|
def __init__(self, decl): |
|
self.tp = decl[0] |
|
self.name = decl[1] |
|
self.readonly = True |
|
if "/RW" in decl[3]: |
|
self.readonly = False |
|
|
|
class ClassInfo(object): |
|
def __init__(self, name, decl=None): |
|
self.name = name |
|
self.mapped_name = normalize_class_name(name) |
|
self.ismap = False #CV_EXPORTS_W_MAP |
|
self.isalgorithm = False #if class inherits from cv::Algorithm |
|
self.methods = {} #Dictionary of methods |
|
self.props = [] #Collection of ClassProp associated with this class |
|
self.base = None #name of base class if current class inherits another class |
|
self.constructors = [] #Array of constructors for this class |
|
self.add_decl(decl) |
|
classes[name] = self |
|
|
|
def add_decl(self, decl): |
|
if decl: |
|
# print(decl) |
|
bases = decl[1].split(',') |
|
if len(bases[0].split()) > 1: |
|
bases[0] = bases[0].split()[1] |
|
|
|
bases = [x.replace(' ','') for x in bases] |
|
# print(bases) |
|
if len(bases) > 1: |
|
# Clear the set a bit |
|
bases = list(set(bases)) |
|
bases.remove('cv::class') |
|
bases_clear = [] |
|
for bb in bases: |
|
if self.name not in bb: |
|
bases_clear.append(bb) |
|
bases = bases_clear |
|
if len(bases) > 1: |
|
print("Note: Class %s has more than 1 base class (not supported by CxxWrap)" % (self.name,)) |
|
print(" Bases: ", " ".join(bases)) |
|
print(" Only the first base class will be used") |
|
if len(bases) >= 1: |
|
self.base = bases[0].replace('.', '::') |
|
if "cv::Algorithm" in bases: |
|
self.isalgorithm = True |
|
|
|
for m in decl[2]: |
|
if m.startswith("="): |
|
self.mapped_name = m[1:] |
|
# if m == "/Map": |
|
# self.ismap = True |
|
self.props = [ClassProp(p) for p in decl[3]] |
|
# return code for functions and setters and getters if simple class or functions and map type |
|
|
|
def get_prop_func_cpp(self, mode, propname): |
|
return "jlopencv_" + self.mapped_name + "_"+mode+"_"+propname |
|
|
|
argumentst = [] |
|
default_values = [] |
|
class ArgInfo(object): |
|
""" |
|
Helper class to parse and contain information about function arguments |
|
""" |
|
|
|
def sec(self, arg_tuple): |
|
self.isbig = arg_tuple[0] in ["Mat", "vector_Mat", "cuda::GpuMat", "GpuMat", "vector_GpuMat", "UMat", "vector_UMat"] # or self.tp.startswith("vector") |
|
|
|
self.tp = handle_cpp_arg(arg_tuple[0]) #C++ Type of argument |
|
argumentst.append(self.tp) |
|
self.name = arg_tuple[1] #Name of argument |
|
# TODO: Handle default values nicely |
|
self.default_value = arg_tuple[2] #Default value |
|
self.inputarg = True #Input argument |
|
self.outputarg = False #output argument |
|
self.ref = False |
|
|
|
for m in arg_tuple[3]: |
|
if m == "/O": |
|
self.inputarg = False |
|
self.outputarg = True |
|
elif m == "/IO": |
|
self.inputarg = True |
|
self.outputarg = True |
|
elif m == '/Ref': |
|
self.ref = True |
|
|
|
if self.tp in pass_by_val_types: |
|
self.outputarg = True |
|
|
|
|
|
|
|
def __init__(self, name, tp = None): |
|
if not tp: |
|
self.sec(name) |
|
else: |
|
self.name = name |
|
self.tp = tp |
|
|
|
|
|
class FuncVariant(object): |
|
""" |
|
Helper class to parse and contain information about different overloaded versions of same function |
|
""" |
|
def __init__(self, classname, name, mapped_name, decl, namespace, istatic=False): |
|
self.classname = classname |
|
self.name = name |
|
self.mapped_name = mapped_name |
|
|
|
self.isconstructor = name.split('::')[-1]==classname.split('::')[-1] |
|
self.isstatic = istatic |
|
self.namespace = namespace |
|
|
|
self.rettype = decl[4] |
|
if self.rettype == "void" or not self.rettype: |
|
self.rettype = "" |
|
else: |
|
self.rettype = handle_cpp_arg(self.rettype) |
|
|
|
self.args = [] |
|
|
|
for ainfo in decl[3]: |
|
a = ArgInfo(ainfo) |
|
if a.default_value and ('(' in a.default_value or ':' in a.default_value): |
|
default_values.append(a.default_value) |
|
assert not a.tp in forbidden_arg_types, 'Forbidden type "{}" for argument "{}" in "{}" ("{}")'.format(a.tp, a.name, self.name, self.classname) |
|
if a.tp in ignored_arg_types: |
|
continue |
|
|
|
self.args.append(a) |
|
self.init_proto() |
|
|
|
if name not in functions: |
|
functions[name]= [] |
|
functions[name].append(self) |
|
|
|
if not registered_tp_search(get_template_arg(self.rettype)): |
|
namespaces[namespace].register_types.append(get_template_arg(self.rettype)) |
|
for arg in self.args: |
|
if not registered_tp_search(get_template_arg(arg.tp)): |
|
namespaces[namespace].register_types.append(get_template_arg(arg.tp)) |
|
|
|
|
|
def get_wrapper_name(self): |
|
""" |
|
Return wrapping function name |
|
""" |
|
name = self.name.replace('::', '_') |
|
if self.classname: |
|
classname = self.classname.replace('::', '_') + "_" |
|
else: |
|
classname = "" |
|
return "jlopencv_" + self.namespace.replace('::','_') + '_' + classname + name |
|
|
|
|
|
def init_proto(self): |
|
# string representation of argument list, with '[', ']' symbols denoting optional arguments, e.g. |
|
# "src1, src2[, dst[, mask]]" for cv.add |
|
prototype = "" |
|
|
|
inlist = [] |
|
optlist = [] |
|
outlist = [] |
|
deflist = [] |
|
biglist = [] |
|
|
|
# This logic can almost definitely be simplified |
|
|
|
for a in self.args: |
|
if a.isbig and not (a.inputarg and not a.default_value): |
|
optlist.append(a) |
|
if a.outputarg: |
|
outlist.append(a) |
|
if a.inputarg and not a.default_value: |
|
inlist.append(a) |
|
elif a.inputarg and a.default_value and not a.isbig: |
|
optlist.append(a) |
|
elif not (a.isbig and not (a.inputarg and not a.default_value)): |
|
deflist.append(a) |
|
|
|
if self.rettype: |
|
outlist = [ArgInfo("retval", self.rettype)] + outlist |
|
|
|
if self.isconstructor: |
|
assert outlist == [] or outlist[0].tp == "explicit" |
|
outlist = [ArgInfo("retval", self.classname)] |
|
|
|
|
|
self.outlist = outlist |
|
self.optlist = optlist |
|
self.deflist = deflist |
|
|
|
self.inlist = inlist |
|
|
|
self.prototype = prototype |
|
|
|
class NameSpaceInfo(object): |
|
def __init__(self, name): |
|
self.funcs = {} |
|
self.classes = {} #Dictionary of classname : ClassInfo objects |
|
self.enums = {} |
|
self.consts = {} |
|
self.register_types = [] |
|
self.name = name |
|
|
|
def add_func(decl): |
|
""" |
|
Creates functions based on declaration and add to appropriate classes and/or namespaces |
|
""" |
|
decl[0] = decl[0].replace('.', '::') |
|
namespace, classes, barename = split_decl_name(decl[0]) |
|
name = "::".join(namespace+classes+[barename]) |
|
full_classname = "::".join(namespace + classes) |
|
classname = "::".join(classes) |
|
namespace = '::'.join(namespace) |
|
is_static = False |
|
isphantom = False |
|
mapped_name = '' |
|
|
|
for m in decl[2]: |
|
if m == "/S": |
|
is_static = True |
|
elif m == "/phantom": |
|
print("phantom not supported yet ") |
|
return |
|
elif m.startswith("="): |
|
mapped_name = m[1:] |
|
elif m.startswith("/mappable="): |
|
print("Mappable not supported yet") |
|
return |
|
# if m == "/V": |
|
# print("skipping ", name) |
|
# return |
|
|
|
if classname and full_classname not in namespaces[namespace].classes: |
|
# print("HH1") |
|
# print(namespace, classname) |
|
namespaces[namespace].classes[full_classname] = ClassInfo(full_classname) |
|
assert(0) |
|
|
|
|
|
if is_static: |
|
# Add it as global function |
|
func_map = namespaces[namespace].funcs |
|
if name not in func_map: |
|
func_map[name] = [] |
|
if not mapped_name: |
|
mapped_name = "_".join(classes + [barename]) |
|
func_map[name].append(FuncVariant("", name, mapped_name, decl, namespace, True)) |
|
else: |
|
if classname: |
|
func = FuncVariant(full_classname, name, barename, decl, namespace, False) |
|
if func.isconstructor: |
|
namespaces[namespace].classes[full_classname].constructors.append(func) |
|
else: |
|
func_map = namespaces[namespace].classes[full_classname].methods |
|
if name not in func_map: |
|
func_map[name] = [] |
|
func_map[name].append(func) |
|
else: |
|
func_map = namespaces[namespace].funcs |
|
if name not in func_map: |
|
func_map[name] = [] |
|
if not mapped_name: |
|
mapped_name = barename |
|
func_map[name].append(FuncVariant("", name, mapped_name, decl, namespace, False)) |
|
|
|
|
|
def add_class(stype, name, decl): |
|
""" |
|
Creates class based on name and declaration. Add it to list of classes and to JSON file |
|
""" |
|
# print("n", name) |
|
name = name.replace('.', '::') |
|
classinfo = ClassInfo(name, decl) |
|
namespace, classes, barename = split_decl_name(name) |
|
namespace = '::'.join(namespace) |
|
|
|
if classinfo.name in classes: |
|
namespaces[namespace].classes[name].add_decl(decl) |
|
else: |
|
namespaces[namespace].classes[name] = classinfo |
|
|
|
|
|
|
|
def add_const(name, decl, tp = ''): |
|
name = name.replace('.','::') |
|
namespace, classes, barename = split_decl_name(name) |
|
namespace = '::'.join(namespace) |
|
mapped_name = '_'.join(classes+[barename]) |
|
ns = namespaces[namespace] |
|
if mapped_name in ns.consts: |
|
print("Generator error: constant %s (name=%s) already exists" \ |
|
% (name, name)) |
|
sys.exit(-1) |
|
ns.consts[name] = mapped_name |
|
|
|
def add_enum(name, decl): |
|
name = name.replace('.', '::') |
|
mapped_name = normalize_class_name(name) |
|
# print(name) |
|
if mapped_name.endswith("<unnamed>"): |
|
mapped_name = None |
|
else: |
|
enums[name.replace(".", "::")] = mapped_name |
|
const_decls = decl[3] |
|
|
|
if mapped_name: |
|
namespace, classes, name2 = split_decl_name(name) |
|
namespace = '::'.join(namespace) |
|
mapped_name = '_'.join(classes+[name2]) |
|
# print(mapped_name) |
|
namespaces[namespace].enums[name] = (name.replace(".", "::"),mapped_name) |
|
|
|
for decl in const_decls: |
|
name = decl[0] |
|
add_const(name.replace("const ", "", ).strip(), decl, "int") |
|
|
|
|
|
|
|
def gen_tree(srcfiles): |
|
parser = hdr_parser.CppHeaderParser(generate_umat_decls=False, generate_gpumat_decls=False) |
|
|
|
allowed_func_list = [] |
|
|
|
with open("funclist.csv", "r") as f: |
|
allowed_func_list = f.readlines() |
|
allowed_func_list = [x[:-1] for x in allowed_func_list] |
|
|
|
|
|
count = 0 |
|
# step 1: scan the headers and build more descriptive maps of classes, consts, functions |
|
for hdr in srcfiles: |
|
decls = parser.parse(hdr) |
|
for ns in parser.namespaces: |
|
ns = ns.replace('.', '::') |
|
if ns not in namespaces: |
|
namespaces[ns] = NameSpaceInfo(ns) |
|
count += len(decls) |
|
if len(decls) == 0: |
|
continue |
|
if hdr.find('opencv2/') >= 0: #Avoid including the shadow files |
|
# code_include.write( '#include "{0}"\n'.format(hdr[hdr.rindex('opencv2/'):]) ) |
|
pass |
|
for decl in decls: |
|
name = decl[0] |
|
if name.startswith("struct") or name.startswith("class"): |
|
# class/struct |
|
p = name.find(" ") |
|
stype = name[:p] |
|
name = name[p+1:].strip() |
|
add_class(stype, name, decl) |
|
elif name.startswith("const"): |
|
# constant |
|
assert(0) |
|
add_const(name.replace("const ", "").strip(), decl) |
|
elif name.startswith("enum"): |
|
# enum |
|
add_enum(name.rsplit(" ", 1)[1], decl) |
|
else: |
|
# function |
|
if decl[0] in allowed_func_list: |
|
add_func(decl) |
|
# step 1.5 check if all base classes exist |
|
# print(classes) |
|
for name, classinfo in classes.items(): |
|
if classinfo.base: |
|
base = classinfo.base |
|
# print(base) |
|
if base not in classes: |
|
print("Generator error: unable to resolve base %s for %s" |
|
% (classinfo.base, classinfo.name)) |
|
sys.exit(-1) |
|
base_instance = classes[base] |
|
classinfo.base = base |
|
classinfo.isalgorithm |= base_instance.isalgorithm # wrong processing of 'isalgorithm' flag: |
|
# doesn't work for trees(graphs) with depth > 2 |
|
classes[name] = classinfo |
|
|
|
# tree-based propagation of 'isalgorithm' |
|
processed = dict() |
|
def process_isalgorithm(classinfo): |
|
if classinfo.isalgorithm or classinfo in processed: |
|
return classinfo.isalgorithm |
|
res = False |
|
if classinfo.base: |
|
res = process_isalgorithm(classes[classinfo.base]) |
|
#assert not (res == True or classinfo.isalgorithm is False), "Internal error: " + classinfo.name + " => " + classinfo.base |
|
classinfo.isalgorithm |= res |
|
res = classinfo.isalgorithm |
|
processed[classinfo] = True |
|
return res |
|
for name, classinfo in classes.items(): |
|
process_isalgorithm(classinfo) |
|
|
|
for name, ns in namespaces.items(): |
|
if name.split('.')[-1] == '': |
|
continue |
|
ns.registered = [] |
|
for name, cl in ns.classes.items(): |
|
registered_types.append(get_template_arg(name)) |
|
ns.registered.append(cl.mapped_name) |
|
nss, clss, bs = split_decl_name(name) |
|
type_paths[bs] = [name.replace("::", ".")] |
|
type_paths["::".join(clss+[bs])] = [name.replace("::", ".")] |
|
|
|
|
|
for e1,e2 in ns.enums.items(): |
|
registered_types.append(get_template_arg(e2[0])) |
|
registered_types.append(get_template_arg(e2[0]).replace('::', '_')) #whyyy typedef |
|
ns.registered.append(e2[1]) |
|
|
|
ns.register_types = list(set(ns.register_types)) |
|
ns.register_types = [tp for tp in ns.register_types if not registered_tp_search(tp) and not tp in ns.registered] |
|
for tp in ns.register_types: |
|
registered_types.append(get_template_arg(tp)) |
|
ns.registered.append(get_template_arg(tp)) |
|
default_valuesr = list(set(default_values)) |
|
# registered_types = registered_types + ns.register_types |
|
return namespaces, default_valuesr
|
|
|