make_interface_header.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import difflib
  2. import json
  3. from collections import OrderedDict
  4. import methods
  5. BASE_TYPES = [
  6. "void",
  7. "int",
  8. "int8_t",
  9. "uint8_t",
  10. "int16_t",
  11. "uint16_t",
  12. "int32_t",
  13. "uint32_t",
  14. "int64_t",
  15. "uint64_t",
  16. "size_t",
  17. "char",
  18. "char16_t",
  19. "char32_t",
  20. "wchar_t",
  21. "float",
  22. "double",
  23. ]
  24. def run(target, source, env):
  25. filename = str(source[0])
  26. buffer = methods.get_buffer(filename)
  27. data = json.loads(buffer, object_pairs_hook=OrderedDict)
  28. check_formatting(buffer.decode("utf-8"), data, filename)
  29. check_allowed_keys(data, ["_copyright", "$schema", "format_version", "types", "interface"])
  30. valid_data_types = {}
  31. for type in BASE_TYPES:
  32. valid_data_types[type] = True
  33. with methods.generated_wrapper(str(target[0])) as file:
  34. file.write("""\
  35. #ifndef __cplusplus
  36. #include <stddef.h>
  37. #include <stdint.h>
  38. typedef uint32_t char32_t;
  39. typedef uint16_t char16_t;
  40. #else
  41. #include <cstddef>
  42. #include <cstdint>
  43. extern "C" {
  44. #endif
  45. """)
  46. handles = []
  47. for type in data["types"]:
  48. kind = type["kind"]
  49. check_type(kind, type, valid_data_types)
  50. valid_data_types[type["name"]] = True
  51. if "description" in type:
  52. write_doc(file, type["description"])
  53. if kind == "handle":
  54. check_allowed_keys(type, ["name", "kind"], ["const", "parent", "description", "deprecated"])
  55. if "parent" in type and type["parent"] not in handles:
  56. raise UnknownTypeError(type["parent"], type["name"])
  57. # @todo In the future, let's write these as `struct *` so the compiler can help us with type checking.
  58. type["type"] = "void*" if not type.get("const", False) else "const void*"
  59. write_simple_type(file, type)
  60. handles.append(type["name"])
  61. elif kind == "alias":
  62. check_allowed_keys(type, ["name", "kind", "type"], ["description", "deprecated"])
  63. write_simple_type(file, type)
  64. elif kind == "enum":
  65. check_allowed_keys(type, ["name", "kind", "values"], ["description", "deprecated"])
  66. write_enum_type(file, type)
  67. elif kind == "function":
  68. check_allowed_keys(type, ["name", "kind", "return_value", "arguments"], ["description", "deprecated"])
  69. write_function_type(file, type)
  70. elif kind == "struct":
  71. check_allowed_keys(type, ["name", "kind", "members"], ["description", "deprecated"])
  72. write_struct_type(file, type)
  73. else:
  74. raise Exception(f"Unknown kind of type: {kind}")
  75. for interface in data["interface"]:
  76. check_type("function", interface, valid_data_types)
  77. check_allowed_keys(
  78. interface,
  79. ["name", "return_value", "arguments", "since", "description"],
  80. ["see", "legacy_type_name", "deprecated"],
  81. )
  82. write_interface(file, interface)
  83. file.write("""\
  84. #ifdef __cplusplus
  85. }
  86. #endif
  87. """)
  88. # Serialize back into JSON in order to see if the formatting remains the same.
  89. def check_formatting(buffer, data, filename):
  90. buffer2 = json.dumps(data, indent=4)
  91. lines1 = buffer.splitlines()
  92. lines2 = buffer2.splitlines()
  93. diff = difflib.unified_diff(
  94. lines1,
  95. lines2,
  96. fromfile="a/" + filename,
  97. tofile="b/" + filename,
  98. lineterm="",
  99. )
  100. diff = list(diff)
  101. if len(diff) > 0:
  102. print(" *** Apply this patch to fix: ***\n")
  103. print("\n".join(diff))
  104. raise Exception(f"Formatting issues in {filename}")
  105. def check_allowed_keys(data, required, optional=[]):
  106. keys = data.keys()
  107. allowed = required + optional
  108. for k in keys:
  109. if k not in allowed:
  110. raise Exception(f"Found unknown key '{k}'")
  111. for r in required:
  112. if r not in keys:
  113. raise Exception(f"Missing required key '{r}'")
  114. class UnknownTypeError(Exception):
  115. def __init__(self, unknown, parent, item=None):
  116. self.unknown = unknown
  117. self.parent = parent
  118. if item:
  119. msg = f"Unknown type '{unknown}' for '{item}' used in '{parent}'"
  120. else:
  121. msg = f"Unknown type '{unknown}' used in '{parent}'"
  122. super().__init__(msg)
  123. def base_type_name(type_name):
  124. if type_name.startswith("const "):
  125. type_name = type_name[6:]
  126. if type_name.endswith("*"):
  127. type_name = type_name[:-1]
  128. return type_name
  129. def format_type_and_name(type, name=None):
  130. ret = type
  131. if ret[-1] == "*":
  132. ret = ret[:-1] + " *"
  133. if name:
  134. if ret[-1] == "*":
  135. ret = ret + name
  136. else:
  137. ret = ret + " " + name
  138. return ret
  139. def check_type(kind, type, valid_data_types):
  140. if kind == "alias":
  141. if base_type_name(type["type"]) not in valid_data_types:
  142. raise UnknownTypeError(type["type"], type["name"])
  143. elif kind == "struct":
  144. for member in type["members"]:
  145. if base_type_name(member["type"]) not in valid_data_types:
  146. raise UnknownTypeError(member["type"], type["name"], member["name"])
  147. elif kind == "function":
  148. for arg in type["arguments"]:
  149. if base_type_name(arg["type"]) not in valid_data_types:
  150. raise UnknownTypeError(arg["type"], type["name"], arg.get("name"))
  151. if "return_value" in type:
  152. if base_type_name(type["return_value"]["type"]) not in valid_data_types:
  153. raise UnknownTypeError(type["return_value"]["type"], type["name"])
  154. def write_doc(file, doc, indent=""):
  155. if len(doc) == 1:
  156. file.write(f"{indent}/* {doc[0]} */\n")
  157. return
  158. first = True
  159. for line in doc:
  160. if first:
  161. file.write(indent + "/*")
  162. first = False
  163. else:
  164. file.write(indent + " *")
  165. if line != "":
  166. file.write(" " + line)
  167. file.write("\n")
  168. file.write(indent + " */\n")
  169. def make_deprecated_note(type):
  170. if "deprecated" not in type:
  171. return ""
  172. return f" /* {type['deprecated']} */"
  173. def write_simple_type(file, type):
  174. file.write(f"typedef {format_type_and_name(type['type'], type['name'])};{make_deprecated_note(type)}\n")
  175. def write_enum_type(file, enum):
  176. file.write("typedef enum {\n")
  177. for value in enum["values"]:
  178. check_allowed_keys(value, ["name", "value"], ["description", "deprecated"])
  179. if "description" in value:
  180. write_doc(file, value["description"], "\t")
  181. file.write(f"\t{value['name']} = {value['value']},\n")
  182. file.write(f"}} {enum['name']};{make_deprecated_note(enum)}\n\n")
  183. def make_args_text(args):
  184. combined = []
  185. for arg in args:
  186. check_allowed_keys(arg, ["type"], ["name", "description"])
  187. combined.append(format_type_and_name(arg["type"], arg.get("name")))
  188. return ", ".join(combined)
  189. def write_function_type(file, fn):
  190. args_text = make_args_text(fn["arguments"]) if ("arguments" in fn) else ""
  191. name_and_args = f"(*{fn['name']})({args_text})"
  192. file.write(
  193. f"typedef {format_type_and_name(fn['return_value']['type'], name_and_args)};{make_deprecated_note(fn)}\n"
  194. )
  195. def write_struct_type(file, struct):
  196. file.write("typedef struct {\n")
  197. for member in struct["members"]:
  198. check_allowed_keys(member, ["name", "type"], ["description"])
  199. if "description" in member:
  200. write_doc(file, member["description"], "\t")
  201. file.write(f"\t{format_type_and_name(member['type'], member['name'])};\n")
  202. file.write(f"}} {struct['name']};{make_deprecated_note(struct)}\n\n")
  203. def write_interface(file, interface):
  204. doc = [
  205. f"@name {interface['name']}",
  206. f"@since {interface['since']}",
  207. ]
  208. if "deprecated" in interface:
  209. if interface["deprecated"].lower().startswith("deprecated "):
  210. parts = interface["deprecated"].split(" ", 1)
  211. interface["deprecated"] = parts[1]
  212. doc.append(f"@deprecated {interface['deprecated']}")
  213. doc += [
  214. "",
  215. interface["description"][0],
  216. ]
  217. if len(interface["description"]) > 1:
  218. doc.append("")
  219. doc += interface["description"][1:]
  220. if "arguments" in interface:
  221. doc.append("")
  222. for arg in interface["arguments"]:
  223. if "description" not in arg:
  224. raise Exception(f"Interface function {interface['name']} is missing docs for {arg['name']} argument")
  225. arg_doc = " ".join(arg["description"])
  226. doc.append(f"@param {arg['name']} {arg_doc}")
  227. if "return_value" in interface and interface["return_value"]["type"] != "void":
  228. if "description" not in interface["return_value"]:
  229. raise Exception(f"Interface function {interface['name']} is missing docs for return value")
  230. ret_doc = " ".join(interface["return_value"]["description"])
  231. doc.append("")
  232. doc.append(f"@return {ret_doc}")
  233. if "see" in interface:
  234. doc.append("")
  235. for see in interface["see"]:
  236. doc.append(f"@see {see}")
  237. file.write("/**\n")
  238. for d in doc:
  239. if d != "":
  240. file.write(f" * {d}\n")
  241. else:
  242. file.write(" *\n")
  243. file.write(" */\n")
  244. fn = interface.copy()
  245. if "deprecated" in fn:
  246. del fn["deprecated"]
  247. fn["name"] = "GDExtensionInterface" + "".join(word.capitalize() for word in interface["name"].split("_"))
  248. write_function_type(file, fn)
  249. file.write("\n")