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