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 fixedstring.fixedstringrange; 11 import fixedstring.opapplymixin; 12 import std.traits: isSomeChar; 13 14 /// short syntax. 15 auto fixedString(string s)() 16 { 17 import std.math.algebraic: nextPow2; 18 return FixedString!(nextPow2(s.length))(s); 19 } 20 21 /// 22 @safe @nogc nothrow unittest 23 { 24 enum testString = "dlang is good"; 25 immutable foo = fixedString!(testString); 26 immutable bar = FixedString!16(testString); 27 28 assert(foo == bar); 29 } 30 31 /// 32 struct FixedString(size_t maxSize, CharT = char) 33 { 34 invariant (length_ <= data.length); 35 36 enum size = maxSize; /// 37 38 private size_t length_; 39 private CharT[maxSize] data = ' '; 40 41 /// 42 size_t length() const @nogc nothrow pure @safe 43 { 44 return length_; 45 } 46 47 /// ditto 48 void length(in size_t rhs) @nogc nothrow pure @safe 49 in (rhs <= maxSize) 50 { 51 if (rhs >= length) 52 { 53 data[length .. rhs] = CharT.init; 54 } 55 length_ = rhs; 56 } 57 58 /// constructor 59 this(in CharT[] rhs) 60 in (rhs.length <= maxSize) 61 { 62 length = rhs.length; 63 data[0 .. length] = rhs[]; 64 } 65 66 /// assignment 67 void opAssign(in CharT[] rhs) 68 in (rhs.length <= maxSize) 69 { 70 length = rhs.length; 71 data[0 .. length] = rhs[]; 72 } 73 74 /// ditto 75 void opAssign(T: CharT, size_t n)(in FixedString!(n, T) rhs) 76 in (rhs.length <= maxSize) 77 { 78 length = rhs.length; 79 data[0 .. length] = rhs[0 .. length]; 80 } 81 82 /// ditto 83 void opOpAssign(string op)(in CharT[] rhs) 84 if (op == "~") 85 in (length + rhs.length <= maxSize) 86 { 87 immutable oldLength = length; 88 length = length + rhs.length; 89 data[oldLength .. length] = rhs[]; 90 } 91 92 /// ditto 93 void opOpAssign(string op)(in CharT rhs) 94 if (op == "~") 95 in (length + 1 <= maxSize) 96 { 97 length = length + 1; 98 data[length - 1] = rhs; 99 } 100 101 /// ditto 102 void opOpAssign(string op, T: CharT, size_t n)(in FixedString!(n, T) rhs) 103 if (op == "~") 104 in (length + rhs.length <= maxSize) 105 { 106 immutable oldLength = length; 107 length = length + rhs.length; 108 data[oldLength .. length] = rhs[0 .. rhs.length]; 109 } 110 111 /// array features... 112 auto opSlice(in size_t first, in size_t last) const @nogc nothrow pure @safe 113 in (first <= length && last <= length) 114 { 115 return data[first .. last]; 116 } 117 118 /// ditto 119 size_t opDollar(size_t pos)() const @nogc nothrow pure @safe 120 if (pos == 0) 121 { 122 return length; 123 } 124 125 /// ditto 126 auto opIndex() const 127 { 128 return FixedStringRange!CharT(data[0 .. length]); 129 } 130 131 /// ditto 132 CharT opIndex(in size_t index) const @nogc nothrow pure @safe 133 in (index < length) 134 { 135 return data[index]; 136 } 137 138 /// ditto 139 void opIndexAssign(in CharT rhs, in size_t index) 140 in (index < length) 141 { 142 data[index] = rhs; 143 } 144 145 /// equality 146 bool opEquals(T : CharT, size_t n)(in FixedString!(n, T) rhs) const 147 { 148 import std.algorithm.comparison: equal; 149 return this[].equal(rhs[]); 150 } 151 152 /// ditto 153 bool opEquals(in CharT[] s) const 154 { 155 if (length != s.length) 156 { 157 return false; 158 } 159 160 import std.algorithm.comparison: equal; 161 return this[].equal(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 auto concat(size_t s, T: CharT, size_t n)(in FixedString!(n, T) rhs) const 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 auto opBinary(string op, T: CharT, size_t n)(in FixedString!(n, T) rhs) const 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 const(CharT)[] toString() const nothrow pure @safe 188 { 189 return data[0 .. length].idup; 190 } 191 } 192 193 /// 194 size_t toHash() const @nogc nothrow pure @safe 195 { 196 ulong result = length; 197 foreach (CharT c; data[0 .. length]) 198 { 199 result += c; 200 } 201 return result; 202 } 203 204 mixin(opApplyWorkaround); 205 } 206 207 /// 208 @safe @nogc nothrow pure unittest 209 { 210 immutable string temp = "cool"; 211 auto foo = FixedString!8(temp); 212 assert(foo[0] == 'c'); 213 assert(foo == "cool"); 214 215 import std.algorithm.comparison: equal; 216 assert(foo[].equal("cool")); 217 assert(foo[0 .. $] == "cool"); 218 219 foo[2] = 'd'; 220 assert(foo == "codl"); 221 assert(foo != ""); 222 223 foo ~= "d"; 224 foo.length = 4; 225 assert(foo == "codl"); 226 227 foo.length = 6; 228 assert(foo == "codl\xff\xff"); 229 230 foo.length = 4; 231 assert(foo == "codl"); 232 233 import std.range: retro; 234 assert(equal(retro(foo[]), "ldoc")); 235 236 import std.range: radial; 237 assert(equal(radial(foo[]), "odcl")); 238 239 import std.range: cycle; 240 assert(foo[].cycle[4 .. 8].equal(foo[])); 241 242 assert(foo[].save == foo[]); 243 244 FixedString!10 bar; 245 bar = " is nic"; 246 bar ~= 'e'; 247 248 immutable truth = fixedString!"codl is nice"; 249 250 assert(foo ~ bar == truth); 251 assert(foo.concat!16(bar) == truth); 252 assert(foo ~ bar == foo.concat!16(bar)); 253 254 FixedString!10 d; 255 d = foo; 256 d.length = 4; 257 258 foreach (i, char c; d) 259 { 260 assert(c == foo[i]); 261 } 262 263 foo = "de"; 264 foo ~= "ad"; 265 assert(foo == "dead"); 266 bar = "beef"; 267 foo ~= bar; 268 assert(foo == "deadbeef"); 269 270 assert(fixedString!"aéiou" == "aéiou"); 271 272 immutable char[4] dead = "dead"; 273 immutable(char)[4] beef = "beef"; 274 275 immutable FixedString!4 deader = dead; 276 immutable FixedString!4 beefer = beef; 277 assert(deader ~ beefer == "deadbeef"); 278 } 279 280 /// 281 pure @safe nothrow unittest 282 { 283 immutable a = FixedString!16("bepis"); 284 assert(a.toString == "bepis"); 285 286 int[FixedString!16] table; 287 table[a] = 1; 288 289 immutable b = FixedString!16("conk"); 290 table[b] = 2; 291 292 assert(table[a] == 1); 293 assert(table[b] == 2); 294 } 295 296 /// readme example code 297 @safe @nogc nothrow pure unittest 298 { 299 FixedString!14 foo = "clang"; 300 foo[0] = 'd'; 301 foo ~= " is cool"; 302 assert(foo == "dlang is cool"); 303 304 foo.length = 9; 305 306 immutable bar = fixedString!"neat"; 307 assert(foo ~ bar == "dlang is neat"); 308 309 // wchars and dchars are also supported 310 assert(FixedString!(5, wchar)("áéíóú") == "áéíóú"); 311 312 // in fact, any type is: 313 immutable int[4] intArray = [1, 2, 3, 4]; 314 assert(FixedString!(5, int)(intArray) == intArray); 315 } 316 317 @system unittest 318 { 319 import std.exception: assertThrown; 320 import core.exception: AssertError; 321 322 assertThrown!AssertError(FixedString!2("too long")); 323 324 FixedString!2 a = "uh"; 325 assertThrown!AssertError(a[69] = 'a'); 326 assertThrown!AssertError(a.concat!1(a)); 327 }