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;