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