BindingsGenerator.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import CppHeaderParser
  2. import ConfigParser
  3. import io
  4. import os
  5. import re
  6. from LuaBindingsGenerator import *
  7. from JSBindingsGenerator import *
  8. class BindingsGenerator(object):
  9. def __init__(self, configFile):
  10. self.config = ConfigParser.RawConfigParser(allow_no_value=True)
  11. self.config.read(configFile)
  12. self.targetDir = self.config.get('global', 'TargetDirectory')
  13. self.ignoreFiles = self.config.get('global', 'IgnoreFiles')
  14. self.symbolsToStrip = self.config.get('global', 'StripSymbols').replace(" ", "").split(",")
  15. self.ignoreClasses = self.config.get('global', 'IgnoreClasses').replace(" ", "").split(",")
  16. self.ignoreMethods = self.config.get('global', 'IgnoreMethods').replace(" ", "").split(",")
  17. self.engines = {LuaBindingsGenerator(self.config), JSBindingsGenerator(self.config)}
  18. # ----------------------------------------------------
  19. # Utility methods
  20. # ----------------------------------------------------
  21. def error(self, msg):
  22. print("\033[91mERROR: %s" % (msg))
  23. print("\033[0m")
  24. # ----------------------------------------------------
  25. # Interface methods called in bindings engines
  26. # ----------------------------------------------------
  27. def processTargetFile(self, targetFile):
  28. for e in self.engines:
  29. e.processTargetFile(targetFile)
  30. def processClass(self, c):
  31. for e in self.engines:
  32. e.processClass(c)
  33. def finalize(self):
  34. for e in self.engines:
  35. e.finalize()
  36. # ----------------------------------------------------
  37. # Clean up doxygen stuff out of comment docs
  38. # ----------------------------------------------------
  39. def cleanDocs(self, docs):
  40. return docs.replace("/*", "").replace("*/", "").replace("*", "").replace("\n", "").replace("\r", "").replace("::", ".").replace("\t", "")
  41. # ----------------------------------------------------
  42. # Clean and reduce types to standard identifiers
  43. # ----------------------------------------------------
  44. def typeFilter(self, ty):
  45. ty = ty.replace("Polycode::", "")
  46. ty = ty.replace("std::", "")
  47. ty = ty.replace("const", "")
  48. ty = ty.replace("inline", "")
  49. ty = ty.replace("static", "")
  50. ty = ty.replace("virtual", "")
  51. ty = ty.replace("&", "")
  52. ty = re.sub(r'^.*\sint\s*$', 'int', ty) # eg "unsigned int"
  53. ty = re.sub(r'^.*\schar\s*$', 'char', ty) # eg "unsigned int"
  54. ty = re.sub(r'^.*\slong\s*$', 'int', ty)
  55. ty = re.sub(r'^.*\swchar_t\s*$', 'int', ty)
  56. ty = re.sub(r'^.*\sshort\s*$', 'int', ty)
  57. ty = re.sub(r'^.*\sfloat\s*$', 'Number', ty)
  58. ty = re.sub(r'^.*\sdouble\s*$', 'Number', ty) # eg "long double"
  59. ty = ty.replace("unsigned int", "int")
  60. ty = ty.replace("unsigned short", "int")
  61. ty = ty.replace("unsigned long", "int")
  62. ty = ty.replace("long", "int")
  63. ty = ty.replace("float", "Number")
  64. ty = ty.replace("double", "Number")
  65. ty = ty.replace(" ", "") # Not very safe!
  66. return ty
  67. # ----------------------------------------------------
  68. # Clean and reduce types to standard identifiers
  69. # ----------------------------------------------------
  70. def cleanTypeFilter(self, ty):
  71. ty = ty.replace("shared_ptr", "")
  72. ty = ty.replace("<", "")
  73. ty = ty.replace(">", "")
  74. ty = ty.replace("*", "")
  75. return ty
  76. # ----------------------------------------------------
  77. # Parse and clean properties from a class
  78. # ----------------------------------------------------
  79. def parseClassProperties(self, c):
  80. properties = []
  81. for pp in c["properties"]["public"]:
  82. classPP = {}
  83. classPP["name"] = pp["name"]
  84. pp["type"] = pp["type"].replace("Polycode::", "")
  85. pp["type"] = pp["type"].replace("std::", "")
  86. classPP["type"] = self.typeFilter(pp["type"])
  87. classPP["cleanType"] = self.cleanTypeFilter(classPP["type"])
  88. if pp["type"].find("POLYIGNORE") != -1:
  89. continue
  90. if pp["name"] == "" or pp["array"] == 1:
  91. continue
  92. if "::union" in pp["name"]:
  93. continue
  94. if "void" in pp["type"]:
  95. continue
  96. if "<" in pp["type"]:
  97. continue
  98. if pp["type"].replace("*", "") in self.ignoreClasses:
  99. continue
  100. if pp["type"].find("static ") != -1:
  101. classPP["isStatic"] = True
  102. if "defaltValue" in pp: #this is misspelled in parser library
  103. classPP["defaultValue"] = pp["defaltValue"]
  104. if 'doxygen' in pp:
  105. classPP["doc"] = self.cleanDocs(pp['doxygen'])
  106. if classPP["type"].find("*") == -1:
  107. properties.append(classPP)
  108. else:
  109. classPP["isStatic"] = False
  110. if pp["type"].find("vector") == -1 and pp["name"] != "setScale" and pp["name"] != "setPosition" and pp["name"] != "BUFFER_CACHE_PRECISION" and not pp["name"].isdigit():
  111. if classPP["type"].find("*") == -1: #do not add raw pointer accessors
  112. properties.append(classPP)
  113. return properties
  114. # ----------------------------------------------------
  115. # Parse and clean methods
  116. # ----------------------------------------------------
  117. def parseClassMethods(self, c):
  118. methods = []
  119. parsedMethods = []
  120. for pm in c["methods"]["public"]:
  121. method = {}
  122. method["name"] = pm["name"]
  123. if pm["rtnType"].find("static ") == -1:
  124. method["isStatic"] = False
  125. else:
  126. method["isStatic"] = True
  127. method["type"] = self.typeFilter(pm["rtnType"])
  128. method["cleanType"] = self.cleanTypeFilter(method["type"])
  129. if pm["name"] in parsedMethods or pm["name"].find("operator") > -1 or pm["rtnType"].find("POLYIGNORE") > -1 or pm["name"] in self.ignoreMethods :
  130. continue
  131. #ignore destructors
  132. if pm["name"] == "~"+c["name"]:
  133. continue
  134. method["parameters"] = []
  135. hasPointerParam = False
  136. for param in pm["parameters"]:
  137. mParam = {}
  138. if not "name" in param:
  139. continue
  140. if not "type" in param:
  141. continue
  142. if param["type"] == "0":
  143. continue
  144. mParam["name"] = param["name"]
  145. mParam["type"] = self.typeFilter(param["type"])
  146. mParam["cleanType"] = self.cleanTypeFilter(mParam["type"])
  147. if "defaltValue" in param:
  148. mParam["defaultValue"] = param["defaltValue"]
  149. method["parameters"].append(mParam)
  150. if mParam["type"].find("*") > -1:
  151. hasPointerParam = True
  152. if method["type"].find("*") == -1 and hasPointerParam == False: #do not add raw pointer return methods or methods that take raw pointers
  153. methods.append(method)
  154. parsedMethods.append(pm["name"])
  155. return methods
  156. # ----------------------------------------------------
  157. # Parse and clean data from a class
  158. # ----------------------------------------------------
  159. def parseClassData(self, c, ckey):
  160. classData = {}
  161. classData["name"] = ckey
  162. if len(c["inherits"]) > 0:
  163. if c["inherits"][0]["class"] not in self.ignoreClasses:
  164. classData["parent"] = c["inherits"][0]["class"]
  165. if 'doxygen' in c:
  166. classData["doc"] = self.cleanDocs(c['doxygen'])
  167. properties = self.parseClassProperties(c)
  168. classData["properties"] = []
  169. classData["staticProperties"] = []
  170. for p in properties:
  171. if p["isStatic"] == True:
  172. classData["staticProperties"].append(p)
  173. else:
  174. classData["properties"].append(p)
  175. classData["methods"] = self.parseClassMethods(c)
  176. return classData
  177. # ----------------------------------------------------
  178. # Parse files and create bindings
  179. # ----------------------------------------------------
  180. def createBindings(self):
  181. print("\nPolycode bindings generator v0.1\n")
  182. print("\033[93mGenerating bindings...")
  183. print("\033[93mParsing files from \033[92m[%s]" % (self.targetDir))
  184. if not os.path.isdir(self.targetDir):
  185. self.error("Target directory not valid!")
  186. return
  187. files = os.listdir(self.targetDir)
  188. filteredFiles = []
  189. for fileName in files:
  190. fileName = "%s/%s" % (self.targetDir, fileName)
  191. if os.path.isdir(fileName):
  192. continue
  193. head, tail = os.path.split(fileName)
  194. ignore = self.ignoreFiles.replace(" ", "").split(",")
  195. if tail.split(".")[1] == "h" and tail.split(".")[0] not in ignore:
  196. filteredFiles.append(fileName)
  197. self.processTargetFile(tail)
  198. for fileName in filteredFiles:
  199. try:
  200. f = open(fileName)
  201. contents = f.read()
  202. for s in self.symbolsToStrip:
  203. contents = contents.replace(s, "")
  204. cppHeader = CppHeaderParser.CppHeader(contents, "string")
  205. for ckey in cppHeader.classes:
  206. if ckey in self.ignoreClasses:
  207. continue
  208. if "::union" in ckey:
  209. continue
  210. print("\033[93mParsing class \033[0m[\033[92m%s\033[0m]" % ckey)
  211. c = cppHeader.classes[ckey]
  212. classData = self.parseClassData(c, ckey)
  213. self.processClass(classData)
  214. except CppHeaderParser.CppParseError as e:
  215. self.error(e)
  216. return
  217. self.finalize()