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 }