1 /**
2 Godot's ref-counted wchar_t String class.
3 
4 Copyright:
5 Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.
6 Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md)
7 Copyright (c) 2017-2018 Godot-D contributors
8 Copyright (c) 2022-2022 Godot-DLang contributors
9 
10 License: $(LINK2 https://opensource.org/licenses/MIT, MIT License)
11 
12 
13 */
14 module godot..string;
15 
16 // import core.stdc.stddef : wchar_t;
17 import std.traits;
18 import std.exception : assumeWontThrow;
19 import godot.builtins;
20 import godot.poolarrays;
21 import godot.abi;
22 import godot.abi.gdextension;
23 
24 import godot.variant;
25 
26 struct CharString {
27     const(char)* data;
28     int length;
29 
30     ~this() {
31         if (data)
32             _godot_api.mem_free(cast(void*) data);
33         data = null;
34         length = 0;
35     }
36 }
37 
38 struct Char16String {
39     const(char16_t)* data;
40     int length;
41 
42     ~this() {
43         if (data)
44             _godot_api.mem_free(cast(void*) data);
45         data = null;
46         length = 0;
47     }
48 }
49 
50 struct Char32String {
51     const(char32_t)* data;
52     int length;
53 
54     ~this() {
55         if (data)
56             _godot_api.mem_free(cast(void*) data);
57         data = null;
58         length = 0;
59     }
60 }
61 
62 struct CharWideString {
63     const(wchar_t)* data;
64     int length;
65 
66     ~this() {
67         if (data)
68             _godot_api.mem_free(cast(void*) data);
69         data = null;
70         length = 0;
71     }
72 }
73 
74 /**
75 This is the built-in string class (and the one used by GDScript). It supports Unicode and provides all necessary means for string handling. Strings are reference counted and use a copy-on-write approach, so passing them around is cheap in resources.
76 */
77 struct String {
78     //@nogc nothrow:
79 
80     package(godot) union _String {
81         godot_string _godot_string;
82         String_Bind _bind;
83     }
84 
85     package(godot) _String _string;
86     alias _string this;
87 
88     this(StringName n) {
89         this = _bind.new2(n);
90         //_godot_api.variant_new_copy(&_godot_string, &n._godot_string_name);
91     }
92 
93     package(godot) this(in godot_string str) {
94         _godot_string = str;
95     }
96 
97     /++
98 	wchar_t constructor. S can be a slice or a null-terminated pointer.
99 	+/
100     this(S)(in S str)
101             if (isImplicitlyConvertible!(S, const(wchar_t)[]) ||
102             isImplicitlyConvertible!(S, const(wchar_t)*)) {
103         static if (isImplicitlyConvertible!(S, const(wchar_t)[])) {
104             const(wchar_t)[] contents = str;
105             _godot_api.string_new_with_wide_chars_and_len(&_godot_string, contents.ptr, cast(int) contents
106                     .length);
107         } else {
108             import core.stdc.wchar_ : wcslen;
109 
110             const(wchar_t)* contents = str;
111             _godot_api.string_new_with_wide_chars_and_len(&_godot_string, contents, cast(int) wcslen(
112                     contents));
113         }
114     }
115 
116     /++
117 	UTF-8 constructor. S can be a slice (like `string`) or a null-terminated pointer.
118 	+/
119     this(S)(in S str)
120             if (isImplicitlyConvertible!(S, const(char)[]) ||
121             isImplicitlyConvertible!(S, const(char)*)) {
122         static if (isImplicitlyConvertible!(S, const(char)[])) {
123             const(char)[] contents = str;
124             _godot_api.string_new_with_utf8_chars_and_len(&_godot_string, contents.ptr, cast(int) contents
125                     .length);
126         } else {
127             const(char)* contents = str;
128             _godot_api.string_new_with_utf8_chars(&_godot_string, contents);
129         }
130     }
131 
132     void _defaultCtor() {
133         this = String_Bind.new0();
134     }
135 
136     ~this() {
137         //_bind._destructor();
138     }
139 
140     void opAssign(in String other) {
141         //_bind._destructor();
142         _godot_string = other._godot_string;
143         // other still owns the string, double free possible?
144     }
145 
146     /+String substr(int p_from,int p_chars) const
147 	{
148 		return String.empty; // todo
149 	}
150 	
151 	alias opSlice = substr;+/
152 
153     ref char32_t opIndex(in size_t idx) {
154         return *_godot_api.string_operator_index(&_godot_string, cast(int) idx);
155     }
156 
157     char32_t opIndex(in size_t idx) const {
158         return *_godot_api.string_operator_index(cast(godot_string*)&_godot_string, cast(int) idx);
159     }
160 
161     /// Returns the length of the wchar_t array, minus the zero terminator.
162     size_t length() const {
163         return _bind.length();
164         //return _godot_api.string_length(&_godot_string);
165     }
166 
167     /// Returns: $(D true) if length is 0
168     bool empty() const {
169         return length == 0;
170     }
171 
172     int opCmp(in String s) const {
173         // TODO: Fix me
174         return 0;
175         //if(_godot_string == s._godot_string) return true;
176         //auto equal = _godot_api.string_operator_equal(&_godot_string, &s._godot_string);
177         //if(equal) return 0;
178         //auto less = _godot_api.string_operator_less(&_godot_string, &s._godot_string);
179         //return less?(-1):1;
180     }
181 
182     bool opEquals(in String other) const {
183         if (_godot_string == other._godot_string)
184             return true;
185         //return _godot_api.string_operator_equal(&_godot_string, &other._godot_string);
186         return _bind == other._bind;
187     }
188 
189     String opBinary(string op)(in String other) const if (op == "~" || op == "+") {
190         // it has to be zero initialized or godot will try to unref it and crash
191         //String ret;
192         godot_string ret;
193 
194         __gshared static GDNativePtrOperatorEvaluator mb;
195         if (!mb)
196             mb = _godot_api.variant_get_ptr_operator_evaluator(GDNATIVE_VARIANT_OP_ADD, GDNATIVE_VARIANT_TYPE_STRING, GDNATIVE_VARIANT_TYPE_STRING);
197         mb(&_godot_string, &other._godot_string, &ret);
198 
199         return String(ret);
200     }
201 
202     void opOpAssign(string op)(in String other) if (op == "~" || op == "+") {
203         //this = opBinary!"+"(other);
204         godot_string tmp;
205 
206         __gshared static GDNativePtrOperatorEvaluator mb;
207         if (!mb)
208             mb = _godot_api.variant_get_ptr_operator_evaluator(GDNATIVE_VARIANT_OP_ADD, GDNATIVE_VARIANT_TYPE_STRING, GDNATIVE_VARIANT_TYPE_STRING);
209         mb(&_godot_string, &other._godot_string, &tmp);
210         _bind._destructor();
211         _godot_string = tmp;
212     }
213 
214     /// Returns a pointer to the wchar_t data. Always zero-terminated.
215     immutable(char32_t)* ptr() const {
216         return cast(immutable(char32_t)*) _godot_api.string_operator_index_const(
217             &_godot_string, 0);
218     }
219 
220     /// Returns a slice of the wchar_t data without the zero terminator.
221     immutable(wchar_t)[] data() const {
222         return cast(typeof(return)) ptr[0 .. length];
223     }
224 
225     alias toString = data;
226 
227     CharString utf8() const {
228         // untested, may overflow?
229         int size = cast(int) _godot_api.string_to_utf8_chars(&_godot_string, null, 0);
230         char* cstr = cast(char*) _godot_api.mem_alloc(size + 1);
231         _godot_api.string_to_utf8_chars(&_godot_string, cstr, size);
232         cstr[size] = '\0';
233         return CharString(cstr, size);
234     }
235 
236     String format(V)(V values) const 
237             if (is(V : Variant) || Variant.compatibleToGodot!V) {
238         const Variant v = values;
239         String new_string = void;
240         new_string._godot_string = _godot_api.string_format(&_godot_string, cast(godot_variant*)&v);
241 
242         return new_string;
243     }
244 
245     String format(V)(V values, String placeholder) const 
246             if (is(V : Variant) || Variant.compatibleToGodot!V) {
247         const Variant v = values;
248         String new_string = void;
249         CharString contents = placeholder.utf8;
250         new_string._godot_string = _godot_api.string_format_with_custom_placeholder(
251             &_godot_string, cast(godot_variant*)&v, contents.ptr);
252 
253         return new_string;
254     }
255 
256     @trusted
257     hash_t toHash() const nothrow {
258         return cast(hash_t) assumeWontThrow(_bind.hash());
259         //static if(hash_t.sizeof == uint.sizeof) return _godot_api.string_hash(&_godot_string);
260         //else return _godot_api.string_hash64(&_godot_string);
261     }
262 }
263 
264 struct StringName {
265     //@nogc nothrow:
266 
267     package(godot) union _StringName {
268         godot_string _godot_string_name;
269         StringName_Bind _bind;
270     }
271 
272     package(godot) _StringName _stringName;
273     alias _stringName this;
274 
275     this(String s) {
276         this = _bind.new2(s);
277     }
278 
279     this(this) {
280 
281     }
282 
283     //this(ref const StringName s)
284     //{
285     //	this = _bind.new1(s);
286     //}
287 
288     void _defaultCtor() {
289         this = StringName_Bind.new0();
290     }
291 
292     package(godot) this(in godot_string strname) {
293         _godot_string_name = strname;
294     }
295 
296     /++
297 	char constructor. S can be a slice (like `string`) or a null-terminated pointer.
298 	+/
299     this(S)(in S str)
300             if (isImplicitlyConvertible!(S, const(char)[]) ||
301             isImplicitlyConvertible!(S, const(char)*)) {
302         static if (isImplicitlyConvertible!(S, const(char)[])) {
303             const(char)[] contents = str;
304             _godot_api.string_new_with_latin1_chars_and_len(&_godot_string_name, contents.ptr, cast(
305                     int) contents.length);
306         } else {
307             import core.stdc.string : strlen;
308 
309             const(char)* contents = str;
310             _godot_api.string_new_with_latin1_chars_and_len(&_godot_string_name, contents, cast(int) strlen(
311                     contents));
312         }
313     }
314 
315     ~this() {
316         //_bind._destructor();
317         _godot_string_name = _godot_string_name.init;
318     }
319 
320     void opAssign(in StringName other) {
321         if (&_godot_string_name)
322             _bind._destructor();
323 
324         _godot_string_name = other._godot_string_name;
325     }
326 
327     bool opEquals(in StringName other) const {
328         if (_godot_string_name == other._godot_string_name)
329             return true;
330         // FIXME: no idea if there is actually such thing
331         //return _godot_api.string_name_operator_equal(&_godot_string_name, &other._godot_string_name);
332         return false;
333     }
334 
335     String opCast(String)() const {
336         return String(_godot_string_name);
337     }
338 
339 }
340 
341 struct GodotStringLiteral(string data) {
342     private __gshared godot_string gs;
343     String str() const {
344         static if (data.length)
345             if (gs == godot_string.init) {
346                 synchronized {
347                     if (gs == godot_string.init)
348                         _godot_api.string_new_with_utf8_chars_and_len(&gs, data.ptr, cast(int) data
349                                 .length);
350                 }
351             }
352         //String ret = void;
353         //_godot_api.variant_new_copy(&ret._godot_string, &gs);
354         //return ret;
355         return String(gs);
356     }
357 
358     static if (data.length) {
359         shared static ~this() {
360             //if(gs != godot_string.init) _godot_api.variant_destroy(&gs);
361         }
362     }
363     alias str this;
364 }
365 
366 /++
367 Create a GodotStringLiteral.
368 
369 D $(D string) to Godot $(D String) conversion is expensive and cannot be done
370 at compile time. This literal does the conversion once the first time it's
371 needed, then caches the String, allowing it to implicitly convert to String at
372 no run time cost.
373 +/
374 enum gs(string str) = GodotStringLiteral!str.init;
375 
376 struct GodotStringNameLiteral(string data) {
377     private __gshared godot_string gs;
378     StringName str() const {
379         static if (data.length)
380             if (gs == godot_string.init) {
381                 synchronized {
382                     if (gs == godot_string.init)
383                         _godot_api.string_new_with_utf8_chars_and_len(&gs, data.ptr, cast(int) data
384                                 .length);
385                 }
386             }
387         // a pointer so it won't destroy itself ahead of time
388         String* p = cast(String*)&gs;
389         //String ret = void;
390         //_godot_api.variant_new_copy(&ret._godot_string, &gs);
391         //return ret;
392         return StringName(*p);
393     }
394 
395     static if (data.length) {
396         shared static ~this() {
397             //if(gs != godot_string.init) _godot_api.variant_destroy(&gs);
398         }
399     }
400     alias str this;
401 }
402 
403 /++
404 Create a GodotStringNameLiteral.
405 
406 D $(D string) to Godot $(D StringName) conversion is expensive and cannot be done
407 at compile time. This literal does the conversion once the first time it's
408 needed, then caches the StringName, allowing it to implicitly convert to StringName at
409 no run time cost.
410 +/
411 enum gn(string str) = GodotStringNameLiteral!str.init;