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 }