gendynapi.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. #!/usr/bin/env python3
  2. # Simple DirectMedia Layer
  3. # Copyright (C) 1997-2022 Sam Lantinga <[email protected]>
  4. #
  5. # This software is provided 'as-is', without any express or implied
  6. # warranty. In no event will the authors be held liable for any damages
  7. # arising from the use of this software.
  8. #
  9. # Permission is granted to anyone to use this software for any purpose,
  10. # including commercial applications, and to alter it and redistribute it
  11. # freely, subject to the following restrictions:
  12. #
  13. # 1. The origin of this software must not be misrepresented; you must not
  14. # claim that you wrote the original software. If you use this software
  15. # in a product, an acknowledgment in the product documentation would be
  16. # appreciated but is not required.
  17. # 2. Altered source versions must be plainly marked as such, and must not be
  18. # misrepresented as being the original software.
  19. # 3. This notice may not be removed or altered from any source distribution.
  20. # WHAT IS THIS?
  21. # When you add a public API to SDL, please run this script, make sure the
  22. # output looks sane (git diff, it adds to existing files), and commit it.
  23. # It keeps the dynamic API jump table operating correctly.
  24. #
  25. # OS-specific API:
  26. # After running the script, you have to manually add #ifdef __WIN32__
  27. # or similar around the function in 'SDL_dynapi_procs.h'
  28. #
  29. import re
  30. import os
  31. import argparse
  32. import pprint
  33. import json
  34. dir_path = os.path.dirname(os.path.realpath(__file__))
  35. sdl_include_dir = dir_path + "/../../include/SDL3/"
  36. sdl_dynapi_procs_h = dir_path + "/../../src/dynapi/SDL_dynapi_procs.h"
  37. sdl_dynapi_overrides_h = dir_path + "/../../src/dynapi/SDL_dynapi_overrides.h"
  38. sdl_dynapi_sym = dir_path + "/../../src/dynapi/SDL_dynapi.sym"
  39. full_API = []
  40. def main():
  41. # Parse 'sdl_dynapi_procs_h' file to find existing functions
  42. existing_procs = find_existing_procs()
  43. # Get list of SDL headers
  44. sdl_list_includes = get_header_list()
  45. reg_externC = re.compile('.*extern[ "]*C[ "].*')
  46. reg_comment_remove_content = re.compile('\/\*.*\*/')
  47. reg_parsing_function = re.compile('(.*SDLCALL[^\(\)]*) ([a-zA-Z0-9_]+) *\((.*)\) *;.*')
  48. #eg:
  49. # void (SDLCALL *callback)(void*, int)
  50. # \1(\2)\3
  51. reg_parsing_callback = re.compile('([^\(\)]*)\(([^\(\)]+)\)(.*)')
  52. for filename in sdl_list_includes:
  53. if args.debug:
  54. print("Parse header: %s" % filename)
  55. input = open(filename)
  56. parsing_function = False
  57. current_func = ""
  58. parsing_comment = False
  59. current_comment = ""
  60. for line in input:
  61. # Discard pre-processor directives ^#.*
  62. if line.startswith("#"):
  63. continue
  64. # Discard "extern C" line
  65. match = reg_externC.match(line)
  66. if match:
  67. continue
  68. # Remove one line comment /* ... */
  69. # eg: extern DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open_path(const char *path, int bExclusive /* = false */);
  70. line = reg_comment_remove_content.sub('', line)
  71. # Get the comment block /* ... */ across several lines
  72. match_start = "/*" in line
  73. match_end = "*/" in line
  74. if match_start and match_end:
  75. continue
  76. if match_start:
  77. parsing_comment = True
  78. current_comment = line
  79. continue
  80. if match_end:
  81. parsing_comment = False
  82. current_comment += line
  83. continue
  84. if parsing_comment:
  85. current_comment += line
  86. continue
  87. # Get the function prototype across several lines
  88. if parsing_function:
  89. # Append to the current function
  90. current_func += " "
  91. current_func += line.strip()
  92. else:
  93. # if is contains "extern", start grabbing
  94. if "extern" not in line:
  95. continue
  96. # Start grabbing the new function
  97. current_func = line.strip()
  98. parsing_function = True;
  99. # If it contains ';', then the function is complete
  100. if ";" not in current_func:
  101. continue
  102. # Got function/comment, reset vars
  103. parsing_function = False;
  104. func = current_func
  105. comment = current_comment
  106. current_func = ""
  107. current_comment = ""
  108. # Discard if it doesn't contain 'SDLCALL'
  109. if "SDLCALL" not in func:
  110. if args.debug:
  111. print(" Discard: " + func)
  112. continue
  113. if args.debug:
  114. print(" Raw data: " + func);
  115. # Replace unusual stuff...
  116. func = func.replace("SDL_PRINTF_VARARG_FUNC(1)", "");
  117. func = func.replace("SDL_PRINTF_VARARG_FUNC(2)", "");
  118. func = func.replace("SDL_PRINTF_VARARG_FUNC(3)", "");
  119. func = func.replace("SDL_SCANF_VARARG_FUNC(2)", "");
  120. func = func.replace("__attribute__((analyzer_noreturn))", "");
  121. func = func.replace("SDL_MALLOC", "");
  122. func = func.replace("SDL_ALLOC_SIZE2(1, 2)", "");
  123. func = func.replace("SDL_ALLOC_SIZE(2)", "");
  124. # Should be a valid function here
  125. match = reg_parsing_function.match(func)
  126. if not match:
  127. print("Cannot parse: "+ func)
  128. exit(-1)
  129. func_ret = match.group(1)
  130. func_name = match.group(2)
  131. func_params = match.group(3)
  132. #
  133. # Parse return value
  134. #
  135. func_ret = func_ret.replace('extern', ' ')
  136. func_ret = func_ret.replace('SDLCALL', ' ')
  137. func_ret = func_ret.replace('DECLSPEC', ' ')
  138. # Remove trailling spaces in front of '*'
  139. tmp = ""
  140. while func_ret != tmp:
  141. tmp = func_ret
  142. func_ret = func_ret.replace(' ', ' ')
  143. func_ret = func_ret.replace(' *', '*')
  144. func_ret = func_ret.strip()
  145. #
  146. # Parse parameters
  147. #
  148. func_params = func_params.strip()
  149. if func_params == "":
  150. func_params = "void"
  151. # Identify each function parameters with type and name
  152. # (eventually there are callbacks of several parameters)
  153. tmp = func_params.split(',')
  154. tmp2 = []
  155. param = ""
  156. for t in tmp:
  157. if param == "":
  158. param = t
  159. else:
  160. param = param + "," + t
  161. # Identify a callback or parameter when there is same count of '(' and ')'
  162. if param.count('(') == param.count(')'):
  163. tmp2.append(param.strip())
  164. param = ""
  165. # Process each parameters, separation name and type
  166. func_param_type = []
  167. func_param_name = []
  168. for t in tmp2:
  169. if t == "void":
  170. func_param_type.append(t)
  171. func_param_name.append("")
  172. continue
  173. if t == "...":
  174. func_param_type.append(t)
  175. func_param_name.append("")
  176. continue
  177. param_name = ""
  178. # parameter is a callback
  179. if '(' in t:
  180. match = reg_parsing_callback.match(t)
  181. if not match:
  182. print("cannot parse callback: " + t);
  183. exit(-1)
  184. a = match.group(1).strip()
  185. b = match.group(2).strip()
  186. c = match.group(3).strip()
  187. try:
  188. (param_type, param_name) = b.rsplit('*', 1)
  189. except:
  190. param_type = t;
  191. param_name = "param_name_not_specified"
  192. # bug rsplit ??
  193. if param_name == "":
  194. param_name = "param_name_not_specified"
  195. # recontruct a callback name for future parsing
  196. func_param_type.append(a + " (" + param_type.strip() + " *REWRITE_NAME)" + c)
  197. func_param_name.append(param_name.strip())
  198. continue
  199. # array like "char *buf[]"
  200. has_array = False
  201. if t.endswith("[]"):
  202. t = t.replace("[]", "")
  203. has_array = True
  204. # pointer
  205. if '*' in t:
  206. try:
  207. (param_type, param_name) = t.rsplit('*', 1)
  208. except:
  209. param_type = t;
  210. param_name = "param_name_not_specified"
  211. # bug rsplit ??
  212. if param_name == "":
  213. param_name = "param_name_not_specified"
  214. val = param_type.strip() + "*REWRITE_NAME"
  215. # Remove trailling spaces in front of '*'
  216. tmp = ""
  217. while val != tmp:
  218. tmp = val
  219. val = val.replace(' ', ' ')
  220. val = val.replace(' *', '*')
  221. # first occurence
  222. val = val.replace('*', ' *', 1)
  223. val = val.strip()
  224. else: # non pointer
  225. # cut-off last word on
  226. try:
  227. (param_type, param_name) = t.rsplit(' ', 1)
  228. except:
  229. param_type = t;
  230. param_name = "param_name_not_specified"
  231. val = param_type.strip() + " REWRITE_NAME"
  232. # set back array
  233. if has_array:
  234. val += "[]"
  235. func_param_type.append(val)
  236. func_param_name.append(param_name.strip())
  237. new_proc = {}
  238. # Return value type
  239. new_proc['retval'] = func_ret
  240. # List of parameters (type + anonymized param name 'REWRITE_NAME')
  241. new_proc['parameter'] = func_param_type
  242. # Real parameter name, or 'param_name_not_specified'
  243. new_proc['parameter_name'] = func_param_name
  244. # Function name
  245. new_proc['name'] = func_name
  246. # Header file
  247. new_proc['header'] = os.path.basename(filename)
  248. # Function comment
  249. new_proc['comment'] = comment
  250. full_API.append(new_proc)
  251. if args.debug:
  252. pprint.pprint(new_proc);
  253. print("\n")
  254. if func_name not in existing_procs:
  255. print("NEW " + func)
  256. add_dyn_api(new_proc)
  257. # For-End line in input
  258. input.close()
  259. # For-End parsing all files of sdl_list_includes
  260. # Dump API into a json file
  261. full_API_json();
  262. # Dump API into a json file
  263. def full_API_json():
  264. if args.dump:
  265. filename = 'sdl.json'
  266. with open(filename, 'w') as f:
  267. json.dump(full_API, f, indent=4, sort_keys=True)
  268. print("dump API to '%s'" % filename);
  269. # Parse 'sdl_dynapi_procs_h' file to find existing functions
  270. def find_existing_procs():
  271. reg = re.compile('SDL_DYNAPI_PROC\([^,]*,([^,]*),.*\)')
  272. ret = []
  273. input = open(sdl_dynapi_procs_h)
  274. for line in input:
  275. match = reg.match(line)
  276. if not match:
  277. continue
  278. existing_func = match.group(1)
  279. ret.append(existing_func);
  280. # print(existing_func)
  281. input.close()
  282. return ret
  283. # Get list of SDL headers
  284. def get_header_list():
  285. reg = re.compile('^.*\.h$')
  286. ret = []
  287. tmp = os.listdir(sdl_include_dir)
  288. for f in tmp:
  289. # Only *.h files
  290. match = reg.match(f)
  291. if not match:
  292. if args.debug:
  293. print("Skip %s" % f)
  294. continue
  295. ret.append(sdl_include_dir + f)
  296. return ret
  297. # Write the new API in files: _procs.h _overrivides.h and .sym
  298. def add_dyn_api(proc):
  299. func_name = proc['name']
  300. func_ret = proc['retval']
  301. func_argtype = proc['parameter']
  302. # File: SDL_dynapi_procs.h
  303. #
  304. # Add at last
  305. # SDL_DYNAPI_PROC(SDL_EGLConfig,SDL_EGL_GetCurrentEGLConfig,(void),(),return)
  306. f = open(sdl_dynapi_procs_h, "a")
  307. dyn_proc = "SDL_DYNAPI_PROC(" + func_ret + "," + func_name + ",("
  308. i = ord('a')
  309. remove_last = False
  310. for argtype in func_argtype:
  311. # Special case, void has no parameter name
  312. if argtype == "void":
  313. dyn_proc += "void"
  314. continue
  315. # Var name: a, b, c, ...
  316. varname = chr(i)
  317. i += 1
  318. tmp = argtype.replace("REWRITE_NAME", varname)
  319. dyn_proc += tmp + ", "
  320. remove_last = True
  321. # remove last 2 char ', '
  322. if remove_last:
  323. dyn_proc = dyn_proc[:-1]
  324. dyn_proc = dyn_proc[:-1]
  325. dyn_proc += "),("
  326. i = ord('a')
  327. remove_last = False
  328. for argtype in func_argtype:
  329. # Special case, void has no parameter name
  330. if argtype == "void":
  331. continue
  332. # Special case, '...' has no parameter name
  333. if argtype == "...":
  334. continue
  335. # Var name: a, b, c, ...
  336. varname = chr(i)
  337. i += 1
  338. dyn_proc += varname + ","
  339. remove_last = True
  340. # remove last char ','
  341. if remove_last:
  342. dyn_proc = dyn_proc[:-1]
  343. dyn_proc += "),"
  344. if func_ret != "void":
  345. dyn_proc += "return"
  346. dyn_proc += ")"
  347. f.write(dyn_proc + "\n")
  348. f.close()
  349. # File: SDL_dynapi_overrides.h
  350. #
  351. # Add at last
  352. # "#define SDL_DelayNS SDL_DelayNS_REAL
  353. f = open(sdl_dynapi_overrides_h, "a")
  354. f.write("#define " + func_name + " " + func_name + "_REAL\n")
  355. f.close()
  356. # File: SDL_dynapi.sym
  357. #
  358. # Add before "extra symbols go here" line
  359. input = open(sdl_dynapi_sym)
  360. new_input = []
  361. for line in input:
  362. if "extra symbols go here" in line:
  363. new_input.append(" " + func_name + ";\n")
  364. new_input.append(line)
  365. input.close()
  366. f = open(sdl_dynapi_sym, 'w')
  367. for line in new_input:
  368. f.write(line)
  369. f.close()
  370. if __name__ == '__main__':
  371. parser = argparse.ArgumentParser()
  372. parser.add_argument('--dump', help='output all SDL API into a .json file', action='store_true')
  373. parser.add_argument('--debug', help='add debug traces', action='store_true')
  374. args = parser.parse_args()
  375. try:
  376. main()
  377. except Exception as e:
  378. print(e)
  379. exit(-1)
  380. print("done!")
  381. exit(0)