1 module mecca.lib.typedid; 2 3 // Licensed under the Boost license. Full copyright information in the AUTHORS file 4 5 import std.traits; 6 import std.conv; 7 8 import mecca.lib.exception; 9 import mecca.lib.reflection : PromotedType; 10 import mecca.log : FMT, notrace; 11 12 template RawTypedIdentifier(string _name, T, T _invalid, T _init, FMT fmt, bool algebraic) 13 if (isIntegral!T) 14 { 15 @fmt 16 struct RawTypedIdentifier { 17 private: 18 T _value = _init; 19 20 enum Algebraic = algebraic; 21 22 public: 23 alias UnderlyingType = T; 24 enum RawTypedIdentifier invalid = RawTypedIdentifier(_invalid); 25 enum name = _name; 26 27 this(T value) @safe pure nothrow @nogc { 28 this._value = value; 29 } 30 // Default this(this) does the right thing 31 32 // For some reason, this form is not covered by this(this) 33 this(RawTypedIdentifier that) @safe pure nothrow @nogc { 34 this._value = that.value; 35 } 36 37 this(string str) @safe { 38 setFromString(str); 39 } 40 41 @property T value() const pure nothrow @safe @nogc { 42 return _value; 43 } 44 @property bool isValid() const pure nothrow @safe @nogc { 45 return _value != _invalid; 46 } 47 // XXX do we want this? 48 ref RawTypedIdentifier opAssign(T val) nothrow @safe @nogc { 49 _value = val; 50 return this; 51 } 52 53 // Default opEquals does the right thing 54 55 int opCmp(in RawTypedIdentifier rhs) const pure nothrow @safe @nogc { 56 return value > rhs.value ? 1 : (value < rhs.value ? -1 : 0); 57 } 58 59 static if (algebraic) { 60 enum RawTypedIdentifier min = T.min; 61 enum RawTypedIdentifier max = T.max; 62 63 int opCmp(in T rhs) const pure nothrow @safe @nogc { 64 return opCmp( RawTypedIdentifier(rhs) ); 65 // return value > rhs ? 1 : (value < rhs ? -1 : 0); 66 } 67 68 // Pointer like semantics: 69 // Can add integer to RawTypedIdentifier to get RawTypedIdentifier 70 ref RawTypedIdentifier opOpAssign(string op)(in T rhs) nothrow @safe @nogc if (op == "+" || op == "-") { 71 mixin("_value "~op~"= rhs;"); 72 return this; 73 } 74 75 // Can do any op at all on another TypedId 76 ref RawTypedIdentifier opOpAssign(string op)(in RawTypedIdentifier rhs) nothrow @safe @nogc { 77 mixin("_value "~op~"= rhs.value;"); 78 return this; 79 } 80 81 // Non-modifying op with integer 82 RawTypedIdentifier opBinary(string op)(in T rhs) const pure nothrow @safe @nogc if (op == "+" || op == "-") { 83 RawTypedIdentifier res = this; 84 // Forward to opOpAssign 85 mixin("res "~op~"= rhs;"); 86 87 return res; 88 } 89 90 ref RawTypedIdentifier opUnary(string op)() nothrow @safe @nogc if (op == "++" || op == "--") { 91 mixin("_value" ~ op ~ ";"); 92 93 return this; 94 } 95 96 // Can subtract two RawTypedIdentifier to get an integer 97 T opBinary(string op : "-")(in RawTypedIdentifier rhs) const pure nothrow @safe @nogc { 98 return value - rhs.value; 99 } 100 } 101 102 // toString is not @nogc 103 string toString() const pure nothrow @safe { 104 return name ~ "<" ~ (isValid ? to!string(_value) : "INVALID") ~ ">"; 105 } 106 107 @notrace void setFromString(string str) @safe { 108 import std.conv : ConvException, parse; 109 import std..string : indexOf; 110 auto idx = str.indexOf('<'); 111 if (idx > 0 && str[$ - 1] == '>') { 112 import std.uni: sicmp; 113 enforceFmt!ConvException(sicmp(str[0 .. idx], name) == 0, "Expected %s not %s", name, str[0 .. idx]); 114 auto tmp2 = str[idx + 1 .. $-1]; 115 if (tmp2 == "INVALID") { 116 _value = _invalid; 117 } else { 118 _value = parse!T(tmp2); 119 } 120 } 121 else { 122 _value = parse!T(str); 123 } 124 } 125 126 alias Allocator = CustomAllocator!true; 127 static struct CustomAllocator(bool SkipInvalid, T Min = T.min, T Max = T.max) { 128 private: 129 T nextValue = Min; 130 131 public: 132 this( RawTypedIdentifier initValue ) { 133 this.nextValue = initValue.value; 134 } 135 136 @notrace RawTypedIdentifier getNext() nothrow @safe @nogc { 137 T next = nextValue++; 138 139 if( next>Max ) { 140 nextValue = Min; 141 return getNext(); 142 } 143 144 static if(SkipInvalid) { 145 if( next==_invalid ) 146 return getNext(); 147 } 148 149 return RawTypedIdentifier(next); 150 } 151 152 void reset(RawTypedIdentifier value = RawTypedIdentifier(Min)) nothrow @safe @nogc { 153 DBG_ASSERT!"Reset value must be in legal range %s<=%s<=%s"( 154 value.value>=Min && value.value<=Max, Min, value, Max); 155 nextValue = value.value; 156 } 157 158 static assert (Allocator.sizeof == RawTypedIdentifier.sizeof); 159 } 160 161 static assert (this.sizeof == T.sizeof); 162 } 163 } 164 165 alias TypedIdentifier(string name, T, T invalid = T.max, T _init = T.init, FMT fmt = FMT("")) = 166 RawTypedIdentifier!(name, T, invalid, _init, fmt, false); 167 alias AlgebraicTypedIdentifier(string name, T, T invalid = T.max, T _init = T.init, FMT fmt = FMT("")) = 168 RawTypedIdentifier!(name, T, invalid, _init, fmt, true); 169 170 unittest { 171 import std..string; 172 173 alias UtId = AlgebraicTypedIdentifier!("UtId", ushort); 174 175 auto val = UtId(12); 176 auto inv = UtId(65535); 177 178 assert( format("%s", val) == "UtId<12>" ); 179 assert( format("%s", inv) == "UtId<INVALID>" ); 180 181 val++; 182 inv++; 183 assert( val == UtId(13) ); 184 //assert( !inv.isValid ); 185 186 val += 2; 187 assert( val == UtId(15) ); 188 189 auto newval = val + 3; 190 191 static assert( is( typeof(newval) == typeof(val) ) ); 192 assert( newval.value == 18 ); 193 194 newval = val - 4; 195 assert( newval.value == 11 ); 196 } 197 198 enum isTypedIdentifier(T) = isInstanceOf!(RawTypedIdentifier, T); 199 enum isAlgebraicTypedIdentifier(T) = { 200 static if( isTypedIdentifier!T ) { 201 return T.Algebraic; 202 } else { 203 return false; 204 } 205 } (); 206 207 auto iota(T, U)(const T start, const T end, const U step) nothrow @safe @nogc 208 if (isAlgebraicTypedIdentifier!T && is(U == T.UnderlyingType)) 209 { 210 import std.range : iota; 211 import std.algorithm : map; 212 alias AlgType = PromotedType!U; 213 return iota(AlgType(start.value), AlgType(end.value), AlgType(step)).map!(x => T(cast(U)x)); 214 } 215 216 auto iota(T, U)(const T start, const T end, const U step) nothrow @safe @nogc 217 if(isAlgebraicTypedIdentifier!T && !is(U == T.UnderlyingType) && isImplicitlyConvertible!(U, T.UnderlyingType)) 218 { 219 return iota(start, end, T.UnderlyingType(step)); 220 } 221 222 auto iota(T)(const T start, const T end) nothrow @safe @nogc if (isAlgebraicTypedIdentifier!T) { 223 return iota(start, end, T.UnderlyingType(1)); 224 } 225 226 auto iota(T)(const T end) nothrow @safe @nogc if (isAlgebraicTypedIdentifier!T) { 227 return iota(T(0), end); 228 } 229 230 unittest { 231 alias T = AlgebraicTypedIdentifier!("SomeType", uint); 232 import std.algorithm : equal; 233 static assert(iota(T(12), T(15), ubyte(2)).equal([T(12), T(14)])); 234 } 235 236 unittest { 237 alias Foo = AlgebraicTypedIdentifier!("FOO", ubyte); 238 bool verify() { 239 import std.algorithm.comparison : equal; 240 assert(iota(Foo(2)).equal([Foo(0), Foo(1)])); 241 assert(iota(Foo(2), Foo(4)).equal([Foo(2), Foo(3)])); 242 assert(iota(Foo(2), Foo(6), ubyte(2)).equal([Foo(2), Foo(4)])); 243 return true; 244 } 245 static assert(verify()); 246 } 247 248 unittest { 249 import std.range : iota; 250 251 alias UTiD = AlgebraicTypedIdentifier!("UTiD", uint); 252 253 uint counter; 254 foreach( i; iota(12) ) { 255 counter++; 256 } 257 assert(counter==12); 258 259 counter = 0; 260 foreach( i; iota(UTiD(12)) ) { 261 counter++; 262 } 263 assert(counter==12); 264 265 counter = 0; 266 foreach( i; iota(UTiD(3), UTiD(12)) ) { 267 counter++; 268 } 269 assert(counter == 9); 270 } 271 272 import std.range : isInputRange, ElementType; 273 import std.array : empty, front, popFront; 274 275 struct TypedIndexArray(KEY, VALUE, size_t LENGTH) if (isAlgebraicTypedIdentifier!KEY) { 276 private: 277 VALUE[LENGTH] _array; 278 279 public: 280 alias Key = KEY; 281 282 this(R)(R values) if(isInputRange!R) { 283 // DMDBUG Work around: https://issues.dlang.org/show_bug.cgi?id=16301 284 // Allowing TypedIndexArray to be used in more CTFE contexts: 285 auto arr = &this._array; 286 uint count = 0; 287 foreach(ref item; values) { 288 (*arr)[count] = item; 289 count++; 290 } 291 ASSERT!("TypedIndexArray initialize from range of wrong length (%s != " ~ LENGTH.stringof ~ ")") (LENGTH == count, count); 292 } 293 this()(VALUE value) { 294 _array[] = value; 295 } 296 297 this()(ref VALUE[LENGTH] value) { 298 _array[] = value[]; 299 } 300 301 alias Slice = TypedIndexSlice!(KEY, VALUE); 302 ref inout(VALUE) opIndex(KEY index) inout { 303 DBG_ASSERT!"opIndex called with invalid index"(index.isValid); 304 return _array[index.value]; 305 } 306 307 inout(Slice) opIndex() inout { 308 return inout(Slice)(_array[]); 309 } 310 311 inout(Slice) opSlice(KEY begin, KEY end) inout { 312 return inout(Slice)(_array[begin.value .. end.value], begin.value); 313 } 314 315 @property KEY opDollar() const { return KEY(LENGTH); } 316 @property static KEY length() { return KEY(LENGTH); } 317 static size_t intLength() { return LENGTH; } 318 319 int opApply(scope int delegate(ref VALUE value) dg) { 320 return this[].opApply(dg); 321 } 322 323 int opApply(scope int delegate(ref const(VALUE) value) dg) const { 324 return this[].opApply(dg); 325 } 326 327 int opApply(scope int delegate(KEY key, ref VALUE value) dg) { 328 return this[].opApply(dg); 329 } 330 331 int opApply(scope int delegate(KEY key, ref const(VALUE) value) dg) const { 332 return this[].opApply(dg); 333 } 334 335 ref TypedIndexArray opAssign()(VALUE value) @nogc { 336 _array[] = value; 337 338 return this; 339 } 340 341 ref TypedIndexArray opAssign()(ref VALUE[LENGTH] value) { 342 _array[] = value[]; 343 344 return this; 345 } 346 347 ref TypedIndexArray opOpAssign(string OP)(VALUE value) { 348 this[].opOpAssign!OP(value); 349 350 return this; 351 } 352 353 ref TypedIndexArray opIndexAssign()(VALUE value, KEY key) { 354 _array[key.value] = value; 355 356 return this; 357 } 358 359 ref TypedIndexArray opIndexAssign()(VALUE value) { 360 this[].opIndexAssign(value); 361 362 return this; 363 } 364 365 ref TypedIndexArray opIndexAssign()(VALUE value, Slice slice) { 366 slice.opIndexAssign(value); 367 368 return this; 369 } 370 371 ref TypedIndexArray opIndexOpAssign(string OP, T)(T value, KEY key) { 372 this[].opIndexOpAssign!OP(value, key); 373 374 return this; 375 } 376 377 ref TypedIndexArray opIndexOpAssign(string OP, T)(T value) { 378 this[].opIndexOpAssign!OP(value); 379 380 return this; 381 } 382 383 ref TypedIndexArray opIndexOpAssign(string OP, T)(T value, Slice slice) { 384 slice.opIndexOpAssign!OP(value); 385 386 return this; 387 } 388 389 @property ref inout(VALUE[LENGTH]) range() inout { 390 return _array; 391 } 392 } 393 394 auto mapTypedArray(alias _func, KEY, VALUE, size_t LENGTH)(ref TypedIndexArray!(KEY, VALUE, LENGTH) arr) { 395 import std.functional : unaryFun; 396 import std.traits; 397 alias func = unaryFun!_func; 398 alias RetType = typeof(func(arr._array[0])); 399 TypedIndexArray!(KEY, Unqual!RetType, LENGTH) result; 400 foreach(KEY idx, ref VALUE oldVal; arr) { 401 result[idx] = func(oldVal); 402 } 403 return result; 404 } 405 406 // Similar to std.array : array 407 auto typedIndexArray(KEY, size_t LENGTH, R)(R items) if(isInputRange!R) 408 { 409 alias Array = TypedIndexArray!(KEY, ElementType!R, LENGTH); 410 return Array(items); 411 } 412 413 unittest { 414 import std.range : iota, take; 415 import std.array : array; 416 int[10] x = iota(10).array; 417 alias Meters = AlgebraicTypedIdentifier!("Meters", uint); 418 auto meters = typedIndexArray!(Meters, 10)(x[]); 419 assert(meters[Meters(0)] == 0); 420 assert(meters[Meters(9)] == 9); 421 assert(meters.length == Meters(10)); 422 423 import std.algorithm : map, equal; 424 assert(meters[].map!(x => x*2).take(5).equal([0, 2, 4, 6, 8])); 425 } 426 427 struct TypedIndexSlice(KEY, VALUE) if (isAlgebraicTypedIdentifier!KEY) { 428 private: 429 VALUE[] _slice; 430 KEY.UnderlyingType _offset = 0; 431 432 public: 433 alias Key = KEY; 434 435 ref inout(VALUE) opIndex(KEY index) inout { 436 DBG_ASSERT!"opIndex called with invalid index"(index.isValid); 437 return _slice[index.value - _offset]; 438 } 439 440 ref inout(TypedIndexSlice) opIndex() inout { 441 return this; 442 } 443 444 inout(TypedIndexSlice) opSlice(KEY begin, KEY end) inout { 445 return inout(TypedIndexSlice)(_slice[begin.value - _offset .. end.value - _offset], begin.value); 446 } 447 448 @property KEY opDollar() const { return KEY(cast(KEY.UnderlyingType)(length + _offset)); } 449 @property size_t length() const { return _slice.length; } 450 451 // DMDBUG: Yes, it is a direct dupliate of the opApply below. Yes, this is precisely what inout was invented 452 // for. No, inout doesn't work here. This is because opApply inference is special-cased and doesn't have the full 453 // delegate type, but rather must select the overloaded method via "ref" or "const" syntax in the foreach and decl. 454 int opApply(scope int delegate(KEY key, ref const(VALUE) value) dg) const { 455 foreach( KEY i; iota(KEY(_offset), opDollar) ) { 456 int result = dg(i, _slice[i.value - _offset]); 457 if (result) return result; 458 } 459 return 0; 460 } 461 462 int opApply(scope int delegate(KEY key, ref VALUE value) dg) { 463 foreach( KEY i; iota(KEY(_offset), opDollar) ) { 464 int result = dg(i, _slice[i.value - _offset]); 465 if (result) return result; 466 } 467 return 0; 468 } 469 470 int opApply(scope int delegate(ref const(VALUE) value) dg) const { 471 return opApply((KEY key, ref const(VALUE) val) => dg(val)); 472 } 473 474 int opApply(scope int delegate(ref VALUE value) dg) { 475 return opApply((KEY key, ref VALUE val) => dg(val)); 476 } 477 478 @property bool empty() { return _slice.empty; } 479 @property ref inout(VALUE) front() inout { return _slice.front; } 480 void popFront() { _slice.popFront(); _offset++; } 481 482 // TODO: Rename to slice 483 @property inout(VALUE)[] range() inout { 484 return _slice; 485 } 486 487 ref TypedIndexSlice opAssign()(VALUE value) @nogc { 488 _slice[] = value; 489 490 return this; 491 } 492 493 ref TypedIndexSlice opOpAssign(string OP)(VALUE value) @nogc { 494 import std.format : format; 495 mixin( q{_slice[] %s= value;}.format(OP) ); 496 497 return this; 498 } 499 500 ref TypedIndexSlice opIndexAssign()(VALUE value, KEY key) @nogc { 501 _slice[key.value - _offset] = value; 502 503 return this; 504 } 505 506 ref TypedIndexSlice opIndexAssign()(VALUE value) @nogc { 507 _slice[] = value; 508 509 return this; 510 } 511 512 ref TypedIndexSlice opIndexOpAssign(string OP, T)(T value, KEY key) @nogc { 513 import std.format : format; 514 mixin(q{_slice[key.value - _offset] %s= value;}.format(OP)); 515 516 return this; 517 } 518 519 ref TypedIndexSlice opIndexOpAssign(string OP, T)(T value) @nogc { 520 import std.format : format; 521 mixin(q{_slice[] %s= value}.format(OP)); 522 523 return this; 524 } 525 } 526 527 528 unittest { 529 import std.stdio; 530 import std.conv; 531 532 alias XXNodeId = AlgebraicTypedIdentifier!("XXNodeId", int); 533 alias XXBucketId = TypedIdentifier!("XXBucketId", ushort, 0); 534 alias MoisheId = TypedIdentifier!("MoisheId", ulong); 535 536 auto x = XXNodeId(5); 537 auto y = XXBucketId(17); 538 MoisheId z = 19; 539 540 assert(to!string(x) == "XXNodeId<5>"); 541 assert(y.value == 17); 542 // assert(z == 19); 543 static assert (XXNodeId.invalid == XXNodeId(int.max)); 544 static assert (XXBucketId.invalid == XXBucketId(0)); 545 546 static assert(!__traits(compiles, x=y)); 547 548 static assert(isTypedIdentifier!XXNodeId); 549 static assert(isTypedIdentifier!XXBucketId); 550 static assert(isTypedIdentifier!MoisheId); 551 static assert(!isTypedIdentifier!int); 552 }