| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- /**************************************************************************/
- /* gdextension_interface_header_generator.cpp */
- /**************************************************************************/
- /* This file is part of: */
- /* GODOT ENGINE */
- /* https://godotengine.org */
- /**************************************************************************/
- /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
- /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
- /* */
- /* Permission is hereby granted, free of charge, to any person obtaining */
- /* a copy of this software and associated documentation files (the */
- /* "Software"), to deal in the Software without restriction, including */
- /* without limitation the rights to use, copy, modify, merge, publish, */
- /* distribute, sublicense, and/or sell copies of the Software, and to */
- /* permit persons to whom the Software is furnished to do so, subject to */
- /* the following conditions: */
- /* */
- /* The above copyright notice and this permission notice shall be */
- /* included in all copies or substantial portions of the Software. */
- /* */
- /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
- /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
- /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
- /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
- /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
- /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
- /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
- /**************************************************************************/
- #ifdef TOOLS_ENABLED
- #include "gdextension_interface_header_generator.h"
- #include "core/io/json.h"
- #include "gdextension_interface_dump.gen.h"
- static const char *FILE_HEADER =
- "/**************************************************************************/\n"
- "/* gdextension_interface.h */\n";
- static const char *INTRO =
- "\n"
- "#pragma once\n"
- "\n"
- "/* This is a C class header, you can copy it and use it directly in your own binders.\n"
- " * Together with the `extension_api.json` file, you should be able to generate any binder.\n"
- " */\n"
- "\n"
- "#ifndef __cplusplus\n"
- "#include <stddef.h>\n"
- "#include <stdint.h>\n"
- "\n"
- "typedef uint32_t char32_t;\n"
- "typedef uint16_t char16_t;\n"
- "#else\n"
- "#include <cstddef>\n"
- "#include <cstdint>\n"
- "\n"
- "extern \"C\" {\n"
- "#endif\n"
- "\n";
- static const char *OUTRO =
- "#ifdef __cplusplus\n"
- "}\n"
- "#endif\n";
- void GDExtensionInterfaceHeaderGenerator::generate_gdextension_interface_header(const String &p_path) {
- Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_MSG(fa.is_null(), vformat("Cannot open file '%s' for writing.", p_path));
- Vector<uint8_t> bytes = GDExtensionInterfaceDump::load_gdextension_interface_file();
- String json_string = String::utf8(Span<char>((char *)bytes.ptr(), bytes.size()));
- Ref<JSON> json;
- json.instantiate();
- Error err = json->parse(json_string);
- ERR_FAIL_COND(err);
- Dictionary data = json->get_data();
- ERR_FAIL_COND(data.is_empty());
- fa->store_string(FILE_HEADER);
- Array copyright = data["_copyright"];
- for (const Variant &line : copyright) {
- fa->store_line(line);
- }
- fa->store_string(INTRO);
- Array types = data["types"];
- for (Dictionary type_dict : types) {
- if (type_dict.has("description")) {
- write_doc(fa, type_dict["description"]);
- }
- String kind = type_dict["kind"];
- if (kind == "handle") {
- type_dict["type"] = type_dict.get("is_const", false) ? "const void*" : "void*";
- write_simple_type(fa, type_dict);
- } else if (kind == "alias") {
- write_simple_type(fa, type_dict);
- } else if (kind == "enum") {
- write_enum_type(fa, type_dict);
- } else if (kind == "function") {
- write_function_type(fa, type_dict);
- } else if (kind == "struct") {
- write_struct_type(fa, type_dict);
- }
- }
- Array interfaces = data["interface"];
- for (const Variant &interface : interfaces) {
- write_interface(fa, interface);
- }
- fa->store_string(OUTRO);
- }
- void GDExtensionInterfaceHeaderGenerator::write_doc(const Ref<FileAccess> &p_fa, const Array &p_doc, const String &p_indent) {
- if (p_doc.size() == 1) {
- p_fa->store_string(vformat("%s/* %s */\n", p_indent, p_doc[0]));
- return;
- }
- bool first = true;
- for (const Variant &line : p_doc) {
- if (first) {
- p_fa->store_string(p_indent + "/*");
- first = false;
- } else {
- p_fa->store_string(p_indent + " *");
- }
- if (line == "") {
- p_fa->store_string("\n");
- } else {
- p_fa->store_line(String(" ") + (String)line);
- }
- }
- p_fa->store_string(p_indent + " */\n");
- }
- void GDExtensionInterfaceHeaderGenerator::write_simple_type(const Ref<FileAccess> &p_fa, const Dictionary &p_type) {
- String type_and_name = format_type_and_name(p_type["type"], p_type["name"]);
- p_fa->store_string(vformat("typedef %s;%s\n", type_and_name, make_deprecated_comment_for_type(p_type)));
- }
- void GDExtensionInterfaceHeaderGenerator::write_enum_type(const Ref<FileAccess> &p_fa, const Dictionary &p_enum) {
- p_fa->store_string("typedef enum {\n");
- Array values = p_enum["values"];
- for (Dictionary value_dict : values) {
- if (value_dict.has("description")) {
- write_doc(p_fa, value_dict["description"], "\t");
- }
- p_fa->store_string(vformat("\t%s = %s,\n", value_dict["name"], (int)value_dict["value"]));
- }
- p_fa->store_string(vformat("} %s;%s\n\n", p_enum["name"], make_deprecated_comment_for_type(p_enum)));
- }
- void GDExtensionInterfaceHeaderGenerator::write_function_type(const Ref<FileAccess> &p_fa, const Dictionary &p_func) {
- String args_text = p_func.has("arguments") ? make_args_text(p_func["arguments"]) : "";
- String name_and_args = vformat("(*%s)(%s)", p_func["name"], args_text);
- String return_type;
- if (p_func.has("return_value")) {
- Dictionary ret = p_func["return_value"];
- return_type = ret["type"];
- } else {
- return_type = "void";
- }
- p_fa->store_string(vformat("typedef %s;%s\n", format_type_and_name(return_type, name_and_args), make_deprecated_comment_for_type(p_func)));
- }
- void GDExtensionInterfaceHeaderGenerator::write_struct_type(const Ref<FileAccess> &p_fa, const Dictionary &p_struct) {
- p_fa->store_string("typedef struct {\n");
- Array members = p_struct["members"];
- for (Dictionary member_dict : members) {
- if (member_dict.has("description")) {
- write_doc(p_fa, member_dict["description"], "\t");
- }
- p_fa->store_string(vformat("\t%s;\n", format_type_and_name(member_dict["type"], member_dict["name"])));
- }
- p_fa->store_string(vformat("} %s;%s\n\n", p_struct["name"], make_deprecated_comment_for_type(p_struct)));
- }
- String GDExtensionInterfaceHeaderGenerator::format_type_and_name(const String &p_type, const String &p_name) {
- String ret = p_type;
- bool is_pointer = false;
- if (ret.ends_with("*")) {
- ret = ret.substr(0, ret.size() - 2) + " *";
- is_pointer = true;
- }
- if (!p_name.is_empty()) {
- if (is_pointer) {
- ret = ret + p_name;
- } else {
- ret = ret + " " + p_name;
- }
- }
- return ret;
- }
- String GDExtensionInterfaceHeaderGenerator::make_deprecated_message(const Dictionary &p_data) {
- PackedStringArray parts;
- parts.push_back(vformat("Deprecated in Godot %s.", p_data["since"]));
- if (p_data.has("message")) {
- parts.push_back(p_data["message"]);
- }
- if (p_data.has("replace_with")) {
- parts.push_back(vformat("Use `%s` instead.", p_data["replace_with"]));
- }
- return String(" ").join(parts);
- }
- String GDExtensionInterfaceHeaderGenerator::make_deprecated_comment_for_type(const Dictionary &p_type) {
- if (!p_type.has("deprecated")) {
- return "";
- }
- return vformat(" /* %s */", make_deprecated_message(p_type["deprecated"]));
- }
- String GDExtensionInterfaceHeaderGenerator::make_args_text(const Array &p_args) {
- Vector<String> combined;
- for (Dictionary arg_dict : p_args) {
- combined.push_back(format_type_and_name(arg_dict["type"], arg_dict.get("name", String())));
- }
- return String(", ").join(combined);
- }
- void GDExtensionInterfaceHeaderGenerator::write_interface(const Ref<FileAccess> &p_fa, const Dictionary &p_interface) {
- Vector<String> doc;
- doc.push_back(String("@name ") + (String)p_interface["name"]);
- doc.push_back(String("@since ") + (String)p_interface["since"]);
- if (p_interface.has("deprecated")) {
- doc.push_back(String("@deprecated ") + make_deprecated_message(p_interface["deprecated"]));
- }
- Array orig_doc = p_interface["description"];
- for (int i = 0; i < orig_doc.size(); i++) {
- // Put an empty line before the 1st and 2nd lines.
- if (i <= 1) {
- doc.push_back("");
- }
- doc.push_back(orig_doc[i]);
- }
- if (p_interface.has("arguments")) {
- Array args = p_interface["arguments"];
- if (args.size() > 0) {
- doc.push_back("");
- for (Dictionary arg_dict : args) {
- String arg_string = String("@param ") + (String)arg_dict["name"];
- if (arg_dict.has("description")) {
- Array arg_doc = arg_dict["description"];
- for (const Variant &d : arg_doc) {
- arg_string += String(" ") + (String)d;
- }
- }
- doc.push_back(arg_string);
- }
- }
- }
- if (p_interface.has("return_value")) {
- Dictionary ret = p_interface["return_value"];
- String ret_string = String("@return");
- if (ret.has("description")) {
- Array arg_doc = ret["description"];
- for (const Variant &d : arg_doc) {
- ret_string += String(" ") + (String)d;
- }
- }
- doc.push_back("");
- doc.push_back(ret_string);
- }
- if (p_interface.has("see")) {
- Array see_array = p_interface["see"];
- if (see_array.size() > 0) {
- doc.push_back("");
- for (const Variant &see : see_array) {
- doc.push_back(String("@see ") + (String)see);
- }
- }
- }
- p_fa->store_string("/**\n");
- for (const String &d : doc) {
- if (d == "") {
- p_fa->store_string(" *\n");
- } else {
- p_fa->store_string(vformat(" * %s\n", d));
- }
- }
- p_fa->store_string(" */\n");
- Dictionary func = p_interface.duplicate();
- func.erase("deprecated");
- if (p_interface.has("legacy_type_name")) {
- // @todo When we can break compat, remove this! This maintains legacy type-o's in some type names.
- func["name"] = p_interface["legacy_type_name"];
- } else {
- // Cannot use `to_pascal_case()` because it'll capitalize after numbers.
- Vector<String> words = ((String)p_interface["name"]).split("_");
- for (String &word : words) {
- // Cannot use `capitalize()` on the whole string, because it'll separate numbers with a space.
- word[0] = String::char_uppercase(word[0]);
- }
- func["name"] = String("GDExtensionInterface") + String().join(words);
- }
- write_function_type(p_fa, func);
- p_fa->store_string("\n");
- }
- #endif // TOOLS_ENABLED
|