1 /++ 2 $(H1 Small String) 3 4 The module contains self-contained generic small string implementaton. 5 6 $(LREF SmallString) supports ASDF - Json Serialisation Library. 7 8 See also `include/mir/small_series.h` for a C++ version of this type. 9 Both C++ and D implementations have the same ABI and name mangling. 10 11 Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments 12 Authors: Ilya Yaroshenko 13 +/ 14 module mir.small_string; 15 16 import mir.serde: serdeScoped, serdeProxy; 17 18 private extern (C) @system nothrow @nogc pure size_t strnlen_s(scope const char* s, size_t n); 19 20 private static immutable errorMsg = "Cannot create SmallString: input string exceeds maximum allowed length."; 21 version(D_Exceptions) 22 private static immutable exception = new Exception(errorMsg); 23 24 extern(C++, "mir"): 25 26 /++ 27 Self-contained generic Small String implementaton. 28 +/ 29 @serdeScoped @serdeProxy!(const(char)[]) 30 struct SmallString(uint maxLength) 31 if (maxLength) 32 { 33 34 import core.stdc..string: memcmp, memcpy, strlen; 35 import std.traits: Unqual, isIterable; 36 37 // maxLength bytes 38 char[maxLength] _data = '\0'; 39 40 extern(D) @safe pure @nogc: 41 42 /// Constructor 43 this(typeof(null)) 44 { 45 } 46 47 /// ditto 48 this(scope const(char)[] str) 49 { 50 this.opAssign(str); 51 } 52 53 /// ditto 54 this(uint n)(auto ref scope const SmallString!n str) 55 { 56 this.opAssign(str); 57 } 58 59 /// ditto 60 this(Range)(auto ref Range str) 61 if (isIterable!Range) 62 { 63 size_t i = 0; 64 foreach(char c; str) 65 { 66 if (i > _data.length) 67 { 68 version(D_Exceptions) throw exception; 69 else assert(0, errorMsg); 70 } 71 _data[i++] = c; 72 } 73 } 74 75 /// `=` operator 76 ref typeof(this) opAssign(typeof(null)) return 77 { 78 _data = '\0'; 79 return this; 80 } 81 82 /// ditto 83 ref typeof(this) opAssign(scope const(char)[] str) return @trusted 84 { 85 if (str.length > _data.sizeof) 86 { 87 version(D_Exceptions) throw exception; 88 else assert(0, errorMsg); 89 } 90 if (__ctfe) 91 _data[0 .. str.length] = str; 92 else 93 memcpy(_data.ptr, str.ptr, str.length); 94 _data[str.length .. $] = '\0'; 95 return this; 96 } 97 98 /// ditto 99 ref typeof(this) opAssign(uint n)(auto ref scope const SmallString!n rhs) return 100 if (n != maxLength) 101 { 102 static if (n < maxLength) 103 { 104 version (LDC) 105 cast(char[n])(_data[0 .. n]) = rhs._data; 106 else 107 _data[0 .. n] = rhs._data; 108 _data[n .. maxLength] = '\0'; 109 } 110 else 111 { 112 if (rhs._data[maxLength]) 113 { 114 version(D_Exceptions) throw exception; 115 else assert(0, errorMsg); 116 } 117 _data = cast(char[maxLength])(rhs._data[0 .. maxLength]); 118 } 119 return this; 120 } 121 122 /// ditto 123 ref typeof(this) opAssign(uint n)(const SmallString!n rhs) return 124 if (n != maxLength) 125 { 126 static if (n < maxLength) 127 { 128 version (LDC) 129 cast(char[n])(_data[0 .. n]) = rhs._data; 130 else 131 _data[0 .. n] = rhs._data; 132 _data[n .. maxLength] = '\0'; 133 } 134 else 135 { 136 if (rhs._data[maxLength]) 137 { 138 version(D_Exceptions) throw exception; 139 else assert(0, errorMsg); 140 } 141 _data = cast(char[maxLength])(rhs._data[0 .. maxLength]); 142 } 143 return this; 144 } 145 146 /// ditto 147 void trustedAssign(scope const(char)[] str) return @trusted nothrow 148 { 149 _data = '\0'; 150 if (__ctfe) 151 _data[0 .. str.length] = str; 152 else 153 memcpy(_data.ptr, str.ptr, str.length); 154 } 155 156 /// 157 ref typeof(this) append(char c) @trusted 158 { 159 auto length = opIndex.length; 160 if (length == maxLength) 161 { 162 version(D_Exceptions) throw exception; 163 else assert(0, errorMsg); 164 } 165 _data[length] = c; 166 return this; 167 } 168 169 /// 170 ref typeof(this) append(scope const(char)[] str) @trusted 171 { 172 auto length = opIndex.length; 173 if (length + str.length > maxLength) 174 { 175 version(D_Exceptions) throw exception; 176 else assert(0, errorMsg); 177 } 178 if (__ctfe) 179 _data[length .. str.length + length] = str; 180 else 181 memcpy(_data.ptr + length, str.ptr, str.length); 182 return this; 183 } 184 185 /// ditto 186 alias put = append; 187 188 /// ditto 189 alias opOpAssign(string op : "~") = append; 190 191 /// 192 SmallString concat(scope const(char)[] str) scope const 193 { 194 SmallString c = this; 195 c.append(str); 196 return c; 197 } 198 199 /// ditto 200 alias opBinary(string op : "~") = concat; 201 202 203 scope nothrow: 204 205 /++ 206 Returns an scope common string. 207 208 The property is used as common string representation self alias. 209 210 The alias helps with `[]`, `[i]`, `[i .. j]`, `==`, and `!=` operations and implicit conversion to strings. 211 +/ 212 inout(char)[] opIndex() inout @trusted scope return 213 { 214 size_t i; 215 if (__ctfe) 216 while (i < maxLength && _data[i]) i++; 217 else 218 i = _data[$ - 1] ? _data.length : strlen(_data.ptr); 219 return _data[0 .. i]; 220 } 221 222 /// 223 ref inout(char) opIndex(size_t index) inout scope return 224 { 225 return opIndex[index]; 226 } 227 228 const: 229 230 /// 231 bool empty() @property 232 { 233 return _data[0] == 0; 234 } 235 236 /// 237 size_t length() @property 238 { 239 return opIndex.length; 240 } 241 242 /// 243 alias toString = opIndex; 244 245 /// Hash implementation 246 size_t toHash() 247 { 248 return hashOf(opIndex); 249 } 250 251 /// Comparisons operator overloads 252 bool opEquals(ref scope const SmallString rhs) scope 253 { 254 return _data == rhs._data; 255 } 256 257 /// ditto 258 bool opEquals(SmallString rhs) scope 259 { 260 return _data == rhs._data; 261 } 262 263 /// ditto 264 bool opEquals(uint rhsMaxLength)(auto ref scope const SmallString!rhsMaxLength rhs) scope 265 if (rhsMaxLength != maxLength) 266 { 267 return opIndex == rhs.opIndex; 268 } 269 270 /// ditto 271 bool opEquals()(scope const(char)[] str) scope 272 { 273 return opIndex == str; 274 } 275 276 /// ditto 277 int opCmp(uint rhsMaxLength)(auto ref scope const SmallString!rhsMaxLength rhs) scope 278 { 279 return __cmp(opIndex, rhs.opIndex); 280 } 281 282 /// ditto 283 int opCmp()(scope const(char)[] str) scope 284 { 285 return __cmp(opIndex, str); 286 } 287 } 288 289 /// 290 @safe pure @nogc version(mir_test) unittest 291 { 292 SmallString!16 s16; 293 assert(s16.empty); 294 295 auto s8 = SmallString!8("Hellow!!"); 296 assert(s8 == "Hellow!!", s8[]); 297 298 s16 = s8; 299 assert(s16 == "Hellow!!", s16[]); 300 s16[7] = '@'; 301 s8 = null; 302 assert(s8.empty); 303 s8 = s16; 304 assert(s8 == "Hellow!@"); 305 306 auto s8_2 = s8; 307 assert(s8_2 == "Hellow!@"); 308 assert(s8_2 == s8); 309 310 assert(s8 < "Hey"); 311 assert(s8 > "Hellow!"); 312 313 assert(s8.opCmp("Hey") < 0); 314 assert(s8.opCmp(s8) == 0); 315 } 316 317 /// Concatenation 318 @safe pure @nogc version(mir_test) unittest 319 { 320 auto a = SmallString!16("asdf"); 321 a ~= " "; 322 auto b = a ~ "qwerty"; 323 static assert(is(typeof(b) == SmallString!16)); 324 assert(b == "asdf qwerty"); 325 b.put('!'); 326 b.put("!"); 327 assert(b == "asdf qwerty!!"); 328 } 329 330 @safe pure @nogc nothrow version(mir_test) unittest 331 { 332 import mir.conv: emplaceRef; 333 SmallString!32 a, b; 334 emplaceRef!(const SmallString!32)(a, cast(const)b); 335 }