1 module auxil.test; 2 3 version(unittest) import unit_threaded : Name, should, be; 4 5 import auxil.model; 6 7 @safe private 8 struct PrettyPrintingVisitor 9 { 10 import std.experimental.allocator.mallocator : Mallocator; 11 import automem.vector : Vector; 12 13 Vector!(char, Mallocator) output; 14 private Vector!(char, Mallocator) _indentation; 15 DefaultVisitor default_visitor; 16 alias default_visitor this; 17 18 this(SizeType size) @nogc 19 { 20 default_visitor = DefaultVisitor(size); 21 } 22 23 auto processItem(T...)(T msg) 24 { 25 () @trusted { 26 output ~= _indentation[]; 27 import nogc.conv : text; 28 output ~= text(msg, "\n")[]; 29 } (); 30 } 31 32 void indent() @nogc @trusted 33 { 34 _indentation ~= '\t'; 35 } 36 37 void unindent() @nogc @trusted 38 { 39 if (_indentation.length) 40 _indentation.popBack; 41 } 42 43 void enterTree(Order order, Data, Model)(auto ref const(Data) data, ref Model model) 44 { 45 position = 0; 46 } 47 48 void enterNode(Order order, Data, Model)(ref const(Data) data, ref Model model) 49 { 50 import std.conv : to; 51 import auxil.traits : hasRenderHeader; 52 53 static if (hasRenderHeader!data) 54 { 55 import auxil.model : FixedAppender; 56 FixedAppender!512 app; 57 data.renderHeader(app); 58 () @trusted { processItem(app[]); } (); 59 } 60 else 61 processItem("Caption: ", Data.stringof); 62 } 63 64 void processLeaf(Order order, Data, Model)(ref const(Data) data, ref Model model) 65 { 66 processItem(data); 67 } 68 } 69 70 version(unittest) @Name("aggregates_trivial") 71 @safe 72 unittest 73 { 74 static struct Test 75 { 76 float f = 7.7; 77 int i = 8; 78 string s = "some text"; 79 } 80 81 static struct StructWithStruct 82 { 83 double d = 8.8; 84 long l = 999; 85 Test t; 86 } 87 88 static class TestClass 89 { 90 91 } 92 93 static struct StructWithPointerAndClass 94 { 95 double* d; 96 TestClass tc; 97 } 98 99 static struct StructWithNestedClass 100 { 101 TestClass tc; 102 } 103 104 auto visitor = PrettyPrintingVisitor(9); 105 auto d = StructWithStruct(); 106 auto m = makeModel(d); 107 m.visitForward(d, visitor); 108 m.size.should.be == 10; 109 d.d = 0; 110 d.l = 1; 111 d.t.f = 2; 112 d.t.i = 3; 113 d.t.s = "s"; 114 m.visitForward(d, visitor); 115 m.collapsed = false; 116 m.visitForward(d, visitor); 117 m.t.collapsed = false; 118 m.visitForward(d, visitor); 119 } 120 121 version(unittest) @Name("static_array") 122 unittest 123 { 124 version(Windows) {} 125 else { 126 import core.stdc.locale; 127 setlocale(LC_NUMERIC, "C"); 128 } 129 float[3] d = [1.1f, 2.2f, 3.3f]; 130 auto m = Model!d(); 131 132 auto visitor = PrettyPrintingVisitor(9); 133 visitor.destination = 100; 134 visitor.processItem; 135 m.collapsed = false; 136 m.visitForward(d, visitor); 137 138 visitor.output ~= '\0'; 139 version(none) 140 { 141 import core.stdc.stdio : printf; 142 printf("%s\nlength: %ld\n", visitor.output[].ptr, visitor.output.length); 143 } 144 145 import std.algorithm : equal; 146 visitor.output[].should.be == " 147 Caption: float[3] 148 1.100000 149 2.200000 150 3.300000 151 \0"; 152 } 153 154 version(unittest) @Name("dynamic_array") 155 unittest 156 { 157 auto visitor = PrettyPrintingVisitor(9); 158 159 float[] d = [1.1f, 2.2f, 3.3f]; 160 auto m = Model!d(); 161 162 visitor.processItem; 163 visitor.destination = visitor.destination.max; 164 m.collapsed = false; 165 m.model.length = d.length; 166 m.visitForward(d, visitor); 167 168 d ~= [4.4f, 5.5f]; 169 m.model.length = d.length; 170 m.visitForward(d, visitor); 171 172 d = d[2..3]; 173 m.model.length = d.length; 174 m.visitForward(d, visitor); 175 176 visitor.output ~= '\0'; 177 version(none) 178 { 179 import core.stdc.stdio : printf; 180 printf("%s\nlength: %ld\n", visitor.output[].ptr, visitor.output.length); 181 } 182 183 import std.algorithm : equal; 184 visitor.output[].should.be == " 185 Caption: float[] 186 1.100000 187 2.200000 188 3.300000 189 Caption: float[] 190 1.100000 191 2.200000 192 3.300000 193 4.400000 194 5.500000 195 Caption: float[] 196 3.300000 197 \0"; 198 } 199 200 version(none) 201 version(unittest) @Name("aggregate_with_only_member") 202 @nogc unittest 203 { 204 static struct OneMember 205 { 206 string one = "one"; 207 } 208 209 auto d = OneMember(); 210 auto m = Model!d(); 211 import std.traits : FieldNameTuple; 212 import std.meta : AliasSeq; 213 static assert(FieldNameTuple!(typeof(m)) == AliasSeq!("single_member_model")); 214 215 auto visitor = PrettyPrintingVisitor(9); 216 visitor.processItem; 217 m.visitForward(d, visitor); 218 219 visitor.output ~= '\0'; 220 221 version(none) 222 { 223 import core.stdc.stdio : printf; 224 printf("---\n%s\n---\nlength: %ld\n", 225 visitor.output[].ptr, visitor.output.length); 226 } 227 228 import std.algorithm : equal; 229 visitor.output[].should.be == " 230 one 231 \0"; 232 } 233 234 version(unittest) @Name("aggregate_with_render_header") 235 unittest 236 { 237 static struct Aggregate 238 { 239 float f; 240 long l; 241 242 void renderHeader(W)(ref W writer) @trusted const 243 { 244 import core.stdc.stdio : snprintf; 245 246 char[128] buffer; 247 const l = snprintf(&buffer[0], buffer.length, "Custom header %f, %lld", f, l); 248 writer.put(buffer[0..l]); 249 } 250 } 251 252 import auxil.traits : hasRenderHeader; 253 static assert(hasRenderHeader!Aggregate); 254 255 auto d = Aggregate(); 256 257 static assert(hasRenderHeader!d); 258 auto m = Model!d(); 259 m.collapsed = false; 260 261 auto visitor = PrettyPrintingVisitor(9); 262 visitor.destination = visitor.destination.max; 263 visitor.processItem; 264 m.visitForward(d, visitor); 265 266 visitor.output ~= '\0'; 267 268 version(none) 269 { 270 import core.stdc.stdio : printf; 271 printf("---\n%s\n---\nlength: %ld\n", 272 visitor.output[].ptr, visitor.output.length); 273 } 274 275 visitor.output[].should.be == " 276 Custom header nan, 0 277 nan 278 0 279 \0"; 280 } 281 282 version(unittest) 283 { 284 import auxil.traits : renderedAs, renderedAsMember; 285 286 private struct Proxy 287 { 288 float f; 289 290 this(ref const(Test3) t3) 291 { 292 f = t3.f; 293 } 294 } 295 296 @renderedAs!Proxy 297 private struct Test3 298 { 299 int i; 300 float f; 301 } 302 303 @renderedAs!int 304 private struct Test4 305 { 306 int i; 307 float f; 308 309 int opCast(T : int)() const 310 { 311 return i; 312 } 313 } 314 315 @renderedAsMember!"t4.i" 316 private struct Test5 317 { 318 Test4 t4; 319 float f; 320 } 321 } 322 323 version(none) 324 version(unittest) @Name("aggregate_proxy") 325 unittest 326 { 327 import auxil.traits :hasRenderedAs; 328 { 329 auto test3 = Test3(123, 123.0f); 330 331 auto d = Proxy(test3); 332 auto m = makeModel(d); 333 334 static assert( hasRenderedAs!Test3); 335 static assert(!hasRenderedAs!test3); 336 import std.traits : FieldNameTuple; 337 import std.meta : AliasSeq; 338 static assert(FieldNameTuple!(typeof(m)) == AliasSeq!("single_member_model")); 339 340 auto visitor = PrettyPrintingVisitor(9); 341 visitor.processItem; 342 m.visitForward(d, visitor); 343 344 visitor.output ~= '\0'; 345 346 version(none) 347 { 348 import core.stdc.stdio : printf; 349 printf("---\n%s\n---\nlength: %ld\n", 350 visitor.output[].ptr, visitor.output.length); 351 } 352 353 visitor.output[].should.be == " 354 123.000000 355 \0"; 356 } 357 358 { 359 auto d = Test3(); 360 auto m = makeModel(d); 361 static assert(!m.Collapsable); 362 363 auto visitor = PrettyPrintingVisitor(9); 364 visitor.processItem; 365 m.visitForward(d, visitor); 366 367 visitor.output ~= '\0'; 368 369 version(none) 370 { 371 import core.stdc.stdio : printf; 372 printf("---\n%s\n---\nlength: %ld\n", 373 visitor.output[].ptr, visitor.output.length); 374 } 375 376 import std.algorithm : equal; 377 visitor.output[].should.be == " 378 nan 379 \0"; 380 } 381 382 { 383 Test4 test4; 384 test4.i = 112; 385 auto m = makeModel(test4); 386 387 static assert(is(m.Proxy)); 388 static assert(is(typeof(m.proxy) == int)); 389 m.proxy.should.be == 112; 390 } 391 392 { 393 auto d = Test5(Test4(11, 22.2)); 394 auto m = makeModel(d); 395 static assert(!m.Collapsable); 396 397 auto visitor = PrettyPrintingVisitor(9); 398 visitor.processItem; 399 m.visitForward(d, visitor); 400 401 visitor.output ~= '\0'; 402 403 version(none) 404 { 405 import core.stdc.stdio : printf; 406 printf("---\n%s\n---\nlength: %ld\n", 407 visitor.output[].ptr, visitor.output.length); 408 } 409 410 import std.algorithm : equal; 411 visitor.output[].should.be == " 412 11 413 \0"; 414 } 415 416 { 417 @("wrongAttribute") 418 @("renderedAsMember.f", "123") 419 @("123") 420 static struct Test 421 { 422 int i; 423 float f; 424 } 425 auto d = Test(11, 1.0); 426 auto m = makeModel(d); 427 static assert(!m.Collapsable); 428 429 auto visitor = PrettyPrintingVisitor(9); 430 visitor.processItem; 431 m.visitForward(d, visitor); 432 433 visitor.output ~= '\0'; 434 435 version(none) 436 { 437 import core.stdc.stdio : printf; 438 printf("---\n%s\n---\nlength: %ld\n", 439 visitor.output[].ptr, visitor.output.length); 440 } 441 442 visitor.output[].should.be == " 443 1.000000 444 \0"; 445 } 446 } 447 448 version(unittest) @Name("TaggedAlgebraic") 449 unittest 450 { 451 import taggedalgebraic : TaggedAlgebraic, Void, get; 452 import unit_threaded : should, be; 453 454 static struct Struct 455 { 456 double d; 457 char c; 458 } 459 460 struct Payload 461 { 462 Void v; 463 float f; 464 int i; 465 string sg; 466 Struct st; 467 string[] sa; 468 double[] da; 469 } 470 471 alias Data = TaggedAlgebraic!Payload; 472 473 Data[] data = [ 474 Data(1.2f), 475 Data(4), 476 Data("string"), 477 Data(Struct(100, 'd')), 478 Data(["str0", "str1", "str2"]), 479 Data([0.1, 0.2, 0.3]), 480 ]; 481 482 auto model = makeModel(data); 483 model.length.should.be == data.length; 484 model[4].get!(Model!(string[])).length.should.be == data[4].length; 485 486 // default value 487 model.collapsed.should.be == true; 488 489 // change it directly 490 model.collapsed = false; 491 model.collapsed.should.be == false; 492 493 // change the value of the root by tree path 494 setPropertyByTreePath!"collapsed"(data, model, [], true); 495 model.collapsed.should.be == true; 496 { 497 const r = getPropertyByTreePath!("collapsed", bool)(data, model, []); 498 r.isNull.should.be == false; 499 r.get.should.be == true; 500 } 501 502 // change the root once again 503 setPropertyByTreePath!"collapsed"(data, model, [], false); 504 model.collapsed.should.be == false; 505 506 // the value of the child by tree path 507 setPropertyByTreePath!"collapsed"(data, model, [3], false); 508 model.model[3].collapsed.should.be == false; 509 510 foreach(ref e; model.model) 511 e.collapsed = false; 512 513 auto visitor = PrettyPrintingVisitor(9); 514 visitor.processItem; 515 visitor.destination = visitor.destination.max; 516 model.visitForward(data, visitor); 517 518 data[4] ~= "recently added 4th element"; 519 model[4].update(data[4]); 520 model.visitForward(data, visitor); 521 522 data[4] = data[4].get!(string[])[3..$]; 523 data[4].get!(string[])[0] = "former 4th element, now the only one"; 524 model[4].update(data[4]); 525 model.visitForward(data, visitor); 526 527 visitor.output ~= '\0'; 528 version(none) 529 { 530 import core.stdc.stdio : printf; 531 printf("%s\nlength: %ld\n", visitor.output[].ptr, visitor.output.length); 532 } 533 534 import std.algorithm : equal; 535 visitor.output[].should.be == " 536 Caption: TaggedAlgebraic!(Payload)[] 537 1.200000 538 4 539 string 540 Caption: Struct 541 100.000000 542 d 543 Caption: string[] 544 str0 545 str1 546 str2 547 Caption: double[] 548 0.100000 549 0.200000 550 0.300000 551 Caption: TaggedAlgebraic!(Payload)[] 552 1.200000 553 4 554 string 555 Caption: Struct 556 100.000000 557 d 558 Caption: string[] 559 str0 560 str1 561 str2 562 recently added 4th element 563 Caption: double[] 564 0.100000 565 0.200000 566 0.300000 567 Caption: TaggedAlgebraic!(Payload)[] 568 1.200000 569 4 570 string 571 Caption: Struct 572 100.000000 573 d 574 Caption: string[] 575 former 4th element, now the only one 576 Caption: double[] 577 0.100000 578 0.200000 579 0.300000 580 \0"; 581 } 582 583 version(unittest) @Name("nogc_dynamic_array") 584 unittest 585 { 586 auto visitor = PrettyPrintingVisitor(14); 587 () { 588 import std.experimental.allocator.mallocator : Mallocator; 589 import automem.vector : Vector; 590 Vector!(float, Mallocator) data; 591 data ~= 0.1f; 592 data ~= 0.2f; 593 data ~= 0.3f; 594 595 auto model = makeModel(data[]); 596 597 visitor.destination = visitor.destination.max; 598 visitor.processItem; 599 model.visitForward(data[], visitor); 600 model.size.should.be == visitor.size + model.Spacing; 601 602 model.collapsed = false; 603 model.visitForward(data[], visitor); 604 605 model.size.should.be == 4*(visitor.size + model.Spacing); 606 foreach(e; model.model) 607 e.size.should.be == (visitor.size + model.Spacing); 608 609 visitor.output ~= '\0'; 610 version(none) 611 { 612 import core.stdc.stdio : printf; 613 printf("%s\nlength: %ld\n", visitor.output[].ptr, visitor.output.length); 614 } 615 } (); 616 617 visitor.output[].should.be == " 618 Caption: float[] 619 Caption: float[] 620 0.100000 621 0.200000 622 0.300000 623 \0"; 624 } 625 626 version(unittest) @Name("size_measuring") 627 unittest 628 { 629 import taggedalgebraic : TaggedAlgebraic, Void, get; 630 import unit_threaded : should, be; 631 632 static struct Struct 633 { 634 double d; 635 char c; 636 } 637 638 struct Payload 639 { 640 Void v; 641 float f; 642 int i; 643 string sg; 644 Struct st; 645 string[] sa; 646 double[] da; 647 } 648 649 alias Data = TaggedAlgebraic!Payload; 650 651 Data[] data = [ 652 Data(1.2f), 653 Data(4), 654 Data("string"), 655 Data(Struct(100, 'd')), 656 Data(["str0", "str1", "str2"]), 657 Data([0.1, 0.2, 0.3]), 658 ]; 659 660 auto model = makeModel(data); 661 model.length.should.be == data.length; 662 model[4].get!(Model!(string[])).length.should.be == data[4].length; 663 664 model.size.should.be == 0; 665 auto visitor = PrettyPrintingVisitor(17); 666 model.visitForward(data, visitor); 667 668 model.collapsed.should.be == true; 669 model.size.should.be == (visitor.size + model.Spacing); 670 model.size.should.be == 18; 671 visitor.position.should.be == 0; 672 673 setPropertyByTreePath!"collapsed"(data, model, [], false); 674 visitor.destination = visitor.destination.max; 675 model.visitForward(data, visitor); 676 model.size.should.be == (visitor.size + model.Spacing)*7; 677 model.size.should.be == 18*7; 678 visitor.position.should.be == 6*18; 679 680 setPropertyByTreePath!"collapsed"(data, model, [3], false); 681 visitor.destination = visitor.destination.max; 682 model.visitForward(data, visitor); 683 model.size.should.be == (visitor.size + model.Spacing)*9; 684 model.size.should.be == 18*9; 685 visitor.position.should.be == (6+2)*18; 686 687 setPropertyByTreePath!"collapsed"(data, model, [4], false); 688 visitor.destination = visitor.destination.max; 689 model.visitForward(data, visitor); 690 model.size.should.be == (visitor.size + model.Spacing)*12; 691 model.size.should.be == 18*12; 692 visitor.position.should.be == (6+2+3)*18; 693 694 setPropertyByTreePath!"collapsed"(data, model, [5], false); 695 visitor.destination = visitor.destination.max; 696 model.visitForward(data, visitor); 697 model.size.should.be == (visitor.size + model.Spacing)*15; 698 model.size.should.be == 18*15; 699 visitor.position.should.be == (6+2+3+3)*18; 700 701 visitor.destination = visitor.destination.max; 702 model.visitForward(data, visitor); 703 model.size.should.be == 270; 704 visitor.position.should.be == 252; 705 706 visitor.position = 0; 707 visitor.destination = 100; 708 model.visitForward(data, visitor); 709 model.size.should.be == 126; 710 visitor.position.should.be == 90; 711 } 712 713 struct RelativeMeasurer 714 { 715 DefaultVisitorImpl!(SizeEnabled.no, TreePathEnabled.yes) default_visitor; 716 alias default_visitor this; 717 718 TreePosition[] output; 719 720 void enterTree(Order order, Data, Model)(ref const(Data) data, ref Model model) 721 { 722 output = null; 723 } 724 725 void enterNode(Order order, Data, Model)(ref const(Data) data, ref Model model) 726 { 727 static if (order == Order.Sinking) 728 output ~= TreePosition(tree_path.value, position); 729 } 730 731 void leaveNode(Order order, Data, Model)(ref const(Data) data, ref Model model) 732 { 733 static if (order == Order.Bubbling) 734 output ~= TreePosition(tree_path.value, position); 735 } 736 737 void processLeaf(Order order, Data, Model)(ref const(Data) data, ref Model model) 738 { 739 output ~= TreePosition(tree_path.value, position); 740 } 741 } 742 743 struct TreePosition 744 { 745 import std.experimental.allocator.mallocator : Mallocator; 746 import automem : Vector; 747 Vector!(int, Mallocator) path; 748 double size; 749 750 @disable this(); 751 752 this(P, S)(P p, S s) 753 if (!is(P == void[])) 754 { 755 path = p; 756 size = s; 757 } 758 759 this(S)(void[] p, S s) 760 { 761 size = s; 762 } 763 764 import std.range : isOutputRange; 765 import std.format : FormatSpec; 766 767 void toString(Writer) (ref Writer w, scope const ref FormatSpec!char fmt) const 768 if (isOutputRange!(Writer, char)) 769 { 770 import std.algorithm : copy; 771 import std.conv : text; 772 773 copy(typeof(this).stringof, w); 774 w.put('('); 775 copy(text(path[], ", ", size), w); 776 w.put(')'); 777 } 778 } 779 780 version(unittest) @Name("reverse_dynamic_array") 781 unittest 782 { 783 import unit_threaded : should, be; 784 785 auto data = [0, 1, 2, 3]; 786 auto model = makeModel(data); 787 auto visitor = RelativeMeasurer(); 788 789 model.collapsed = false; 790 { 791 auto mv = MeasuringVisitor(9); 792 model.visitForward(data, mv); 793 } 794 visitor.position = 0; 795 visitor.destination = visitor.destination.max; 796 model.visitForward(data, visitor); 797 visitor.output.should.be == [ 798 TreePosition([ ], 0), 799 TreePosition([0], 10), 800 TreePosition([1], 20), 801 TreePosition([2], 30), 802 TreePosition([3], 40), 803 ]; 804 805 visitor.position.should.be == 40; 806 807 visitor.destination = -visitor.destination.max; 808 model.visitBackward(data, visitor); 809 visitor.output.should.be == [ 810 TreePosition([3], 40), 811 TreePosition([2], 30), 812 TreePosition([1], 20), 813 TreePosition([0], 10), 814 TreePosition([ ], 0), 815 ]; 816 817 visitor.path.value = [1,]; 818 visitor.position = 20; 819 visitor.destination = visitor.destination.max; 820 model.visitForward(data, visitor); 821 visitor.output.should.be == [ 822 TreePosition([1], 20), 823 TreePosition([2], 30), 824 TreePosition([3], 40), 825 ]; 826 visitor.position = 20; 827 visitor.destination = -visitor.destination.max; 828 model.visitBackward(data, visitor); 829 visitor.output.should.be == [ 830 TreePosition([1], 20), 831 TreePosition([0], 10), 832 TreePosition([ ], 0), 833 ]; 834 } 835 836 version(unittest) @Name("aggregate") 837 unittest 838 { 839 import unit_threaded : should, be; 840 841 static struct NestedData1 842 { 843 long l; 844 char ch; 845 } 846 847 static struct NestedData2 848 { 849 short sh; 850 NestedData1 data1; 851 string str; 852 } 853 854 static struct Data 855 { 856 int i; 857 float f; 858 double d; 859 string s; 860 NestedData2 data2; 861 } 862 863 const data = Data(0, 1, 2, "3", NestedData2(ushort(2), NestedData1(1_000_000_000, 'z'), "text")); 864 auto model = makeModel(data); 865 auto visitor = RelativeMeasurer(); 866 867 model.collapsed = false; 868 { 869 auto mv = MeasuringVisitor(9); 870 model.visitForward(data, mv); 871 } 872 visitor.position = 0; 873 visitor.destination = visitor.destination.max; 874 model.visitForward(data, visitor); 875 visitor.output.should.be == [ 876 TreePosition([], 0), 877 TreePosition([0], 10), 878 TreePosition([1], 20), 879 TreePosition([2], 30), 880 TreePosition([3], 40), 881 TreePosition([4], 50), 882 ]; 883 visitor.position.should.be == 50; 884 885 { 886 visitor.path.clear; 887 visitor.position = 0; 888 visitor.destination = 30; 889 model.visitForward(data, visitor); 890 visitor.output.should.be == [ 891 TreePosition([], 0), 892 TreePosition([0], 10), 893 TreePosition([1], 20), 894 TreePosition([2], 30), 895 ]; 896 visitor.position.should.be == 30; 897 } 898 899 { 900 visitor.path.clear; 901 visitor.position = 30; 902 visitor.destination = visitor.position + 30; 903 model.visitForward(data, visitor); 904 visitor.output.should.be == [ 905 TreePosition([], 30), 906 TreePosition([0], 40), 907 TreePosition([1], 50), 908 TreePosition([2], 60), 909 ]; 910 visitor.position.should.be == 60; 911 } 912 913 { 914 visitor.path.value = [0]; 915 visitor.position = 130; 916 visitor.destination = visitor.position + 20; 917 model.visitForward(data, visitor); 918 visitor.output.should.be == [ 919 TreePosition([0], 130), 920 TreePosition([1], 140), 921 TreePosition([2], 150), 922 ]; 923 visitor.position.should.be == 150; 924 } 925 926 visitor.path.value = [2]; 927 visitor.position = 30; 928 visitor.destination = visitor.destination.max; 929 model.visitForward(data, visitor); 930 931 visitor.output.should.be == [ 932 TreePosition([2], 30), 933 TreePosition([3], 40), 934 TreePosition([4], 50), 935 ]; 936 937 visitor.path.clear; 938 visitor.destination = -visitor.destination.max; 939 model.visitBackward(data, visitor); 940 visitor.output.should.be == [ 941 TreePosition([4], 50), 942 TreePosition([3], 40), 943 TreePosition([2], 30), 944 TreePosition([1], 20), 945 TreePosition([0], 10), 946 TreePosition([], 0), 947 ]; 948 } 949 950 version(unittest) @Name("new_paradigm") 951 unittest 952 { 953 static struct TrivialStruct 954 { 955 int i; 956 float f; 957 } 958 959 static struct StructNullable 960 { 961 import std.typecons : Nullable; 962 int i; 963 Nullable!float f; 964 } 965 966 auto d = StructNullable(); 967 auto m = makeModel(d); 968 m.collapsed = false; 969 auto visitor = DefaultVisitor(19); 970 m.visitForward(d, visitor); 971 import std; 972 writeln(m); 973 }