1 module godot.util.generator.methods;
2 
3 import godot.util.tools.string;
4 import godot.util.generator.classes, godot.util.generator.enums, godot.util.generator.util;
5 
6 import asdf;
7 
8 import std.range;
9 import std.algorithm.searching, std.algorithm.iteration;
10 import std.path;
11 import std.conv : text;
12 import std.string;
13 
14 import std.typecons;
15 
16 class GodotMethod {
17     @serdeOptional
18     string name; // constructors doesn't have name
19     @serdeOptional @serdeKeys("return_type", "return_value")
20     Type return_type;
21     @serdeOptional
22     bool is_editor;
23     @serdeOptional
24     bool is_noscript;
25     @serdeOptional
26     bool is_const;
27     @serdeOptional
28     bool is_virtual;
29     @serdeOptional
30     bool is_static;
31     @serdeOptional
32     string category;
33     @serdeOptional @serdeKeys("is_vararg", "has_varargs")
34     bool has_varargs;
35     @serdeOptional
36     bool is_from_script;
37     @serdeOptional
38     uint hash;
39     @serdeOptional
40     GodotArgument[] arguments;
41 
42     void finalizeDeserialization(Asdf data) {
43         if (!return_type)
44             return_type = Type.get("void");
45         foreach (i, ref a; arguments) {
46             a.index = i;
47             a.parent = this;
48         }
49     }
50 
51 @serdeIgnore:
52     GodotClass parent;
53 
54     string ddoc;
55 
56     Constructor isConstructor() const {
57         return null;
58     }
59     // Operator isOperator() { return null; }
60     // Indexer isIndexer() { return null; }
61 
62     bool same(in GodotMethod other) const {
63         return name == other.name && is_const == other.is_const;
64     }
65 
66     string templateArgsString() const {
67         string ret = "";
68         bool first = true; // track first arg to skip comma
69         foreach (i, ref a; arguments) {
70             if (a.type.acceptImplicit) {
71                 if (first)
72                     first = false;
73                 else
74                     ret ~= ", ";
75                 ret ~= text(a.type.godotType, "Arg", i);
76             }
77         }
78         if (has_varargs) {
79             if (!first)
80                 ret ~= ", ";
81             ret ~= "VarArgs...";
82         }
83         // template parens only if it actually is a template
84         if (ret.length != 0)
85             ret = text("(", ret, ")");
86         return ret;
87     }
88 
89     string argsString() const {
90         string ret = "(";
91 
92         foreach (i, ref a; arguments) {
93             if (i != 0)
94                 ret ~= ", ";
95             if (a.type.acceptImplicit)
96                 ret ~= text(a.type.dCallParamPrefix,
97                     a.type.godotType, "Arg", i);
98             else
99                 ret ~= text(a.type.dCallParamPrefix, a.type.dType);
100 
101             ret ~= " " ~ a.name.escapeD;
102 
103             // HACK: look at GodotArgument
104             // FIXME: Causes forward reference
105             // if (a.default_value != "\0") {
106             //     ret ~= " = " ~ escapeDefault(a.type, a.default_value);
107             // }
108         }
109         if (has_varargs) {
110             if (arguments.length != 0)
111                 ret ~= ", ";
112             ret ~= "VarArgs varArgs";
113         }
114         ret ~= ")";
115         return ret;
116     }
117 
118     /++
119 	Outputs binding method declaration with meta information. 
120 	e.g.:
121 
122 		@GodotName("insert") @MethodHash(0) GodotMethod!(long, long, Variant) method_insert;
123 	+/
124     string binding() const {
125         string ret;
126 
127         ret ~= "\t\t@GodotName(\"" ~ name ~ "\") GodotMethod!(" ~ return_type.dType;
128         foreach (ai, const a; arguments) {
129             ret ~= ", " ~ a.type.dType;
130         }
131         if (has_varargs)
132             ret ~= ", GodotVarArgs";
133         ret ~= ") " ~ wrapperIdentifier ~ ";\n";
134 
135         return ret;
136     }
137 
138     /// Function pointer name for this method
139     /// 	"constructor_new_0", "method_normalize", ...
140     string wrapperIdentifier() const {
141         return funKindName ~ "_" ~ name.snakeToCamel.escapeD;
142     }
143 
144     /// Function type name used in some cases: like "method", "ctor", "getter", etc...
145     string funKindName() const {
146         return "method";
147     }
148 
149     /++ 
150 	Formats whole method including function signature and body with implementation.
151 	e.g.:
152 
153 		Array slice(in long begin, in long end, in long step, in bool deep) const
154 		{
155 			if (!GDNativeGDNativeClassBinding.method_slice)
156 				GDNativeClassBinding.slice = _godot_api.get_method_bind("Class", "Method", 42);
157 			return callBuiltinMethod!(Array)(cast(GDNativePtrBuiltInMethod) GDNativeClassBinding.slice.mb, cast(void*) &_godot_object, cast() begin, cast() end, cast() step, cast() deep);
158 		}
159 	+/
160     string source() const {
161         string ret;
162 
163         // ddoc comment (if any)
164         ret ~= "\t/**\n\t" ~ ddoc.replace("\n", "\n\t") ~ "\n\t*/\n";
165         ret ~= "\t";
166 
167         ret ~= signature();
168 
169         ret ~= "\n\t{\n";
170 
171         ret ~= body_();
172 
173         ret ~= "\t}\n";
174 
175         return ret;
176     }
177 
178     /// Formats function signature, e.g.
179     ///   Array slice(in long begin, in long end, in long step, in bool deep) const
180     string signature() const {
181         string ret;
182 
183         // optional static modifier
184         if (isConstructor)
185             ret ~= "static ";
186         // note that even though it strips constness of return type the method is still marked const
187         // const in D is transitive, which means compiler should disallow modifying returned reference types
188         ret ~= return_type.stripConst.dRef ~ " ";
189         // none of the types (Classes/Core/Primitive) are pointers in D
190         // Classes are reference types; the others are passed by value.
191         ret ~= name.snakeToCamel.escapeD;
192 
193         ret ~= templateArgsString;
194         ret ~= argsString;
195 
196         // function const attribute
197         if (is_const)
198             ret ~= " const";
199         else if (name == "callv" && parent.name.godotType == "Object")
200             ret ~= " const"; /// HACK
201 
202         return ret;
203     }
204 
205     /// Formats body containing implementation, omitting outer braces
206     string body_() const {
207         string ret;
208 
209         // load function pointer
210         ret ~= "\t\tif (!GDNativeClassBinding." ~ wrapperIdentifier ~ ".mb)\n";
211         ret ~= "\t\t\t" ~ loader() ~ "\n";
212 
213         if (is_virtual || has_varargs) {
214             // keep it like this for now, serves as example.
215             // function will put normal arguments first, then varargs
216             // next, in order to call that function we need actually array of pointers
217             // after that we call the function with array of pointers instead of plain args array
218             version (none)
219                 if (name == "emit_signal") {
220                     // two tabs
221                     ret ~= `		Variant[varArgs.length+2] _GODOT_args;
222 	_GODOT_args[0] = String("emit_signal");
223 	_GODOT_args[1] = signal;
224 	foreach(vai, VA; VarArgs)
225 	{
226 		_GODOT_args[vai+2] = Variant(varArgs[vai]);
227 	}
228 	Variant*[varArgs.length+2] _args;
229 	foreach(i; 0.._GODOT_args.length)
230 	{
231 		_args[i] = &_GODOT_args[i];
232 	}
233 	Variant ret;
234 	GDNativeCallError err;
235 	_godot_api.object_method_bind_call(GDNativeClassBinding.method_emitSignal.mb, _godot_object.ptr, cast(void**) _args.ptr, _GODOT_args.length, cast(void*) &ret, &err);
236 	debug if (int code = ret.as!int())
237 	{
238 		import godot.api;
239 		print("signal error: ", signal, " code: ", code);
240 	}
241 	return cast(GodotError) err.error;`;
242                 }
243 
244             // static array must have at least 1 element
245             import std.algorithm : max;
246 
247             int argsLength = max(1, (cast(int) arguments.length));
248             // choose between varargs and regular function for arguments
249             if (has_varargs) {
250                 ret ~= "\t\tVariant[varArgs.length+" ~ text(argsLength) ~ "] _GODOT_args;\n";
251                 ret ~= "\t\tVariant*[varArgs.length+" ~ text(argsLength) ~ "] _args;\n";
252             } else {
253                 ret ~= "\t\tVariant[" ~ text(argsLength) ~ "] _GODOT_args;\n";
254                 ret ~= "\t\tVariant*[" ~ text(argsLength) ~ "] _args;\n";
255 
256             }
257             foreach (i, const a; arguments) {
258                 // gathers normal parameters in variant array to be later used as pointers
259                 ret ~= "\t\t_GODOT_args[" ~ text(cast(int) i) ~ "] = " ~ escapeD(a.name) ~ ";\n";
260             }
261 
262             if (has_varargs) {
263                 // copy varargs after regular args
264                 ret ~= "\t\tforeach(vai, VA; VarArgs)\n";
265                 ret ~= "\t\t{\n";
266                 ret ~= "\t\t\t_GODOT_args[vai+" ~ text(
267                     cast(int) arguments.length) ~ "] = Variant(varArgs[vai]);\n";
268                 ret ~= "\t\t}\n";
269             }
270 
271             // make pointer array
272             ret ~= "\t\tforeach(i; 0.._GODOT_args.length)\n";
273             ret ~= "\t\t{\n";
274             ret ~= "\t\t\t_args[i] = &_GODOT_args[i];\n";
275             ret ~= "\t\t}\n";
276 
277             //ret ~= "\t\tStringName _GODOT_method_name = StringName(\""~name~"\");\n";
278 
279             ret ~= "\t\tVariant ret;\n";
280             ret ~= "\t\tGDNativeCallError err;\n";
281             ret ~= "\t\t_godot_api.object_method_bind_call(GDNativeClassBinding." ~ wrapperIdentifier ~ ".mb, cast(void*) _godot_object.ptr, cast(void**) _args.ptr, _GODOT_args.length, cast(void*) &ret, &err);\n";
282             ret ~= "\t\t";
283             if (return_type.dType != "void") {
284                 ret ~= "return ";
285                 if (return_type.dType != "Variant")
286                     ret ~= "ret.as!(RefOrT!(" ~ return_type.stripConst.dType ~ "))";
287                 else
288                     ret ~= "ret";
289                 ret ~= ";\n";
290             }
291         }  // end varargs/virtual impl
292         else {
293             // add temp variable for static ctor
294             if (isConstructor) {
295                 if (parent.name.canBeCopied)
296                     ret ~= parent.name.dType;
297                 else
298                     ret ~= parent.name.opaqueType;
299                 ret ~= " _godot_object;\n\t\t";
300             }
301             // omit return for constructors, it will be wrapped and returned later
302             if (return_type.dType != "void" && !(isConstructor && parent.name.isCoreType))
303                 ret ~= "return ";
304             ret ~= callType() ~ "!(" ~ return_type.dType ~ ")(";
305             if (parent.isBuiltinClass)
306                 ret ~= "cast(GDNativePtrBuiltInMethod) ";
307             ret ~= "GDNativeClassBinding." ~ wrapperIdentifier;
308             if (parent.isBuiltinClass) // Adds method pointer accessor instead of template itself
309                 ret ~= ".mb";
310             ret ~= ", ";
311             if (parent.isBuiltinClass)
312                 ret ~= "cast(void*) &_godot_object";
313             else
314                 ret ~= "_godot_object";
315             foreach (ai, const a; arguments) {
316                 ret ~= ", cast() " ~ a.name.escapeD; // FIXME: const cast hack
317             }
318             ret ~= ");\n";
319             // wrap temporary object
320             if (isConstructor) {
321                 if (parent.name.canBeCopied)
322                     ret ~= "\t\treturn _godot_object;\n";
323                 else
324                     ret ~= "\t\treturn " ~ return_type.dType ~ "(_godot_object);\n";
325             }
326         } // end normal method impl
327 
328         return ret;
329     }
330 
331     /// call type wrapper, "ptrcall", "callv", "callBuiltinMethod", etc...
332     string callType() const {
333         if (parent.isBuiltinClass)
334             return "callBuiltinMethod";
335         //if (has_varargs)
336         //	return "callv";
337         return "ptrcall";
338     }
339 
340     /// formats function pointer loader, e.g.
341     /// 	GDNativeClassBinding.method_append.mb = _godot_api.clasdb_get_methodbind("class", "method", hash);
342     string loader() const {
343         // probably better to move in its own subclass
344         if (parent.isBuiltinClass) {
345             return format(`GDNativeClassBinding.%s.mb = _godot_api.variant_get_ptr_builtin_method(%s, "%s", %d);`,
346                 wrapperIdentifier,
347                 parent.name.toNativeVariantType,
348                 name,
349                 hash
350             );
351         }
352 
353         return format(`GDNativeClassBinding.%s.mb = _godot_api.classdb_get_method_bind("%s", "%s", %d);`,
354             wrapperIdentifier,
355             parent.name.godotType,
356             name,
357             hash,
358         );
359     }
360 }
361 
362 struct GodotArgument {
363     string name;
364     Type type;
365     
366     // HACK: when godot doesn't want to specifically
367     // tell you default it leaves it empty ("default_value": "")
368     // so when asdf hits it sets default_value to []
369     // which is the same as if it's undefined
370     @serdeOptional
371     string default_value = "\0";
372 
373 @serdeIgnore:
374 
375     size_t index;
376     GodotMethod parent;
377 }
378 
379 class GodotProperty {
380     string name;
381     Type type;
382     string getter, setter;
383     int index;
384 
385 @serdeIgnore:
386 
387     string ddoc;
388 
389     string getterSource(in GodotMethod m) const {
390         string ret;
391         ret ~= "\t/**\n\t" ~ ddoc.replace("\n", "\n\t") ~ "\n\t*/\n";
392         ret ~= "\t@property " ~ m.return_type.dType ~ " " ~ name.replace("/", "_")
393             .snakeToCamel.escapeD ~ "()\n\t{\n"; /// TODO: const?
394         ret ~= "\t\treturn " ~ getter.snakeToCamel.escapeD ~ "(";
395         if (index != -1) {
396             // add cast to enum types
397             if (m.arguments[0].type.isEnum)
398                 ret ~= "cast(" ~ m.arguments[0].type.dType ~ ") ";
399             ret ~= text(index);
400         }
401         ret ~= ");\n";
402         ret ~= "\t}\n";
403         return ret;
404     }
405 
406     string setterSource(in GodotMethod m) const {
407         string ret;
408         ret ~= "\t/// ditto\n";
409         ret ~= "\t@property void " ~ name.replace("/", "_")
410             .snakeToCamel.escapeD ~ "(" ~ m.arguments[$ - 1].type.dType ~ " v)\n\t{\n";
411         ret ~= "\t\t" ~ setter.snakeToCamel.escapeD ~ "(";
412         if (index != -1) {
413             // add cast to enum types
414             if (m.arguments[0].type.isEnum)
415                 ret ~= "cast(" ~ m.arguments[0].type.dType ~ ") ";
416             ret ~= text(index) ~ ", ";
417         }
418         ret ~= "v);\n";
419         ret ~= "\t}\n";
420         return ret;
421     }
422 }