gen_jai.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. #-------------------------------------------------------------------------------
  2. # gen_jai.py
  3. #
  4. # Generate Jai bindings.
  5. #-------------------------------------------------------------------------------
  6. import textwrap
  7. import gen_ir
  8. import gen_util as util
  9. import os, shutil, sys
  10. bindings_root = 'sokol-jai'
  11. c_root = f'{bindings_root}/sokol/c'
  12. module_root = f'{bindings_root}/sokol'
  13. module_names = {
  14. 'slog_': 'log',
  15. 'sg_': 'gfx',
  16. 'sapp_': 'app',
  17. 'stm_': 'time',
  18. 'saudio_': 'audio',
  19. 'sgl_': 'gl',
  20. 'sdtx_': 'debugtext',
  21. 'sshape_': 'shape',
  22. 'sglue_': 'glue',
  23. }
  24. system_libs = {
  25. 'sg_': {
  26. 'windows': {
  27. 'd3d11': '#system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32"; #system_library,link_always "d3d11";',
  28. 'gl': '#system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32";',
  29. },
  30. 'macos': {
  31. 'metal': '#library,link_always "../../libclang_rt.osx"; #system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore"; #system_library,link_always "Metal"; #system_library,link_always "MetalKit";',
  32. 'gl': '#system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore"; #system_library,link_always "OpenGL";',
  33. },
  34. 'linux': {
  35. 'gl': '#system_library,link_always "libXcursor"; #system_library,link_always "libX11"; #system_library,link_always "libXi"; #system_library,link_always "libGL";',
  36. }
  37. },
  38. 'sapp_': {
  39. 'windows': {
  40. 'd3d11': '#system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32";',
  41. 'gl': '#system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32";',
  42. },
  43. 'macos': {
  44. 'metal': '#library "../../libclang_rt.osx"; #system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore";',
  45. 'gl': '#system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore";',
  46. },
  47. 'linux': {
  48. 'gl': '#system_library,link_always "libXcursor"; #system_library,link_always "libX11"; #system_library,link_always "libXi"; #system_library,link_always "libGL";',
  49. }
  50. },
  51. 'saudio_': {
  52. 'windows': {
  53. 'd3d11': '#system_library,link_always "ole32";',
  54. 'gl': '#system_library,link_always "ole32";',
  55. },
  56. 'macos': {
  57. 'metal': '#system_library,link_always "AudioToolbox";',
  58. 'gl': '#system_library,link_always "AudioToolbox";',
  59. },
  60. 'linux': {
  61. 'gl': '#system_library,link_always "asound"; #system_library,link_always "dl"; #system_library,link_always "pthread";',
  62. }
  63. }
  64. }
  65. c_source_names = {
  66. 'slog_': 'sokol_log.c',
  67. 'sg_': 'sokol_gfx.c',
  68. 'sapp_': 'sokol_app.c',
  69. 'sapp_sg': 'sokol_glue.c',
  70. 'stm_': 'sokol_time.c',
  71. 'saudio_': 'sokol_audio.c',
  72. 'sgl_': 'sokol_gl.c',
  73. 'sdtx_': 'sokol_debugtext.c',
  74. 'sshape_': 'sokol_shape.c',
  75. 'sglue_': 'sokol_glue.c',
  76. }
  77. ignores = [
  78. 'sdtx_printf',
  79. 'sdtx_vprintf',
  80. ]
  81. # NOTE: syntax for function results: "func_name.RESULT"
  82. overrides = {
  83. 'context': 'ctx', # reserved keyword
  84. 'SGL_NO_ERROR': 'SGL_ERROR_NO_ERROR',
  85. }
  86. prim_types = {
  87. 'int': 's32',
  88. 'bool': 'bool',
  89. 'char': 'u8',
  90. 'int8_t': 's8',
  91. 'uint8_t': 'u8',
  92. 'int16_t': 's16',
  93. 'uint16_t': 'u16',
  94. 'int32_t': 's32',
  95. 'uint32_t': 'u32',
  96. 'int64_t': 's64',
  97. 'uint64_t': 'u64',
  98. 'float': 'float',
  99. 'double': 'float64',
  100. 'uintptr_t': 'u64',
  101. 'intptr_t': 's64',
  102. 'size_t': 'u64'
  103. }
  104. prim_defaults = {
  105. 'int': '0',
  106. 'bool': 'false',
  107. 'int8_t': '0',
  108. 'uint8_t': '0',
  109. 'int16_t': '0',
  110. 'uint16_t': '0',
  111. 'int32_t': '0',
  112. 'uint32_t': '0',
  113. 'int64_t': '0',
  114. 'uint64_t': '0',
  115. 'float': '0.0',
  116. 'double': '0.0',
  117. 'uintptr_t': '0',
  118. 'intptr_t': '0',
  119. 'size_t': '0'
  120. }
  121. struct_types = []
  122. enum_types = []
  123. enum_items = {}
  124. out_lines = ''
  125. def reset_globals():
  126. global struct_types
  127. global enum_types
  128. global enum_items
  129. global out_lines
  130. struct_types = []
  131. enum_types = []
  132. enum_items = {}
  133. out_lines = ''
  134. def l(s):
  135. global out_lines
  136. out_lines += s + '\n'
  137. def c(s, indent=""):
  138. if not s:
  139. return
  140. if '\n' in s:
  141. l(f'{indent}/*')
  142. l(textwrap.indent(textwrap.dedent(s), prefix=f" {indent}"))
  143. l(f'{indent}*/')
  144. else:
  145. l(f'{indent}// {s.strip()}')
  146. def check_override(name, default=None):
  147. if name in overrides:
  148. return overrides[name]
  149. elif default is None:
  150. return name
  151. else:
  152. return default
  153. def check_ignore(name):
  154. return name in ignores
  155. # PREFIX_BLA_BLUB to BLA_BLUB, prefix_bla_blub to bla_blub
  156. def as_snake_case(s, prefix):
  157. outp = s
  158. if outp.lower().startswith(prefix):
  159. outp = outp[len(prefix):]
  160. return outp
  161. def get_jai_module_path(c_prefix):
  162. return f'{module_root}/{module_names[c_prefix]}'
  163. def get_csource_path(c_prefix):
  164. return f'{c_root}/{c_source_names[c_prefix]}'
  165. def make_jai_module_directory(c_prefix):
  166. path = get_jai_module_path(c_prefix)
  167. if not os.path.isdir(path):
  168. os.makedirs(path)
  169. def as_prim_type(s):
  170. return prim_types[s]
  171. def as_struct_or_enum_type(s, prefix):
  172. return s
  173. # PREFIX_ENUM_BLA_BLUB => BLA_BLUB, _PREFIX_ENUM_BLA_BLUB => BLA_BLUB
  174. def as_enum_item_name(s):
  175. outp = s.lstrip('_')
  176. parts = outp.split('_')[2:]
  177. outp = '_'.join(parts)
  178. if outp[0].isdigit():
  179. outp = '_' + outp
  180. return outp
  181. def enum_default_item(enum_name):
  182. return enum_items[enum_name][0]
  183. def is_prim_type(s):
  184. return s in prim_types
  185. def is_int_type(s):
  186. return s == "int"
  187. def is_struct_type(s):
  188. return s in struct_types
  189. def is_enum_type(s):
  190. return s in enum_types
  191. def is_const_prim_ptr(s):
  192. for prim_type in prim_types:
  193. if s == f"const {prim_type} *":
  194. return True
  195. return False
  196. def is_prim_ptr(s):
  197. for prim_type in prim_types:
  198. if s == f"{prim_type} *":
  199. return True
  200. return False
  201. def is_const_struct_ptr(s):
  202. for struct_type in struct_types:
  203. if s == f"const {struct_type} *":
  204. return True
  205. return False
  206. def type_default_value(s):
  207. return prim_defaults[s]
  208. def map_type(type, prefix, sub_type):
  209. if sub_type not in ['c_arg', 'struct_field']:
  210. sys.exit(f"Error: map_type(): unknown sub_type '{sub_type}")
  211. if type == "void":
  212. return ""
  213. elif is_struct_type(type):
  214. return as_struct_or_enum_type(type, prefix)
  215. elif is_enum_type(type):
  216. return as_struct_or_enum_type(type, prefix)
  217. elif util.is_void_ptr(type):
  218. return "*void"
  219. elif util.is_const_void_ptr(type):
  220. return "*void"
  221. elif util.is_string_ptr(type):
  222. return "*u8"
  223. elif is_const_struct_ptr(type):
  224. return f"*{as_struct_or_enum_type(util.extract_ptr_type(type), prefix)}"
  225. elif is_prim_ptr(type):
  226. return f"*{as_prim_type(util.extract_ptr_type(type))}"
  227. elif is_const_prim_ptr(type):
  228. return f"*{as_prim_type(util.extract_ptr_type(type))}"
  229. elif util.is_1d_array_type(type):
  230. array_type = util.extract_array_type(type)
  231. array_sizes = util.extract_array_sizes(type)
  232. return f"[{array_sizes[0]}]{map_type(array_type, prefix, sub_type)}"
  233. elif util.is_2d_array_type(type):
  234. array_type = util.extract_array_type(type)
  235. array_sizes = util.extract_array_sizes(type)
  236. return f"[{array_sizes[0]}][{array_sizes[1]}]{map_type(array_type, prefix, sub_type)}"
  237. elif util.is_func_ptr(type):
  238. res_type = funcptr_result_c(type, prefix)
  239. res_str = '' if res_type == '' else f' -> {res_type}'
  240. return f'({funcptr_args_c(type, prefix)}){res_str} #c_call'
  241. elif is_prim_type(type):
  242. return as_prim_type(type)
  243. else:
  244. sys.exit(f"Error map_type(): unknown type '{type}'")
  245. def funcdecl_args_c(decl, prefix):
  246. s = ''
  247. func_name = decl['name']
  248. for param_decl in decl['params']:
  249. if s != '':
  250. s += ', '
  251. param_name = param_decl['name']
  252. param_type = check_override(f'{func_name}.{param_name}', default=param_decl['type'])
  253. s += f"{param_name}: {map_type(param_type, prefix, 'c_arg')}"
  254. return s
  255. def funcptr_args_c(field_type, prefix):
  256. tokens = field_type[field_type.index('(*)')+4:-1].split(',')
  257. s = ''
  258. arg_index = 0
  259. for token in tokens:
  260. arg_type = token.strip()
  261. if s != '':
  262. s += ', '
  263. c_arg = map_type(arg_type, prefix, 'c_arg')
  264. if c_arg == '':
  265. return ''
  266. else:
  267. s += f'a{arg_index}: {c_arg}'
  268. arg_index += 1
  269. return s
  270. def funcptr_result_c(field_type, prefix):
  271. res_type = field_type[:field_type.index('(*)')].strip()
  272. return map_type(res_type, prefix, 'c_arg')
  273. def funcdecl_result_c(decl, prefix):
  274. func_name = decl['name']
  275. decl_type = decl['type']
  276. res_c_type = decl_type[:decl_type.index('(')].strip()
  277. return map_type(check_override(f'{func_name}.RESULT', default=res_c_type), prefix, 'c_arg')
  278. def get_system_libs(module, platform, backend):
  279. if module in system_libs:
  280. if platform in system_libs[module]:
  281. if backend in system_libs[module][platform]:
  282. libs = system_libs[module][platform][backend]
  283. if libs != '':
  284. return f"{libs}"
  285. return ''
  286. def gen_c_imports(inp, c_prefix, prefix):
  287. module_name = inp["module"]
  288. clib_prefix = f'sokol_{module_name}'
  289. clib_import = f'{clib_prefix}_clib'
  290. windows_d3d11_libs = get_system_libs(prefix, 'windows', 'd3d11')
  291. windows_gl_libs = get_system_libs(prefix, 'windows', 'gl')
  292. macos_metal_libs = get_system_libs(prefix, 'macos', 'metal')
  293. macos_gl_libs = get_system_libs(prefix, 'macos', 'gl')
  294. linux_gl_libs = get_system_libs(prefix, 'linux', 'gl')
  295. l( '#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false);')
  296. l( '')
  297. l( '#scope_export;')
  298. l( '')
  299. l( '#if OS == .WINDOWS {')
  300. l( ' #if USE_DLL {')
  301. l( ' #if USE_GL {')
  302. l(f' {windows_gl_libs}')
  303. l(f' #if DEBUG {{ {clib_import} :: #library "{clib_prefix}_windows_x64_gl_debug"; }}')
  304. l(f' else {{ {clib_import} :: #library "{clib_prefix}_windows_x64_gl_release"; }}')
  305. l( ' } else {')
  306. l(f' {windows_d3d11_libs}')
  307. l(f' #if DEBUG {{ {clib_import} :: #library "{clib_prefix}_windows_x64_d3d11_debug"; }}')
  308. l(f' else {{ {clib_import} :: #library "{clib_prefix}_windows_x64_d3d11_release"; }}')
  309. l( ' }')
  310. l( ' } else {')
  311. l( ' #if USE_GL {')
  312. l(f' {windows_gl_libs}')
  313. l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_windows_x64_gl_debug"; }}')
  314. l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_windows_x64_gl_release"; }}')
  315. l( ' } else {')
  316. l(f' {windows_d3d11_libs}')
  317. l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_windows_x64_d3d11_debug"; }}')
  318. l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_windows_x64_d3d11_release"; }}')
  319. l( ' }')
  320. l( ' }')
  321. l( '}')
  322. l( 'else #if OS == .MACOS {')
  323. l( ' #if USE_DLL {')
  324. l(f' #if USE_GL && CPU == .ARM64 && DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; }}')
  325. l(f' else #if USE_GL && CPU == .ARM64 && !DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; }}')
  326. l(f' else #if USE_GL && CPU == .X64 && DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; }}')
  327. l(f' else #if USE_GL && CPU == .X64 && !DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; }}')
  328. l(f' else #if !USE_GL && CPU == .ARM64 && DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; }}')
  329. l(f' else #if !USE_GL && CPU == .ARM64 && !DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; }}')
  330. l(f' else #if !USE_GL && CPU == .X64 && DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; }}')
  331. l(f' else #if !USE_GL && CPU == .X64 && !DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; }}')
  332. l( ' } else {')
  333. l( ' #if USE_GL {')
  334. l(f' {macos_gl_libs}')
  335. l( ' #if CPU == .ARM64 {')
  336. l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_arm64_gl_debug"; }}')
  337. l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_arm64_gl_release"; }}')
  338. l( ' } else {')
  339. l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_x64_gl_debug"; }}')
  340. l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_x64_gl_release"; }}')
  341. l( ' }')
  342. l( ' } else {')
  343. l(f' {macos_metal_libs}')
  344. l( ' #if CPU == .ARM64 {')
  345. l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_arm64_metal_debug"; }}')
  346. l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_arm64_metal_release"; }}')
  347. l( ' } else {')
  348. l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_x64_metal_debug"; }}')
  349. l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_x64_metal_release"; }}')
  350. l( ' }')
  351. l( ' }')
  352. l( ' }')
  353. l( '} else #if OS == .LINUX {')
  354. if linux_gl_libs:
  355. l(f' {linux_gl_libs}')
  356. l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_linux_x64_gl_debug"; }}')
  357. l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_linux_x64_gl_release"; }}')
  358. l( '} else #if OS == .WASM {')
  359. l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_wasm_gl_debug"; }}')
  360. l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_wasm_gl_release"; }}')
  361. l( '} else {')
  362. l( ' log_error("This OS is currently not supported");')
  363. l( '}')
  364. l( '')
  365. prefix = inp['prefix']
  366. for decl in inp['decls']:
  367. if decl['kind'] == 'func' and not decl['is_dep'] and not check_ignore(decl['name']):
  368. args = funcdecl_args_c(decl, prefix)
  369. res_type = funcdecl_result_c(decl, prefix)
  370. res_str = '-> void' if res_type == '' else f'-> {res_type}'
  371. if decl.get('comment'):
  372. c(decl['comment'])
  373. l(f"{decl['name']} :: ({args}) {res_str} #foreign {clib_import};")
  374. l('')
  375. def gen_consts(decl, prefix):
  376. for item in decl['items']:
  377. item_name = check_override(item['name'])
  378. c(item.get('comment'))
  379. l(f"{as_snake_case(item_name, prefix)} :: {item['value']};")
  380. l('')
  381. def gen_struct(decl, prefix):
  382. c_struct_name = check_override(decl['name'])
  383. struct_name = as_struct_or_enum_type(c_struct_name, prefix)
  384. l(f'{struct_name} :: struct {{')
  385. for field in decl['fields']:
  386. field_name = check_override(field['name'])
  387. field_type = map_type(check_override(f'{c_struct_name}.{field_name}', default=field['type']), prefix, 'struct_field')
  388. # any field name starting with _ is considered private
  389. if field_name.startswith('_'):
  390. l(f' _ : {field_type};')
  391. else:
  392. l(f' {field_name} : {field_type};')
  393. l('}')
  394. l('')
  395. def gen_enum(decl, prefix):
  396. enum_name = check_override(decl['name'])
  397. if decl.get('comment'):
  398. c(decl['comment'])
  399. l(f'{as_struct_or_enum_type(enum_name, prefix)} :: enum u32 {{')
  400. for item in decl['items']:
  401. item_name = as_enum_item_name(check_override(item['name']))
  402. if item_name != 'FORCE_U32' and item_name != 'NUM':
  403. if 'value' in item:
  404. l(f" {item_name} :: {item['value']};")
  405. else:
  406. l(f" {item_name};")
  407. l('}')
  408. l('')
  409. def gen_imports(dep_prefixes):
  410. for dep_prefix in dep_prefixes:
  411. dep_module_name = module_names[dep_prefix]
  412. l(f'#import,dir "../{dep_module_name}"(DEBUG = USE_DLL, USE_GL = USE_DLL, USE_DLL = USE_DLL);')
  413. l('')
  414. def gen_helpers(inp):
  415. if inp['prefix'] == 'sdtx_':
  416. l('sdtx_printf :: (s: string, args: ..Any) {')
  417. l(' #import "Basic";')
  418. l(' fstr := tprint(s, ..args);')
  419. l(' sdtx_putr(to_c_string(fstr), xx fstr.count);')
  420. l('}')
  421. def gen_module(inp, c_prefix, dep_prefixes):
  422. pre_parse(inp)
  423. l('// machine generated, do not edit')
  424. if inp.get('comment'):
  425. l('')
  426. c(inp['comment'])
  427. gen_imports(dep_prefixes)
  428. gen_helpers(inp)
  429. prefix = inp['prefix']
  430. gen_c_imports(inp, c_prefix, prefix)
  431. for decl in inp['decls']:
  432. if not decl['is_dep']:
  433. kind = decl['kind']
  434. if kind == 'consts':
  435. gen_consts(decl, prefix)
  436. elif not check_ignore(decl['name']):
  437. if kind == 'struct':
  438. gen_struct(decl, prefix)
  439. elif kind == 'enum':
  440. gen_enum(decl, prefix)
  441. def pre_parse(inp):
  442. global struct_types
  443. global enum_types
  444. for decl in inp['decls']:
  445. kind = decl['kind']
  446. if kind == 'struct':
  447. struct_types.append(decl['name'])
  448. elif kind == 'enum':
  449. enum_name = decl['name']
  450. enum_types.append(enum_name)
  451. enum_items[enum_name] = []
  452. for item in decl['items']:
  453. enum_items[enum_name].append(as_enum_item_name(item['name']))
  454. def prepare():
  455. print('=== Generating Jai bindings:')
  456. if not os.path.isdir(module_root):
  457. os.makedirs(module_root)
  458. if not os.path.isdir(c_root):
  459. os.makedirs(c_root)
  460. def gen(c_header_path, c_prefix, dep_c_prefixes):
  461. if not c_prefix in module_names:
  462. print(f' >> warning: skipping generation for {c_prefix} prefix...')
  463. return
  464. reset_globals()
  465. make_jai_module_directory(c_prefix)
  466. print(f' {c_header_path} => {module_names[c_prefix]}')
  467. shutil.copyfile(c_header_path, f'{c_root}/{os.path.basename(c_header_path)}')
  468. csource_path = get_csource_path(c_prefix)
  469. module_name = module_names[c_prefix]
  470. ir = gen_ir.gen(c_header_path, csource_path, module_name, c_prefix, dep_c_prefixes, with_comments=True)
  471. gen_module(ir, c_prefix, dep_c_prefixes)
  472. with open(f"{module_root}/{ir['module']}/module.jai", 'w', newline='\n') as f_outp:
  473. f_outp.write(out_lines)