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