Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/extension/make_interface_header.py
20985 views
1
import difflib
2
import json
3
from collections import OrderedDict
4
5
import methods
6
7
BASE_TYPES = [
8
"void",
9
"int8_t",
10
"uint8_t",
11
"int16_t",
12
"uint16_t",
13
"int32_t",
14
"uint32_t",
15
"int64_t",
16
"uint64_t",
17
"size_t",
18
"char",
19
"char16_t",
20
"char32_t",
21
"wchar_t",
22
"float",
23
"double",
24
]
25
26
27
def run(target, source, env):
28
filename = str(source[0])
29
buffer = methods.get_buffer(filename)
30
data = json.loads(buffer, object_pairs_hook=OrderedDict)
31
check_formatting(buffer.decode("utf-8"), data, filename)
32
check_allowed_keys(data, ["_copyright", "$schema", "format_version", "types", "interface"])
33
34
valid_data_types = {}
35
for type in BASE_TYPES:
36
valid_data_types[type] = True
37
38
with methods.generated_wrapper(str(target[0])) as file:
39
file.write("""\
40
#ifndef __cplusplus
41
#include <stddef.h>
42
#include <stdint.h>
43
44
typedef uint32_t char32_t;
45
typedef uint16_t char16_t;
46
#else
47
#include <cstddef>
48
#include <cstdint>
49
50
extern "C" {
51
#endif
52
53
""")
54
55
handles = []
56
type_replacements = []
57
for type in data["types"]:
58
kind = type["kind"]
59
60
check_type(kind, type, valid_data_types)
61
valid_data_types[type["name"]] = type
62
63
if "deprecated" in type:
64
check_allowed_keys(type["deprecated"], ["since"], ["message", "replace_with"])
65
if "replace_with" in type["deprecated"]:
66
type_replacements.append((type["name"], type["deprecated"]["replace_with"]))
67
68
if "description" in type:
69
write_doc(file, type["description"])
70
71
if kind == "handle":
72
check_allowed_keys(
73
type, ["name", "kind"], ["is_const", "is_uninitialized", "parent", "description", "deprecated"]
74
)
75
if "parent" in type and type["parent"] not in handles:
76
raise UnknownTypeError(type["parent"], type["name"])
77
# @todo In the future, let's write these as `struct *` so the compiler can help us with type checking.
78
type["type"] = "void*" if not type.get("is_const", False) else "const void*"
79
write_simple_type(file, type)
80
handles.append(type["name"])
81
elif kind == "alias":
82
check_allowed_keys(type, ["name", "kind", "type"], ["description", "deprecated"])
83
write_simple_type(file, type)
84
elif kind == "enum":
85
check_allowed_keys(type, ["name", "kind", "values"], ["is_bitfield", "description", "deprecated"])
86
write_enum_type(file, type)
87
elif kind == "function":
88
check_allowed_keys(type, ["name", "kind", "arguments"], ["return_value", "description", "deprecated"])
89
write_function_type(file, type)
90
elif kind == "struct":
91
check_allowed_keys(type, ["name", "kind", "members"], ["description", "deprecated"])
92
write_struct_type(file, type)
93
else:
94
raise Exception(f"Unknown kind of type: {kind}")
95
96
for type_name, replace_with in type_replacements:
97
if replace_with not in valid_data_types:
98
raise Exception(f"Unknown type '{replace_with}' used as replacement for '{type_name}'")
99
replacement = valid_data_types[replace_with]
100
if isinstance(replacement, dict) and "deprecated" in replacement:
101
raise Exception(
102
f"Cannot use '{replace_with}' as replacement for '{type_name}' because it's deprecated too"
103
)
104
105
interface_replacements = []
106
valid_interfaces = {}
107
for interface in data["interface"]:
108
check_type("function", interface, valid_data_types)
109
check_allowed_keys(
110
interface,
111
["name", "arguments", "since", "description"],
112
["return_value", "see", "legacy_type_name", "deprecated"],
113
)
114
valid_interfaces[interface["name"]] = interface
115
if "deprecated" in interface:
116
check_allowed_keys(interface["deprecated"], ["since"], ["message", "replace_with"])
117
if "replace_with" in interface["deprecated"]:
118
interface_replacements.append((interface["name"], interface["deprecated"]["replace_with"]))
119
write_interface(file, interface)
120
121
for function_name, replace_with in interface_replacements:
122
if replace_with not in valid_interfaces:
123
raise Exception(
124
f"Unknown interface function '{replace_with}' used as replacement for '{function_name}'"
125
)
126
replacement = valid_interfaces[replace_with]
127
if "deprecated" in replacement:
128
raise Exception(
129
f"Cannot use '{replace_with}' as replacement for '{function_name}' because it's deprecated too"
130
)
131
132
file.write("""\
133
#ifdef __cplusplus
134
}
135
#endif
136
""")
137
138
139
# Serialize back into JSON in order to see if the formatting remains the same.
140
def check_formatting(buffer, data, filename):
141
buffer2 = json.dumps(data, indent=4)
142
143
lines1 = buffer.splitlines()
144
lines2 = buffer2.splitlines()
145
146
diff = difflib.unified_diff(
147
lines1,
148
lines2,
149
fromfile="a/" + filename,
150
tofile="b/" + filename,
151
lineterm="",
152
)
153
154
diff = list(diff)
155
if len(diff) > 0:
156
print(" *** Apply this patch to fix: ***\n")
157
print("\n".join(diff))
158
raise Exception(f"Formatting issues in {filename}")
159
160
161
def check_allowed_keys(data, required, optional=[]):
162
keys = data.keys()
163
allowed = required + optional
164
for k in keys:
165
if k not in allowed:
166
raise Exception(f"Found unknown key '{k}'")
167
for r in required:
168
if r not in keys:
169
raise Exception(f"Missing required key '{r}'")
170
171
172
class UnknownTypeError(Exception):
173
def __init__(self, unknown, parent, item=None):
174
self.unknown = unknown
175
self.parent = parent
176
if item:
177
msg = f"Unknown type '{unknown}' for '{item}' used in '{parent}'"
178
else:
179
msg = f"Unknown type '{unknown}' used in '{parent}'"
180
super().__init__(msg)
181
182
183
def base_type_name(type_name):
184
if type_name.startswith("const "):
185
type_name = type_name[6:]
186
if type_name.endswith("*"):
187
type_name = type_name[:-1]
188
return type_name
189
190
191
def format_type_and_name(type, name=None):
192
ret = type
193
if ret[-1] == "*":
194
ret = ret[:-1] + " *"
195
if name:
196
if ret[-1] == "*":
197
ret = ret + name
198
else:
199
ret = ret + " " + name
200
return ret
201
202
203
def is_valid_type(type, valid_data_types):
204
if type in ["void", "const void"]:
205
# The "void" type can only be used with the pointer modifier.
206
return False
207
return base_type_name(type) in valid_data_types
208
209
210
def check_type(kind, type, valid_data_types):
211
if kind == "alias":
212
if not is_valid_type(type["type"], valid_data_types):
213
raise UnknownTypeError(type["type"], type["name"])
214
elif kind == "struct":
215
for member in type["members"]:
216
if not is_valid_type(member["type"], valid_data_types):
217
raise UnknownTypeError(member["type"], type["name"], member["name"])
218
elif kind == "function":
219
for arg in type["arguments"]:
220
if not is_valid_type(arg["type"], valid_data_types):
221
raise UnknownTypeError(arg["type"], type["name"], arg.get("name"))
222
if "return_value" in type:
223
if not is_valid_type(type["return_value"]["type"], valid_data_types):
224
raise UnknownTypeError(type["return_value"]["type"], type["name"])
225
226
227
def write_doc(file, doc, indent=""):
228
if len(doc) == 1:
229
file.write(f"{indent}/* {doc[0]} */\n")
230
return
231
232
first = True
233
for line in doc:
234
if first:
235
file.write(indent + "/*")
236
first = False
237
else:
238
file.write(indent + " *")
239
240
if line != "":
241
file.write(" " + line)
242
file.write("\n")
243
file.write(indent + " */\n")
244
245
246
def make_deprecated_message(data):
247
parts = [
248
f"Deprecated in Godot {data['since']}.",
249
data["message"] if "message" in data else "",
250
f"Use `{data['replace_with']}` instead." if "replace_with" in data else "",
251
]
252
return " ".join([x for x in parts if x.strip() != ""])
253
254
255
def make_deprecated_comment_for_type(type):
256
if "deprecated" not in type:
257
return ""
258
message = make_deprecated_message(type["deprecated"])
259
return f" /* {message} */"
260
261
262
def write_simple_type(file, type):
263
file.write(f"typedef {format_type_and_name(type['type'], type['name'])};{make_deprecated_comment_for_type(type)}\n")
264
265
266
def write_enum_type(file, enum):
267
file.write("typedef enum {\n")
268
for value in enum["values"]:
269
check_allowed_keys(value, ["name", "value"], ["description", "deprecated"])
270
if "description" in value:
271
write_doc(file, value["description"], "\t")
272
file.write(f"\t{value['name']} = {value['value']},\n")
273
file.write(f"}} {enum['name']};{make_deprecated_comment_for_type(enum)}\n\n")
274
275
276
def make_args_text(args):
277
combined = []
278
for arg in args:
279
check_allowed_keys(arg, ["type"], ["name", "description"])
280
combined.append(format_type_and_name(arg["type"], arg.get("name")))
281
return ", ".join(combined)
282
283
284
def write_function_type(file, fn):
285
args_text = make_args_text(fn["arguments"]) if ("arguments" in fn) else ""
286
name_and_args = f"(*{fn['name']})({args_text})"
287
return_type = fn["return_value"]["type"] if "return_value" in fn else "void"
288
file.write(f"typedef {format_type_and_name(return_type, name_and_args)};{make_deprecated_comment_for_type(fn)}\n")
289
290
291
def write_struct_type(file, struct):
292
file.write("typedef struct {\n")
293
for member in struct["members"]:
294
check_allowed_keys(member, ["name", "type"], ["description"])
295
if "description" in member:
296
write_doc(file, member["description"], "\t")
297
file.write(f"\t{format_type_and_name(member['type'], member['name'])};\n")
298
file.write(f"}} {struct['name']};{make_deprecated_comment_for_type(struct)}\n\n")
299
300
301
def write_interface(file, interface):
302
doc = [
303
f"@name {interface['name']}",
304
f"@since {interface['since']}",
305
]
306
307
if "deprecated" in interface:
308
doc.append(f"@deprecated {make_deprecated_message(interface['deprecated'])}")
309
310
doc += [
311
"",
312
interface["description"][0],
313
]
314
315
if len(interface["description"]) > 1:
316
doc.append("")
317
doc += interface["description"][1:]
318
319
if "arguments" in interface:
320
doc.append("")
321
for arg in interface["arguments"]:
322
if "description" not in arg:
323
raise Exception(f"Interface function {interface['name']} is missing docs for {arg['name']} argument")
324
arg_doc = " ".join(arg["description"])
325
doc.append(f"@param {arg['name']} {arg_doc}")
326
327
if "return_value" in interface:
328
if "description" not in interface["return_value"]:
329
raise Exception(f"Interface function {interface['name']} is missing docs for return value")
330
ret_doc = " ".join(interface["return_value"]["description"])
331
doc.append("")
332
doc.append(f"@return {ret_doc}")
333
334
if "see" in interface:
335
doc.append("")
336
for see in interface["see"]:
337
doc.append(f"@see {see}")
338
339
file.write("/**\n")
340
for d in doc:
341
if d != "":
342
file.write(f" * {d}\n")
343
else:
344
file.write(" *\n")
345
file.write(" */\n")
346
347
fn = interface.copy()
348
if "deprecated" in fn:
349
del fn["deprecated"]
350
fn["name"] = "GDExtensionInterface" + "".join(word.capitalize() for word in interface["name"].split("_"))
351
write_function_type(file, fn)
352
353
file.write("\n")
354
355