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