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;