1 /**
2 Godot's ref-counted wchar_t String class.
3 
4 Copyright:
5 Copyright (c) 2007 Juan Linietsky, Ariel Manzur.
6 Copyright (c) 2014 Godot Engine contributors (cf. AUTHORS.md)
7 Copyright (c) 2017 Godot-D contributors
8 Copyright (c) 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 import godot.abi.types;
24 import godot.stringname;
25 import godot.charstring;
26 
27 import godot.variant;
28 
29 /**
30 This is the built-in string class (and the one used by GDScript). 
31 It supports Unicode and provides all necessary means for string handling. 
32 Strings are reference counted and use a copy-on-write approach, 
33 so passing them around is cheap in resources.
34 */
35 struct String {
36     //@nogc nothrow:
37 
38     package(godot) union _String {
39         godot_string _godot_string;
40         // some issue with forward reference 
41         // error: `godot.api.traits.getGodotObject!(Resource).getGodotObject.ret` size of type `Resource` is invalid
42         //String_Bind _bind; 
43         void* s;
44     }
45 
46     // TODO: deal with union problem
47     ref String_Bind _bind() const { return *cast(String_Bind*) s;}
48 
49     package(godot) _String _string;
50     alias _string this;
51 
52     this(StringName n) {
53         this = _bind.new2(n);
54         //_godot_api.variant_new_copy(&_godot_string, &n._godot_string_name);
55     }
56 
57     package(godot) this(in godot_string str) {
58         _godot_string = str;
59     }
60 
61     // this one is supposedly not needed because of this(S)(in S str) constructors
62     // this(string s) {
63     //     this = toGodotString(s);
64     // }
65 
66     /++
67 	Numeric constructor. S can be a built-in numeric type.
68 	+/
69     this(S)(in S num) if (isNumeric!S) {
70         import std.conv : text;
71         this(num.text);
72     }
73 
74     /++
75 	wchar_t constructor. S can be a slice or a null-terminated pointer.
76 	+/
77     this(S)(in S str) if (isImplicitlyConvertible!(S, const(wchar_t)[]) ||
78                           isImplicitlyConvertible!(S, const(wchar_t)*)) {
79         static if (isImplicitlyConvertible!(S, const(wchar_t)[])) {
80             const(wchar_t)[] contents = str;
81             _godot_api.string_new_with_wide_chars_and_len(&_godot_string, contents.ptr, cast(int) contents.length);
82         } else {
83             import core.stdc.wchar_ : wcslen;
84 
85             const(wchar_t)* contents = str;
86             _godot_api.string_new_with_wide_chars_and_len(&_godot_string, contents, cast(int) wcslen(contents));
87         }
88     }
89 
90     /++
91 	UTF-8 constructor. S can be a slice (like `string`) or a null-terminated pointer.
92 	+/
93     this(S)(in S str) if (isImplicitlyConvertible!(S, const(char)[]) ||
94                           isImplicitlyConvertible!(S, const(char)*)) {
95         static if (isImplicitlyConvertible!(S, const(char)[])) {
96             const(char)[] contents = str;
97             _godot_api.string_new_with_utf8_chars_and_len(&_string, contents.ptr, cast(int) contents.length);
98         } else {
99             const(char)* contents = str;
100             _godot_api.string_new_with_utf8_chars(&_godot_string, contents);
101         }
102     }
103 
104     void _defaultCtor() {
105         this = String_Bind.new0();
106     }
107 
108     ~this() {
109         //_bind._destructor();
110     }
111 
112     void opAssign(in String other) {
113         //_bind._destructor();
114         _godot_string = other._godot_string;
115         // other still owns the string, double free possible?
116     }
117 
118     // TODO rewrite it with constructor?
119     void opAssign(in string other) {
120         //_bind._destructor();
121 
122         // godot_string gs;
123         // _godot_api.string_new_with_utf8_chars_and_len(&gs, other.ptr, cast(int) other.length);
124         // _godot_string = gs;
125 
126         // FIXME: might allocate mem?
127         this = String(other);
128         
129         // other still owns the string, double free possible?
130     }
131 
132     /+String substr(int p_from,int p_chars) const
133 	{
134 		return String.empty; // todo
135 	}
136 	
137 	alias opSlice = substr;+/
138 
139     ref char32_t opIndex(in size_t idx) {
140         return *_godot_api.string_operator_index(&_godot_string, cast(int) idx);
141     }
142 
143     char32_t opIndex(in size_t idx) const {
144         return *_godot_api.string_operator_index(cast(godot_string*)&_godot_string, cast(int) idx);
145     }
146 
147     /// Returns the length of the wchar_t array, minus the zero terminator.
148     size_t length() const {
149         //return _bind.length(); // bug: infinite recursion
150         return _godot_api.string_to_utf8_chars(&_godot_string, null, 0);
151     }
152 
153     /// Returns: $(D true) if length is 0
154     bool empty() const {
155         return length == 0;
156     }
157 
158     int opCmp(in String s) const {
159         // TODO: Fix me
160         return 0;
161         //if(_godot_string == s._godot_string) return true;
162         //auto equal = _godot_api.string_operator_equal(&_godot_string, &s._godot_string);
163         //if(equal) return 0;
164         //auto less = _godot_api.string_operator_less(&_godot_string, &s._godot_string);
165         //return less?(-1):1;
166     }
167 
168     bool opEquals(in String other) const {
169         if (_godot_string == other._godot_string)
170             return true;
171         //return _godot_api.string_operator_equal(&_godot_string, &other._godot_string);
172         return _bind == other._bind;
173     }
174 
175     String opBinary(string op)(in String other) const if (op == "~" || op == "+") {
176         // it has to be zero initialized or godot will try to unref it and crash
177         //String ret;
178         godot_string ret;
179 
180         // __gshared static GDExtensionPtrOperatorEvaluator mb;
181         GDExtensionPtrOperatorEvaluator mb;
182         if (!mb) {
183             mb = _godot_api.variant_get_ptr_operator_evaluator(
184                 GDEXTENSION_VARIANT_OP_ADD, 
185                 GDEXTENSION_VARIANT_TYPE_STRING, 
186                 GDEXTENSION_VARIANT_TYPE_STRING
187                 );
188         }
189         mb(&_godot_string, &other._godot_string, &ret);
190 
191         return String(ret);
192     }
193 
194     void opOpAssign(string op)(in String other) if (op == "~" || op == "+") {
195         //this = opBinary!"+"(other);
196         godot_string tmp;
197 
198         // __gshared static GDExtensionPtrOperatorEvaluator mb;
199         GDExtensionPtrOperatorEvaluator mb;
200         if (!mb) {
201             mb = _godot_api.variant_get_ptr_operator_evaluator(
202                 GDEXTENSION_VARIANT_OP_ADD, 
203                 GDEXTENSION_VARIANT_TYPE_STRING, 
204                 GDEXTENSION_VARIANT_TYPE_STRING
205                 );
206         }
207         mb(&_godot_string, &other._godot_string, &tmp);
208         //_bind._destructor();
209         _godot_string = tmp;
210     }
211 
212     /// Returns a pointer to the wchar_t data. Always zero-terminated.
213     immutable(char32_t)* ptr() const {
214         return cast(immutable(char32_t)*) _godot_api.string_operator_index_const(
215             &_godot_string, 0);
216     }
217 
218     /// Returns a slice of the wchar_t data without the zero terminator.
219     immutable(wchar_t)[] data() const {
220         import std.conv : to;
221         version(Windows)
222             return cast(typeof(return)) to!wstring(cast(dchar[])(ptr[0 .. length]));
223         else
224             return cast(typeof(return)) cast(dchar[])(ptr[0 .. length]);
225     }
226 
227     alias toString = data;
228 
229     CharString utf8() const {
230         // untested, may overflow?
231         int size = cast(int) _godot_api.string_to_utf8_chars(&_godot_string, null, 0);
232         char* cstr = cast(char*) _godot_api.mem_alloc(size + 1);
233         _godot_api.string_to_utf8_chars(&_godot_string, cstr, size);
234         cstr[size] = '\0';
235         return CharString(cstr, size);
236     }
237 
238     String format(V)(V values) const if (is(V : Variant) || Variant.compatibleToGodot!V) {
239         const Variant v = values;
240         String new_string = void;
241         new_string._godot_string = _godot_api.string_format(&_godot_string, cast(godot_variant*)&v);
242 
243         return new_string;
244     }
245 
246     String format(V)(V values, String placeholder) const 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 /** 
265  * Constructs Godot String from str
266  * Params:
267  *   str = string to convert from
268  * Returns: Godot String
269  */
270 String toGodotString(string str) {
271     // FIXME: this is going to be slow as hell
272     godot_string gs;
273     _godot_api.string_new_with_utf8_chars_and_len(&gs, str.ptr, cast(int) str.length);
274     return String(gs);
275 }
276 
277 /** 
278  * Constructs string from str
279  * Params:
280  *   str = Godot String
281  * Returns: D string
282  */
283 string toDString(String str) {
284     // FIXME: check memaloc
285     import std.conv: to;
286     return str.data.to!string;
287 
288     // Another unsafer way to do that would be (converts to wstring)
289     // return str.data.idup;
290 }
291 
292 struct GodotStringLiteral(string data) {
293     private __gshared godot_string gs;
294     String str() const {
295         static if (data.length)
296             if (gs == godot_string.init) {
297                 synchronized {
298                     if (gs == godot_string.init)
299                         _godot_api.string_new_with_utf8_chars_and_len(&gs, data.ptr, cast(int) data
300                                 .length);
301                 }
302             }
303         //String ret = void;
304         //_godot_api.variant_new_copy(&ret._godot_string, &gs);
305         //return ret;
306         return String(gs);
307     }
308 
309     static if (data.length) {
310         shared static ~this() {
311             //if(gs != godot_string.init) _godot_api.variant_destroy(&gs);
312         }
313     }
314     alias str this;
315 }
316 
317 /++
318 Create a GodotStringLiteral.
319 
320 D $(D string) to Godot $(D String) conversion is expensive and cannot be done
321 at compile time. This literal does the conversion once the first time it's
322 needed, then caches the String, allowing it to implicitly convert to String at
323 no run time cost.
324 +/
325 enum gs(string str) = GodotStringLiteral!str.init;