1 /** Helper functions for working with $(I C strings). 2 3 This module is intended to provide fast, safe and garbage free 4 way to work with $(I C strings). 5 6 Examples: 7 --- 8 version(Posix): 9 10 import core.stdc.stdlib: free; 11 import core.sys.posix.unistd: getcwd; 12 import core.sys.posix.stdlib: getenv, setenv; 13 import std.exception: enforce; 14 15 @property string cwd() 16 { return enforce(getcwd(null, 0).moveToString!free()); } 17 18 string getEnvironment(in char[] name) 19 { return enforce(getenv(name.tempCString()).toString()); } 20 21 void setEnvironment(in char[] name, in char[] value) 22 { enforce(setenv(name.tempCString(), value.tempCString(), 1) != -1); } 23 --- 24 --- 25 version(Windows): 26 27 import core.sys.windows.windows: SetEnvironmentVariableW; 28 import std.exception: enforce; 29 30 void setEnvironment(in char[] name, in char[] value) 31 { enforce(SetEnvironmentVariableW(name.tempCString!wchar(), value.tempCString!wchar())); } 32 --- 33 34 Copyright: Denis Shelomovskij 2013 35 36 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 37 38 Authors: Denis Shelomovskij 39 40 Macros: 41 COREREF = $(HTTP dlang.org/phobos/core_$1.html#$2, $(D core.$1.$2)) 42 */ 43 module unstd.c..string; 44 45 46 import core.exception; 47 48 import std.traits; 49 import unstd.utf; 50 version(unittest) import unstd.generictuple; 51 import unstd.memory.allocation; 52 53 54 /// Returns $(I C string) length. If $(D cstr) is null returns 0. 55 @property size_t length(C)(in C* cstr) pure nothrow @nogc 56 if(isSomeChar!C) 57 { 58 if(!cstr) 59 return 0; 60 size_t res = 0; 61 while(cstr[res]) 62 ++res; 63 return res; 64 } 65 66 pure nothrow @nogc unittest 67 { 68 assert(!(cast(char*) null).length); 69 assert(!"".ptr.length); 70 foreach(s; expressionTuple!("abc", "abc"w, "abc"d)) 71 assert(s.ptr.length == 3); 72 } 73 74 75 /** 76 Compare $(I C strings) lexicographically. 77 78 Preconditions: 79 $(D cstr1 != null && cstr2 != null) 80 */ 81 @property int cmpCStrings(C)(in C* cstr1, in C* cstr2) pure nothrow @nogc 82 if(isSomeChar!C) 83 in { assert(cstr1 && cstr2); } 84 body 85 { 86 if(cstr1 == cstr2) 87 return 0; 88 for(size_t i = 0; ; ++i) 89 { 90 const c1 = cstr1[i], c2 = cstr2[i]; 91 if(!c1 && !c2) 92 return 0; 93 if(c1 != c2) 94 return c1 > c2 ? 1 : -1; 95 } 96 } 97 98 /// 99 pure nothrow @nogc unittest 100 { 101 assert(cmpCStrings("ab".ptr, "ab".ptr) == 0); 102 assert(cmpCStrings("ab".ptr, "abc".ptr) < 0); 103 assert(cmpCStrings("abc".ptr, "ab".ptr) > 0); 104 } 105 106 pure nothrow unittest 107 { 108 string prev = null; 109 foreach(s; ["", "a", "abc", "abcd", "я"]) 110 { 111 assert(cmpCStrings(s.ptr, s.ptr) == 0); 112 assert(cmpCStrings(s.ptr, (s ~ '\0').dup.ptr) == 0); 113 if(prev) 114 { 115 assert(cmpCStrings(prev.ptr, s.ptr) == -1); 116 assert(cmpCStrings(s.ptr, prev.ptr) == 1); 117 } 118 prev = s; 119 } 120 } 121 122 123 /** 124 Returns whether two $(I C strings) are equal. 125 126 Preconditions: 127 $(D cstr1 != null && cstr2 != null) 128 */ 129 @property bool equalCStrings(C)(in C* cstr1, in C* cstr2) pure nothrow @nogc 130 if(isSomeChar!C) 131 in { assert(cstr1 && cstr2); } 132 body 133 { 134 if(cstr1 == cstr2) 135 return true; 136 for(size_t i = 0; ; ++i) 137 { 138 const c1 = cstr1[i]; 139 if(c1 != cstr2[i]) 140 return false; 141 if(!c1) 142 return true; 143 } 144 } 145 146 /// 147 pure nothrow @nogc unittest 148 { 149 assert( equalCStrings("ab".ptr, "ab".ptr)); 150 assert(!equalCStrings("ab".ptr, "abc".ptr)); 151 assert( equalCStrings("ab".ptr, "ab\0cd".ptr)); 152 } 153 154 155 /** 156 Returns array representing $(I C string) where $(D '\0') character is placed 157 after the end of the array. If $(D cstr) is null returns null. 158 */ 159 @property inout(C)[] asArray(C)(inout C* cstr) pure nothrow @nogc 160 if(isSomeChar!C) 161 { 162 if(!cstr) 163 return null; 164 165 return cstr[0 .. cstr.length]; 166 } 167 168 pure nothrow @nogc unittest 169 { 170 foreach(s; expressionTuple!(cast(char[]) null, "", "abc", "abc"w, "abc"d)) 171 { 172 auto var = s; // Have to assign to variable first. 173 assert(var.ptr.asArray is var); 174 } 175 } 176 177 178 /** 179 Creates GC-allocated $(D string) with copy of $(I C string) text. 180 If $(D cstr) is null returns null, otherwise if $(D cstr) is empty 181 returns $(D ""). 182 */ 183 string toString(C)(in C* cstr) pure 184 if(isSomeChar!C) 185 { 186 if(!cstr) 187 return null; 188 if(!*cstr) 189 return ""; 190 const arr = cstr.asArray; 191 static if(is(C : char)) 192 return arr.idup; 193 else 194 return arr.toUTF8(); 195 } 196 197 pure unittest 198 { 199 foreach(s; expressionTuple!(cast(char[]) null, "", "abc", "abc"w, "abc"d)) 200 assert(s.ptr.toString() == s.toUTF8()); 201 } 202 203 204 /** 205 Creates $(I C string) allocated using $(D tryAllocate) with copy of $(D str). 206 If $(D str) is null returns null. 207 208 $(D tryAllocate) is assumed to return properly aligned for $(D To) memory or 209 null if allocation fails. 210 211 If allocation fails toCString will call $(COREREF exception, onOutOfMemoryError) 212 which is expected to throw an $(COREREF exception, OutOfMemoryError). 213 */ 214 To* toCString(alias tryAllocate, To = char, From)(in From[] str) 215 if(isSomeChar!To && isSomeChar!From) 216 { 217 if(!str) 218 return null; 219 220 const maxLen = maxLength!To(str); 221 To* cstr = null; 222 if(const totalCount = memoryAdd(maxLen, 1)) 223 if(const totalBytes = memoryMult(To.sizeof, totalCount)) 224 cstr = cast(To*) tryAllocate(totalBytes); 225 if(!cstr) 226 onOutOfMemoryError(); 227 To[] carr = copyEncoded(str, cstr[0 .. maxLen]); 228 *(cstr + carr.length) = '\0'; 229 return cstr; 230 } 231 232 /// 233 nothrow @nogc unittest 234 { 235 import core.stdc.stdlib; 236 import core.stdc..string; 237 238 string str = "abc"; 239 240 char* cstr = str.toCString!malloc(); 241 scope(exit) free(cstr); 242 assert(strlen(cstr) == 3); 243 } 244 245 unittest 246 { 247 import core.stdc.stdlib; 248 249 assert("abc".toCString!malloc().moveToString!free() == "abc"); 250 assert("abc"d.toCString!malloc().moveToString!free() == "abc"); 251 assert("abc".toCString!(malloc, wchar)().moveToString!free() == "abc"); 252 } 253 254 255 /** 256 Returns same as $(MREF toString) but also if $(D cstr) is not null 257 releases its memory calling $(D release(cast(void*) cstr)). 258 */ 259 string moveToString(alias release, C)(C* cstr) 260 if(isSomeChar!C) 261 { 262 scope(exit) if(cstr) release(cast(void*) cstr); 263 return cstr.toString(); 264 } 265 266 unittest 267 { 268 import core.stdc.stdlib; 269 270 auto cstr = cast(char*) malloc(4); 271 assert(cstr); 272 cstr[0 .. 4] = "abc\0"; 273 assert(cstr.moveToString!free() == "abc"); 274 } 275 276 277 /** 278 Creates temporary $(I C string) with copy of passed text. 279 280 Returned object is implicitly convertible to $(D const To*) and 281 has two properties: $(D ptr) to access $(I C string) as $(D const To*) 282 and $(D buffPtr) to access it as $(D To*). 283 284 The temporary $(I C string) is valid unless returned object is destroyed. 285 Thus if returned object is assigned to a variable the temporary is 286 valid unless the variable goes out of scope. If returned object isn't 287 assigned to a variable it will be destroyed at the end of creating 288 primary expression. 289 290 Implementation_note: 291 For small strings tempCString will use stack allocated buffer, 292 for large strings (approximately 1000 characters and more) it will 293 allocate temporary one from $(DPREF2 memory, allocation, threadHeap). 294 295 Note: 296 This function is intended to be used in function call expression (like 297 $(D strlen(str.tempCString()))). Incorrect usage of this function may 298 lead to memory corruption. 299 See $(RED WARNING) in $(B Examples) section. 300 301 See_Also: 302 $(DPREF2 memory, allocation, tempAlloc) 303 */ 304 auto tempCString(To = char, From)(in From[] str) 305 if(isSomeChar!To && isSomeChar!From) 306 { 307 enum useStack = cast(To*) -1; 308 309 static struct Res 310 { 311 @disable this(); 312 @disable this(this); 313 alias ptr this; 314 315 @property inout(To)* buffPtr() inout @safe pure nothrow @nogc 316 { return _ptr == useStack ? _buff.ptr : _ptr; } 317 318 @property const(To)* ptr() const @safe pure nothrow @nogc 319 { return buffPtr; } 320 321 ~this() 322 { if(_ptr != useStack) threadHeap.free(_ptr); } 323 324 private: 325 To* _ptr; 326 To[1024] _buff; 327 } 328 329 // TODO: Don't stack allocate uninitialized array to 330 // not confuse unprecise GC. 331 332 Res res = void; 333 if(!str) 334 { 335 res._ptr = null; 336 return res; 337 } 338 339 // Note: res._ptr can't point to res._buff as structs are movable. 340 341 const totalCount = memoryAdd(maxLength!To(str), 1); 342 if(!totalCount) 343 onOutOfMemoryError(); 344 const needAllocate = totalCount > res._buff.length; 345 To[] arr = copyEncoded(str, needAllocate ? 346 threadHeap.allocate!To(totalCount)[0 .. $ - 1] : res._buff[0 .. totalCount - 1]); 347 *(arr.ptr + arr.length) = '\0'; 348 res._ptr = needAllocate ? arr.ptr : useStack; 349 return res; 350 } 351 352 /// 353 unittest 354 { 355 import core.stdc..string; 356 357 string str = "abc"; 358 359 // Intended usage 360 assert(strlen(str.tempCString()) == 3); 361 362 // Correct usage 363 auto tmp = str.tempCString(); 364 assert(strlen(tmp) == 3); // or `tmp.ptr`, or `tmp.buffPtr` 365 366 // $(RED WARNING): $(RED Incorrect usage) 367 auto pInvalid1 = str.tempCString().ptr; 368 const char* pInvalid2 = str.tempCString(); 369 // Both pointers refer to invalid memory here as 370 // returned values aren't assigned to a variable and 371 // both primary expressions are ended. 372 } 373 374 unittest 375 { 376 assert("abc".tempCString().asArray == "abc"); 377 assert("abc"d.tempCString().ptr.asArray == "abc"); 378 assert("abc".tempCString!wchar().buffPtr.asArray == "abc"w); 379 }