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         Commented out because CommonArrayMembers already has a constructor for this
178         /**
179         Coerce the given `array` to a `SentinelArray`. It checks and asserts
180         if the given array does not contain the sentinel value at `array.ptr[array.length]`.
181         */
182         this(T[] array) @system
183         in { assert(array.ptr[array.length] == sentinelValue,
184             "array does not end with sentinel value"); } do
185         {
186             this.array = array;
187         }
188         +/
189     }
190     struct ImmutableArray
191     {
192         private alias SpecificT = immutable(T);
193         private alias SentinelPtr = ImmutablePtr;
194 
195         mixin(CommonArrayMembers);
196         alias asConst this; // facilitates implicit conversion to const type
197         // alias array this; // NEED MULTIPLE ALIAS THIS!!!
198 
199         /+
200         /**
201         Coerce the given `array` to a `SentinelArray`. It checks and asserts
202         if the given array does not contain the sentinel value at `array.ptr[array.length]`.
203         */
204         this(immutable(T)[] array) @system
205         in { assert(array.ptr[array.length] == sentinelValue,
206             "array does not end with sentinel value"); } do
207         {
208             this.array = array;
209         }
210         +/
211     }
212     struct ConstArray
213     {
214         private alias SpecificT = const(T);
215         private alias SentinelPtr = ConstPtr;
216 
217         mixin(CommonArrayMembers);
218         alias array this;
219 
220         bool opEquals(const(T)[] other) const
221         {
222             return array == other;
223         }
224     }
225 }
226 
227 /**
228 A pointer to an array with a sentinel value.
229 */
230 template SentinelPtr(T, T sentinelValue = defaultSentinel!T)
231 {
232          static if (is(T U ==     const U)) alias SentinelPtr = SentinelTemplate!(U, sentinelValue).ConstPtr;
233     else static if (is(T U == immutable U)) alias SentinelPtr = SentinelTemplate!(U, sentinelValue).ImmutablePtr;
234     else                                    alias SentinelPtr = SentinelTemplate!(T, sentinelValue).MutablePtr;
235 }
236 /**
237 An array with the extra requirement that it ends with a sentinel value at `ptr[length]`.
238 */
239 template SentinelArray(T, T sentinelValue = defaultSentinel!T)
240 {
241          static if (is(T U ==     const U)) alias SentinelArray = SentinelTemplate!(U, sentinelValue).ConstArray;
242     else static if (is(T U == immutable U)) alias SentinelArray = SentinelTemplate!(U, sentinelValue).ImmutableArray;
243     else                                    alias SentinelArray = SentinelTemplate!(T, sentinelValue).MutableArray;
244 }
245 
246 /**
247 Create a SentinelPtr from a normal pointer without checking
248 that the array it is pointing to contains the sentinel value.
249 */
250 @property auto assumeSentinel(T)(T* ptr) @system
251 {
252     return SentinelPtr!T.assume(ptr);
253 }
254 @property auto assumeSentinel(alias sentinelValue, T)(T* ptr) @system
255     if (is(typeof(sentinelValue) == typeof(T.init)))
256 {
257     return SentinelPtr!(T, sentinelValue).assume(ptr);
258 }
259 
260 /**
261 Coerce the given `array` to a `SentinelPtr`. It checks and asserts
262 if the given array does not contain the sentinel value at `array.ptr[array.length]`.
263 */
264 @property auto verifySentinel(T)(T[] array) @system
265 {
266     return SentinelArray!T.verify(array);
267 }
268 /// ditto
269 @property auto verifySentinel(alias sentinelValue, T)(T[] array) @system
270     if (is(typeof(sentinelValue == T.init)))
271 {
272     return SentinelArray!(T, sentinelValue).verify(array);
273 }
274 
275 /**
276 Coerce the given `array` to a `SentinelArray` without verifying that it
277 contains the sentinel value at `array.ptr[array.length]`.
278 */
279 @property auto assumeSentinel(T)(T[] array) @system
280 {
281     return SentinelArray!T.assume(array);
282 }
283 @property auto assumeSentinel(alias sentinelValue, T)(T[] array) @system
284     if (is(typeof(sentinelValue == T.init)))
285 {
286     return SentinelArray!(T, sentinelValue).assume(array);
287 }
288 unittest
289 {
290     auto s1 = "abcd".verifySentinel;
291     auto s2 = "abcd".assumeSentinel;
292     auto s3 = "abcd".ptr.assumeSentinel;
293 
294     auto full = "abcd-";
295     auto s = full[0..4];
296     auto s4 = s.verifySentinel!'-';
297     auto s5 = s.assumeSentinel!'-';
298 }
299 unittest
300 {
301     auto s1 = "abcd".verifySentinel;
302     auto s2 = "abcd".assumeSentinel;
303 
304     auto full = "abcd-";
305     auto s = full[0..4];
306     auto s3 = s.verifySentinel!'-';
307     auto s4 = s.assumeSentinel!'-';
308 }
309 
310 // test as ranges
311 unittest
312 {
313     {
314         auto s = "abcd".verifySentinel;
315         size_t count = 0;
316         foreach(c; s) { count++; }
317         assert(count == 4);
318     }
319     {
320         auto s = "abcd".verifySentinel;
321         size_t count = 0;
322         foreach(c; s) { count++; }
323         assert(count == 4);
324     }
325     auto abcd = "abcd";
326     {
327         auto s = abcd[0..3].verifySentinel!'d'.ptr;
328         size_t count = 0;
329         foreach(c; s) { count++; }
330         assert(count == 3);
331     }
332     {
333         auto s = abcd[0..3].verifySentinel!'d'.ptr;
334         size_t count = 0;
335         foreach(c; s) { count++; }
336         assert(count == 3);
337     }
338 }
339 
340 unittest
341 {
342     auto p1 = "hello".verifySentinel.ptr;
343     auto p2 = "hello".assumeSentinel.ptr;
344     assert(p1.walkLength() == 5);
345     assert(p2.walkLength() == 5);
346 
347     assert(p1.walkToArray() == "hello");
348     assert(p2.walkToArray() == "hello");
349 }
350 
351 // Check that sentinel types can be passed to functions
352 // with mutable/immutable implicitly converting to const
353 unittest
354 {
355     import more.c : cstring;
356 
357     static void immutableFooString(SentinelString str) { }
358     immutableFooString("hello".verifySentinel);
359     immutableFooString(StringLiteral!"hello");
360     // NOTE: this only works if type of string literals is changed to SentinelString
361     //immutableFooString("hello");
362 
363     static void mutableFooArray(SentinelArray!char str) { }
364     mutableFooArray((cast(char[])"hello").verifySentinel);
365 
366     static void constFooArray(SentinelArray!(const(char)) str) { }
367     constFooArray("hello".verifySentinel);
368     constFooArray(StringLiteral!"hello");
369     constFooArray((cast(const(char)[])"hello").verifySentinel);
370     constFooArray((cast(char[])"hello").verifySentinel);
371 
372     // NOTE: this only works if type of string literals is changed to SentinelString
373     //constFooArray("hello");
374 
375     static void immutableFooCString(cstring str) { }
376     immutableFooCString("hello".verifySentinel.ptr);
377     immutableFooCString(StringLiteral!"hello".ptr);
378 
379     static void mutableFooPtr(SentinelPtr!char str) { }
380     mutableFooPtr((cast(char[])"hello").verifySentinel.ptr);
381 
382     static void fooPtr(cstring str) { }
383     fooPtr("hello".verifySentinel.ptr);
384     fooPtr(StringLiteral!"hello".ptr);
385     fooPtr((cast(const(char)[])"hello").verifySentinel.ptr);
386     fooPtr((cast(char[])"hello").verifySentinel.ptr);
387 }
388 
389 // Check that sentinel array/ptr implicitly convert to non-sentinel array/ptr
390 unittest
391 {
392     static void mutableFooArray(char[] str) { }
393     // NEED MULTIPLE ALIAS THIS !!!
394     //mutableFooArray((cast(char[])"hello").verifySentinel);
395 
396     static void immutableFooArray(string str) { }
397     // NEED MULTIPLE ALIAS THIS !!!
398     //immutableFooArray("hello".verifySentinel);
399     //immutableFooArray(StringLiteral!"hello");
400 
401     static void constFooArray(const(char)[] str) { }
402     constFooArray((cast(char[])"hello").verifySentinel);
403     constFooArray((cast(const(char)[])"hello").verifySentinel);
404     constFooArray("hello".verifySentinel);
405     constFooArray(StringLiteral!"hello");
406 
407     static void mutableFooPtr(char* str) { }
408     // NEED MULTIPLE ALIAS THIS !!!
409     //mutableFooPtr((cast(char[])"hello").verifySentinel.ptr);
410 
411     static void immutableFooPtr(immutable(char)* str) { }
412     // NEED MULTIPLE ALIAS THIS !!!
413     //immutableFooPtr("hello".verifySentinel.ptr);
414     //immutableFooPtr(StringLiteral!"hello");
415 
416     static void constFooPtr(const(char)* str) { }
417     constFooPtr((cast(char[])"hello").verifySentinel.ptr);
418     constFooPtr((cast(const(char)[])"hello").verifySentinel.ptr);
419     constFooPtr("hello".verifySentinel.ptr);
420     constFooPtr(StringLiteral!"hello".ptr);
421 }
422 
423 /**
424 An array of characters that contains a null-terminator at the `length` index.
425 
426 NOTE: the type of string literals could be changed to SentinelString
427 */
428 alias SentinelString = SentinelArray!(immutable(char));
429 alias SentinelWstring = SentinelArray!(immutable(wchar));
430 alias SentinelDstring = SentinelArray!(immutable(dchar));
431 
432 unittest
433 {
434     {
435         auto s1 = "hello".verifySentinel;
436         auto s2 = "hello".assumeSentinel;
437     }
438     {
439         SentinelString s = "hello";
440     }
441 }
442 
443 /**
444 A template that coerces a string literal to a SentinelString.
445 Note that this template becomes unnecessary if the type of string literal
446 is changed to SentinelString.
447 */
448 pragma(inline) @property SentinelString StringLiteral(string s)() @trusted
449 {
450    SentinelString ss = void;
451    ss.array = s;
452    return ss;
453 }
454 /// ditto
455 pragma(inline) @property SentinelWstring StringLiteral(wstring s)() @trusted
456 {
457    SentinelWstring ss = void;
458    ss.array = s;
459    return ss;
460 }
461 /// ditto
462 pragma(inline) @property SentinelDstring StringLiteral(dstring s)() @trusted
463 {
464    SentinelDstring ss = void;
465    ss.array = s;
466    return ss;
467 }
468 
469 unittest
470 {
471     // just instantiate for now to make sure they compile
472     auto sc = StringLiteral!"hello";
473     auto sw = StringLiteral!"hello"w;
474     auto sd = StringLiteral!"hello"d;
475 }
476 
477 /**
478 This function converts an array to a SentinelArray.  It requires that the last element `array[$-1]`
479 be equal to the sentinel value. This differs from the function `asSentinelArray` which requires
480 the first value outside of the bounds of the array `array[$]` to be equal to the sentinel value.
481 This function does not require the array to "own" elements outside of its bounds.
482 */
483 @property auto reduceSentinel(T)(T[] array) @trusted
484 in {
485     assert(array.length > 0);
486     assert(array[$ - 1] == defaultSentinel!T);
487    } do
488 {
489     return array[0 .. $-1].assumeSentinel;
490 }
491 /// ditto
492 @property auto reduceSentinel(alias sentinelValue, T)(T[] array) @trusted
493     if (is(typeof(sentinelValue == T.init)))
494     in {
495         assert(array.length > 0);
496         assert(array[$ - 1] == sentinelValue);
497     } do
498 {
499     return array[0 .. $ - 1].assumeSentinel!sentinelValue;
500 }
501 
502 ///
503 @safe unittest
504 {
505     auto s1 = "abc\0".reduceSentinel;
506     assert(s1.length == 3);
507     () @trusted {
508         assert(s1.ptr[s1.length] == '\0');
509     }();
510 
511     auto s2 = "foobar-".reduceSentinel!'-';
512     assert(s2.length == 6);
513     () @trusted {
514         assert(s2.ptr[s2.length] == '-');
515     }();
516 }
517 
518 // poor mans Unqual
519 private template Unqual(T)
520 {
521          static if (is(T U ==     const U)) alias Unqual = U;
522     else static if (is(T U == immutable U)) alias Unqual = U;
523     else                                    alias Unqual = T;
524 }