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