1 /** 2 * a @safe, @nogc-compatible template string type. 3 * Authors: Susan 4 * Date: 2021-11-06 5 * Licence: AGPL-3.0 or later 6 * Copyright: Susan, 2021 7 */ 8 module fixedstring; 9 10 import std.traits: isSomeChar; 11 12 /// short syntax. 13 auto FixedString(string s)() 14 { 15 import std.math.algebraic: nextPow2; 16 return FixedString!(nextPow2(s.length))(s); 17 } 18 19 /// 20 @safe @nogc nothrow unittest 21 { 22 enum testString = "dlang is good"; 23 immutable foo = FixedString!(testString); 24 immutable bar = FixedString!16("dlang is good"); 25 26 assert(foo == bar); 27 } 28 29 /// 30 struct FixedString(size_t maxSize, CharT = char) 31 { 32 invariant (_length <= data.length); 33 34 enum size = maxSize; /// 35 36 private size_t _length; 37 private CharT[maxSize] data = ' '; 38 39 /// 40 public size_t length() const pure @nogc @safe 41 { 42 return _length; 43 } 44 45 /// ditto 46 public void length(in size_t rhs) pure @safe @nogc 47 in (rhs <= maxSize) 48 { 49 if (rhs >= length) 50 { 51 data[length .. rhs] = CharT.init; 52 } 53 _length = rhs; 54 } 55 56 /// constructor 57 public this(in CharT[] rhs) @safe @nogc nothrow pure 58 in (rhs.length <= maxSize) 59 { 60 length = rhs.length; 61 data[0 .. length] = rhs[]; 62 } 63 64 /// assignment 65 public void opAssign(in CharT[] rhs) @safe @nogc nothrow pure 66 in (rhs.length <= maxSize) 67 { 68 length = rhs.length; 69 data[0 .. length] = rhs[]; 70 } 71 72 /// ditto 73 public void opAssign(T : FixedString!n, size_t n)(in T rhs) @safe @nogc nothrow pure 74 in (rhs.length <= maxSize) 75 { 76 length = rhs.length; 77 data[0 .. length] = rhs[]; 78 } 79 80 /// ditto 81 public void opOpAssign(string op)(in CharT[] rhs) @safe @nogc nothrow pure 82 if (op == "~") 83 in (length + rhs.length <= maxSize) 84 { 85 immutable oldLength = length; 86 length = length + rhs.length; 87 data[oldLength .. length] = rhs[]; 88 } 89 90 /// ditto 91 public void opOpAssign(string op)(in CharT rhs) @safe @nogc nothrow pure 92 if (op == "~") 93 in (length + 1 <= maxSize) 94 { 95 length = length + 1; 96 data[length - 1] = rhs; 97 } 98 99 /// ditto 100 public void opOpAssign(string op, T: FixedString!n, size_t n)(in T rhs) @safe @nogc nothrow pure 101 if (op == "~") 102 in (length + rhs.length <= maxSize) 103 { 104 immutable oldLength = length; 105 length = length + rhs.length; 106 data[oldLength .. length] = rhs[]; 107 } 108 109 /// array features... 110 public auto opSlice(in size_t first, in size_t last) @safe @nogc nothrow const pure 111 { 112 auto temp = data[first .. last]; 113 return temp; 114 } 115 116 /// ditto 117 public size_t opDollar(size_t pos)() @safe @nogc nothrow const pure 118 if (pos == 0) 119 { 120 return length; 121 } 122 123 /// ditto 124 public const(CharT)[] opIndex() @safe @nogc nothrow const pure 125 { 126 return data[0 .. length]; 127 } 128 129 /// ditto 130 public CharT opIndex(in size_t index) @safe @nogc nothrow const pure 131 in (index < length) 132 { 133 return data[index]; 134 } 135 136 /// ditto 137 public void opIndexAssign(in CharT rhs, in size_t index) @safe @nogc nothrow pure 138 in (index <= maxSize) 139 { 140 if (index >= length) 141 { 142 length = index + 1; 143 } 144 data[index] = rhs; 145 } 146 147 /// equality 148 public bool opEquals(T : FixedString!n, size_t n)(in T rhs) @safe @nogc nothrow const pure 149 { 150 return this[] == rhs[]; 151 } 152 153 /// ditto 154 public bool opEquals(in CharT[] s) @safe @nogc nothrow const pure 155 { 156 if (length != s.length) 157 { 158 return false; 159 } 160 161 return this[] == s[]; 162 } 163 164 /// concatenation. note that you should probably use the ~ operator instead - only use this version when you are pressed for ram and aren't making many calls, or you will end up with template bloat. 165 public auto concat(size_t s, T: FixedString!n, size_t n)(in T rhs) @safe @nogc nothrow const pure 166 in (s >= length + rhs.length) 167 { 168 FixedString!(s) result; 169 170 result = this[]; 171 result ~= rhs[]; 172 173 return result; 174 } 175 176 /// concatenation operator. generally, you should prefer this version. 177 public auto opBinary(string op, T: FixedString!n, size_t n)(in T rhs) @safe @nogc nothrow const pure 178 if (op == "~") 179 { 180 import std.math.algebraic: nextPow2; 181 return concat!(nextPow2(size + rhs.size))(rhs); 182 } 183 184 static if (isSomeChar!CharT) 185 { 186 /// 187 public const(CharT)[] toString() @safe nothrow const pure 188 { 189 return data[0 .. length].idup; 190 } 191 } 192 193 /// 194 public size_t toHash() @safe @nogc nothrow const pure 195 { 196 ulong result = length; 197 foreach (CharT c; data[0 .. length]) 198 { 199 result += c; 200 } 201 return result; 202 } 203 204 /// range interface 205 public bool empty() @safe @nogc nothrow const pure 206 { 207 return (length <= 0); 208 } 209 210 /// ditto 211 public CharT front() @safe @nogc nothrow const pure 212 { 213 return data[0]; 214 } 215 216 /// ditto 217 public void popFront() @safe @nogc nothrow 218 { 219 for (auto i = 0; i < length; ++i) 220 { 221 data[i] = data[i + 1]; 222 } 223 length = length - 1; 224 } 225 226 mixin(opApplyWorkaround); 227 } 228 229 /// readme example code 230 @safe @nogc nothrow unittest 231 { 232 FixedString!14 foo = "clang"; 233 foo[0] = 'd'; 234 foo ~= " is cool"; 235 assert (foo == "dlang is cool"); 236 237 foo.length = 9; 238 239 immutable bar = FixedString!"neat"; 240 assert (foo ~ bar == "dlang is neat"); 241 242 // wchars and dchars are also supported 243 assert(FixedString!(5, wchar)("áéíóú") == "áéíóú"); 244 245 // in fact, any type is: 246 immutable int[4] intArray = [1, 2, 3, 4]; 247 assert(FixedString!(5, int)(intArray) == intArray); 248 } 249 250 private string resultAssign(in int n) 251 { 252 switch (n) 253 { 254 case 1: 255 return "result = dg(temp);"; 256 case 2: 257 return "result = dg(i, temp);"; 258 default: 259 assert(false, "this will never happen."); 260 } 261 } 262 263 private string delegateType(in int n, in string attributes) 264 { 265 string params; 266 switch (n) 267 { 268 case 1: 269 params = "ref CharT"; 270 break; 271 case 2: 272 params = "ref int, ref CharT"; 273 break; 274 default: 275 assert(false, "unsupported number of parameters."); 276 } 277 278 return "delegate(" ~ params ~ ") " ~ attributes; 279 } 280 281 private string opApplyWorkaround() 282 { 283 // dfmt off 284 return paramNumbers("") ~ 285 paramNumbers("@safe") ~ 286 paramNumbers("@nogc") ~ 287 paramNumbers("@safe @nogc") ~ 288 paramNumbers("nothrow") ~ 289 paramNumbers("@safe nothrow") ~ 290 paramNumbers("@nogc nothrow") ~ 291 paramNumbers("@safe @nogc nothrow") ~ 292 paramNumbers("pure") ~ 293 paramNumbers("pure @safe") ~ 294 paramNumbers("pure @nogc") ~ 295 paramNumbers("pure @safe @nogc") ~ 296 paramNumbers("pure nothrow") ~ 297 paramNumbers("pure @safe nothrow") ~ 298 paramNumbers("pure @nogc nothrow") ~ 299 paramNumbers("pure @safe @nogc nothrow"); 300 // dfmt on 301 } 302 303 private string paramNumbers(in string params) 304 { 305 // dfmt off 306 return good(1, params, true) ~ 307 good(2, params, true) ~ 308 good(1, params, false) ~ 309 good(2, params, false); 310 // dfmt on 311 } 312 313 private string good(in int n, in string parameters, in bool isConst) 314 { 315 string s; 316 if (isConst) 317 { 318 s = "const"; 319 } 320 else 321 { 322 s = ""; 323 } 324 325 string result = " 326 public int opApply(scope int " ~ delegateType(n, parameters) ~ " dg) " ~ s ~ " 327 { 328 int result = 0; 329 330 for (int i = 0; i != length; ++i) 331 { 332 CharT temp = data[i]; 333 " ~ resultAssign(n) ~ " 334 if (result) 335 { 336 break; 337 } 338 } 339 340 return result; 341 }"; 342 343 return result; 344 } 345 346 @safe @nogc nothrow unittest 347 { 348 immutable string temp = "cool"; 349 auto a = FixedString!8(temp); 350 assert(a[0] == 'c'); 351 assert(a == "cool"); 352 assert(a[] == "cool"); 353 assert(a[0 .. $] == "cool"); 354 355 a[2] = 'd'; 356 assert(a == "codl"); 357 assert(a != ""); 358 359 a[5] = 'd'; 360 assert(a == "codl\xffd"); 361 362 a.length = 4; 363 assert(a == "codl"); 364 365 a.length = 6; 366 assert(a == "codl\xff\xff"); 367 368 a.length = 4; 369 assert(a == "codl"); 370 371 FixedString!10 b; 372 b = " is nic"; 373 b ~= 'e'; 374 assert(a ~ b == "codl is nice"); 375 assert(a.concat!16(b) == "codl is nice"); 376 assert(a ~ b == a.concat!16(b)); 377 378 FixedString!10 d; 379 d = a; 380 381 foreach (i, char c; a) 382 { 383 switch (i) 384 { 385 case 0: 386 assert(c == 'c'); 387 break; 388 389 case 1: 390 assert(c == 'o'); 391 break; 392 393 case 2: 394 assert(c == 'd'); 395 break; 396 397 case 3: 398 assert(c == 'l'); 399 break; 400 401 default: 402 assert(false); 403 } 404 } 405 406 a = "de"; 407 a ~= "ad"; 408 assert(a == "dead"); 409 b = "beef"; 410 a ~= b; 411 assert(a == "deadbeef"); 412 413 assert(FixedString!"aéiou" == "aéiou"); 414 415 immutable char[4] dead = "dead"; 416 immutable(char)[4] beef = "beef"; 417 418 immutable FixedString!4 deader = dead; 419 immutable FixedString!4 beefer = beef; 420 assert(deader ~ beefer == "deadbeef"); 421 } 422 423 @safe nothrow unittest 424 { 425 immutable a = FixedString!16("bepis"); 426 assert (a.toString == "bepis"); 427 428 int[FixedString!16] table; 429 table[a] = 1; 430 431 immutable b = FixedString!16("conk"); 432 table[b] = 2; 433 434 assert(table[a] == 1); 435 assert(table[b] == 2); 436 437 } 438 439 @system unittest 440 { 441 import std.exception; 442 import core.exception : AssertError; 443 444 assertThrown!AssertError(FixedString!2("too long")); 445 446 FixedString!2 a = "uh"; 447 assertThrown!AssertError(a[69] = 'a'); 448 assertThrown!AssertError(a.concat!1(a)); 449 }