1 /** 2 * Collection of utilities for stream components. 3 */ 4 module streams.utils; 5 6 /** 7 * A simple nullable type, where a boolean flag indicates whether a value is 8 * present. 9 */ 10 struct Optional(T) { 11 /** 12 * Whether the value is present. 13 */ 14 bool present = false; 15 16 /** 17 * The value that's present, if any. 18 */ 19 T value = T.init; 20 21 /** 22 * Constructs an optional with a given value. 23 * Params: 24 * value = The value to use. 25 */ 26 this(T value) { 27 this.present = true; 28 this.value = value; 29 } 30 31 /** 32 * Determines if a value is not present. 33 * Returns: True if the value is not present. 34 */ 35 bool notPresent() const { 36 return !this.present; 37 } 38 } 39 40 unittest { 41 Optional!int op1; 42 assert(op1.present == false); 43 assert(op1.value == int.init); 44 assert(op1.notPresent); 45 Optional!bool op2 = Optional!bool(true); 46 assert(op2.present); 47 assert(op2.value == true); 48 } 49 50 /** 51 * A type that contains either an element of A, or an element of B, but not both. 52 * You can access the given names directly, so for example: 53 * 54 * `auto s = Either!(bool, "first", float, "second")(true);` 55 * 56 * will allow you to call `s.first`, `s.second`, `s.hasFirst`, and `s.hasSecond`. 57 */ 58 struct Either(A, string NameA, B, string NameB) if (!is(A == B)) { 59 alias firstType = A; 60 alias secondType = B; 61 62 private enum checkA = "has" ~ (NameA[0] - ('a' - 'A')) ~ NameA[1 .. $]; 63 private enum checkB = "has" ~ (NameB[0] - ('a' - 'A')) ~ NameB[1 .. $]; 64 65 union U { 66 A a; 67 B b; 68 this(A a) { 69 this.a = a; 70 } 71 this(B b) { 72 this.b = b; 73 } 74 } 75 private bool hasA = true; 76 private U u; 77 78 this(A value) { 79 this.u = U(value); 80 this.hasA = true; 81 } 82 83 this(B value) { 84 this.u = U(value); 85 this.hasA = false; 86 } 87 88 A opDispatch(string member)() if (member == NameA) { 89 return this.u.a; 90 } 91 92 B opDispatch(string member)() if (member == NameB) { 93 return this.u.b; 94 } 95 96 bool opDispatch(string member)() const if (member == checkA) { 97 return this.hasA; 98 } 99 100 bool opDispatch(string member)() const if (member == checkB) { 101 return !this.hasA; 102 } 103 104 bool has(string member)() const if (member == NameA || member == NameB) { 105 static if (member == NameA) { 106 return this.hasA; 107 } else { 108 return !this.hasA; 109 } 110 } 111 112 T map(T)(T delegate(A) dgA, T delegate(B) dgB) const { 113 if (this.hasA) return dgA(this.u.a); 114 return dgB(this.u.b); 115 } 116 } 117 118 unittest { 119 auto e1 = Either!(int, "first", bool, "second")(5); 120 assert(e1.has!"first"); 121 assert(e1.hasFirst); 122 assert(!e1.has!"second"); 123 assert(!e1.hasSecond); 124 assert(e1.first == 5); 125 126 auto e2 = Either!(float, "first", ubyte, "second")(3u); 127 assert(!e2.has!"first"); 128 assert(!e2.hasFirst); 129 assert(e2.has!"second"); 130 assert(e2.hasSecond); 131 assert(e2.second == 3); 132 } 133 134 /** 135 * A strategy for how to grow a buffer as items are added, used by the AppendableBuffer. 136 */ 137 enum BufferAllocationStrategy { Linear, Doubling, None } 138 139 /** 140 * A betterC-compatible array buffer that grows as needed to accommodate new 141 * elements. 142 */ 143 struct AppendableBuffer(T) { 144 // import std.stdio; 145 import core.stdc.stdlib : malloc, realloc, free; 146 147 private T* ptr; 148 private const BufferAllocationStrategy allocationStrategy; 149 private const uint initialCapacity; 150 private uint capacity; 151 private uint nextIndex; 152 153 @disable this(); 154 155 /** 156 * Constructs the buffer using the given initial capacity and allocation 157 * strategy. No memory is allocated yet. 158 * Params: 159 * initialCapacity = The capacity of the buffer. 160 * allocationStrategy = The strategy for memory allocation. 161 */ 162 this(uint initialCapacity, BufferAllocationStrategy allocationStrategy) { 163 this.initialCapacity = initialCapacity; 164 this.allocationStrategy = allocationStrategy; 165 } 166 167 ~this() { 168 // writeln("Freeing appendable buffer"); 169 if (this.ptr !is null) { 170 free(this.ptr); 171 } 172 } 173 174 /** 175 * Appends items to the buffer, expanding the buffer if needed. 176 * Params: 177 * items = The items to add. 178 */ 179 void appendItems(T[] items) { 180 // writefln!"Appending %d items"(items.length); 181 if (this.ptr is null) reset(); 182 183 uint len = cast(uint) items.length; 184 this.ensureCapacityFor(len); 185 T[] array = this.ptr[0 .. this.capacity]; 186 array[this.nextIndex .. this.nextIndex + len] = items[0 .. $]; 187 this.nextIndex += len; 188 } 189 190 /** 191 * Gets a slice representing the buffer's contents. 192 * Returns: The buffer's contents. 193 */ 194 T[] toArray() { 195 return this.ptr[0 .. this.nextIndex]; 196 } 197 198 /** 199 * Gets a copy of this buffer's contents in a new allocated array. You must 200 * free this array yourself. 201 * Returns: The array copy. 202 */ 203 T[] toArrayCopy() { 204 T* copyPtr = cast(T*) malloc(this.length() * T.sizeof); 205 if (copyPtr is null) assert(false, "Could not allocate memory for arrayCopy."); 206 T[] copy = copyPtr[0 .. this.length()]; 207 copy[0 .. $] = this.toArray()[0 .. $]; 208 return copy; 209 } 210 211 /** 212 * Gets the length of the buffer, or the total number of items in it. 213 * Returns: The buffer's length. 214 */ 215 uint length() const { 216 return this.nextIndex; 217 } 218 219 /** 220 * Resets the buffer. 221 */ 222 void reset() { 223 // writeln("Resetting appendable buffer"); 224 if (this.ptr !is null) { 225 free(this.ptr); 226 } 227 this.ptr = cast(T*) malloc(this.initialCapacity * T.sizeof); 228 if (this.ptr is null) { 229 assert(false, "Failed to allocate appendable buffer."); 230 } 231 this.capacity = this.initialCapacity; 232 this.nextIndex = 0; 233 } 234 235 private void ensureCapacityFor(uint count) { 236 while ((this.capacity - this.nextIndex) < count) { 237 // writefln!"Ensuring capacity for %d new items"(count); 238 uint newCapacity; 239 final switch (this.allocationStrategy) { 240 case BufferAllocationStrategy.Linear: 241 newCapacity = this.capacity + this.initialCapacity; 242 break; 243 case BufferAllocationStrategy.Doubling: 244 newCapacity = this.capacity * 2; 245 break; 246 case BufferAllocationStrategy.None: 247 assert(false, "Cannot allocate more space to appendable buffer using None strategy."); 248 } 249 // writeln("Reallocating pointer"); 250 T* newPtr = cast(T*) realloc(this.ptr, newCapacity * T.sizeof); 251 if (newPtr is null) { 252 free(this.ptr); // Can't test this without mocking realloc... cov-ignore 253 assert(false, "Could not reallocate appendable buffer."); 254 } 255 this.ptr = newPtr; 256 this.capacity = newCapacity; 257 // writefln!"New capacity: %d"(this.capacity); 258 } 259 } 260 } 261 262 unittest { 263 auto ab1 = AppendableBuffer!ubyte(4, BufferAllocationStrategy.Doubling); 264 assert(ab1.length() == 0); 265 assert(ab1.toArray() == []); 266 ubyte[3] buf = [1, 2, 3]; 267 ab1.appendItems(buf); 268 assert(ab1.length() == 3); 269 assert(ab1.toArray() == [1, 2, 3]); 270 271 buf = [4, 5, 6]; 272 ab1.appendItems(buf); 273 assert(ab1.toArray() == [1, 2, 3, 4, 5, 6]); 274 ubyte[] copy = ab1.toArrayCopy(); 275 assert(copy.length == 6); 276 import core.stdc.stdlib : free; 277 free(copy.ptr); 278 279 // Test a linear buffer allocation strategy. 280 auto ab2 = AppendableBuffer!int(4, BufferAllocationStrategy.Linear); 281 assert(ab2.length() == 0); 282 for (uint i = 0; i < 10; i++) { 283 int[1] buf2 = [i]; 284 ab2.appendItems(buf2); 285 } 286 assert(ab2.length() == 10); 287 assert(ab2.toArray() == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 288 } 289 290 /** 291 * Reverses the elements of the given array in-place. 292 * Params: 293 * array = The array to reverse the elements of. 294 */ 295 void reverseArray(T)(T[] array) { 296 for (uint i = 0; i < array.length / 2; i++) { 297 T tmp = array[i]; 298 array[i] = array[array.length - i - 1]; 299 array[array.length - i - 1] = tmp; 300 } 301 } 302 303 /** 304 * Reads an unsigned integer value from a hex-string. 305 * Params: 306 * chars = The characters to read from. 307 * Returns: An optional unsigned integer. 308 */ 309 Optional!uint readHexString(const(char[]) chars) { 310 uint value = 0; 311 foreach (c; chars) { 312 ubyte b; 313 if (c >= '0' && c <= '9') { 314 b = cast(ubyte) (c - '0'); 315 } else if (c >= 'a' && c <= 'f') { 316 b = cast(ubyte) (c - 'a' + 10); 317 } else if (c >= 'A' && c <= 'F') { 318 b = cast(ubyte) (c - 'A' + 10); 319 } else { 320 return Optional!uint.init; 321 } 322 value = (value << 4) | (b & 0xF); 323 } 324 return Optional!uint(value); 325 } 326 327 unittest { 328 char[10] buffer; 329 buffer[0] = '4'; 330 assert(readHexString(buffer[0 .. 1]) == Optional!uint(4)); 331 buffer[0 .. 2] = cast(char[2]) "2A"; 332 assert(readHexString(buffer[0 .. 2]) == Optional!uint(42)); 333 buffer[0 .. 4] = cast(char[4]) "bleh"; 334 assert(readHexString(buffer[0 .. 4]) == Optional!uint.init); 335 buffer[0 .. 6] = cast(char[6]) "4779CA"; 336 assert(readHexString(buffer[0 .. 6]) == Optional!uint(4_684_234)); 337 buffer[0] = '0'; 338 assert(readHexString(buffer[0 .. 1]) == Optional!uint(0)); 339 } 340 341 /** 342 * Writes a hex string to a buffer for a given value. 343 * Params: 344 * value = The unsigned integer value to write. 345 * buffer = The buffer to write to. 346 * Returns: The number of characters that were written. 347 */ 348 uint writeHexString(uint value, char[] buffer) { 349 const(char[16]) chars = "0123456789ABCDEF"; 350 if (value == 0) { 351 buffer[0] = '0'; 352 return 1; 353 } 354 uint index = 0; 355 while (value > 0) { 356 buffer[index++] = chars[value & 0xF]; 357 value = value >>> 4; 358 } 359 reverseArray(buffer[0 .. index]); 360 return index; 361 } 362 363 unittest { 364 char[10] buffer; 365 assert(writeHexString(4, buffer) == 1); 366 assert(buffer[0] == '4', cast(string) buffer[0 .. 1]); 367 368 assert(writeHexString(42, buffer) == 2); 369 assert(buffer[0 .. 2] == cast(char[2]) "2A", cast(string) buffer[0 .. 2]); 370 371 assert(writeHexString(0, buffer) == 1); 372 assert(buffer[0] == '0', cast(string) buffer[0 .. 1]); 373 374 assert(writeHexString(4_684_234, buffer) == 6); 375 assert(buffer[0 .. 6] == cast(char[6]) "4779CA", cast(string) buffer[0 .. 6]); 376 }