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 }