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