Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
hdr_parser.py
1#!/usr/bin/env python
2
3from __future__ import print_function
4import sys, re, io
5
6
7"""
8Each declaration is [funcname, return_value_type /* in C, not in Python */, <list_of_modifiers>, <list_of_arguments>, original_return_type, docstring],
9where each element of <list_of_arguments> is 4-element list itself:
10[argtype, argname, default_value /* or "" if none */, <list_of_modifiers>]
11where the list of modifiers is yet another nested list of strings
12 (currently recognized are "/O" for output argument, "/S" for static (i.e. class) methods
13 and "/A value" for the plain C arrays with counters)
14original_return_type is None if the original_return_type is the same as return_value_type
15"""
16
17
18class CppHeaderParser(object):
19
20 def __init__(self):
21 self.BLOCK_TYPE = 0
22 self.BLOCK_NAME = 1
23 self.PROCESS_FLAG = 2
25 self.CLASS_DECL = 4
26
27 self.namespaces = set()
28
29 def batch_replace(self, s, pairs):
30 for before, after in pairs:
31 s = s.replace(before, after)
32 return s
33
34 def get_macro_arg(self, arg_str, npos):
35 npos2 = npos3 = arg_str.find("(", npos)
36 if npos2 < 0:
37 print("Error: no arguments for the macro at %d" % (self.lineno,))
38 sys.exit(-1)
39 balance = 1
40 while 1:
41 t, npos3 = self.find_next_token(arg_str, ['(', ')'], npos3 + 1)
42 if npos3 < 0:
43 print("Error: no matching ')' in the macro call at %d" % (self.lineno,))
44 sys.exit(-1)
45 if t == '(':
46 balance += 1
47 if t == ')':
48 balance -= 1
49 if balance == 0:
50 break
51
52 return arg_str[npos2 + 1:npos3].strip(), npos3
53
54 def parse_arg(self, arg_str, argno):
55 """
56 Parses <arg_type> [arg_name]
57 Returns arg_type, arg_name, modlist, argno, where
58 modlist is the list of wrapper-related modifiers (such as "output argument", "has counter", ...)
59 and argno is the new index of an anonymous argument.
60 That is, if no arg_str is just an argument type without argument name, the argument name is set to
61 "arg" + str(argno), and then argno is incremented.
62 """
63 modlist = []
64
65 # pass 0: extracts the modifiers
66 if "CV_OUT" in arg_str:
67 modlist.append("/O")
68 arg_str = arg_str.replace("CV_OUT", "")
69
70 if "CV_IN_OUT" in arg_str:
71 modlist.append("/IO")
72 arg_str = arg_str.replace("CV_IN_OUT", "")
73
74 isarray = False
75 npos = arg_str.find("CV_CARRAY")
76 if npos >= 0:
77 isarray = True
78 macro_arg, npos3 = self.get_macro_arg(arg_str, npos)
79
80 modlist.append("/A " + macro_arg)
81 arg_str = arg_str[:npos] + arg_str[npos3 + 1:]
82
83 npos = arg_str.find("CV_CUSTOM_CARRAY")
84 if npos >= 0:
85 isarray = True
86 macro_arg, npos3 = self.get_macro_arg(arg_str, npos)
87
88 modlist.append("/CA " + macro_arg)
89 arg_str = arg_str[:npos] + arg_str[npos3 + 1:]
90
91 npos = arg_str.find("const")
92 if npos >= 0:
93 modlist.append("/C")
94
95 npos = arg_str.find("&")
96 if npos >= 0:
97 modlist.append("/Ref")
98
99 arg_str = arg_str.strip()
100 word_start = 0
101 word_list = []
102 npos = -1
103
104 # print self.lineno, ":\t", arg_str
105
106 # pass 1: split argument type into tokens
107 while 1:
108 npos += 1
109 t, npos = self.find_next_token(arg_str, [" ", "&", "*", "<", ">", ","], npos)
110 w = arg_str[word_start:npos].strip()
111 if w == "operator":
112 word_list.append("operator " + arg_str[npos:].strip())
113 break
114 if w not in ["", "const"]:
115 word_list.append(w)
116 if t not in ["", " ", "&"]:
117 word_list.append(t)
118 if not t:
119 break
120 word_start = npos + 1
121 npos = word_start - 1
122
123 arg_type = ""
124 arg_name = ""
125 angle_stack = []
126
127 # print self.lineno, ":\t", word_list
128
129 # pass 2: decrypt the list
130 wi = -1
131 prev_w = ""
132 for w in word_list:
133 wi += 1
134 if w == "*":
135 if prev_w == "char" and not isarray:
136 arg_type = arg_type[:-len("char")] + "c_string"
137 else:
138 arg_type += w
139 continue
140 elif w == "<":
141 arg_type += "_"
142 angle_stack.append(0)
143 elif w == "," or w == '>':
144 if not angle_stack:
145 print("Error at %d: argument contains ',' or '>' not within template arguments" % (self.lineno,))
146 sys.exit(-1)
147 if w == ",":
148 arg_type += "_and_"
149 elif w == ">":
150 if angle_stack[0] == 0:
151 print("Error at %s:%d: template has no arguments" % (self.hname, self.lineno))
152 sys.exit(-1)
153 if angle_stack[0] > 1:
154 arg_type += "_end_"
155 angle_stack[-1:] = []
156 elif angle_stack:
157 arg_type += w
158 angle_stack[-1] += 1
159 elif arg_type == "struct":
160 arg_type += " " + w
161 elif arg_type and arg_type != "~":
162 arg_name = " ".join(word_list[wi:])
163 break
164 else:
165 arg_type += w
166 prev_w = w
167
168 counter_str = ""
169 add_star = False
170 if ("[" in arg_name) and not ("operator" in arg_str):
171 # print arg_str
172 p1 = arg_name.find("[")
173 p2 = arg_name.find("]", p1 + 1)
174 if p2 < 0:
175 print("Error at %d: no closing ]" % (self.lineno,))
176 sys.exit(-1)
177 counter_str = arg_name[p1 + 1:p2].strip()
178 if counter_str == "":
179 counter_str = "?"
180 if not isarray:
181 modlist.append("/A " + counter_str.strip())
182 arg_name = arg_name[:p1]
183 add_star = True
184
185 if not arg_name:
186 if arg_type.startswith("operator"):
187 arg_type, arg_name = "", arg_type
188 else:
189 arg_name = "arg" + str(argno)
190 argno += 1
191
192 while arg_type.endswith("_end_"):
193 arg_type = arg_type[:-len("_end_")]
194
195 if add_star:
196 arg_type += "*"
197
198 arg_type = self.batch_replace(arg_type, [("std::", ""), ("cv::", ""), ("::", "_")])
199
200 return arg_type, arg_name, modlist, argno
201
202 def parse_enum(self, decl_str):
203 l = decl_str
204 ll = l.split(",")
205 if ll[-1].strip() == "":
206 ll = ll[:-1]
207 prev_val = ""
208 prev_val_delta = -1
209 decl = []
210 for pair in ll:
211 pv = pair.split("=")
212 if len(pv) == 1:
213 prev_val_delta += 1
214 val = ""
215 if prev_val:
216 val = prev_val + "+"
217 val += str(prev_val_delta)
218 else:
219 prev_val_delta = 0
220 prev_val = val = pv[1].strip()
221 decl.append(["const " + self.get_dotted_name(pv[0].strip()), val, [], [], None, ""])
222 return decl
223
224 def parse_class_decl(self, decl_str):
225 """
226 Parses class/struct declaration start in the form:
227 {class|struct} [CV_EXPORTS] <class_name> [: public <base_class1> [, ...]]
228 Returns class_name1, <list of base_classes>
229 """
230 l = decl_str
231 modlist = []
232 if "VISP_EXPORT" in l:
233 l = l.replace("VISP_EXPORT", "")
234
235 # INFO: Keep things simple for now, later uncomment and read these
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, [("public virtual ", " "), ("public ", " "), ("::", ".")]).strip()
246 ll = re.split(r'\s+|\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, docstring=""):
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, [], [], None, docstring]
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, docstring=""):
367 """
368 Parses the function or method declaration in the form:
369 [[VISP_EXPORTS] <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>, <original return type>, <docstring>] (see above)
376 """
377
378 # TODO Lets see how many functions can I add if I remove EXPORT constrain
379 # if self.wrap_mode:
380 # if not (("VISP_EXPORT" in decl_str)):
381 # return []
382
383 static_method = False
384 if "VISP_EXPORT" in decl_str:
385 static_method = True
386
387 top = self.block_stack[-1]
388 func_modlist = []
389
390 virtual_method = False
391 pure_virtual_method = False
392 const_method = False
393
394 # filter off some common prefixes, which are meaningless for Python wrappers.
395 # note that we do not strip "static" prefix, which does matter;
396 # it means class methods, not instance methods
397 # INFO: Handle friend methods. open-cv didn't have any
398 # INFO: Handle unsigned args/return type too. open-cv didn't have any
399 # TODO: I'm removing `unsigned` keyword from function declaration
400 decl_str = self.batch_replace(decl_str, [("static inline", ""),("inline", ""),
401 ("VISP_EXPORT", ""), ("VP_EXPLICIT", ""), ("friend", ""),('explicit','')
402 ,("unsigned","")]).strip()
403
404 if decl_str.strip().startswith('virtual'):
405 virtual_method = True
406
407 decl_str = decl_str.replace('virtual', '')
408
409 end_tokens = decl_str[decl_str.rfind(')'):].split()
410 const_method = 'const' in end_tokens
411 pure_virtual_method = '=' in end_tokens and '0' in end_tokens
412
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 # INFO: open-cv had some CV-API thing here. I guess VISP doesn't need such
420 if args_begin < 0:
421 print("Error at %d: no args in '%s'" % (self.lineno, decl_str))
422 sys.exit(-1)
423
424 decl_start = decl_str[:args_begin].strip()
425 # INFO: Handle operator (). Not operator << or + or any other
426 if decl_start.endswith("operator"):
427 args_begin = decl_str.find("(", args_begin + 1)
428 if args_begin < 0:
429 print("Error at %d: no args in '%s'" % (self.lineno, decl_str))
430 sys.exit(-1)
431 decl_start = decl_str[:args_begin].strip()
432 # TODO: normalize all type of operators
433 if decl_start.endswith("()"):
434 decl_start = decl_start[0:-2].rstrip() + " ()"
435
436 # INFO: Its not () operator but some other operator, like +,-,*
437 if 'operator' in decl_str:
438 return decl_str,"operator","","","",""
439
440 # constructor/destructor case
441 if bool(re.match(r"^(\w+::)*(?P<x>\w+)::~?(?P=x)$", decl_start)):
442 decl_start = "void " + decl_start
443
444 rettype, funcname, modlist, argno = self.parse_arg(decl_start, -1)
445
446 # determine original return type, hack for return types with underscore
447 original_type = None
448 i = decl_start.rfind(funcname)
449 if i > 0:
450 original_type = decl_start[:i].replace("&", "").replace("const", "").strip()
451
452 if argno >= 0:
453 classname = top[1]
454 if rettype == classname or rettype == "~" + classname:
455 rettype, funcname = "", rettype
456 else:
457 if bool(re.match(r"\w+\s+\‍(\*\w+\‍)\s*\‍(.*\‍)", decl_str)):
458 return [] # function typedef
459 elif bool(re.match(r"\w+\s+\‍(\w+::\*\w+\‍)\s*\‍(.*\‍)", decl_str)):
460 return [] # class method typedef
461 elif bool(re.match("[A-Z_]+", decl_start)):
462 return [] # it seems to be a macro instantiation
463 elif "__declspec" == decl_start:
464 return []
465 elif bool(re.match(r"\w+\s+\‍(\*\w+\‍)\‍[\d+\‍]", decl_str)):
466 return [] # exotic - dynamic 2d array
467 else:
468 # print rettype, funcname, modlist, argno
469 print("Error at %s:%d the function/method name is missing: '%s'" % (
470 self.hname, self.lineno, decl_start))
471
472 # TODO: Dont abort on an error. Just ignore and move
473 # sys.exit(-1)
474 return []
475
476 if self.wrap_mode and (("::" in funcname) or funcname.startswith("~")):
477 # if there is :: in function name (and this is in the header file),
478 # it means, this is inline implementation of a class method.
479 # Thus the function has been already declared within the class and we skip this repeated
480 # declaration.
481 # Also, skip the destructors, as they are always wrapped
482 return []
483
484 funcname = self.get_dotted_name(funcname)
485
486 if not self.wrap_mode:
487 decl = self.parse_func_decl_no_wrap(decl_str, static_method, docstring)
488 decl[0] = funcname
489 return decl
490
491 arg_start = args_begin + 1
492 npos = arg_start - 1
493 balance = 1
494 angle_balance = 0
495 # scan the argument list; handle nested parentheses
496 args_decls = []
497 args = []
498 argno = 1
499
500 while balance > 0:
501 npos += 1
502 t, npos = self.find_next_token(decl_str, ["(", ")", ",", "<", ">"], npos)
503 if not t:
504 print("Error: no closing ')' at %d" % (self.lineno,))
505 print(decl_str)
506 print(decl_str[arg_start:])
507 sys.exit(-1)
508 if t == "<":
509 angle_balance += 1
510 if t == ">":
511 angle_balance -= 1
512 if t == "(":
513 balance += 1
514 if t == ")":
515 balance -= 1
516
517 if (t == "," and balance == 1 and angle_balance == 0) or balance == 0:
518 # process next function argument
519 a = decl_str[arg_start:npos].strip()
520 # print "arg = ", a
521 arg_start = npos + 1
522 if a:
523 eqpos = a.find("=")
524 defval = ""
525 modlist = []
526 if eqpos >= 0:
527 defval = a[eqpos + 1:].strip()
528 else:
529 eqpos = a.find("CV_DEFAULT")
530 if eqpos >= 0:
531 defval, pos3 = self.get_macro_arg(a, eqpos)
532 else:
533 eqpos = a.find("CV_WRAP_DEFAULT")
534 if eqpos >= 0:
535 defval, pos3 = self.get_macro_arg(a, eqpos)
536 if defval == "nullptr":
537 defval = "0"
538 if eqpos >= 0:
539 a = a[:eqpos].strip()
540 arg_type, arg_name, modlist, argno = self.parse_arg(a, argno)
541 if self.wrap_mode:
542 mat = "vpMatrix"
543
544 vector_mat = "vector_{}".format("vpMatrix")
545 vector_mat_template = "vector<{}>".format("vpMatrix")
546
547 if arg_type == "InputArray":
548 arg_type = mat
549 elif arg_type == "InputOutputArray":
550 arg_type = mat
551 modlist.append("/IO")
552 elif arg_type == "OutputArray":
553 arg_type = mat
554 modlist.append("/O")
555 elif arg_type == "InputArrayOfArrays":
556 arg_type = vector_mat
557 elif arg_type == "InputOutputArrayOfArrays":
558 arg_type = vector_mat
559 modlist.append("/IO")
560 elif arg_type == "OutputArrayOfArrays":
561 arg_type = vector_mat
562 modlist.append("/O")
563 defval = self.batch_replace(defval, [("InputArrayOfArrays", vector_mat_template),
564 ("InputOutputArrayOfArrays", vector_mat_template),
565 ("OutputArrayOfArrays", vector_mat_template),
566 ("InputArray", mat),
567 ("InputOutputArray", mat),
568 ("OutputArray", mat),
569 ("noArray", arg_type)]).strip()
570 args.append([arg_type, arg_name, defval, modlist])
571 npos = arg_start - 1
572
573 if static_method:
574 func_modlist.append("/S")
575 if const_method:
576 func_modlist.append("/C")
577 if virtual_method:
578 func_modlist.append("/V")
579 if pure_virtual_method:
580 func_modlist.append("/PV")
581
582 return [funcname, rettype, func_modlist, args, original_type, docstring]
583
584 def get_dotted_name(self, name):
585 """
586 adds the dot-separated container class/namespace names to the bare function/class name, e.g. when we have
587
588 namespace cv {
589 class A {
590 public:
591 f(int);
592 };
593 }
594
595 the function will convert "A" to "cv.A" and "f" to "cv.A.f".
596 """
597 if not self.block_stack:
598 return name
599 if name.startswith("vp."):
600 return name
601 qualified_name = (("." in name) or ("::" in name))
602 n = ""
603 for b in self.block_stack:
604 block_type, block_name = b[self.BLOCK_TYPE], b[self.BLOCK_NAME]
605 if block_type in ["file", "enum"]:
606 continue
607 if block_type not in ["struct", "class", "namespace"]:
608 print("Error at %d: there are non-valid entries in the current block stack " % (
609 self.lineno, self.block_stack))
610 sys.exit(-1)
611 if block_name and (block_type == "namespace" or not qualified_name):
612 n += block_name + "."
613 n += name.replace("::", ".")
614
615 return n
616
617 def parse_stmt(self, stmt, end_token, docstring=""):
618 """
619 parses the statement (ending with ';' or '}') or a block head (ending with '{')
620
621 The function calls parse_class_decl or parse_func_decl when necessary. It returns
622 <block_type>, <block_name>, <parse_flag>, <declaration>
623 where the first 3 values only make sense for blocks (i.e. code blocks, namespaces, classes, enums and such)
624 """
625 stack_top = self.block_stack[-1]
626 context = stack_top[self.BLOCK_TYPE]
627
628 stmt_type = ""
629 if end_token == "{":
630 stmt_type = "block"
631
632 if context == "block":
633 print("Error at %d: should not call parse_stmt inside blocks" % (self.lineno,))
634 sys.exit(-1)
635
636 if context == "class" or context == "struct":
637 while 1:
638 colon_pos = stmt.find(":")
639 if colon_pos < 0:
640 break
641 w = stmt[:colon_pos].strip()
642 if w in ["public", "protected", "private"]:
643 if w == "public" or (not self.wrap_mode and w == "protected"):
644 stack_top[self.PUBLIC_SECTION] = True
645 else:
646 stack_top[self.PUBLIC_SECTION] = False
647 stmt = stmt[colon_pos + 1:].strip()
648 break
649
650 # do not process hidden class members and template classes/functions
651 if not stack_top[self.PUBLIC_SECTION] or stmt.startswith("template"):
652 return stmt_type, "", False, None
653
654 if end_token == "{":
655 if not self.wrap_mode and stmt.startswith("typedef struct"):
656 stmt_type = "struct"
657 try:
658 classname, bases, modlist = self.parse_class_decl(stmt[len("typedef "):])
659 except:
660 print("Error at %s:%d" % (self.hname, self.lineno))
661 exit(1)
662 if classname.startswith("_Ipl"):
663 classname = classname[1:]
664 decl = [stmt_type + " " + self.get_dotted_name(classname), "", modlist, [], None, docstring]
665 if bases:
666 decl[1] = ": " + ", ".join([self.get_dotted_name(b).replace(".", "::") for b in bases])
667 return stmt_type, classname, True, decl
668
669 if stmt.startswith("class") or stmt.startswith("struct"):
670 stmt_type = stmt.split()[0]
671 if stmt.strip() != stmt_type:
672 try:
673 classname, bases, modlist = self.parse_class_decl(stmt)
674 except:
675 print("Error at %s:%d" % (self.hname, self.lineno))
676 exit(1)
677 decl = []
678 if ("CV_EXPORTS_W" in stmt) or ("CV_EXPORTS_AS" in stmt) or (
679 not self.wrap_mode): # and ("CV_EXPORTS" in stmt)):
680 decl = [stmt_type + " " + self.get_dotted_name(classname), "", modlist, [], None, docstring]
681 if bases:
682 decl[1] = ": " + ", ".join([self.get_dotted_name(b).replace(".", "::") for b in bases])
683 return stmt_type, classname, True, decl
684
685 # INFO: Unlike open-cv, visp declares enum's starting with 'enum' and 'typdef enum' both.
686 # INFO: If we just check for 'enum' literal at the start, might lose 'typedef enum' literals
687 if stmt.startswith("enum") or stmt.startswith("typedef enum"):
688 return "enum", "", True, None
689
690 if stmt.startswith("namespace"):
691 stmt_list = stmt.split()
692 if len(stmt_list) < 2:
693 stmt_list.append("<unnamed>")
694 return stmt_list[0], stmt_list[1], True, None
695 if stmt.startswith("extern") and "\"C\"" in stmt:
696 return "namespace", "", True, None
697
698 if end_token == "}" and context == "enum":
699 decl = self.parse_enum(stmt)
700 return "enum", "", False, decl
701
702 if end_token == ";" and stmt.startswith("typedef"):
703 # TODO: handle typedef's more intelligently
704 return stmt_type, "", False, None
705
706 paren_pos = stmt.find("(")
707 if paren_pos >= 0:
708 # assume it's function or method declaration,
709 # since we filtered off the other places where '(' can normally occur:
710 # - code blocks
711 # - function pointer typedef's
712 decl = self.parse_func_decl(stmt, docstring=docstring)
713 # we return parse_flag == False to prevent the parser to look inside function/method bodies
714 # (except for tracking the nested blocks)
715 return stmt_type, "", False, decl
716
717 if (context == "struct" or context == "class") and end_token == ";" and stmt:
718 # looks like it's member declaration; append the members to the class declaration
719 class_decl = stack_top[self.CLASS_DECL]
720 if ("CV_PROP" in stmt): # or (class_decl and ("/Map" in class_decl[2])):
721 var_modlist = []
722 if "CV_PROP_RW" in stmt:
723 var_modlist.append("/RW")
724 stmt = self.batch_replace(stmt, [("CV_PROP_RW", ""), ("CV_PROP", "")]).strip()
725 var_list = stmt.split(",")
726 var_type, var_name1, modlist, argno = self.parse_arg(var_list[0], -1)
727 var_list = [var_name1] + [i.strip() for i in var_list[1:]]
728
729 for v in var_list:
730 class_decl[3].append([var_type, v, "", var_modlist])
731 return stmt_type, "", False, None
732
733 # something unknown
734 return stmt_type, "", False, None
735
736 def find_next_token(self, s, tlist, p=0):
737 """
738 Finds the next token from the 'tlist' in the input 's', starting from position 'p'.
739 Returns the first occurred token and its position, or ("", len(s)) when no token is found
740 """
741 token = ""
742 tpos = len(s)
743 for t in tlist:
744 pos = s.find(t, p)
745 if pos < 0:
746 continue
747 if pos < tpos:
748 tpos = pos
749 token = t
750 return token, tpos
751
752 def parse(self, hname, wmode=True):
753 """
754 The main method. Parses the input file.
755 Returns the list of declarations (that can be print using print_decls)
756 """
757 self.hname = hname
758 decls = []
759 f = io.open(hname, 'rt', encoding='utf-8')
760 linelist = list(f.readlines())
761 f.close()
762
763 # states:
764 SCAN = 0 # outside of a comment or preprocessor directive
765 COMMENT = 1 # inside a multi-line comment
766 DIRECTIVE = 2 # inside a multi-line preprocessor directive
767 DOCSTRING = 3 # inside a multi-line docstring
768
769 state = SCAN
770
771 self.block_stack = [["file", hname, True, True, None]]
772 block_head = ""
773 docstring = ""
774 self.lineno = 0
775 self.wrap_mode = wmode
776
777 for l0 in linelist:
778 self.lineno += 1
779
780 l = l0.strip() # INFO: Remove Trailing Whitespaces
781
782 if state == SCAN and l.startswith("#"):
783 state = DIRECTIVE
784 # fall through to the if state == DIRECTIVE check
785
786 # INFO: All #inlcude <visp/stg.h> are ignored
787 if state == DIRECTIVE:
788 if not l.endswith("\\"):
789 state = SCAN
790 continue
791
792 if state == COMMENT:
793 pos = l.find("*/")
794 if pos < 0:
795 continue
796 l = l[pos + 2:]
797 state = SCAN
798
799 if state == DOCSTRING:
800 pos = l.find("*/")
801 if pos < 0:
802 docstring += l + "\n"
803 continue
804 docstring += l[:pos] + "\n"
805 l = l[pos + 2:]
806 state = SCAN
807
808 if state != SCAN:
809 print("Error at %d: invalid state = %d" % (self.lineno, state))
810 sys.exit(-1)
811
812 while 1:
813 token, pos = self.find_next_token(l, [";", "\"", "{", "}", "//", "/*"])
814
815 if not token:
816 block_head += " " + l
817 break
818
819 if token == "//":
820 block_head += " " + l[:pos]
821 break
822
823 if token == "/*":
824 block_head += " " + l[:pos]
825 end_pos = l.find("*/", pos + 2)
826
827 # INFO: open-cv follows Javadoc style (/**) for docstring, visp follows C Style(/*!) or Javadoc style (/**)
828 if len(l) > pos + 2 and ((l[pos + 2] == "!") or (l[pos + 2] == "*")):
829 # '/**', it's a docstring
830 if end_pos < 0:
831 state = DOCSTRING
832 docstring = l[pos + 3:] + "\n"
833 break
834 else:
835 docstring = l[pos + 3:end_pos]
836
837 elif end_pos < 0:
838 state = COMMENT
839 break
840 l = l[end_pos + 2:]
841 continue
842
843 if token == "\"":
844 pos2 = pos + 1
845 while 1:
846 t2, pos2 = self.find_next_token(l, ["\\", "\""], pos2)
847 if t2 == "":
848 print("Error at %d: no terminating '\"'" % (self.lineno,))
849 sys.exit(-1)
850 if t2 == "\"":
851 break
852 pos2 += 2
853
854 block_head += " " + l[:pos2 + 1]
855 l = l[pos2 + 1:]
856 continue
857
858 # INFO: U reach here means token is ;
859
860 stmt = (block_head + " " + l[:pos]).strip()
861 stmt = " ".join(stmt.split()) # normalize the statement
862 # print(stmt)
863 stack_top = self.block_stack[-1]
864
865 if stmt.startswith("@"):
866 # Objective C ?
867 break
868
869 decl = None
870 if "BEGIN_VISP_NAMESPACE" in stmt:
871 stmt = stmt.replace("BEGIN_VISP_NAMESPACE", "")
872 stmt = stmt.strip()
873 if "END_VISP_NAMESPACE" in stmt:
874 stmt = stmt.replace("END_VISP_NAMESPACE", "")
875 stmt = stmt.strip()
876 if stack_top[self.PROCESS_FLAG]:
877 # even if stack_top[PUBLIC_SECTION] is False, we still try to process the statement,
878 # since it can start with "public:"
879 docstring = docstring.strip()
880 stmt_type, name, parse_flag, decl = self.parse_stmt(stmt, token, docstring=docstring)
881 if decl:
882 if stmt_type == "enum":
883 for d in decl:
884 decls.append(d)
885 else:
886 decls.append(decl)
887 docstring = ""
888 if stmt_type == "namespace":
889 chunks = [block[1] for block in self.block_stack if block[0] == 'namespace'] + [name]
890 for i in range(len(chunks)):
891 if chunks[i] == "VISP_NAMESPACE_NAME":
892 chunks[i] = "visp"
893 self.namespaces.add('.'.join(chunks))
894 else:
895 stmt_type, name, parse_flag = "block", "", False
896
897 if token == "{":
898 if stmt_type == "class":
899 public_section = False
900 else:
901 public_section = True
902 self.block_stack.append([stmt_type, name, parse_flag, public_section, decl])
903
904 if token == "}":
905 if not self.block_stack:
906 print("Error at %d: the block stack is empty" % (self.lineno,))
907 self.block_stack[-1:] = []
908 if pos + 1 < len(l) and l[pos + 1] == ';':
909 pos += 1
910
911 block_head = ""
912 l = l[pos + 1:]
913
914 return decls
915
916 def print_decls(self, decls):
917 """
918 Prints the list of declarations, retrieived by the parse() method
919 """
920 for d in decls:
921 print(d[0], d[1], ";".join(d[2]))
922 # Uncomment below line to see docstrings
923 # print('"""\n' + d[5] + '\n"""')
924 for a in d[3]:
925 print(" ", a[0], a[1], a[2], end="")
926 if a[3]:
927 print("; ".join(a[3]))
928 else:
929 print()
parse_class_decl(self, decl_str)
parse_stmt(self, stmt, end_token, docstring="")
parse_arg(self, arg_str, argno)
Definition hdr_parser.py:54
parse_func_decl(self, decl_str, docstring="")
get_macro_arg(self, arg_str, npos)
Definition hdr_parser.py:34
parse_func_decl_no_wrap(self, decl_str, static_method=False, docstring="")
batch_replace(self, s, pairs)
Definition hdr_parser.py:29
parse(self, hname, wmode=True)
find_next_token(self, s, tlist, p=0)
parse_enum(self, decl_str)