1 module nxt.typecons_ex; 2 3 // TODO Add to Phobos and refer to http://forum.dlang.org/thread/lzyqywovlmdseqgqfvun@forum.dlang.org#post-ibvkvjwexdafpgtsamut:40forum.dlang.org 4 // TODO Better with?: 5 /* inout(Nullable!T) nullable(T)(inout T a) */ 6 /* { */ 7 /* return typeof(return)(a); */ 8 /* } */ 9 /* inout(Nullable!(T, nullValue)) nullable(alias nullValue, T)(inout T value) */ 10 /* if (is (typeof(nullValue) == T)) */ 11 /* { */ 12 /* return typeof(return)(value); */ 13 /* } */ 14 15 import std.typecons : Nullable, NullableRef; 16 17 /** Instantiator for `Nullable`. 18 */ 19 auto nullable(T)(T a) 20 { 21 return Nullable!T(a); 22 } 23 24 /// 25 @safe pure nothrow @nogc unittest 26 { 27 auto x = 42.5.nullable; 28 assert(is(typeof(x) == Nullable!double)); 29 } 30 31 /** Instantiator for `Nullable`. 32 */ 33 auto nullable(alias nullValue, T)(T value) 34 if (is (typeof(nullValue) == T)) 35 { 36 return Nullable!(T, nullValue)(value); 37 } 38 39 /// 40 @safe pure nothrow @nogc unittest 41 { 42 auto x = 3.nullable!(int.max); 43 assert(is (typeof(x) == Nullable!(int, int.max))); 44 } 45 46 /** Instantiator for `NullableRef`. 47 */ 48 auto nullableRef(T)(T* a) @safe pure nothrow 49 { 50 return NullableRef!T(a); 51 } 52 53 /// 54 /*TODO @safe*/ pure nothrow @nogc unittest 55 { 56 auto x = 42.5; 57 auto xr = nullableRef(&x); 58 assert(!xr.isNull); 59 xr.nullify; 60 assert(xr.isNull); 61 } 62 63 /** See_Also: http://forum.dlang.org/thread/jwdbjlobbilowlnpdzzo@forum.dlang.org 64 */ 65 template New(T) 66 if (is(T == class)) 67 { 68 T New(Args...) (Args args) 69 { 70 return new T(args); 71 } 72 } 73 74 import std.traits : isArray, isUnsigned, isInstanceOf; 75 import std.range.primitives : hasSlicing; 76 77 /** Check if `T` is castable to `U`. 78 */ 79 enum isCastableTo(T, U) = __traits(compiles, { cast(U)(T.init); }); 80 81 enum isIndex(I) = (is(I == enum) || 82 isUnsigned!I || // TODO should we allow isUnsigned here? 83 isCastableTo!(I, size_t)); 84 85 /** Check if `T` can be indexed by an instance of `I`. 86 * 87 * See_Also: http://forum.dlang.org/post/ajxtksnsxqmeulsedmae@forum.dlang.org 88 * 89 * TODO move to traits_ex.d 90 * TODO Add to Phobos 91 */ 92 enum hasIndexing(T, I = size_t) = __traits(compiles, 93 { 94 void f(E)(ref E t) {} 95 f(T.init[I.init]); 96 }); // TODO better to use `auto _ = (T.init[I.init]);` 97 98 /// 99 @safe pure nothrow @nogc unittest 100 { 101 static assert(hasIndexing!(byte[])); 102 static assert(hasIndexing!(byte[], uint)); 103 } 104 105 /** Check if `R` is indexable by `I`. */ 106 enum isIndexableBy(R, I) = (hasIndexing!R && isIndex!I); 107 108 /// 109 @safe pure nothrow @nogc unittest 110 { 111 static assert(isIndexableBy!(int[3], ubyte)); 112 } 113 114 /** Check if `R` is indexable by a automatically `R`-local defined integer type named `I`. 115 */ 116 enum isIndexableBy(R, alias I) = (hasIndexing!R && is(string == typeof(I))); // TODO extend to isSomeString? 117 118 /// 119 @safe pure nothrow @nogc unittest 120 { 121 static assert(isIndexableBy!(int[], "I")); 122 } 123 124 /** Generate bounds-checked `opIndex` and `opSlice`. 125 */ 126 static private 127 mixin template _genIndexAndSliceOps(I) 128 { 129 import nxt.conv_ex : toDefaulted; 130 131 // indexing 132 133 /// Get element at compile-time index `i`. 134 auto ref at(size_t i)() inout 135 { 136 assert(cast(size_t)i < _r.length, "Index " ~ i ~ " must be smaller than array length" ~ _r.length.stringof); 137 return _r[i]; 138 } 139 140 /// Get element at index `i`. 141 auto ref opIndex(I i) inout 142 { 143 assert(cast(size_t)i < _r.length, "Range violation with index of type " ~ I.stringof); 144 return _r[cast(size_t)i]; 145 } 146 147 /// Set element at index `i` to `value`. 148 auto ref opIndexAssign(V)(V value, I i) 149 { 150 assert(cast(size_t)i < _r.length, "Range violation with index of type " ~ I.stringof); 151 152 import core.lifetime : move; 153 154 move(value, _r[cast(size_t)i]); 155 return _r[cast(size_t)i]; 156 } 157 158 // slicing 159 static if (hasSlicing!R) 160 { 161 auto ref opSlice(I i, I j) inout 162 { 163 return _r[cast(size_t)i .. cast(size_t)j]; 164 } 165 auto ref opSliceAssign(V)(V value, I i, I j) 166 { 167 return _r[cast(size_t)i .. cast(size_t)j] = value; // TODO use `move()` 168 } 169 } 170 } 171 172 /** Generate @trusted non-bounds-checked `opIndex` and `opSlice`. 173 */ 174 static private 175 mixin template _genIndexAndSliceOps_unchecked(I) 176 { 177 @trusted: 178 179 // indexing 180 181 /// Get element at compile-time index `i`. 182 auto ref at(size_t i)() inout 183 { 184 static assert(i < _r.length, "Index " ~ i.stringof ~ " must be smaller than array length " ~ _r.length.stringof); 185 return _r.ptr[i]; 186 } 187 188 /// Get element at index `i`. 189 auto ref opIndex(I i) inout 190 { 191 return _r.ptr[cast(size_t)i]; // safe to avoid range checking 192 } 193 194 /// Set element at index `i` to `value`. 195 auto ref opIndexAssign(V)(V value, I i) 196 { 197 return _r.ptr[cast(size_t)i] = value; 198 } 199 200 // slicing 201 static if (hasSlicing!R) 202 { 203 auto ref opSlice(I i, I j) inout { return _r.ptr[cast(size_t)i .. 204 cast(size_t)j]; } 205 auto ref opSliceAssign(V)(V value, I i, I j) { return _r.ptr[cast(size_t)i .. 206 cast(size_t)j] = value; } 207 } 208 } 209 210 /** Wrapper for `R` with Type-Safe `I`-Indexing. 211 See_Also: http://forum.dlang.org/thread/gayfjaslyairnzrygbvh@forum.dlang.org#post-gayfjaslyairnzrygbvh:40forum.dlang.org 212 213 TODO Merge with https://github.com/rcorre/enumap 214 215 TODO Use std.range.indexed when I is an enum with non-contigious 216 enumerators. Perhaps use among aswell. 217 218 TODO Rename to something more concise such as [Bb]y. 219 220 TODO Allow `I` to be a string and if so derive `Index` to be that string. 221 */ 222 struct IndexedBy(R, I) 223 if (isIndexableBy!(R, I)) 224 { 225 alias Index = I; /// indexing type 226 mixin _genIndexAndSliceOps!I; 227 R _r; 228 alias _r this; // TODO Use opDispatch instead; to override only opSlice and opIndex 229 } 230 231 /** Statically-Sized Array of ElementType `E` indexed by `I`. 232 TODO assert that `I` is continuous if it is a `enum`. 233 */ 234 struct IndexedArray(E, I) 235 if (isIndex!I) 236 { 237 static assert(I.min == 0, "Index type I is currently limited to start at 0 and be continuous"); 238 alias Index = I; /// indexing type 239 mixin _genIndexAndSliceOps!I; 240 alias R = E[I.max + 1]; // needed by mixins 241 R _r; // static array 242 alias _r this; // TODO Use opDispatch instead; to override only opSlice and opIndex 243 } 244 245 /// 246 @safe pure nothrow unittest 247 { 248 enum N = 7; 249 enum I { x = 0, y = 1, z = 2} 250 alias E = int; 251 alias A = IndexedArray!(E, I); 252 static assert(A.sizeof == 3*int.sizeof); 253 A x; 254 x[I.x] = 1; 255 x[I.y] = 2; 256 x[I.z] = 3; 257 static assert(!__traits(compiles, { x[1] = 3; })); // no integer indexing 258 } 259 260 /** Instantiator for `IndexedBy`. 261 */ 262 auto indexedBy(I, R)(R range) 263 if (isIndexableBy!(R, I)) 264 { 265 return IndexedBy!(R, I)(range); 266 } 267 268 struct IndexedBy(R, string IndexTypeName) 269 if (hasIndexing!R && 270 IndexTypeName != "IndexTypeName") // prevent name lookup failure 271 { 272 static if (__traits(isStaticArray, R)) // if length is known at compile-time 273 { 274 import nxt.modulo : Mod; 275 mixin(`alias ` ~ IndexTypeName ~ ` = Mod!(R.length);`); // TODO relax integer precision argument of `Mod` 276 277 // dummy variable needed for symbol argument to `_genIndexAndSliceOps_unchecked` 278 mixin(`private static alias I__ = ` ~ IndexTypeName ~ `;`); 279 280 mixin _genIndexAndSliceOps_unchecked!(I__); // no range checking needed because I is always < R.length 281 282 /** Get index of element `E` wrapped in a `bool`-convertable struct. */ 283 auto findIndex(E)(E e) @safe pure nothrow @nogc 284 { 285 static struct Result 286 { 287 Index index; // index if exists is `true', 0 otherwise 288 bool exists; // `true` iff `index` is defined, `false` otherwise 289 bool opCast(T : bool)() const @safe pure nothrow @nogc { return exists; } 290 } 291 import std.algorithm : countUntil; 292 const ix = _r[].countUntil(e); // is safe 293 if (ix >= 0) 294 { 295 return Result(Index(ix), true); 296 } 297 return Result(Index(0), false); 298 } 299 } 300 else 301 { 302 mixin(q{ struct } ~ IndexTypeName ~ 303 q{ { 304 alias T = size_t; 305 this(T ix) { this._ix = ix; } 306 T opCast(U : T)() const { return _ix; } 307 private T _ix = 0; 308 } 309 }); 310 mixin _genIndexAndSliceOps!(mixin(IndexTypeName)); 311 } 312 R _r; 313 alias _r this; // TODO Use opDispatch instead; to override only opSlice and opIndex 314 } 315 316 /* Wrapper type for `R' indexable/sliceable only with type `R.Index`. */ 317 template TypesafelyIndexed(R) 318 if (hasIndexing!R) // prevent name lookup failure 319 { 320 alias TypesafelyIndexed = IndexedBy!(R, "Index"); 321 } 322 323 /** Instantiator for `IndexedBy`. 324 */ 325 auto indexedBy(string I, R)(R range) 326 if (isArray!R && 327 I != "IndexTypeName") // prevent name lookup failure 328 { 329 return IndexedBy!(R, I)(range); 330 } 331 332 /** Instantiator for `TypesafelyIndexed`. 333 */ 334 auto strictlyIndexed(R)(R range) 335 if (hasIndexing!(R)) 336 { 337 return TypesafelyIndexed!(R)(range); 338 } 339 340 /// 341 @safe pure nothrow unittest 342 { 343 enum m = 3; 344 int[m] x = [11, 22, 33]; 345 auto y = x.strictlyIndexed; 346 347 alias Y = typeof(y); 348 349 static assert(is(typeof(y.findIndex(11).index) == Y.Index)); 350 351 assert(y.findIndex(11).exists); 352 assert(y.findIndex(22).exists); 353 assert(y.findIndex(33).exists); 354 355 if (auto hit = y.findIndex(11)) { assert(hit.index == 0); } else { assert(false); } 356 if (auto hit = y.findIndex(22)) { assert(hit.index == 1); } else { assert(false); } 357 if (auto hit = y.findIndex(33)) { assert(hit.index == 2); } else { assert(false); } 358 359 assert(!y.findIndex(44)); 360 assert(!y.findIndex(55)); 361 362 assert(!y.findIndex(44).exists); 363 assert(!y.findIndex(55).exists); 364 } 365 366 /// 367 @safe pure nothrow unittest 368 { 369 enum N = 7; 370 alias T = TypesafelyIndexed!(size_t[N]); // static array 371 static assert(T.sizeof == N*size_t.sizeof); 372 import nxt.modulo : Mod, mod; 373 374 T x; 375 376 x[Mod!N(1)] = 11; 377 x[1.mod!N] = 11; 378 assert(x[1.mod!N] == 11); 379 380 x.at!1 = 12; 381 static assert(!__traits(compiles, { x.at!N; })); 382 assert(x.at!1 == 12); 383 } 384 385 /// 386 @safe pure nothrow unittest 387 { 388 int[3] x = [1, 2, 3]; 389 390 // sample index 391 struct Index(T = size_t) 392 if (isUnsigned!T) 393 { 394 this(T i) { this._i = i; } 395 T opCast(U : T)() const { return _i; } 396 private T _i = 0; 397 } 398 alias J = Index!size_t; 399 400 enum E { e0, e1, e2 } 401 402 with (E) 403 { 404 auto xb = x.indexedBy!ubyte; 405 auto xi = x.indexedBy!uint; 406 auto xj = x.indexedBy!J; 407 auto xe = x.indexedBy!E; 408 auto xf = x.strictlyIndexed; 409 410 auto xs = x.indexedBy!"I"; 411 alias XS = typeof(xs); 412 XS xs_; 413 414 // indexing with correct type 415 xb[ 0 ] = 11; assert(xb[ 0 ] == 11); 416 xi[ 0 ] = 11; assert(xi[ 0 ] == 11); 417 xj[J(0)] = 11; assert(xj[J(0)] == 11); 418 xe[ e0 ] = 11; assert(xe[ e0 ] == 11); 419 420 // indexing with wrong type 421 static assert(!__traits(compiles, { xb[J(0)] = 11; })); 422 static assert(!__traits(compiles, { xi[J(0)] = 11; })); 423 static assert(!__traits(compiles, { xj[ 0 ] = 11; })); 424 static assert(!__traits(compiles, { xe[ 0 ] = 11; })); 425 static assert(!__traits(compiles, { xs[ 0 ] = 11; })); 426 static assert(!__traits(compiles, { xs_[ 0 ] = 11; })); 427 428 import std.algorithm.comparison : equal; 429 import std.algorithm.iteration : filter; 430 431 assert(equal(xb[].filter!(a => a < 11), [2, 3])); 432 assert(equal(xi[].filter!(a => a < 11), [2, 3])); 433 assert(equal(xj[].filter!(a => a < 11), [2, 3])); 434 assert(equal(xe[].filter!(a => a < 11), [2, 3])); 435 // assert(equal(xs[].filter!(a => a < 11), [2, 3])); 436 } 437 } 438 439 /// 440 @safe pure nothrow unittest 441 { 442 auto x = [1, 2, 3]; 443 444 // sample index 445 struct Index(T = size_t) 446 if (isUnsigned!T) 447 { 448 this(T ix) { this._ix = ix; } 449 T opCast(U : T)() const { return _ix; } 450 private T _ix = 0; 451 } 452 alias J = Index!size_t; 453 454 enum E { e0, e1, e2 } 455 456 with (E) 457 { 458 auto xb = x.indexedBy!ubyte; 459 auto xi = x.indexedBy!uint; 460 auto xj = x.indexedBy!J; 461 auto xe = x.indexedBy!E; 462 463 // indexing with correct type 464 xb[ 0 ] = 11; assert(xb[ 0 ] == 11); 465 xi[ 0 ] = 11; assert(xi[ 0 ] == 11); 466 xj[J(0)] = 11; assert(xj[J(0)] == 11); 467 xe[ e0 ] = 11; assert(xe[ e0 ] == 11); 468 469 // slicing with correct type 470 xb[ 0 .. 1 ] = 12; assert(xb[ 0 .. 1 ] == [12]); 471 xi[ 0 .. 1 ] = 12; assert(xi[ 0 .. 1 ] == [12]); 472 xj[J(0) .. J(1)] = 12; assert(xj[J(0) .. J(1)] == [12]); 473 xe[ e0 .. e1 ] = 12; assert(xe[ e0 .. e1 ] == [12]); 474 475 // indexing with wrong type 476 static assert(!__traits(compiles, { xb[J(0)] = 11; })); 477 static assert(!__traits(compiles, { xi[J(0)] = 11; })); 478 static assert(!__traits(compiles, { xj[ 0 ] = 11; })); 479 static assert(!__traits(compiles, { xe[ 0 ] = 11; })); 480 481 // slicing with wrong type 482 static assert(!__traits(compiles, { xb[J(0) .. J(0)] = 11; })); 483 static assert(!__traits(compiles, { xi[J(0) .. J(0)] = 11; })); 484 static assert(!__traits(compiles, { xj[ 0 .. 0 ] = 11; })); 485 static assert(!__traits(compiles, { xe[ 0 .. 0 ] = 11; })); 486 487 import std.algorithm.comparison : equal; 488 import std.algorithm.iteration : filter; 489 490 assert(equal(xb.filter!(a => a < 11), [2, 3])); 491 assert(equal(xi.filter!(a => a < 11), [2, 3])); 492 assert(equal(xj.filter!(a => a < 11), [2, 3])); 493 assert(equal(xe.filter!(a => a < 11), [2, 3])); 494 } 495 } 496 497 /// 498 @safe pure nothrow unittest 499 { 500 auto x = [1, 2, 3]; 501 struct I(T = size_t) 502 { 503 this(T ix) { this._ix = ix; } 504 T opCast(U : T)() const { return _ix; } 505 private T _ix = 0; 506 } 507 alias J = I!size_t; 508 auto xj = x.indexedBy!J; 509 } 510 511 /// 512 @safe pure nothrow unittest 513 { 514 auto x = [1, 2, 3]; 515 struct I(T = size_t) 516 { 517 private T _ix = 0; 518 } 519 alias J = I!size_t; 520 static assert(!__traits(compiles, { auto xj = x.indexedBy!J; })); 521 } 522 523 /// 524 version(none) 525 @safe pure nothrow unittest 526 { 527 auto x = [1, 2, 3]; 528 import nxt.bound : Bound; 529 alias B = Bound!(ubyte, 0, 2); 530 B b; 531 auto c = cast(size_t)b; 532 auto y = x.indexedBy!B; 533 } 534 535 /// 536 @safe pure nothrow unittest 537 { 538 import nxt.dynamic_array : Array = DynamicArray; 539 540 enum Lang { en, sv, fr } 541 542 alias Ixs = Array!int; 543 544 struct S 545 { 546 Lang lang; 547 string data; 548 Ixs ixs; 549 } 550 551 alias A = Array!S; 552 553 struct I 554 { 555 size_t opCast(U : size_t)() const @safe pure nothrow @nogc { return _ix; } 556 uint _ix; 557 alias _ix this; 558 } 559 560 I i; 561 static assert(isCastableTo!(I, size_t)); 562 static assert(isIndexableBy!(A, I)); 563 564 alias IA = IndexedBy!(A, I); 565 IA ia; 566 ia ~= S.init; 567 assert(ia.length == 1); 568 auto s = S(Lang.en, "alpha", Ixs.withLength(42)); 569 570 import core.lifetime : move; 571 572 ia ~= move(s); 573 assert(ia.length == 2); 574 } 575 576 /** Returns: a `string` containing the definition of an `enum` named `name` and 577 with enumerator names given by `Es`, optionally prepended with `prefix` and 578 appended with `suffix`. 579 580 TODO Move to Phobos std.typecons 581 */ 582 template makeEnumFromSymbolNames(string prefix = `__`, 583 string suffix = ``, 584 bool firstUndefined = true, 585 bool useMangleOf = false, 586 Es...) 587 if (Es.length != 0) 588 { 589 enum members = 590 { 591 string s = firstUndefined ? `undefined, ` : ``; 592 foreach (E; Es) 593 { 594 static if (useMangleOf) 595 { 596 enum E_ = E.mangleof; 597 } 598 else 599 { 600 import std.traits : isPointer; 601 static if (isPointer!E) 602 { 603 import std.traits : TemplateOf; 604 enum isTemplateInstance = is(typeof(TemplateOf!(typeof(*E.init)))); 605 static if (isTemplateInstance) // strip template params for now 606 { 607 enum E_ = __traits(identifier, TemplateOf!(typeof(*E))) ~ `Ptr`; 608 } 609 else 610 { 611 enum E_ = typeof(*E.init).stringof ~ `Ptr`; 612 } 613 } 614 else 615 { 616 enum E_ = E.stringof; 617 } 618 619 } 620 s ~= prefix ~ E_ ~ suffix ~ `, `; 621 } 622 return s; 623 }(); 624 mixin("enum makeEnumFromSymbolNames : ubyte {" ~ members ~ "}"); 625 } 626 627 /// 628 @safe pure nothrow @nogc unittest 629 { 630 import std.meta : AliasSeq; 631 struct E(T) { T x; } 632 alias Types = AliasSeq!(byte, short, int*, E!int*); 633 alias Type = makeEnumFromSymbolNames!(`_`, `_`, true, false, Types); 634 static assert(is(Type == enum)); 635 static assert(Type.undefined.stringof == `undefined`); 636 static assert(Type._byte_.stringof == `_byte_`); 637 static assert(Type._short_.stringof == `_short_`); 638 static assert(Type._intPtr_.stringof == `_intPtr_`); 639 static assert(Type._EPtr_.stringof == `_EPtr_`); 640 } 641 642 /// 643 @safe pure nothrow @nogc unittest 644 { 645 import std.meta : AliasSeq; 646 647 struct E(T) { T x; } 648 649 alias Types = AliasSeq!(byte, short, int*, E!int*); 650 alias Type = makeEnumFromSymbolNames!(`_`, `_`, true, true, Types); 651 652 static assert(is(Type == enum)); 653 654 static assert(Type.undefined.stringof == `undefined`); 655 static assert(Type._g_.stringof == `_g_`); 656 static assert(Type._s_.stringof == `_s_`); 657 static assert(Type._Pi_.stringof == `_Pi_`); 658 } 659 660 /** 661 See_Also: https://p0nce.github.io/d-idioms/#Rvalue-references:-Understanding-auto-ref-and-then-not-using-it 662 */ 663 mixin template RvalueRef() 664 { 665 alias T = typeof(this); // typeof(this) get us the type we're in 666 static assert (is(T == struct)); 667 668 @nogc @safe 669 ref inout(T) asRef() inout pure nothrow return 670 { 671 return this; 672 } 673 } 674 675 @safe @nogc pure nothrow unittest 676 { 677 static struct Vec 678 { 679 @safe @nogc pure nothrow: 680 float x, y; 681 this(float x, float y) pure nothrow 682 { 683 this.x = x; 684 this.y = y; 685 } 686 mixin RvalueRef; 687 } 688 689 static void foo(ref const Vec pos) 690 { 691 } 692 693 Vec v = Vec(42, 23); 694 foo(v); // works 695 foo(Vec(42, 23).asRef); // works as well, and use the same function 696 } 697 698 /** Convert enum value `v` to `string`. 699 See also http://forum.dlang.org/post/aqqhlbaepoimpopvouwv@forum.dlang.org 700 */ 701 string enumToString(E)(E v) 702 { 703 static assert(is(E == enum), "emumToString is only meant for enums"); 704 switch(v) 705 { 706 foreach (m; __traits(allMembers, E)) 707 { 708 case mixin("E." ~ m) : return m; 709 } 710 default: 711 { 712 string result = "cast(" ~ E.stringof ~ ")"; 713 uint val = v; 714 715 enum headLength = E.stringof.length + "cast()".length; 716 uint log10Val = (val < 10) ? 0 : (val < 100) ? 1 : (val < 1_000) ? 2 : 717 (val < 10_000) ? 3 : (val < 100_000) ? 4 : (val < 1_000_000) ? 5 : 718 (val < 10_000_000) ? 6 : (val < 100_000_000) ? 7 : (val < 1000_000_000) ? 8 : 9; 719 720 result.length += log10Val + 1; 721 722 foreach (uint i; 0 .. log10Val + 1) 723 { 724 cast(char)result[headLength + log10Val - i] = cast(char) ('0' + (val % 10)); 725 val /= 10; 726 } 727 return cast(string) result; 728 } 729 } 730 } 731 732 @safe pure nothrow unittest 733 { 734 enum ET { one, two } 735 // static assert(to!string(ET.one) == "one"); 736 static assert (enumToString(ET.one) == "one"); 737 assert (enumToString(ET.one) == "one"); 738 }