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 }