1 /** 2 Some additional generic types. 3 4 Copyright: © 2019 Arne Ludwig <arne.ludwig@posteo.de> 5 License: Subject to the terms of the MIT license, as written in the 6 included LICENSE file. 7 Authors: Arne Ludwig <arne.ludwig@posteo.de> 8 */ 9 module dalicious.typecons; 10 11 import std.traits; 12 13 14 /// A statically allocated array with up to `maxElements` elements. 15 struct BoundedArray(E, size_t maxElements_) 16 { 17 import std.meta : allSatisfy; 18 19 private static enum isBaseType(T) = is(T : E); 20 21 22 /// An alias to the element type. 23 static alias ElementType = E; 24 25 26 /// The maximum number of elements this bounded array can hold. 27 static enum maxElements = maxElements_; 28 29 30 private E[maxElements] _elements; 31 private size_t _length; 32 33 34 this(E[] values) @nogc 35 { 36 this._length = values.length; 37 this._elements[0 .. values.length] = values[]; 38 } 39 40 static if (isCopyable!(const(E))) 41 this(const E[] values) const @nogc 42 { 43 this._length = values.length; 44 this._elements[0 .. values.length] = values[]; 45 } 46 47 this(V...)(V values) @nogc if (V.length > 0 && V.length <= maxElements && allSatisfy!(isBaseType, V)) 48 { 49 this._length = V.length; 50 static foreach (i; 0 .. V.length) 51 this._elements[i] = values[i]; 52 } 53 54 unittest 55 { 56 alias BArray = BoundedArray!(int, 3); 57 58 BArray arr0; 59 auto arr1 = BArray(1); 60 auto arr2 = BArray(1, 2); 61 auto arr3 = BArray(1, 2, 3); 62 63 assert(arr0._length == 0); 64 assert(arr0._elements == [0, 0, 0]); 65 assert(arr1._length == 1); 66 assert(arr1._elements == [1, 0, 0]); 67 assert(arr2._length == 2); 68 assert(arr2._elements == [1, 2, 0]); 69 assert(arr3._length == 3); 70 assert(arr3._elements == [1, 2, 3]); 71 } 72 73 74 /// 75 @property size_t length() pure const nothrow @safe 76 { 77 return _length; 78 } 79 80 /// ditto 81 alias opDollar = length; 82 83 unittest 84 { 85 alias BArray = BoundedArray!(int, 3); 86 87 BArray arr0; 88 auto arr1 = BArray(1); 89 auto arr2 = BArray(1, 2); 90 auto arr3 = BArray(1, 2, 3); 91 92 assert(arr0.length == 0); 93 assert(arr1.length == 1); 94 assert(arr2.length == 2); 95 assert(arr3.length == 3); 96 } 97 98 99 /// 100 inout(E[]) opIndex() inout pure nothrow @safe 101 { 102 return _elements[0 .. _length]; 103 } 104 105 unittest 106 { 107 alias BArray = BoundedArray!(int, 3); 108 109 BArray arr0; 110 auto arr1 = BArray(1); 111 auto arr2 = BArray(1, 2); 112 auto arr3 = BArray(1, 2, 3); 113 114 assert(arr0[] == []); 115 assert(arr1[] == [1]); 116 assert(arr2[] == [1, 2]); 117 assert(arr3[] == [1, 2, 3]); 118 } 119 120 121 /// 122 auto ref inout(E) opIndex(size_t idx) inout pure @safe 123 { 124 return this[][idx]; 125 } 126 127 unittest 128 { 129 import core.exception : RangeError; 130 import std.exception : assertThrown; 131 132 alias BArray = BoundedArray!(int, 3); 133 134 BArray arr0; 135 auto arr1 = BArray(1); 136 auto arr2 = BArray(1, 2); 137 auto arr3 = BArray(1, 2, 3); 138 139 assertThrown!RangeError(arr0[0]); 140 assert(arr1[0] == 1); 141 assertThrown!RangeError(arr1[1]); 142 assert(arr2[0] == 1); 143 assert(arr2[1] == 2); 144 assertThrown!RangeError(arr2[2]); 145 assert(arr3[0] == 1); 146 assert(arr3[1] == 2); 147 assert(arr3[2] == 3); 148 assertThrown!RangeError(arr3[3]); 149 } 150 151 unittest 152 { 153 alias BArray = BoundedArray!(int, 3); 154 155 auto arr = BArray(1, 2, 3); 156 157 arr[0] = 3; 158 arr[1] = 2; 159 arr[2] = 1; 160 161 assert(arr == BArray(3, 2, 1)); 162 163 arr[0] += 1; 164 arr[1] += 1; 165 arr[2] += 1; 166 167 assert(arr == BArray(4, 3, 2)); 168 } 169 170 171 /// 172 typeof(this) opIndex(size_t[2] bounds) @safe 173 { 174 return typeof(return)(this._elements[bounds[0] .. bounds[1]]); 175 } 176 177 /// ditto 178 static if (isCopyable!(const(E))) 179 const(typeof(this)) opIndex(size_t[2] bounds) const @safe 180 { 181 return typeof(return)(this._elements[bounds[0] .. bounds[1]]); 182 } 183 184 unittest 185 { 186 import core.exception : RangeError; 187 import std.exception : assertThrown; 188 189 alias BArray = BoundedArray!(int, 3); 190 191 auto arr = BArray(1, 2, 3); 192 193 assert(arr[0 .. 0] == BArray()); 194 assert(arr[1 .. 1] == BArray()); 195 assert(arr[2 .. 2] == BArray()); 196 assert(arr[0 .. 1] == BArray(1)); 197 assert(arr[1 .. 2] == BArray(2)); 198 assert(arr[2 .. 3] == BArray(3)); 199 assert(arr[0 .. 2] == BArray(1, 2)); 200 assert(arr[1 .. 3] == BArray(2, 3)); 201 assert(arr[0 .. 3] == arr); 202 203 assertThrown!RangeError(arr[0 .. 4] == arr); 204 assertThrown!RangeError(arr[3 .. 0] == arr); 205 } 206 207 208 size_t[2] opSlice(size_t dim)(size_t from, size_t to) const @safe if (dim == 0) 209 { 210 return [from, to]; 211 } 212 213 214 /// 215 typeof(this) opOpAssign(string op)(ElementType element) @safe if (op == "~") 216 { 217 this._elements[this.length] = element; 218 this._length += 1; 219 220 return this; 221 } 222 223 /// ditto 224 typeof(this) opOpAssign(string op)(typeof(this) other) @safe if (op == "~") 225 { 226 auto catLength = this.length + other.length; 227 this._elements[this.length .. catLength] = other[]; 228 this._length = catLength; 229 230 return this; 231 } 232 233 unittest 234 { 235 import core.exception : RangeError; 236 import std.exception : 237 assertNotThrown, 238 assertThrown; 239 240 alias BArray = BoundedArray!(int, 3); 241 242 auto arr1 = BArray(1); 243 244 arr1 ~= 2; 245 arr1 ~= 3; 246 247 assert(arr1 == BArray(1, 2, 3)); 248 assertThrown!RangeError(arr1 ~= 4); 249 250 auto arr2 = BArray(1); 251 arr2 ~= BArray(2, 3); 252 253 assert(arr2 == BArray(1, 2, 3)); 254 assertNotThrown!RangeError(arr2 ~= BArray()); 255 assertThrown!RangeError(arr2 ~= BArray(4)); 256 } 257 258 259 /// 260 typeof(this) opBinary(string op)(ElementType element) @safe if (op == "~") 261 { 262 typeof(this) copy = this; 263 264 return copy ~= element; 265 } 266 267 /// ditto 268 typeof(this) opBinary(string op)(typeof(this) other) @safe if (op == "~") 269 { 270 typeof(this) copy = this; 271 272 return copy ~= other; 273 } 274 275 unittest 276 { 277 import core.exception : RangeError; 278 import std.exception : 279 assertNotThrown, 280 assertThrown; 281 282 alias BArray = BoundedArray!(int, 3); 283 284 auto arr = BArray(1); 285 286 assert(arr ~ 2 == BArray(1, 2)); 287 assert(arr == BArray(1)); 288 289 assert(arr ~ BArray(2, 3) == BArray(1, 2, 3)); 290 assert(arr == BArray(1)); 291 assertThrown!RangeError(arr ~ BArray(2, 3, 4)); 292 assertNotThrown!RangeError(BArray(2, 3, 4) ~ BArray()); 293 } 294 295 296 /// Range primitives for a random access range. 297 void popFront() pure @safe 298 { 299 assert(!empty, "Attempting to popFront an empty " ~ typeof(this).stringof); 300 301 this = this[1 .. $]; 302 } 303 304 /// 305 @property ref ElementType front() pure @safe 306 { 307 assert(!empty, "Attempting to fetch the front of an empty " ~ typeof(this).stringof); 308 309 return this[0]; 310 } 311 312 /// 313 void popBack() pure @safe 314 { 315 assert(!empty, "Attempting to popBack an empty " ~ typeof(this).stringof); 316 317 this = this[0 .. $ - 1]; 318 } 319 320 /// 321 @property ref ElementType back() pure @safe 322 { 323 assert(!empty, "Attempting to fetch the back of an empty " ~ typeof(this).stringof); 324 325 return this[$ - 1]; 326 } 327 328 /// 329 @property bool empty() const pure nothrow @safe 330 { 331 return length == 0; 332 } 333 334 /// 335 @property typeof(this) save() pure nothrow @safe 336 { 337 return this; 338 } 339 340 unittest 341 { 342 import std.algorithm : minElement; 343 import std.range : dropBackOne; 344 import std.range.primitives; 345 346 alias BArray = BoundedArray!(int, 3); 347 348 static assert(isInputRange!BArray); 349 static assert(isForwardRange!BArray); 350 static assert(isBidirectionalRange!BArray); 351 static assert(isRandomAccessRange!BArray); 352 static assert(hasMobileElements!BArray); 353 static assert(hasSwappableElements!BArray); 354 static assert(hasAssignableElements!BArray); 355 static assert(hasLvalueElements!BArray); 356 static assert(hasLength!BArray); 357 static assert(hasSlicing!BArray); 358 359 // Test popFront, front and empy 360 assert(minElement(BArray(1, 2, 3)) == 1); 361 // Test popBack, back and empy 362 assert(dropBackOne(BArray(1, 2, 3)).back == 2); 363 364 // Test save 365 auto arr = BArray(1, 2, 3); 366 arr.save.popFront(); 367 assert(arr.front == 1); 368 } 369 370 371 import vibe.data.json : Json; 372 373 /// Convert from/to Json. 374 Json toJson() const @safe 375 { 376 import vibe.data.json : serializeToJson; 377 378 return serializeToJson(this[]); 379 } 380 381 /// ditto 382 static typeof(this) fromJson(Json src) @safe 383 { 384 import std.exception : enforce; 385 import std.json : JSONException; 386 import std.range : enumerate; 387 import vibe.data.json : deserializeJson; 388 389 typeof(this) result; 390 391 enforce!JSONException( 392 src.length <= maxElements, 393 "could not parse JSON: " ~ typeof(this).stringof ~ 394 " must contain <= " ~ maxElements.stringof ~ " elements", 395 ); 396 397 result._length = src.length; 398 foreach (i, element; src.byValue().enumerate) 399 result[i] = deserializeJson!ElementType(element); 400 401 return result; 402 } 403 404 unittest 405 { 406 import std.exception : assertThrown; 407 import std.json : JSONException; 408 import vibe.data.json : 409 deserializeJson, 410 serializeToJson; 411 412 alias BArray = BoundedArray!(int, 3); 413 414 BArray arr = BArray(1, 2, 3); 415 416 assert(serializeToJson(arr) == serializeToJson([1, 2, 3])); 417 assert(deserializeJson!BArray(serializeToJson(arr)) == arr); 418 419 auto tooLong = serializeToJson([1, 2, 3, 4]); 420 assertThrown!JSONException(deserializeJson!BArray(tooLong)); 421 auto mismatchedElementType = serializeToJson(["1", "2", "3", "4"]); 422 assertThrown!JSONException(deserializeJson!BArray(mismatchedElementType)); 423 } 424 425 426 version (Have_sbin) 427 { 428 void sbinCustomSerialize(R)(ref R r) const 429 { 430 import sbin : sbinSerialize; 431 432 sbinSerialize(r, _length); 433 foreach (ref element; this[]) 434 sbinSerialize(r, element); 435 } 436 437 static void sbinCustomDeserialize(R)(ref R r, ref typeof(this) array) 438 { 439 import sbin : sbinDeserializePart; 440 441 sbinDeserializePart(r, array._length); 442 foreach (i; 0 .. array._length) 443 sbinDeserializePart(r, array._elements[i]); 444 } 445 446 unittest 447 { 448 import sbin; 449 import std.array : appender; 450 451 alias BArray = BoundedArray!(int, 3); 452 453 auto array = BArray([1, 2]); 454 455 auto buffer = appender!(ubyte[]); 456 457 sbinSerialize(buffer, array); 458 459 auto binaryData = buffer.data; 460 auto recoveredArray = sbinDeserialize!BArray(binaryData); 461 462 assert(recoveredArray == array); 463 } 464 } 465 } 466 467 unittest 468 { 469 import vibe.data.json : 470 deserializeJson, 471 serializeToJson; 472 473 struct Complex 474 { 475 int foo; 476 } 477 478 alias ComplexArray = BoundedArray!(Complex, 3); 479 480 auto arr = ComplexArray(Complex(1), Complex(2), Complex(3)); 481 482 assert(deserializeJson!ComplexArray(serializeToJson(arr)) == arr); 483 } 484 485 unittest 486 { 487 BoundedArray!(int, 3) triggerUnitTests; 488 } 489 490 491 /// Create a tuple of `variables` using their native names. Mostly works with 492 /// symbols but not with values (see example). 493 auto tupleOf(variables...)() 494 { 495 import std.meta; 496 import std.typecons; 497 498 enum symbolName(alias sym) = sym.stringof; 499 alias variableNames = staticMap!(symbolName, variables); 500 501 return tuple!(variableNames)(variables); 502 } 503 504 /// 505 unittest 506 { 507 import std.typecons; 508 509 auto a = 1; 510 auto b = false; 511 auto c = "foobar"; 512 auto tup = tupleOf!(a, b, c); 513 514 assert(tup == tuple!("a", "b", "c")(a, b, c)); 515 } 516 517 518 519 /// Does not work with values. 520 unittest 521 { 522 import std.typecons; 523 524 auto a = 1; 525 auto b = false; 526 527 static assert(!is(tupleOf!(a, b, 42))); 528 // compiler error: 529 // std.typecons.Tuple!(int, "a", bool, "b", int, "42")` does not match 530 // template declaration `Tuple(Specs...)` 531 } 532 533