parse_tree.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. import collections
  2. from textwrap import fill
  3. from filters import *
  4. try:
  5. # Python 2.7+
  6. basestring
  7. except NameError:
  8. # Python 3.3+
  9. basestring = str
  10. valid_types = (
  11. 'int', 'bool', 'float', 'double', 'size_t', 'char',
  12. 'Mat', 'Scalar', 'String',
  13. 'TermCriteria', 'Size', 'Point', 'Point2f', 'Point2d', 'Rect', 'RotatedRect',
  14. 'RNG', 'DMatch', 'Moments',
  15. 'vector_Mat', 'vector_Point', 'vector_int', 'vector_float', 'vector_double', 'vector_String', 'vector_uchar', 'vector_Rect', 'vector_DMatch', 'vector_KeyPoint',
  16. 'vector_Point2f', 'vector_vector_char', 'vector_vector_DMatch', 'vector_vector_KeyPoint',
  17. 'Ptr_StereoBM', 'Ptr_StereoSGBM', 'Ptr_FeatureDetector', 'Ptr_CLAHE', 'Ptr_LineSegmentDetector', 'Ptr_AlignMTB', 'Ptr_CalibrateDebevec',
  18. 'Ptr_CalibrateRobertson', 'Ptr_DenseOpticalFlow', 'Ptr_DualTVL1OpticalFlow', 'Ptr_MergeDebevec', 'Ptr_MergeMertens', 'Ptr_MergeRobertson',
  19. 'Ptr_Stitcher', 'Ptr_Tonemap', 'Ptr_TonemapDrago', 'Ptr_TonemapDurand', 'Ptr_TonemapMantiuk', 'Ptr_TonemapReinhard', 'Ptr_float',
  20. # Not supported:
  21. #vector_vector_KeyPoint
  22. )
  23. class ParseTree(object):
  24. """
  25. The ParseTree class produces a semantic tree of C++ definitions given
  26. the output of the CppHeaderParser (from opencv/modules/python/src2/hdr_parser.py)
  27. The full hierarchy is as follows:
  28. Namespaces
  29. |
  30. |- name
  31. |- Classes
  32. |
  33. |- name
  34. |- Methods
  35. |- Constants
  36. |- Methods
  37. |
  38. |- name
  39. |- static (T/F)
  40. |- return type
  41. |- required Arguments
  42. |
  43. |- name
  44. |- const (T/F)
  45. |- reference ('&'/'*')
  46. |- type
  47. |- input
  48. |- output (pass return by reference)
  49. |- default value
  50. |- optional Arguments
  51. |- Constants
  52. |
  53. |- name
  54. |- const (T/F)
  55. |- reference ('&'/'*')
  56. |- type
  57. |- value
  58. The semantic tree contains substantial information for easily introspecting
  59. information about objects. How many methods does the 'core' namespace have?
  60. Does the 'randn' method have any return by reference (output) arguments?
  61. How many required and optional arguments does the 'add' method have? Is the
  62. variable passed by reference or raw pointer?
  63. Individual definitions from the parse tree (Classes, Functions, Constants)
  64. are passed to the Jinja2 template engine where they are manipulated to
  65. produce Matlab mex sources.
  66. A common call tree for constructing and using a ParseTree object is:
  67. # parse a set of definitions into a dictionary of namespaces
  68. parser = CppHeaderParser()
  69. ns['core'] = parser.parse('path/to/opencv/core.hpp')
  70. # refactor into a semantic tree
  71. parse_tree = ParseTree()
  72. parse_tree.build(ns)
  73. # iterate over the tree
  74. for namespace in parse_tree.namespaces:
  75. for clss in namespace.classes:
  76. # do stuff
  77. for method in namespace.methods:
  78. # do stuff
  79. Calling 'print' on a ParseTree object will reconstruct the definitions
  80. to produce an output resembling the original C++ code.
  81. """
  82. def __init__(self, namespaces=None):
  83. self.namespaces = namespaces if namespaces else []
  84. def __str__(self):
  85. return '\n\n\n'.join(ns.__str__() for ns in self.namespaces)
  86. def build(self, namespaces):
  87. babel = Translator()
  88. for name, definitions in namespaces.items():
  89. class_tree = {}
  90. methods = []
  91. constants = []
  92. for defn in definitions:
  93. try:
  94. obj = babel.translate(defn)
  95. except Exception as e:
  96. print(e)
  97. obj = None
  98. if obj is None:
  99. continue
  100. if type(obj) is Class or obj.clss:
  101. self.insertIntoClassTree(obj, class_tree)
  102. elif type(obj) is Method:
  103. methods.append(obj)
  104. elif type(obj) is Constant:
  105. constants.append(obj)
  106. else:
  107. raise TypeError('Unexpected object type: '+str(type(obj)))
  108. self.namespaces.append(Namespace(name, constants, list(class_tree.values()), methods))
  109. def insertIntoClassTree(self, obj, class_tree):
  110. cname = obj.name if type(obj) is Class else obj.clss
  111. if not cname:
  112. return
  113. if not cname in class_tree:
  114. # add a new class to the tree
  115. class_tree[cname] = Class(cname)
  116. # insert the definition into the class
  117. val = class_tree[cname]
  118. if type(obj) is Method:
  119. val.methods.append(obj)
  120. elif type(obj) is Constant:
  121. val.constants.append(obj)
  122. else:
  123. raise TypeError('Unexpected object type: '+str(type(obj)))
  124. class Translator(object):
  125. """
  126. The Translator class does the heavy lifting of translating the nested
  127. list representation of the hdr_parser into individual definitions that
  128. are inserted into the ParseTree.
  129. Translator consists of a top-level method: translate()
  130. along with a number of helper methods: translateClass(), translateMethod(),
  131. translateArgument(), translateConstant(), translateName(), and
  132. translateClassName()
  133. """
  134. def translate(self, defn):
  135. # --- class ---
  136. # classes have 'class' prefixed on their name
  137. if 'class' in defn[0].split(' ') or 'struct' in defn[0].split(' '):
  138. return self.translateClass(defn)
  139. # --- operators! ---
  140. #TODO: implement operators: http://www.mathworks.com.au/help/matlab/matlab_oop/implementing-operators-for-your-class.html
  141. if 'operator' in defn[0]:
  142. return
  143. # --- constant ---
  144. elif convertibleToInt(defn[1]):
  145. return self.translateConstant(defn)
  146. # --- function ---
  147. # functions either need to have input arguments, or not uppercase names
  148. elif defn[3] or not self.translateName(defn[0]).split('_')[0].isupper():
  149. return self.translateMethod(defn)
  150. # --- constant ---
  151. else:
  152. return self.translateConstant(defn)
  153. def translateClass(self, defn):
  154. return Class()
  155. def translateMethod(self, defn, class_tree=None):
  156. name = self.translateName(defn[0])
  157. clss = self.translateClassName(defn[0])
  158. rtp = defn[1]
  159. static = True if 'S' in ''.join(defn[2]) else False
  160. args = defn[3]
  161. req = []
  162. opt = []
  163. for arg in args:
  164. if arg:
  165. a = self.translateArgument(arg)
  166. opt.append(a) if a.default else req.append(a)
  167. return Method(name, clss, static, '', rtp, False, req, opt)
  168. def translateConstant(self, defn):
  169. const = True if 'const' in defn[0] else False
  170. name = self.translateName(defn[0])
  171. clss = self.translateClassName(defn[0])
  172. tp = 'int'
  173. val = defn[1]
  174. return Constant(name, clss, tp, const, '', val)
  175. def translateArgument(self, defn):
  176. modifiers = defn[3]
  177. ref = '*' if '*' in defn[0] else ''
  178. ref = '&' if '&' in defn[0] or '/Ref' in modifiers else ref
  179. const = '/C' in modifiers
  180. tp = " ".join([word for word in defn[0].replace(ref, '').split() if not ' const ' in ' '+word+' '])
  181. name = defn[1]
  182. default = defn[2] if defn[2] else ''
  183. I = True if '/I' in modifiers or not '/O' in modifiers else False
  184. O = True if '/O' in modifiers else False
  185. return Argument(name, tp, const, I, O, ref, default)
  186. def translateName(self, name):
  187. return name.split(' ')[-1].split('.')[-1]
  188. def translateClassName(self, name):
  189. name = name.split(' ')[-1]
  190. parts = name.split('.')
  191. return parts[-2] if len(parts) > 1 and not parts[-2] == 'cv' else ''
  192. class Namespace(object):
  193. """
  194. Namespace
  195. |
  196. |- name
  197. |- Constants
  198. |- Methods
  199. |- Constants
  200. """
  201. def __init__(self, name='', constants=None, classes=None, methods=None):
  202. self.name = name
  203. self.constants = constants if constants else []
  204. self.classes = classes if classes else []
  205. self.methods = methods if methods else []
  206. def __str__(self):
  207. return 'namespace '+self.name+' {\n\n'+\
  208. ('\n'.join(c.__str__() for c in self.constants)+'\n\n' if self.constants else '')+\
  209. ('\n'.join(f.__str__() for f in self.methods)+'\n\n' if self.methods else '')+\
  210. ('\n\n'.join(o.__str__() for o in self.classes) if self.classes else '')+'\n};'
  211. class Class(object):
  212. """
  213. Class
  214. |
  215. |- name
  216. |- Methods
  217. |- Constants
  218. """
  219. def __init__(self, name='', namespace='', constants=None, methods=None):
  220. self.name = name
  221. self.namespace = namespace
  222. self.constants = constants if constants else []
  223. self.methods = methods if methods else []
  224. def __str__(self):
  225. return 'class '+self.name+' {\n\t'+\
  226. ('\n\t'.join(c.__str__() for c in self.constants)+'\n\n\t' if self.constants else '')+\
  227. ('\n\t'.join(f.__str__() for f in self.methods) if self.methods else '')+'\n};'
  228. class Method(object):
  229. """
  230. Method
  231. int VideoWriter::read( cv::Mat& frame, const cv::Mat& mask=cv::Mat() );
  232. --- ----- ---- -------- ----------------
  233. rtp class name required optional
  234. name the method name
  235. clss the class the method belongs to ('' if free)
  236. static static?
  237. namespace the namespace the method belongs to ('' if free)
  238. rtp the return type
  239. const const?
  240. req list of required arguments
  241. opt list of optional arguments
  242. """
  243. def __init__(self, name='', clss='', static=False, namespace='', rtp='', const=False, req=None, opt=None):
  244. self.name = name
  245. self.clss = clss
  246. self.constructor = True if name == clss else False
  247. self.static = static
  248. self.const = const
  249. self.namespace = namespace
  250. self.rtp = rtp
  251. self.req = req if req else []
  252. self.opt = opt if opt else []
  253. def __str__(self):
  254. return (self.rtp+' ' if self.rtp else '')+self.name+'('+\
  255. ', '.join(arg.__str__() for arg in self.req+self.opt)+\
  256. ')'+(' const' if self.const else '')+';'
  257. class Argument(object):
  258. """
  259. Argument
  260. const cv::Mat& mask=cv::Mat()
  261. ----- ---- --- ---- -------
  262. const tp ref name default
  263. name the argument name
  264. tp the argument type
  265. const const?
  266. I is the argument treated as an input?
  267. O is the argument treated as an output (return by reference)
  268. ref is the argument passed by reference? ('*'/'&')
  269. default the default value of the argument ('' if required)
  270. """
  271. def __init__(self, name='', tp='', const=False, I=True, O=False, ref='', default=''):
  272. self.name = name
  273. self.tp = tp
  274. self.ref = ref
  275. self.I = I
  276. self.O = O
  277. self.const = const
  278. self.default = default
  279. if not tp in valid_types:
  280. raise Exception("Non-supported argument type: {} (name: {})".format(tp, name))
  281. def __str__(self):
  282. return ('const ' if self.const else '')+self.tp+self.ref+\
  283. ' '+self.name+('='+self.default if self.default else '')
  284. class Constant(object):
  285. """
  286. Constant
  287. DFT_COMPLEX_OUTPUT = 12;
  288. ---- -------
  289. name default
  290. name the name of the constant
  291. clss the class that the constant belongs to ('' if free)
  292. tp the type of the constant ('' if int)
  293. const const?
  294. ref is the constant a reference? ('*'/'&')
  295. default default value, required for constants
  296. """
  297. def __init__(self, name='', clss='', tp='', const=False, ref='', default=''):
  298. self.name = name
  299. self.clss = clss
  300. self.tp = tp
  301. self.ref = ref
  302. self.const = const
  303. self.default = default
  304. def __str__(self):
  305. return ('const ' if self.const else '')+self.tp+self.ref+\
  306. ' '+self.name+('='+self.default if self.default else '')+';'
  307. def constants(tree):
  308. """
  309. recursive generator to strip all Constant objects from the ParseTree
  310. and place them into a flat dictionary of { name, value (default) }
  311. """
  312. if isinstance(tree, dict) and 'constants' in tree and isinstance(tree['constants'], list):
  313. for node in tree['constants']:
  314. yield (node['name'], node['default'])
  315. if isinstance(tree, dict):
  316. for key, val in tree.items():
  317. for gen in constants(val):
  318. yield gen
  319. if isinstance(tree, list):
  320. for val in tree:
  321. for gen in constants(val):
  322. yield gen
  323. def todict(obj):
  324. """
  325. Recursively convert a Python object graph to sequences (lists)
  326. and mappings (dicts) of primitives (bool, int, float, string, ...)
  327. """
  328. if isinstance(obj, basestring):
  329. return obj
  330. elif isinstance(obj, dict):
  331. return dict((key, todict(val)) for key, val in obj.items())
  332. elif isinstance(obj, collections.Iterable):
  333. return [todict(val) for val in obj]
  334. elif hasattr(obj, '__dict__'):
  335. return todict(vars(obj))
  336. elif hasattr(obj, '__slots__'):
  337. return todict(dict((name, getattr(obj, name)) for name in getattr(obj, '__slots__')))
  338. return obj