1 /++ SDLang struct serialize 2 +/ 3 module sdlss; 4 5 public import sdlang; 6 7 import std.meta; 8 import std.traits; 9 import std.range : ElementType; 10 import std.datetime; 11 import std.conv; 12 import std.string : toLower; 13 14 /// 15 T readStruct(T)(string fname) 16 { return buildStruct!T(fname.parseFile); } 17 18 /// 19 void readStruct(T)(ref T st, string fname) 20 { fillStruct(st, fname.parseFile); } 21 22 /// 23 void writeStruct(T)(auto ref const T st, string fname) 24 { 25 import std.file : write; 26 fname.write(structToSDLDocument(st)); 27 } 28 29 /// 30 string structToSDLDocument(T)(auto ref const T st) 31 { return buildTag(st).toSDLDocument; } 32 33 /// 34 T buildStruct(T)(Tag tag) if (is(T==struct)) 35 { 36 T ret; 37 fillStruct(ret, tag); 38 return ret; 39 } 40 41 bool isUserStruct(T)() @property 42 { 43 return is(T == struct) && 44 !is(T == Date) && 45 !is(T == SysTime) && 46 !is(T == DateTime) && 47 !is(T == DateTimeFrac) && 48 !is(T == DateTimeFracUnknownZone) && 49 !is(T == Duration); 50 } 51 52 bool isSerializableArray(T)() @property 53 { 54 return isArray!T && 55 !isSomeString!T && 56 !(is(Unqual!T == char[])) && 57 !(is(Unqual!T : const(char)[])) && 58 !(is(Unqual!T == ubyte[])) && 59 !(is(Unqual!T : const(ubyte)[])); 60 } 61 62 alias FloatingType = double; 63 64 template maskType(T) 65 { 66 static if (is(T == enum)) 67 alias maskType = string; 68 else static if (is(T == byte) || 69 is(T == ubyte) || 70 is(T == short) || 71 is(T == ushort) || 72 is(T == uint) 73 ) 74 alias maskType = int; 75 else static if (is(T == ulong)) 76 alias maskType = long; 77 else static if(isFloatingPoint!T) 78 alias maskType = FloatingType; 79 else alias maskType = T; 80 } 81 82 bool isSerializableDynamicArray(T)() @property 83 { return isDynamicArray!T && isSerializableArray!T; } 84 85 /// 86 void fillStruct(OT)(ref OT st, Tag[] tags...) 87 { 88 static void getValue(X, OX)(ref OX f, auto ref const Value val) 89 { 90 static if (isFloatingPoint!OX) 91 { 92 if (val.type == typeid(string) && val.get!string.idup.toLower == "nan") 93 f = OX.nan; 94 else if (val.type == typeid(X)) 95 f = val.get!X.to!OX; 96 else if (val.type == typeid(int)) 97 f = val.get!long.to!OX; 98 else if (val.type == typeid(long)) 99 f = val.get!long.to!OX; 100 else { /+ TODO: warning +/ } 101 } 102 else f = val.get!X.to!OX; 103 } 104 105 import std.array; 106 107 alias T = maskType!OT; 108 109 static if (isArray!T) 110 { 111 alias OElem = Unqual!(ElementType!T); 112 alias Elem = maskType!(Unqual!(ElementType!T)); 113 } 114 115 static if (isSerializableDynamicArray!T) st = []; 116 117 if (tags.length == 0) return; 118 119 static if (isUserStruct!T) 120 { 121 if (tags.length > 1) { /+ TODO warning +/ } 122 auto tag = tags[$-1]; // get last tag 123 foreach (i, ref f; st.tupleof) 124 { 125 enum name = __traits(identifier, st.tupleof[i]); 126 static if (name != "this") // const(void)* context ptr 127 { 128 if (name in tag.tags) 129 fillStruct(f, tag.tags[name].array); 130 else { /+ TODO something +/ } 131 } 132 } 133 } 134 else static if (isSerializableArray!T) 135 { 136 static if (isUserStruct!Elem) 137 foreach (i, ct; tags) 138 { 139 if (ct.tags.length) 140 { 141 static if (isStaticArray!T) 142 { 143 if (i >= st.length) /+ TODO warning +/ break; 144 fillStruct(ct, st[i]); 145 } 146 else st ~= buildStruct!Elem(ct); 147 } 148 } 149 else 150 { 151 if (tags.length > 1) { /+ TODO warning +/ } 152 foreach (i, v; tags[$-1].values) // get last tag 153 { 154 static if (isStaticArray!T) 155 { 156 if (i >= st.length) /+ TODO warning +/ break; 157 getValue!(Elem, OElem)(st[i], v); 158 } 159 else 160 { 161 OElem tmp; 162 getValue!(Elem, OElem)(tmp, v); 163 st ~= tmp; 164 } 165 } 166 } 167 } 168 else 169 { 170 if (tags.length > 1) { /+ TODO warning +/ } 171 if (tags[$-1].values.length == 0) return; // no values (TODO warning?) 172 getValue!(T, OT)(st, tags[$-1].values[0]); 173 } 174 } 175 176 unittest 177 { 178 static struct Test 179 { 180 string str = "okda"; 181 } 182 183 enum s1 = `str "hello"`; 184 enum s2 = `abc "hello"`; 185 186 auto t1 = buildStruct!Test(parseSource(s1)); 187 assert(t1.str == "hello"); 188 auto t2 = buildStruct!Test(parseSource(s2)); 189 assert(t2.str == "okda"); 190 } 191 192 /// 193 Tag buildTag(T)(auto ref const T st) 194 { 195 auto tag = new Tag; 196 fillTag(tag, st); 197 return tag; 198 } 199 200 /// 201 void fillTag(T)(Tag parent, auto ref const T st) 202 if (is(T == struct)) 203 { 204 static void addValue(X)(Tag ct, X val) 205 { 206 static if (is(X == enum)) 207 ct.add(Value(val.to!string)); 208 else static if (isFloatingPoint!X) 209 { 210 if (val == val) ct.add(Value(val.to!FloatingType)); 211 else ct.add(Value("nan")); 212 } 213 //static if (isNumeric!X) // WTF? on windows dmd-nightly (2.080) isNumeric!bool is true 214 else static if (isNumeric!X && !is(Unqual!X == bool)) 215 ct.add(Value(val.to!int)); 216 else static if (is(X == const(ubyte)[])) 217 ct.add(Value(cast(ubyte[])val.dup)); 218 else 219 ct.add(Value(cast(Unqual!X)val)); 220 } 221 222 foreach (i, ref f; st.tupleof) 223 { 224 alias FT = Unqual!(typeof(f)); 225 enum name = __traits(identifier, st.tupleof[i]); 226 227 static if (name != "this") // const(void)* context ptr 228 { 229 static if (isUserStruct!FT) 230 fillTag(new Tag(parent, "", name), f); 231 else static if (isSerializableArray!FT) 232 { 233 static if (isUserStruct!(Unqual!(ElementType!FT))) 234 { 235 if (f.length) 236 foreach (v; f) 237 fillTag(new Tag(parent, "", name), v); 238 else 239 new Tag(parent, "", name); 240 } 241 else 242 { 243 auto ct = new Tag(parent, "", name); 244 foreach (v; f) 245 addValue(ct, v); 246 } 247 } 248 else 249 { 250 auto ct = new Tag(parent, "", name); 251 addValue(ct, f); 252 } 253 } 254 } 255 } 256 257 unittest 258 { 259 static struct Test 260 { 261 string str = "okda"; 262 263 static struct Inner 264 { 265 int abc = 12; 266 } 267 268 Inner inner; 269 } 270 271 auto t1 = Test("hello"); 272 273 auto tt1 = buildTag(t1); 274 275 assert(tt1.getTagValue!string("str") == "hello"); 276 assert(tt1.getTag("inner").getTagValue!int("abc") == 12); 277 } 278 279 version (unittest) 280 { 281 struct Foo 282 { 283 bool boolValue; 284 int intValue = 12; 285 double doubleValue = 0.0; 286 string stringValue = "okda"; 287 ubyte[] ubyteArrValue = [0xa, 0xb]; 288 } 289 } 290 291 unittest 292 { 293 auto st = buildStruct!Foo(` 294 boolValue true 295 `.parseSource); 296 297 assert(st.boolValue == true); 298 assert(st.intValue == 12); 299 assert(st.doubleValue == 0.0); 300 assert(st.stringValue == "okda"); 301 assert(st.ubyteArrValue == [0xa, 0xb]); 302 } 303 304 unittest 305 { 306 auto st = buildStruct!Foo(` 307 boolValue2 true 308 `.parseSource); 309 310 assert(st.boolValue == false); 311 assert(st.intValue == 12); 312 assert(st.doubleValue == 0.0); 313 assert(st.stringValue == "okda"); 314 assert(st.ubyteArrValue == [0xa, 0xb]); 315 } 316 317 unittest 318 { 319 import std.base64; 320 auto st = buildStruct!Foo((` 321 ubyteArrValue [`~Base64.encode(cast(ubyte[])[0xab, 0xbc, 0xcd])~`] 322 `).idup.parseSource); 323 324 assert(st.boolValue == false); 325 assert(st.intValue == 12); 326 assert(st.doubleValue == 0.0); 327 assert(st.stringValue == "okda"); 328 assert(st.ubyteArrValue == [0xab, 0xbc, 0xcd]); 329 } 330 331 unittest 332 { 333 import std.base64; 334 auto st = buildStruct!Foo((` 335 ubyteArrValue [`~Base64.encode(cast(ubyte[])[0xab, 0xbc, 0xcd])~`] 336 ubyteArrValue [`~Base64.encode(cast(ubyte[])[0xcd])~`] 337 `).idup.parseSource); 338 339 assert(st.boolValue == false); 340 assert(st.intValue == 12); 341 assert(st.doubleValue == 0.0); 342 assert(st.stringValue == "okda"); 343 assert(st.ubyteArrValue == [0xcd]); 344 } 345 346 version (unittest) 347 { 348 struct Dt 349 { 350 Date date; 351 DateTimeFrac dtf; 352 SysTime systime; 353 DateTimeFracUnknownZone dtfuz; 354 Duration duration; 355 } 356 } 357 358 unittest 359 { 360 auto st = buildStruct!Dt(` 361 date 2018/12/06 362 `.parseSource); 363 assert (st.date == Date(2018, 12, 06)); 364 } 365 366 unittest 367 { 368 auto st = buildStruct!Dt(` 369 dtf 2018/12/06 15:05:12 370 `.parseSource); 371 assert (st.dtf != DateTimeFrac(DateTime(2018, 12, 06, 15, 05, 13))); 372 assert (st.dtf == DateTimeFrac(DateTime(2018, 12, 06, 15, 05, 12))); 373 } 374 375 unittest 376 { 377 auto st = buildStruct!Dt(` 378 duration 2:32:11.123 379 `.parseSource); 380 assert (st.duration != 2.hours + 32.minutes + 12.seconds + 123.msecs); 381 assert (st.duration == 2.hours + 32.minutes + 11.seconds + 123.msecs); 382 } 383 384 unittest 385 { 386 auto st = buildStruct!Dt(` 387 duration 2:32:11.123 388 `.parseSource); 389 assert (st.duration != 2.hours + 32.minutes + 12.seconds + 123.msecs); 390 assert (st.duration == 2.hours + 32.minutes + 11.seconds + 123.msecs); 391 } 392 393 unittest 394 { 395 auto st = buildStruct!Dt(` 396 duration 5d:2:32:11.123 397 `.parseSource); 398 assert (st.duration != 5.days + 2.hours + 32.minutes + 12.seconds + 123.msecs); 399 assert (st.duration == 5.days + 2.hours + 32.minutes + 11.seconds + 123.msecs); 400 401 } 402 403 version (unittest) 404 { 405 enum initialFoo = Foo(true, 42, 2.2, "hhh", [0xc, 0xd]); 406 407 struct Bar 408 { 409 bool[] boolValue = [true, true, false, true]; 410 int[] intValue = [3, 5, 7, 8]; 411 double[] doubleValue = [0.0]; 412 string[] stringValue = ["hello", "world"]; 413 Duration[] durArr = [5.minutes, 2.seconds]; 414 Foo[] foo = [initialFoo]; 415 } 416 } 417 418 unittest 419 { 420 auto st = buildStruct!Bar(` 421 `.parseSource); 422 423 assert(st.boolValue == [true, true, false, true]); 424 assert(st.intValue == [3,5,7,8]); 425 assert(st.doubleValue == [0.0]); 426 assert(st.stringValue == ["hello", "world"]); 427 assert(st.durArr == [5.minutes, 2.seconds]); 428 assert(st.foo == [initialFoo]); 429 } 430 431 unittest 432 { 433 auto st = buildStruct!Bar(` 434 boolValue 435 doubleValue 436 `.parseSource); 437 438 assert(st.boolValue == []); 439 assert(st.intValue == [3,5,7,8]); 440 assert(st.doubleValue == []); 441 assert(st.stringValue == ["hello", "world"]); 442 assert(st.durArr == [5.minutes, 2.seconds]); 443 assert(st.foo == [initialFoo]); 444 } 445 446 unittest 447 { 448 auto ttag = `boolValue off on 449 stringValue "ok" "da" "net" 450 durArr 00:00:00.123 00:00:05 00:01:00 451 `.parseSource; 452 auto st = buildStruct!Bar(ttag); 453 454 assert(st.boolValue == [false, true]); 455 assert(st.intValue == [3,5,7,8]); 456 assert(st.doubleValue == [0.0]); 457 assert(st.stringValue == ["ok", "da", "net"]); 458 assert(st.durArr == [123.msecs, 5.seconds, 1.minutes]); 459 assert(st.foo == [initialFoo]); 460 } 461 462 unittest 463 { 464 auto st = buildStruct!Bar(` 465 foo { 466 boolValue true 467 } 468 foo { 469 boolValue false 470 } 471 `.parseSource); 472 473 assert(st.boolValue == [true, true, false, true]); 474 assert(st.intValue == [3,5,7,8]); 475 assert(st.doubleValue == [0.0]); 476 assert(st.stringValue == ["hello", "world"]); 477 assert(st.durArr == [5.minutes, 2.seconds]); 478 assert(st.foo == [Foo(true), Foo(false)]); 479 } 480 481 unittest 482 { 483 static struct Baz { 484 int[3] crd = [0, 1, 2]; 485 } 486 487 assert(buildStruct!Baz(`crd`.parseSource).crd == [0, 1, 2]); 488 assert(buildStruct!Baz(`crd 5`.parseSource).crd == [5, 1, 2]); 489 assert(buildStruct!Baz(`crd 5 7`.parseSource).crd == [5, 7, 2]); 490 assert(buildStruct!Baz(`crd 5 7 8`.parseSource).crd == [5, 7, 8]); 491 assert(buildStruct!Baz(`crd 5 7 8 10`.parseSource).crd == [5, 7, 8]); 492 } 493 494 unittest 495 { 496 auto tt = Bar( 497 [false, true, false, false, true], 498 [1, 4, 8, 15, 16, 23, 42], 499 [], ["ok", "da"], [1.minutes, 2.seconds, 5.msecs], 500 [Foo(true, 42, 0.0, "hello"), 501 Foo(false, 13, 1.1, "world", [0x1, 0x2, 0xa, 0xff]) 502 ] 503 ); 504 505 assert(tt == buildStruct!Bar(buildTag(tt))); 506 } 507 508 unittest 509 { 510 struct Dt { 511 DateTimeFrac[] dt; 512 } 513 514 auto tt = Dt([DateTimeFrac(DateTime(2010, 02, 03, 12, 18, 0), 5.msecs), 515 DateTimeFrac(DateTime(2018, 04, 04, 10, 15, 30)), 516 ]); 517 518 assert(tt == buildStruct!Dt(`dt 2010/02/03 12:18:00.005 2018/04/04 10:15:30`.parseSource)); 519 assert(tt == buildStruct!Dt(buildTag(tt))); 520 } 521 522 unittest 523 { 524 enum Type 525 { 526 one, 527 two, 528 three, 529 } 530 531 struct TFoo 532 { 533 Type type = Type.two; 534 } 535 536 assert(TFoo.init.buildTag.toSDLDocument.parseSource.buildStruct!TFoo == TFoo.init); 537 } 538 539 unittest 540 { 541 enum Type 542 { 543 one, 544 two, 545 three, 546 } 547 548 struct TFoo 549 { 550 Type[] type = [Type.two, Type.three]; 551 } 552 553 assert(TFoo.init.buildTag.toSDLDocument.parseSource.buildStruct!TFoo == TFoo.init); 554 } 555 556 unittest 557 { 558 struct TFoo 559 { 560 short a=10, b=12; 561 byte x=122, y=100; 562 } 563 564 assert(TFoo.init.buildTag.toSDLDocument.parseSource.buildStruct!TFoo == TFoo.init); 565 } 566 567 unittest 568 { 569 struct TFoo 570 { 571 float x; 572 } 573 574 assert(TFoo.init.x is float.nan); 575 auto v = TFoo.init.buildTag.toSDLDocument.parseSource.buildStruct!TFoo.x; 576 assert(v != v); // nan 577 } 578 579 unittest 580 { 581 struct TFoo { float[] x; } 582 583 assert(TFoo.init.buildTag.toSDLDocument.parseSource.buildStruct!TFoo == TFoo.init); 584 585 auto tt = TFoo([1, 2, float.nan, 3]); 586 auto nt = tt.buildTag.toSDLDocument.parseSource.buildStruct!TFoo; 587 assert(nt.x.length == 4); 588 assert(nt.x[0] == 1); 589 assert(nt.x[1] == 2); 590 assert(nt.x[2] != nt.x[2]); 591 assert(nt.x[3] == 3); 592 } 593 594 unittest 595 { 596 struct TFoo 597 { 598 float a; 599 double b; 600 } 601 602 auto tt = ` 603 a 10 604 b 12`.parseSource.buildStruct!TFoo; 605 606 assert(tt == TFoo(10,12)); 607 } 608 609 unittest 610 { 611 struct TFoo { uint[] x = [1,2,3]; } 612 assert(TFoo.init.buildTag.toSDLDocument.parseSource.buildStruct!TFoo == TFoo.init); 613 } 614 615 unittest 616 { 617 struct TFoo { ulong[] x = [1,2,3]; } 618 assert(TFoo.init.buildTag.toSDLDocument.parseSource.buildStruct!TFoo == TFoo.init); 619 } 620 621 version (unittest) 622 { 623 mixin template FieldsAndAccess(T, E) 624 if (is(E == enum)) 625 { 626 alias Field = T; 627 alias Enum = E; 628 629 import std.traits: EnumMembers; 630 import std.conv : to; 631 import std.algorithm : map; 632 import std.string : join, format; 633 import std.array : array; 634 635 private static pure string buildFields() 636 { 637 return [EnumMembers!E] 638 .map!(a=>format("Field %s;", a.to!string)) 639 .array.join("\n"); 640 } 641 642 mixin(buildFields()); 643 644 static bool isField(string f) pure nothrow @nogc 645 { return is(typeof(f.to!E)); } 646 647 ref inout(T) opIndex(E v) inout 648 { 649 final switch(v) 650 foreach (e; EnumMembers!E) 651 case e: return mixin(e.to!string); 652 } 653 } 654 655 } 656 657 unittest 658 { 659 enum ASubject { foo, bar, baz } 660 enum BSubject { vo, cu } 661 662 static struct Limit { float min, max; } 663 664 static struct ALimits { mixin FieldsAndAccess!(Limit, ASubject); } 665 static struct BLimits { mixin FieldsAndAccess!(Limit, BSubject); } 666 667 static struct DiffRule 668 { 669 enum Type 670 { 671 ignore = "ignore", 672 value = "value", 673 strict = "strict", 674 } 675 Type type; 676 float v = 0.0; 677 678 static DiffRule ignore() @property { return DiffRule(Type.ignore); } 679 static DiffRule value(float v) { return DiffRule(Type.value, v); } 680 static DiffRule strict() @property { return DiffRule(Type.strict); } 681 } 682 683 static struct ADiff 684 { 685 DiffRule a = DiffRule.strict; /// 686 DiffRule b = DiffRule.value(0.01); /// 687 DiffRule c = DiffRule.value(1); /// 688 DiffRule d = DiffRule.value(0.001); /// 689 DiffRule e = DiffRule.strict; /// 690 } 691 692 static struct BDiff 693 { 694 DiffRule a = DiffRule.strict; /// 695 DiffRule b = DiffRule.value(5); /// 696 DiffRule c = DiffRule.value(1); /// 697 DiffRule d = DiffRule.strict; /// 698 DiffRule e = DiffRule.strict; /// 699 DiffRule f = DiffRule.value(1); /// 700 DiffRule g = DiffRule.strict; /// 701 } 702 703 static struct BDescription 704 { 705 ulong id; 706 ushort mid = 1; 707 string name = "строка utf-8"; 708 short hours = 1; 709 size_t sid; 710 float direction = 1; 711 } 712 713 static struct CommonSettings 714 { 715 } 716 717 static struct StorageSettings 718 { 719 string dbname="./db.sqlite"; 720 uint updTime = 5 * 60; 721 } 722 723 static struct MonitorSettings 724 { 725 string port = "/dev/ttyUSB0"; 726 int baudrate = 9600; 727 } 728 729 static struct FrontendSettings 730 { 731 uint count = 100; 732 uint maxCount = 2000; 733 } 734 735 static struct Serial { ushort party, number; } 736 737 static struct ModbusSlaveSettings 738 { 739 string port = "/dev/ttyUSB1"; 740 int baudrate = 9600; 741 string mode = "8N1"; 742 int deviceNo = 4; 743 Serial serial = Serial(1,1); 744 } 745 746 enum PIN_COUNT = 4; 747 748 static struct RelayOutSettings 749 { 750 uint[PIN_COUNT] pins = [17, 23, 26, 27]; 751 string cmd_hi = `raspi-gpio set %s op dh`; 752 string cmd_lo = `raspi-gpio set %s op dl`; 753 } 754 755 static struct AppSettings 756 { 757 CommonSettings common; 758 StorageSettings storage; 759 MonitorSettings monitor; 760 FrontendSettings frontend; 761 ModbusSlaveSettings mbslave; 762 RelayOutSettings relayout; 763 } 764 765 static struct LBT { mixin FieldsAndAccess!(float, BSubject); } 766 767 static struct LSettings 768 { 769 static struct Limits 770 { 771 ALimits al; 772 BLimits bl; 773 } 774 775 static struct DiffRules 776 { 777 ADiff ar; 778 BDiff br; 779 } 780 781 Limits limits; 782 DiffRules diff; 783 LBT lbt; 784 } 785 786 static struct TFoo 787 { 788 LSettings[] settings; 789 BDescription[] description; 790 AppSettings apps; 791 } 792 793 assert(TFoo.init.buildTag.toSDLDocument.parseSource.buildStruct!TFoo == TFoo.init); 794 }