• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2
3from __future__ import print_function
4import os, sys, re, string, io
5
6# the list only for debugging. The real list, used in the real OpenCV build, is specified in CMakeLists.txt
7opencv_hdr_list = [
8"../../core/include/opencv2/core.hpp",
9"../../core/include/opencv2/core/ocl.hpp",
10"../../flann/include/opencv2/flann/miniflann.hpp",
11"../../ml/include/opencv2/ml.hpp",
12"../../imgproc/include/opencv2/imgproc.hpp",
13"../../calib3d/include/opencv2/calib3d.hpp",
14"../../features2d/include/opencv2/features2d.hpp",
15"../../video/include/opencv2/video/tracking.hpp",
16"../../video/include/opencv2/video/background_segm.hpp",
17"../../objdetect/include/opencv2/objdetect.hpp",
18"../../imgcodecs/include/opencv2/imgcodecs.hpp",
19"../../videoio/include/opencv2/videoio.hpp",
20"../../highgui/include/opencv2/highgui.hpp"
21]
22
23"""
24Each declaration is [funcname, return_value_type /* in C, not in Python */, <list_of_modifiers>, <list_of_arguments>],
25where each element of <list_of_arguments> is 4-element list itself:
26[argtype, argname, default_value /* or "" if none */, <list_of_modifiers>]
27where the list of modifiers is yet another nested list of strings
28   (currently recognized are "/O" for output argument, "/S" for static (i.e. class) methods
29   and "/A value" for the plain C arrays with counters)
30"""
31
32class CppHeaderParser(object):
33
34    def __init__(self):
35        self.BLOCK_TYPE = 0
36        self.BLOCK_NAME = 1
37        self.PROCESS_FLAG = 2
38        self.PUBLIC_SECTION = 3
39        self.CLASS_DECL = 4
40
41        self.namespaces = set()
42
43    def batch_replace(self, s, pairs):
44        for before, after in pairs:
45            s = s.replace(before, after)
46        return s
47
48    def get_macro_arg(self, arg_str, npos):
49        npos2 = npos3 = arg_str.find("(", npos)
50        if npos2 < 0:
51            print("Error: no arguments for the macro at %d" % (self.lineno,))
52            sys.exit(-1)
53        balance = 1
54        while 1:
55            t, npos3 = self.find_next_token(arg_str, ['(', ')'], npos3+1)
56            if npos3 < 0:
57                print("Error: no matching ')' in the macro call at %d" % (self.lineno,))
58                sys.exit(-1)
59            if t == '(':
60                balance += 1
61            if t == ')':
62                balance -= 1
63                if balance == 0:
64                    break
65
66        return arg_str[npos2+1:npos3].strip(), npos3
67
68    def parse_arg(self, arg_str, argno):
69        """
70        Parses <arg_type> [arg_name]
71        Returns arg_type, arg_name, modlist, argno, where
72        modlist is the list of wrapper-related modifiers (such as "output argument", "has counter", ...)
73        and argno is the new index of an anonymous argument.
74        That is, if no arg_str is just an argument type without argument name, the argument name is set to
75        "arg" + str(argno), and then argno is incremented.
76        """
77        modlist = []
78
79        # pass 0: extracts the modifiers
80        if "CV_OUT" in arg_str:
81            modlist.append("/O")
82            arg_str = arg_str.replace("CV_OUT", "")
83
84        if "CV_IN_OUT" in arg_str:
85            modlist.append("/IO")
86            arg_str = arg_str.replace("CV_IN_OUT", "")
87
88        isarray = False
89        npos = arg_str.find("CV_CARRAY")
90        if npos >= 0:
91            isarray = True
92            macro_arg, npos3 = self.get_macro_arg(arg_str, npos)
93
94            modlist.append("/A " + macro_arg)
95            arg_str = arg_str[:npos] + arg_str[npos3+1:]
96
97        npos = arg_str.find("CV_CUSTOM_CARRAY")
98        if npos >= 0:
99            isarray = True
100            macro_arg, npos3 = self.get_macro_arg(arg_str, npos)
101
102            modlist.append("/CA " + macro_arg)
103            arg_str = arg_str[:npos] + arg_str[npos3+1:]
104
105        arg_str = arg_str.strip()
106        word_start = 0
107        word_list = []
108        npos = -1
109
110        #print self.lineno, ":\t", arg_str
111
112        # pass 1: split argument type into tokens
113        while 1:
114            npos += 1
115            t, npos = self.find_next_token(arg_str, [" ", "&", "*", "<", ">", ","], npos)
116            w = arg_str[word_start:npos].strip()
117            if w == "operator":
118                word_list.append("operator " + arg_str[npos:].strip())
119                break
120            if w not in ["", "const"]:
121                word_list.append(w)
122            if t not in ["", " ", "&"]:
123                word_list.append(t)
124            if not t:
125                break
126            word_start = npos+1
127            npos = word_start - 1
128
129        arg_type = ""
130        arg_name = ""
131        angle_stack = []
132
133        #print self.lineno, ":\t", word_list
134
135        # pass 2: decrypt the list
136        wi = -1
137        prev_w = ""
138        for w in word_list:
139            wi += 1
140            if w == "*":
141                if prev_w == "char" and not isarray:
142                    arg_type = arg_type[:-len("char")] + "c_string"
143                else:
144                    arg_type += w
145                continue
146            elif w == "<":
147                arg_type += "_"
148                angle_stack.append(0)
149            elif w == "," or w == '>':
150                if not angle_stack:
151                    print("Error at %d: argument contains ',' or '>' not within template arguments" % (self.lineno,))
152                    sys.exit(-1)
153                if w == ",":
154                    arg_type += "_and_"
155                elif w == ">":
156                    if angle_stack[0] == 0:
157                        print("Error at %s:%d: template has no arguments" % (self.hname, self.lineno))
158                        sys.exit(-1)
159                    if angle_stack[0] > 1:
160                        arg_type += "_end_"
161                    angle_stack[-1:] = []
162            elif angle_stack:
163                arg_type += w
164                angle_stack[-1] += 1
165            elif arg_type == "struct":
166                arg_type += " " + w
167            elif arg_type and arg_type != "~":
168                arg_name = " ".join(word_list[wi:])
169                break
170            else:
171                arg_type += w
172            prev_w = w
173
174        counter_str = ""
175        add_star = False
176        if ("[" in arg_name) and not ("operator" in arg_str):
177            #print arg_str
178            p1 = arg_name.find("[")
179            p2 = arg_name.find("]",p1+1)
180            if p2 < 0:
181                print("Error at %d: no closing ]" % (self.lineno,))
182                sys.exit(-1)
183            counter_str = arg_name[p1+1:p2].strip()
184            if counter_str == "":
185                counter_str = "?"
186            if not isarray:
187                modlist.append("/A " + counter_str.strip())
188            arg_name = arg_name[:p1]
189            add_star = True
190
191        if not arg_name:
192            if arg_type.startswith("operator"):
193                arg_type, arg_name = "", arg_type
194            else:
195                arg_name = "arg" + str(argno)
196                argno += 1
197
198        while arg_type.endswith("_end_"):
199            arg_type = arg_type[:-len("_end_")]
200
201        if add_star:
202            arg_type += "*"
203
204        arg_type = self.batch_replace(arg_type, [("std::", ""), ("cv::", ""), ("::", "_")])
205
206        return arg_type, arg_name, modlist, argno
207
208    def parse_enum(self, decl_str):
209        l = decl_str
210        ll = l.split(",")
211        if ll[-1].strip() == "":
212            ll = ll[:-1]
213        prev_val = ""
214        prev_val_delta = -1
215        decl = []
216        for pair in ll:
217            pv = pair.split("=")
218            if len(pv) == 1:
219                prev_val_delta += 1
220                val = ""
221                if prev_val:
222                    val = prev_val + "+"
223                val += str(prev_val_delta)
224            else:
225                prev_val_delta = 0
226                prev_val = val = pv[1].strip()
227            decl.append(["const " + self.get_dotted_name(pv[0].strip()), val, [], []])
228        return decl
229
230    def parse_class_decl(self, decl_str):
231        """
232        Parses class/struct declaration start in the form:
233           {class|struct} [CV_EXPORTS] <class_name> [: public <base_class1> [, ...]]
234        Returns class_name1, <list of base_classes>
235        """
236        l = decl_str
237        modlist = []
238        if "CV_EXPORTS_W_MAP" in l:
239            l = l.replace("CV_EXPORTS_W_MAP", "")
240            modlist.append("/Map")
241        if "CV_EXPORTS_W_SIMPLE" in l:
242            l = l.replace("CV_EXPORTS_W_SIMPLE", "")
243            modlist.append("/Simple")
244        npos = l.find("CV_EXPORTS_AS")
245        if npos >= 0:
246            macro_arg, npos3 = self.get_macro_arg(l, npos)
247            modlist.append("=" + macro_arg)
248            l = l[:npos] + l[npos3+1:]
249
250        l = self.batch_replace(l, [("CV_EXPORTS_W", ""), ("CV_EXPORTS", ""), ("public virtual ", " "), ("public ", " "), ("::", ".")]).strip()
251        ll = re.split(r'\s*[,:]?\s*', l)
252        ll = [le for le in ll if le]
253        classname = ll[1]
254        bases = ll[2:]
255        return classname, bases, modlist
256
257    def parse_func_decl_no_wrap(self, decl_str, static_method = False):
258        decl_str = (decl_str or "").strip()
259        virtual_method = False
260        explicit_method = False
261        if decl_str.startswith("explicit"):
262            decl_str = decl_str[len("explicit"):].lstrip()
263            explicit_method = True
264        if decl_str.startswith("virtual"):
265            decl_str = decl_str[len("virtual"):].lstrip()
266            virtual_method = True
267        if decl_str.startswith("static"):
268            decl_str = decl_str[len("static"):].lstrip()
269            static_method = True
270
271        fdecl = decl_str.replace("CV_OUT", "").replace("CV_IN_OUT", "")
272        fdecl = fdecl.strip().replace("\t", " ")
273        while "  " in fdecl:
274            fdecl = fdecl.replace("  ", " ")
275        fname = fdecl[:fdecl.find("(")].strip()
276        fnpos = fname.rfind(" ")
277        if fnpos < 0:
278            fnpos = 0
279        fname = fname[fnpos:].strip()
280        rettype = fdecl[:fnpos].strip()
281
282        if rettype.endswith("operator"):
283            fname = ("operator " + fname).strip()
284            rettype = rettype[:rettype.rfind("operator")].strip()
285            if rettype.endswith("::"):
286                rpos = rettype.rfind(" ")
287                if rpos >= 0:
288                    fname = rettype[rpos+1:].strip() + fname
289                    rettype = rettype[:rpos].strip()
290                else:
291                    fname = rettype + fname
292                    rettype = ""
293
294        apos = fdecl.find("(")
295        if fname.endswith("operator"):
296            fname += " ()"
297            apos = fdecl.find("(", apos+1)
298
299        fname = "cv." + fname.replace("::", ".")
300        decl = [fname, rettype, [], []]
301
302        # inline constructor implementation
303        implmatch = re.match(r"(\(.*?\))\s*:\s*(\w+\(.*?\),?\s*)+", fdecl[apos:])
304        if bool(implmatch):
305            fdecl = fdecl[:apos] + implmatch.group(1)
306
307        args0str = fdecl[apos+1:fdecl.rfind(")")].strip()
308
309        if args0str != "" and args0str != "void":
310            args0str = re.sub(r"\([^)]*\)", lambda m: m.group(0).replace(',', "@comma@"), args0str)
311            args0 = args0str.split(",")
312
313            args = []
314            narg = ""
315            for arg in args0:
316                narg += arg.strip()
317                balance_paren = narg.count("(") - narg.count(")")
318                balance_angle = narg.count("<") - narg.count(">")
319                if balance_paren == 0 and balance_angle == 0:
320                    args.append(narg.strip())
321                    narg = ""
322
323            for arg in args:
324                dfpos = arg.find("=")
325                defval = ""
326                if dfpos >= 0:
327                    defval = arg[dfpos+1:].strip()
328                else:
329                    dfpos = arg.find("CV_DEFAULT")
330                    if dfpos >= 0:
331                        defval, pos3 = self.get_macro_arg(arg, dfpos)
332                    else:
333                        dfpos = arg.find("CV_WRAP_DEFAULT")
334                        if dfpos >= 0:
335                            defval, pos3 = self.get_macro_arg(arg, dfpos)
336                if dfpos >= 0:
337                    defval = defval.replace("@comma@", ",")
338                    arg = arg[:dfpos].strip()
339                pos = len(arg)-1
340                while pos >= 0 and (arg[pos] in "_[]" or arg[pos].isalpha() or arg[pos].isdigit()):
341                    pos -= 1
342                if pos >= 0:
343                    aname = arg[pos+1:].strip()
344                    atype = arg[:pos+1].strip()
345                    if aname.endswith("&") or aname.endswith("*") or (aname in ["int", "String", "Mat"]):
346                        atype = (atype + " " + aname).strip()
347                        aname = ""
348                else:
349                    atype = arg
350                    aname = ""
351                if aname.endswith("]"):
352                    bidx = aname.find('[')
353                    atype += aname[bidx:]
354                    aname = aname[:bidx]
355                decl[3].append([atype, aname, defval, []])
356
357        if static_method:
358            decl[2].append("/S")
359        if virtual_method:
360            decl[2].append("/V")
361        if explicit_method:
362            decl[2].append("/E")
363        if bool(re.match(r".*\)\s*(const)?\s*=\s*0", decl_str)):
364            decl[2].append("/A")
365        if bool(re.match(r".*\)\s*const(\s*=\s*0)?", decl_str)):
366            decl[2].append("/C")
367        if "virtual" in decl_str:
368            print(decl_str)
369        return decl
370
371    def parse_func_decl(self, decl_str):
372        """
373        Parses the function or method declaration in the form:
374        [([CV_EXPORTS] <rettype>) | CVAPI(rettype)]
375            [~]<function_name>
376            (<arg_type1> <arg_name1>[=<default_value1>] [, <arg_type2> <arg_name2>[=<default_value2>] ...])
377            [const] {; | <function_body>}
378
379        Returns the function declaration entry:
380        [<func name>, <return value C-type>, <list of modifiers>, <list of arguments>] (see above)
381        """
382
383        if self.wrap_mode:
384            if not (("CV_EXPORTS_AS" in decl_str) or ("CV_EXPORTS_W" in decl_str) or \
385                ("CV_WRAP" in decl_str) or ("CV_WRAP_AS" in decl_str)):
386                return []
387
388        # ignore old API in the documentation check (for now)
389        if "CVAPI(" in decl_str and self.wrap_mode:
390            return []
391
392        top = self.block_stack[-1]
393        func_modlist = []
394
395        npos = decl_str.find("CV_EXPORTS_AS")
396        if npos >= 0:
397            arg, npos3 = self.get_macro_arg(decl_str, npos)
398            func_modlist.append("="+arg)
399            decl_str = decl_str[:npos] + decl_str[npos3+1:]
400        npos = decl_str.find("CV_WRAP_AS")
401        if npos >= 0:
402            arg, npos3 = self.get_macro_arg(decl_str, npos)
403            func_modlist.append("="+arg)
404            decl_str = decl_str[:npos] + decl_str[npos3+1:]
405
406        # filter off some common prefixes, which are meaningless for Python wrappers.
407        # note that we do not strip "static" prefix, which does matter;
408        # it means class methods, not instance methods
409        decl_str = self.batch_replace(decl_str, [("virtual", ""), ("static inline", ""), ("inline", ""),\
410            ("CV_EXPORTS_W", ""), ("CV_EXPORTS", ""), ("CV_CDECL", ""), ("CV_WRAP ", " "), ("CV_INLINE", "")]).strip()
411
412        static_method = False
413        context = top[0]
414        if decl_str.startswith("static") and (context == "class" or context == "struct"):
415            decl_str = decl_str[len("static"):].lstrip()
416            static_method = True
417
418        args_begin = decl_str.find("(")
419        if decl_str.startswith("CVAPI"):
420            rtype_end = decl_str.find(")", args_begin+1)
421            if rtype_end < 0:
422                print("Error at %d. no terminating ) in CVAPI() macro: %s" % (self.lineno, decl_str))
423                sys.exit(-1)
424            decl_str = decl_str[args_begin+1:rtype_end] + " " + decl_str[rtype_end+1:]
425            args_begin = decl_str.find("(")
426        if args_begin < 0:
427            print("Error at %d: no args in '%s'" % (self.lineno, decl_str))
428            sys.exit(-1)
429
430        decl_start = decl_str[:args_begin].strip()
431        # handle operator () case
432        if decl_start.endswith("operator"):
433            args_begin = decl_str.find("(", args_begin+1)
434            if args_begin < 0:
435                print("Error at %d: no args in '%s'" % (self.lineno, decl_str))
436                sys.exit(-1)
437            decl_start = decl_str[:args_begin].strip()
438            # TODO: normalize all type of operators
439            if decl_start.endswith("()"):
440                decl_start = decl_start[0:-2].rstrip() + " ()"
441
442        # constructor/destructor case
443        if bool(re.match(r'^(\w+::)*(?P<x>\w+)::~?(?P=x)$', decl_start)):
444            decl_start = "void " + decl_start
445
446        rettype, funcname, modlist, argno = self.parse_arg(decl_start, -1)
447
448        # determine original return type, hack for return types with underscore
449        original_type = None
450        i = decl_start.rfind(funcname)
451        if i > 0:
452            original_type = decl_start[:i].replace("&", "").replace("const", "").strip()
453
454        if argno >= 0:
455            classname = top[1]
456            if rettype == classname or rettype == "~" + classname:
457                rettype, funcname = "", rettype
458            else:
459                if bool(re.match('\w+\s+\(\*\w+\)\s*\(.*\)', decl_str)):
460                    return [] # function typedef
461                elif bool(re.match('\w+\s+\(\w+::\*\w+\)\s*\(.*\)', decl_str)):
462                    return [] # class method typedef
463                elif bool(re.match('[A-Z_]+', decl_start)):
464                    return [] # it seems to be a macro instantiation
465                elif "__declspec" == decl_start:
466                    return []
467                elif bool(re.match(r'\w+\s+\(\*\w+\)\[\d+\]', decl_str)):
468                    return [] # exotic - dynamic 2d array
469                else:
470                    #print rettype, funcname, modlist, argno
471                    print("Error at %s:%d the function/method name is missing: '%s'" % (self.hname, self.lineno, decl_start))
472                    sys.exit(-1)
473
474        if self.wrap_mode and (("::" in funcname) or funcname.startswith("~")):
475            # if there is :: in function name (and this is in the header file),
476            # it means, this is inline implementation of a class method.
477            # Thus the function has been already declared within the class and we skip this repeated
478            # declaration.
479            # Also, skip the destructors, as they are always wrapped
480            return []
481
482        funcname = self.get_dotted_name(funcname)
483
484        if not self.wrap_mode:
485            decl = self.parse_func_decl_no_wrap(decl_str, static_method)
486            decl[0] = funcname
487            return decl
488
489        arg_start = args_begin+1
490        npos = arg_start-1
491        balance = 1
492        angle_balance = 0
493        # scan the argument list; handle nested parentheses
494        args_decls = []
495        args = []
496        argno = 1
497
498        while balance > 0:
499            npos += 1
500            t, npos = self.find_next_token(decl_str, ["(", ")", ",", "<", ">"], npos)
501            if not t:
502                print("Error: no closing ')' at %d" % (self.lineno,))
503                print(decl_str)
504                print(decl_str[arg_start:])
505                sys.exit(-1)
506            if t == "<":
507                angle_balance += 1
508            if t == ">":
509                angle_balance -= 1
510            if t == "(":
511                balance += 1
512            if t == ")":
513                balance -= 1
514
515            if (t == "," and balance == 1 and angle_balance == 0) or balance == 0:
516                # process next function argument
517                a = decl_str[arg_start:npos].strip()
518                #print "arg = ", a
519                arg_start = npos+1
520                if a:
521                    eqpos = a.find("=")
522                    defval = ""
523                    modlist = []
524                    if eqpos >= 0:
525                        defval = a[eqpos+1:].strip()
526                    else:
527                        eqpos = a.find("CV_DEFAULT")
528                        if eqpos >= 0:
529                            defval, pos3 = self.get_macro_arg(a, eqpos)
530                        else:
531                            eqpos = a.find("CV_WRAP_DEFAULT")
532                            if eqpos >= 0:
533                                defval, pos3 = self.get_macro_arg(a, eqpos)
534                    if defval == "NULL":
535                        defval = "0"
536                    if eqpos >= 0:
537                        a = a[:eqpos].strip()
538                    arg_type, arg_name, modlist, argno = self.parse_arg(a, argno)
539                    if self.wrap_mode:
540                        if arg_type == "InputArray":
541                            arg_type = "Mat"
542                        elif arg_type == "InputOutputArray":
543                            arg_type = "Mat"
544                            modlist.append("/IO")
545                        elif arg_type == "OutputArray":
546                            arg_type = "Mat"
547                            modlist.append("/O")
548                        elif arg_type == "InputArrayOfArrays":
549                            arg_type = "vector_Mat"
550                        elif arg_type == "InputOutputArrayOfArrays":
551                            arg_type = "vector_Mat"
552                            modlist.append("/IO")
553                        elif arg_type == "OutputArrayOfArrays":
554                            arg_type = "vector_Mat"
555                            modlist.append("/O")
556                        defval = self.batch_replace(defval, [("InputArrayOfArrays", "vector<Mat>"),
557                                                             ("InputOutputArrayOfArrays", "vector<Mat>"),
558                                                             ("OutputArrayOfArrays", "vector<Mat>"),
559                                                             ("InputArray", "Mat"),
560                                                             ("InputOutputArray", "Mat"),
561                                                             ("OutputArray", "Mat"),
562                                                             ("noArray", arg_type)]).strip()
563                    args.append([arg_type, arg_name, defval, modlist])
564                npos = arg_start-1
565
566        if static_method:
567            func_modlist.append("/S")
568
569        if original_type is None:
570            return [funcname, rettype, func_modlist, args]
571        else:
572            return [funcname, rettype, func_modlist, args, original_type]
573
574    def get_dotted_name(self, name):
575        """
576        adds the dot-separated container class/namespace names to the bare function/class name, e.g. when we have
577
578        namespace cv {
579        class A {
580        public:
581            f(int);
582        };
583        }
584
585        the function will convert "A" to "cv.A" and "f" to "cv.A.f".
586        """
587        if not self.block_stack:
588            return name
589        if name.startswith("cv."):
590            return name
591        qualified_name = (("." in name) or ("::" in name))
592        n = ""
593        for b in self.block_stack:
594            block_type, block_name = b[self.BLOCK_TYPE], b[self.BLOCK_NAME]
595            if block_type in ["file", "enum"]:
596                continue
597            if block_type not in ["struct", "class", "namespace"]:
598                print("Error at %d: there are non-valid entries in the current block stack " % (self.lineno, self.block_stack))
599                sys.exit(-1)
600            if block_name and (block_type == "namespace" or not qualified_name):
601                n += block_name + "."
602        n += name.replace("::", ".")
603        if n.endswith(".Algorithm"):
604            n = "cv.Algorithm"
605        return n
606
607    def parse_stmt(self, stmt, end_token):
608        """
609        parses the statement (ending with ';' or '}') or a block head (ending with '{')
610
611        The function calls parse_class_decl or parse_func_decl when necessary. It returns
612        <block_type>, <block_name>, <parse_flag>, <declaration>
613        where the first 3 values only make sense for blocks (i.e. code blocks, namespaces, classes, enums and such)
614        """
615        stack_top = self.block_stack[-1]
616        context = stack_top[self.BLOCK_TYPE]
617
618        stmt_type = ""
619        if end_token == "{":
620            stmt_type = "block"
621
622        if context == "block":
623            print("Error at %d: should not call parse_stmt inside blocks" % (self.lineno,))
624            sys.exit(-1)
625
626        if context == "class" or context == "struct":
627            while 1:
628                colon_pos = stmt.find(":")
629                if colon_pos < 0:
630                    break
631                w = stmt[:colon_pos].strip()
632                if w in ["public", "protected", "private"]:
633                    if w == "public" or (not self.wrap_mode and w == "protected"):
634                        stack_top[self.PUBLIC_SECTION] = True
635                    else:
636                        stack_top[self.PUBLIC_SECTION] = False
637                    stmt = stmt[colon_pos+1:].strip()
638                break
639
640        # do not process hidden class members and template classes/functions
641        if not stack_top[self.PUBLIC_SECTION] or stmt.startswith("template"):
642            return stmt_type, "", False, None
643
644        if end_token == "{":
645            if not self.wrap_mode and stmt.startswith("typedef struct"):
646                stmt_type = "struct"
647                try:
648                    classname, bases, modlist = self.parse_class_decl(stmt[len("typedef "):])
649                except:
650                    print("Error at %s:%d" % (self.hname, self.lineno))
651                    exit(1)
652                if classname.startswith("_Ipl"):
653                    classname = classname[1:]
654                decl = [stmt_type + " " + self.get_dotted_name(classname), "", modlist, []]
655                if bases:
656                    decl[1] = ": " + ", ".join([self.get_dotted_name(b).replace(".","::") for b in bases])
657                return stmt_type, classname, True, decl
658
659            if stmt.startswith("class") or stmt.startswith("struct"):
660                stmt_type = stmt.split()[0]
661                if stmt.strip() != stmt_type:
662                    try:
663                        classname, bases, modlist = self.parse_class_decl(stmt)
664                    except:
665                        print("Error at %s:%d" % (self.hname, self.lineno))
666                        exit(1)
667                    decl = []
668                    if ("CV_EXPORTS_W" in stmt) or ("CV_EXPORTS_AS" in stmt) or (not self.wrap_mode):# and ("CV_EXPORTS" in stmt)):
669                        decl = [stmt_type + " " + self.get_dotted_name(classname), "", modlist, []]
670                        if bases:
671                            decl[1] = ": " + ", ".join([self.get_dotted_name(b).replace(".","::") for b in bases])
672                    return stmt_type, classname, True, decl
673
674            if stmt.startswith("enum"):
675                return "enum", "", True, None
676
677            if stmt.startswith("namespace"):
678                stmt_list = stmt.split()
679                if len(stmt_list) < 2:
680                    stmt_list.append("<unnamed>")
681                return stmt_list[0], stmt_list[1], True, None
682            if stmt.startswith("extern") and "\"C\"" in stmt:
683                return "namespace", "", True, None
684
685        if end_token == "}" and context == "enum":
686            decl = self.parse_enum(stmt)
687            return "enum", "", False, decl
688
689        if end_token == ";" and stmt.startswith("typedef"):
690            # TODO: handle typedef's more intelligently
691            return stmt_type, "", False, None
692
693        paren_pos = stmt.find("(")
694        if paren_pos >= 0:
695            # assume it's function or method declaration,
696            # since we filtered off the other places where '(' can normally occur:
697            #   - code blocks
698            #   - function pointer typedef's
699            decl = self.parse_func_decl(stmt)
700            # we return parse_flag == False to prevent the parser to look inside function/method bodies
701            # (except for tracking the nested blocks)
702            return stmt_type, "", False, decl
703
704        if (context == "struct" or context == "class") and end_token == ";" and stmt:
705            # looks like it's member declaration; append the members to the class declaration
706            class_decl = stack_top[self.CLASS_DECL]
707            if ("CV_PROP" in stmt): # or (class_decl and ("/Map" in class_decl[2])):
708                var_modlist = []
709                if "CV_PROP_RW" in stmt:
710                    var_modlist.append("/RW")
711                stmt = self.batch_replace(stmt, [("CV_PROP_RW", ""), ("CV_PROP", "")]).strip()
712                var_list = stmt.split(",")
713                var_type, var_name1, modlist, argno = self.parse_arg(var_list[0], -1)
714                var_list = [var_name1] + [i.strip() for i in var_list[1:]]
715
716                for v in var_list:
717                    class_decl[3].append([var_type, v, "", var_modlist])
718            return stmt_type, "", False, None
719
720        # something unknown
721        return stmt_type, "", False, None
722
723    def find_next_token(self, s, tlist, p=0):
724        """
725        Finds the next token from the 'tlist' in the input 's', starting from position 'p'.
726        Returns the first occured token and its position, or ("", len(s)) when no token is found
727        """
728        token = ""
729        tpos = len(s)
730        for t in tlist:
731            pos = s.find(t, p)
732            if pos < 0:
733                continue
734            if pos < tpos:
735                tpos = pos
736                token = t
737        return token, tpos
738
739    def parse(self, hname, wmode=True):
740        """
741        The main method. Parses the input file.
742        Returns the list of declarations (that can be print using print_decls)
743        """
744        self.hname = hname
745        decls = []
746        f = io.open(hname, 'rt', encoding='utf-8')
747        linelist = list(f.readlines())
748        f.close()
749
750        # states:
751        SCAN = 0 # outside of a comment or preprocessor directive
752        COMMENT = 1 # inside a multi-line comment
753        DIRECTIVE = 2 # inside a multi-line preprocessor directive
754
755        state = SCAN
756
757        self.block_stack = [["file", hname, True, True, None]]
758        block_head = ""
759        self.lineno = 0
760        self.wrap_mode = wmode
761
762        for l0 in linelist:
763            self.lineno += 1
764            #print self.lineno
765
766            l = l0.strip()
767
768            if state == SCAN and l.startswith("#"):
769                state = DIRECTIVE
770                # fall through to the if state == DIRECTIVE check
771
772            if state == DIRECTIVE:
773                if not l.endswith("\\"):
774                    state = SCAN
775                continue
776
777            if state == COMMENT:
778                pos = l.find("*/")
779                if pos < 0:
780                    continue
781                l = l[pos+2:]
782                state = SCAN
783
784            if state != SCAN:
785                print("Error at %d: invlid state = %d" % (self.lineno, state))
786                sys.exit(-1)
787
788            while 1:
789                token, pos = self.find_next_token(l, [";", "\"", "{", "}", "//", "/*"])
790
791                if not token:
792                    block_head += " " + l
793                    break
794
795                if token == "//":
796                    block_head += " " + l[:pos]
797                    break
798
799                if token == "/*":
800                    block_head += " " + l[:pos]
801                    pos = l.find("*/", pos+2)
802                    if pos < 0:
803                        state = COMMENT
804                        break
805                    l = l[pos+2:]
806                    continue
807
808                if token == "\"":
809                    pos2 = pos + 1
810                    while 1:
811                        t2, pos2 = self.find_next_token(l, ["\\", "\""], pos2)
812                        if t2 == "":
813                            print("Error at %d: no terminating '\"'" % (self.lineno,))
814                            sys.exit(-1)
815                        if t2 == "\"":
816                            break
817                        pos2 += 2
818
819                    block_head += " " + l[:pos2+1]
820                    l = l[pos2+1:]
821                    continue
822
823                stmt = (block_head + " " + l[:pos]).strip()
824                stmt = " ".join(stmt.split()) # normalize the statement
825                stack_top = self.block_stack[-1]
826
827                if stmt.startswith("@"):
828                    # Objective C ?
829                    break
830
831                decl = None
832                if stack_top[self.PROCESS_FLAG]:
833                    # even if stack_top[PUBLIC_SECTION] is False, we still try to process the statement,
834                    # since it can start with "public:"
835                    stmt_type, name, parse_flag, decl = self.parse_stmt(stmt, token)
836                    if decl:
837                        if stmt_type == "enum":
838                            for d in decl:
839                                decls.append(d)
840                        else:
841                            decls.append(decl)
842                    if stmt_type == "namespace":
843                        chunks = [block[1] for block in self.block_stack if block[0] == 'namespace'] + [name]
844                        self.namespaces.add('.'.join(chunks))
845                else:
846                    stmt_type, name, parse_flag = "block", "", False
847
848                if token == "{":
849                    if stmt_type == "class":
850                        public_section = False
851                    else:
852                        public_section = True
853                    self.block_stack.append([stmt_type, name, parse_flag, public_section, decl])
854
855                if token == "}":
856                    if not self.block_stack:
857                        print("Error at %d: the block stack is empty" % (self.lineno,))
858                    self.block_stack[-1:] = []
859                    if pos+1 < len(l) and l[pos+1] == ';':
860                        pos += 1
861
862                block_head = ""
863                l = l[pos+1:]
864
865        return decls
866
867    def print_decls(self, decls):
868        """
869        Prints the list of declarations, retrieived by the parse() method
870        """
871        for d in decls:
872            print(d[0], d[1], ";".join(d[2]))
873            for a in d[3]:
874                print("   ", a[0], a[1], a[2], end="")
875                if a[3]:
876                    print("; ".join(a[3]))
877                else:
878                    print()
879
880if __name__ == '__main__':
881    parser = CppHeaderParser()
882    decls = []
883    for hname in opencv_hdr_list:
884        decls += parser.parse(hname)
885    #for hname in sys.argv[1:]:
886        #decls += parser.parse(hname, wmode=False)
887    parser.print_decls(decls)
888    print(len(decls))
889    print("namespaces:", " ".join(sorted(parser.namespaces)))
890