1 /**
2 Contains types to differentiate arrays with sentinel values.
3 */
4 module more.sentinel;
5 
6 /**
7 Selects the default sentinel value for a type `T`.
8 
9 It has a special case for the char types, and also allows
10 the type to define its own default sentinel value if it
11 has the member `defaultSentinel`. Otherwise, it uses `T.init`.
12 */
13 private template defaultSentinel(T)
14 {
15          static if (is(Unqual!T ==  char)) enum defaultSentinel = '\0';
16     else static if (is(Unqual!T == wchar)) enum defaultSentinel = cast(wchar)'\0';
17     else static if (is(Unqual!T == dchar)) enum defaultSentinel = cast(dchar)'\0';
18     else static if (__traits(hasMember, T, "defaultSentinel")) enum defaultSentinel = T.defaultSentinel;
19     else                                   enum defaultSentinel = T.init;
20 }
21 
22 // NOTE: T should be unqalified (not const/immutable etc)
23 //       This "unqualification" of T is done by the `SentinelPtr` and `SentinelArray` templates.
24 private template SentinelTemplate(T, immutable T sentinelValue)
25 {
26     private enum CommonPtrMembers = q{
27         static auto nullPtr() { return typeof(this)(null); }
28 
29         /**
30         Interpret a raw pointer `ptr` as a `SentinelPtr` without checking that
31         the array it is pointing to has a sentinel value.
32         Params:
33             ptr = the raw pointer to be converted
34         Returns:
35             the given `ptr` interpreted as a `SentinelPtr`
36         */
37         static auto assume(SpecificT* ptr) pure
38         {
39             return typeof(this)(ptr);
40         }
41 
42         SpecificT* ptr;
43         private this(SpecificT* ptr) { this.ptr = ptr; }
44         this(typeof(this) other) { this.ptr = other.ptr; }
45 
46         pragma(inline) ConstPtr asConst() inout { return ConstPtr(cast(const(T)*)ptr); }
47 
48         /**
49         Converts the ptr to an array by "walking" it for the sentinel value to determine its length.
50 
51         Returns:
52             the ptr as a SentinelArray
53         */
54         SentinelArray walkToArray() inout
55         {
56             return SentinelArray((cast(SpecificT*)ptr)[0 .. walkLength()]);
57         }
58 
59         /**
60         Return the current value pointed to by `ptr`.
61         */
62         auto front() inout { return *ptr; }
63 
64         /**
65         Move ptr to the next value.
66         */
67         void popFront() { ptr++; }
68     };
69     struct MutablePtr
70     {
71         private alias SpecificT = T;
72         private alias SentinelArray = MutableArray;
73 
74         mixin(CommonPtrMembers);
75 
76         alias asConst this; // facilitates implicit conversion to const type
77         // alias ptr this; // NEED MULTIPLE ALIAS THIS!!!
78     }
79     struct ImmutablePtr
80     {
81         private alias SpecificT = immutable(T);
82         private alias SentinelArray = ImmutableArray;
83 
84         mixin(CommonPtrMembers);
85         alias asConst this; // facilitates implicit conversion to const type
86         // alias ptr this; // NEED MULTIPLE ALIAS THIS!!!
87     }
88     struct ConstPtr
89     {
90         private alias SpecificT = const(T);
91         private alias SentinelArray = ConstArray;
92 
93         mixin(CommonPtrMembers);
94         alias ptr this;
95 
96         /**
97         Returns true if `ptr` is pointing at the sentinel value.
98         */
99         @property bool empty() const { return *this == sentinelValue; }
100 
101         /**
102         Walks the array to determine its length.
103         Returns:
104             the length of the array
105         */
106         size_t walkLength() const
107         {
108             for(size_t i = 0; ; i++)
109             {
110                 if (ptr[i] == sentinelValue)
111                 {
112                     return i;
113                 }
114             }
115         }
116     }
117 
118     private enum CommonArrayMembers = q{
119         /**
120         Interpret `array` as a `SentinalArray` without checking that
121         the array it is pointing to has a sentinel value.
122         Params:
123             ptr = the raw pointer to be converted
124         Returns:
125             the given `ptr` interpreted as a `SentinelPtr`
126         */
127         static auto assume(SpecificT[] array) pure
128         {
129             return typeof(this)(array);
130         }
131 
132         /**
133         Interpret `array`` as a `SentinalArray` checking that the array it
134         is pointing to ends with a sentinel value.
135         Params:
136             ptr = the raw pointer to be converted
137         Returns:
138             the given `ptr` interpreted as a `SentinelPtr`
139         */
140         static auto verify(SpecificT[] array) pure
141         in { assert(array.ptr[array.length] == sentinelValue, "array does not end with sentinel value"); } do
142         {
143             return typeof(this)(array);
144         }
145 
146         SpecificT[] array;
147         private this(SpecificT[] array) { this.array = array; }
148         this(typeof(this) other) { this.array = other.array; }
149 
150         pragma(inline) SentinelPtr ptr() const { return SentinelPtr(cast(SpecificT*)array.ptr); }
151 
152         pragma(inline) ConstArray asConst() inout { return ConstArray(cast(const(T)[])array); }
153 
154         /**
155         A no-op that just returns the array as is.  This is to be useful for templates that can accept
156         normal arrays an sentinel arrays. The function is marked as `@system` not because it is unsafe
157         but because it should only be called in unsafe code, mirroring the interface of the free function
158         version of asSentinelArray.
159 
160         Returns:
161             this
162         */
163         pragma(inline) auto asSentinelArray() @system inout { return this; }
164         /// ditto
165         pragma(inline) auto asSentinelArrayUnchecked() @system inout { return this; }
166     };
167     struct MutableArray
168     {
169         private alias SpecificT = T;
170         private alias SentinelPtr = MutablePtr;
171 
172         mixin(CommonArrayMembers);
173         alias asConst this; // facilitates implicit conversion to const type
174         // alias array this; // NEED MULTIPLE ALIAS THIS!!!
175 
176         /**
177         Coerce the given `array` to a `SentinelArray`. It checks and asserts
178         if the given array does not contain the sentinel value at `array.ptr[array.length]`.
179         */
180         this(T[] array) @system
181         in { assert(array.ptr[array.length] == sentinelValue,
182             "array does not end with sentinel value"); } do
183         {
184             this.array = array;
185         }
186     }
187     struct ImmutableArray
188     {
189         private alias SpecificT = immutable(T);
190         private alias SentinelPtr = ImmutablePtr;
191 
192         mixin(CommonArrayMembers);
193         alias asConst this; // facilitates implicit conversion to const type
194         // alias array this; // NEED MULTIPLE ALIAS THIS!!!
195 
196         /**
197         Coerce the given `array` to a `SentinelArray`. It checks and asserts
198         if the given array does not contain the sentinel value at `array.ptr[array.length]`.
199         */
200         this(immutable(T)[] array) @system
201         in { assert(array.ptr[array.length] == sentinelValue,
202             "array does not end with sentinel value"); } do
203         {
204             this.array = array;
205         }
206     }
207     struct ConstArray
208     {
209         private alias SpecificT = const(T);
210         private alias SentinelPtr = ConstPtr;
211 
212         mixin(CommonArrayMembers);
213         alias array this;
214 
215         bool opEquals(const(T)[] other) const
216         {
217             return array == other;
218         }
219     }
220 }
221 
222 /**
223 A pointer to an array with a sentinel value.
224 */
225 template SentinelPtr(T, T sentinelValue = defaultSentinel!T)
226 {
227          static if (is(T U ==     const U)) alias SentinelPtr = SentinelTemplate!(U, sentinelValue).ConstPtr;
228     else static if (is(T U == immutable U)) alias SentinelPtr = SentinelTemplate!(U, sentinelValue).ImmutablePtr;
229     else                                    alias SentinelPtr = SentinelTemplate!(T, sentinelValue).MutablePtr;
230 }
231 /**
232 An array with the extra requirement that it ends with a sentinel value at `ptr[length]`.
233 */
234 template SentinelArray(T, T sentinelValue = defaultSentinel!T)
235 {
236          static if (is(T U ==     const U)) alias SentinelArray = SentinelTemplate!(U, sentinelValue).ConstArray;
237     else static if (is(T U == immutable U)) alias SentinelArray = SentinelTemplate!(U, sentinelValue).ImmutableArray;
238     else                                    alias SentinelArray = SentinelTemplate!(T, sentinelValue).MutableArray;
239 }
240 
241 /**
242 Create a SentinelPtr from a normal pointer without checking
243 that the array it is pointing to contains the sentinel value.
244 */
245 @property auto assumeSentinel(T)(T* ptr) @system
246 {
247     return SentinelPtr!T.assume(ptr);
248 }
249 @property auto assumeSentinel(alias sentinelValue, T)(T* ptr) @system
250     if (is(typeof(sentinelValue) == typeof(T.init)))
251 {
252     return SentinelPtr!(T, sentinelValue).assume(ptr);
253 }
254 
255 /**
256 Coerce the given `array` to a `SentinelPtr`. It checks and asserts
257 if the given array does not contain the sentinel value at `array.ptr[array.length]`.
258 */
259 @property auto verifySentinel(T)(T[] array) @system
260 {
261     return SentinelArray!T.verify(array);
262 }
263 /// ditto
264 @property auto verifySentinel(alias sentinelValue, T)(T[] array) @system
265     if (is(typeof(sentinelValue == T.init)))
266 {
267     return SentinelArray!(T, sentinelValue).verify(array);
268 }
269 
270 /**
271 Coerce the given `array` to a `SentinelArray` without verifying that it
272 contains the sentinel value at `array.ptr[array.length]`.
273 */
274 @property auto assumeSentinel(T)(T[] array) @system
275 {
276     return SentinelArray!T.assume(array);
277 }
278 @property auto assumeSentinel(alias sentinelValue, T)(T[] array) @system
279     if (is(typeof(sentinelValue == T.init)))
280 {
281     return SentinelArray!(T, sentinelValue).assume(array);
282 }
283 unittest
284 {
285     auto s1 = "abcd".verifySentinel;
286     auto s2 = "abcd".assumeSentinel;
287     auto s3 = "abcd".ptr.assumeSentinel;
288 
289     auto full = "abcd-";
290     auto s = full[0..4];
291     auto s4 = s.verifySentinel!'-';
292     auto s5 = s.assumeSentinel!'-';
293 }
294 unittest
295 {
296     auto s1 = "abcd".verifySentinel;
297     auto s2 = "abcd".assumeSentinel;
298 
299     auto full = "abcd-";
300     auto s = full[0..4];
301     auto s3 = s.verifySentinel!'-';
302     auto s4 = s.assumeSentinel!'-';
303 }
304 
305 // test as ranges
306 unittest
307 {
308     {
309         auto s = "abcd".verifySentinel;
310         size_t count = 0;
311         foreach(c; s) { count++; }
312         assert(count == 4);
313     }
314     {
315         auto s = "abcd".verifySentinel;
316         size_t count = 0;
317         foreach(c; s) { count++; }
318         assert(count == 4);
319     }
320     auto abcd = "abcd";
321     {
322         auto s = abcd[0..3].verifySentinel!'d'.ptr;
323         size_t count = 0;
324         foreach(c; s) { count++; }
325         assert(count == 3);
326     }
327     {
328         auto s = abcd[0..3].verifySentinel!'d'.ptr;
329         size_t count = 0;
330         foreach(c; s) { count++; }
331         assert(count == 3);
332     }
333 }
334 
335 unittest
336 {
337     auto p1 = "hello".verifySentinel.ptr;
338     auto p2 = "hello".assumeSentinel.ptr;
339     assert(p1.walkLength() == 5);
340     assert(p2.walkLength() == 5);
341 
342     assert(p1.walkToArray() == "hello");
343     assert(p2.walkToArray() == "hello");
344 }
345 
346 // Check that sentinel types can be passed to functions
347 // with mutable/immutable implicitly converting to const
348 unittest
349 {
350     import more.c : cstring;
351 
352     static void immutableFooString(SentinelString str) { }
353     immutableFooString("hello".verifySentinel);
354     immutableFooString(StringLiteral!"hello");
355     // NOTE: this only works if type of string literals is changed to SentinelString
356     //immutableFooString("hello");
357 
358     static void mutableFooArray(SentinelArray!char str) { }
359     mutableFooArray((cast(char[])"hello").verifySentinel);
360 
361     static void constFooArray(SentinelArray!(const(char)) str) { }
362     constFooArray("hello".verifySentinel);
363     constFooArray(StringLiteral!"hello");
364     constFooArray((cast(const(char)[])"hello").verifySentinel);
365     constFooArray((cast(char[])"hello").verifySentinel);
366 
367     // NOTE: this only works if type of string literals is changed to SentinelString
368     //constFooArray("hello");
369 
370     static void immutableFooCString(cstring str) { }
371     immutableFooCString("hello".verifySentinel.ptr);
372     immutableFooCString(StringLiteral!"hello".ptr);
373 
374     static void mutableFooPtr(SentinelPtr!char str) { }
375     mutableFooPtr((cast(char[])"hello").verifySentinel.ptr);
376 
377     static void fooPtr(cstring str) { }
378     fooPtr("hello".verifySentinel.ptr);
379     fooPtr(StringLiteral!"hello".ptr);
380     fooPtr((cast(const(char)[])"hello").verifySentinel.ptr);
381     fooPtr((cast(char[])"hello").verifySentinel.ptr);
382 }
383 
384 // Check that sentinel array/ptr implicitly convert to non-sentinel array/ptr
385 unittest
386 {
387     static void mutableFooArray(char[] str) { }
388     // NEED MULTIPLE ALIAS THIS !!!
389     //mutableFooArray((cast(char[])"hello").verifySentinel);
390 
391     static void immutableFooArray(string str) { }
392     // NEED MULTIPLE ALIAS THIS !!!
393     //immutableFooArray("hello".verifySentinel);
394     //immutableFooArray(StringLiteral!"hello");
395 
396     static void constFooArray(const(char)[] str) { }
397     constFooArray((cast(char[])"hello").verifySentinel);
398     constFooArray((cast(const(char)[])"hello").verifySentinel);
399     constFooArray("hello".verifySentinel);
400     constFooArray(StringLiteral!"hello");
401 
402     static void mutableFooPtr(char* str) { }
403     // NEED MULTIPLE ALIAS THIS !!!
404     //mutableFooPtr((cast(char[])"hello").verifySentinel.ptr);
405 
406     static void immutableFooPtr(immutable(char)* str) { }
407     // NEED MULTIPLE ALIAS THIS !!!
408     //immutableFooPtr("hello".verifySentinel.ptr);
409     //immutableFooPtr(StringLiteral!"hello");
410 
411     static void constFooPtr(const(char)* str) { }
412     constFooPtr((cast(char[])"hello").verifySentinel.ptr);
413     constFooPtr((cast(const(char)[])"hello").verifySentinel.ptr);
414     constFooPtr("hello".verifySentinel.ptr);
415     constFooPtr(StringLiteral!"hello".ptr);
416 }
417 
418 /**
419 An array of characters that contains a null-terminator at the `length` index.
420 
421 NOTE: the type of string literals could be changed to SentinelString
422 */
423 alias SentinelString = SentinelArray!(immutable(char));
424 alias SentinelWstring = SentinelArray!(immutable(wchar));
425 alias SentinelDstring = SentinelArray!(immutable(dchar));
426 
427 unittest
428 {
429     {
430         auto s1 = "hello".verifySentinel;
431         auto s2 = "hello".assumeSentinel;
432     }
433     {
434         SentinelString s = "hello";
435     }
436 }
437 
438 /**
439 A template that coerces a string literal to a SentinelString.
440 Note that this template becomes unnecessary if the type of string literal
441 is changed to SentinelString.
442 */
443 pragma(inline) @property SentinelString StringLiteral(string s)() @trusted
444 {
445    SentinelString ss = void;
446    ss.array = s;
447    return ss;
448 }
449 /// ditto
450 pragma(inline) @property SentinelWstring StringLiteral(wstring s)() @trusted
451 {
452    SentinelWstring ss = void;
453    ss.array = s;
454    return ss;
455 }
456 /// ditto
457 pragma(inline) @property SentinelDstring StringLiteral(dstring s)() @trusted
458 {
459    SentinelDstring ss = void;
460    ss.array = s;
461    return ss;
462 }
463 
464 unittest
465 {
466     // just instantiate for now to make sure they compile
467     auto sc = StringLiteral!"hello";
468     auto sw = StringLiteral!"hello"w;
469     auto sd = StringLiteral!"hello"d;
470 }
471 
472 /**
473 This function converts an array to a SentinelArray.  It requires that the last element `array[$-1]`
474 be equal to the sentinel value. This differs from the function `asSentinelArray` which requires
475 the first value outside of the bounds of the array `array[$]` to be equal to the sentinel value.
476 This function does not require the array to "own" elements outside of its bounds.
477 */
478 @property auto reduceSentinel(T)(T[] array) @trusted
479 in {
480     assert(array.length > 0);
481     assert(array[$ - 1] == defaultSentinel!T);
482    } do
483 {
484     return array[0 .. $-1].assumeSentinel;
485 }
486 /// ditto
487 @property auto reduceSentinel(alias sentinelValue, T)(T[] array) @trusted
488     if (is(typeof(sentinelValue == T.init)))
489     in {
490         assert(array.length > 0);
491         assert(array[$ - 1] == sentinelValue);
492     } do
493 {
494     return array[0 .. $ - 1].assumeSentinel!sentinelValue;
495 }
496 
497 ///
498 @safe unittest
499 {
500     auto s1 = "abc\0".reduceSentinel;
501     assert(s1.length == 3);
502     () @trusted {
503         assert(s1.ptr[s1.length] == '\0');
504     }();
505 
506     auto s2 = "foobar-".reduceSentinel!'-';
507     assert(s2.length == 6);
508     () @trusted {
509         assert(s2.ptr[s2.length] == '-');
510     }();
511 }
512 
513 // poor mans Unqual
514 private template Unqual(T)
515 {
516          static if (is(T U ==     const U)) alias Unqual = U;
517     else static if (is(T U == immutable U)) alias Unqual = U;
518     else                                    alias Unqual = T;
519 }