| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- import difflib
- import json
- from collections import OrderedDict
- import methods
- BASE_TYPES = [
- "void",
- "int",
- "int8_t",
- "uint8_t",
- "int16_t",
- "uint16_t",
- "int32_t",
- "uint32_t",
- "int64_t",
- "uint64_t",
- "size_t",
- "char",
- "char16_t",
- "char32_t",
- "wchar_t",
- "float",
- "double",
- ]
- def run(target, source, env):
- filename = str(source[0])
- buffer = methods.get_buffer(filename)
- data = json.loads(buffer, object_pairs_hook=OrderedDict)
- check_formatting(buffer.decode("utf-8"), data, filename)
- check_allowed_keys(data, ["_copyright", "$schema", "format_version", "types", "interface"])
- valid_data_types = {}
- for type in BASE_TYPES:
- valid_data_types[type] = True
- with methods.generated_wrapper(str(target[0])) as file:
- file.write("""\
- #ifndef __cplusplus
- #include <stddef.h>
- #include <stdint.h>
- typedef uint32_t char32_t;
- typedef uint16_t char16_t;
- #else
- #include <cstddef>
- #include <cstdint>
- extern "C" {
- #endif
- """)
- handles = []
- for type in data["types"]:
- kind = type["kind"]
- check_type(kind, type, valid_data_types)
- valid_data_types[type["name"]] = True
- if "description" in type:
- write_doc(file, type["description"])
- if kind == "handle":
- check_allowed_keys(type, ["name", "kind"], ["const", "parent", "description", "deprecated"])
- if "parent" in type and type["parent"] not in handles:
- raise UnknownTypeError(type["parent"], type["name"])
- # @todo In the future, let's write these as `struct *` so the compiler can help us with type checking.
- type["type"] = "void*" if not type.get("const", False) else "const void*"
- write_simple_type(file, type)
- handles.append(type["name"])
- elif kind == "alias":
- check_allowed_keys(type, ["name", "kind", "type"], ["description", "deprecated"])
- write_simple_type(file, type)
- elif kind == "enum":
- check_allowed_keys(type, ["name", "kind", "values"], ["description", "deprecated"])
- write_enum_type(file, type)
- elif kind == "function":
- check_allowed_keys(type, ["name", "kind", "return_value", "arguments"], ["description", "deprecated"])
- write_function_type(file, type)
- elif kind == "struct":
- check_allowed_keys(type, ["name", "kind", "members"], ["description", "deprecated"])
- write_struct_type(file, type)
- else:
- raise Exception(f"Unknown kind of type: {kind}")
- for interface in data["interface"]:
- check_type("function", interface, valid_data_types)
- check_allowed_keys(
- interface,
- ["name", "return_value", "arguments", "since", "description"],
- ["see", "legacy_type_name", "deprecated"],
- )
- write_interface(file, interface)
- file.write("""\
- #ifdef __cplusplus
- }
- #endif
- """)
- # Serialize back into JSON in order to see if the formatting remains the same.
- def check_formatting(buffer, data, filename):
- buffer2 = json.dumps(data, indent=4)
- lines1 = buffer.splitlines()
- lines2 = buffer2.splitlines()
- diff = difflib.unified_diff(
- lines1,
- lines2,
- fromfile="a/" + filename,
- tofile="b/" + filename,
- lineterm="",
- )
- diff = list(diff)
- if len(diff) > 0:
- print(" *** Apply this patch to fix: ***\n")
- print("\n".join(diff))
- raise Exception(f"Formatting issues in {filename}")
- def check_allowed_keys(data, required, optional=[]):
- keys = data.keys()
- allowed = required + optional
- for k in keys:
- if k not in allowed:
- raise Exception(f"Found unknown key '{k}'")
- for r in required:
- if r not in keys:
- raise Exception(f"Missing required key '{r}'")
- class UnknownTypeError(Exception):
- def __init__(self, unknown, parent, item=None):
- self.unknown = unknown
- self.parent = parent
- if item:
- msg = f"Unknown type '{unknown}' for '{item}' used in '{parent}'"
- else:
- msg = f"Unknown type '{unknown}' used in '{parent}'"
- super().__init__(msg)
- def base_type_name(type_name):
- if type_name.startswith("const "):
- type_name = type_name[6:]
- if type_name.endswith("*"):
- type_name = type_name[:-1]
- return type_name
- def format_type_and_name(type, name=None):
- ret = type
- if ret[-1] == "*":
- ret = ret[:-1] + " *"
- if name:
- if ret[-1] == "*":
- ret = ret + name
- else:
- ret = ret + " " + name
- return ret
- def check_type(kind, type, valid_data_types):
- if kind == "alias":
- if base_type_name(type["type"]) not in valid_data_types:
- raise UnknownTypeError(type["type"], type["name"])
- elif kind == "struct":
- for member in type["members"]:
- if base_type_name(member["type"]) not in valid_data_types:
- raise UnknownTypeError(member["type"], type["name"], member["name"])
- elif kind == "function":
- for arg in type["arguments"]:
- if base_type_name(arg["type"]) not in valid_data_types:
- raise UnknownTypeError(arg["type"], type["name"], arg.get("name"))
- if "return_value" in type:
- if base_type_name(type["return_value"]["type"]) not in valid_data_types:
- raise UnknownTypeError(type["return_value"]["type"], type["name"])
- def write_doc(file, doc, indent=""):
- if len(doc) == 1:
- file.write(f"{indent}/* {doc[0]} */\n")
- return
- first = True
- for line in doc:
- if first:
- file.write(indent + "/*")
- first = False
- else:
- file.write(indent + " *")
- if line != "":
- file.write(" " + line)
- file.write("\n")
- file.write(indent + " */\n")
- def make_deprecated_note(type):
- if "deprecated" not in type:
- return ""
- return f" /* {type['deprecated']} */"
- def write_simple_type(file, type):
- file.write(f"typedef {format_type_and_name(type['type'], type['name'])};{make_deprecated_note(type)}\n")
- def write_enum_type(file, enum):
- file.write("typedef enum {\n")
- for value in enum["values"]:
- check_allowed_keys(value, ["name", "value"], ["description", "deprecated"])
- if "description" in value:
- write_doc(file, value["description"], "\t")
- file.write(f"\t{value['name']} = {value['value']},\n")
- file.write(f"}} {enum['name']};{make_deprecated_note(enum)}\n\n")
- def make_args_text(args):
- combined = []
- for arg in args:
- check_allowed_keys(arg, ["type"], ["name", "description"])
- combined.append(format_type_and_name(arg["type"], arg.get("name")))
- return ", ".join(combined)
- def write_function_type(file, fn):
- args_text = make_args_text(fn["arguments"]) if ("arguments" in fn) else ""
- name_and_args = f"(*{fn['name']})({args_text})"
- file.write(
- f"typedef {format_type_and_name(fn['return_value']['type'], name_and_args)};{make_deprecated_note(fn)}\n"
- )
- def write_struct_type(file, struct):
- file.write("typedef struct {\n")
- for member in struct["members"]:
- check_allowed_keys(member, ["name", "type"], ["description"])
- if "description" in member:
- write_doc(file, member["description"], "\t")
- file.write(f"\t{format_type_and_name(member['type'], member['name'])};\n")
- file.write(f"}} {struct['name']};{make_deprecated_note(struct)}\n\n")
- def write_interface(file, interface):
- doc = [
- f"@name {interface['name']}",
- f"@since {interface['since']}",
- ]
- if "deprecated" in interface:
- if interface["deprecated"].lower().startswith("deprecated "):
- parts = interface["deprecated"].split(" ", 1)
- interface["deprecated"] = parts[1]
- doc.append(f"@deprecated {interface['deprecated']}")
- doc += [
- "",
- interface["description"][0],
- ]
- if len(interface["description"]) > 1:
- doc.append("")
- doc += interface["description"][1:]
- if "arguments" in interface:
- doc.append("")
- for arg in interface["arguments"]:
- if "description" not in arg:
- raise Exception(f"Interface function {interface['name']} is missing docs for {arg['name']} argument")
- arg_doc = " ".join(arg["description"])
- doc.append(f"@param {arg['name']} {arg_doc}")
- if "return_value" in interface and interface["return_value"]["type"] != "void":
- if "description" not in interface["return_value"]:
- raise Exception(f"Interface function {interface['name']} is missing docs for return value")
- ret_doc = " ".join(interface["return_value"]["description"])
- doc.append("")
- doc.append(f"@return {ret_doc}")
- if "see" in interface:
- doc.append("")
- for see in interface["see"]:
- doc.append(f"@see {see}")
- file.write("/**\n")
- for d in doc:
- if d != "":
- file.write(f" * {d}\n")
- else:
- file.write(" *\n")
- file.write(" */\n")
- fn = interface.copy()
- if "deprecated" in fn:
- del fn["deprecated"]
- fn["name"] = "GDExtensionInterface" + "".join(word.capitalize() for word in interface["name"].split("_"))
- write_function_type(file, fn)
- file.write("\n")
|