1 module ggplotd.aes;
2 
3 import std.range : front, popFront, empty;
4 
5 version (unittest)
6 {
7     import dunit.toolkit;
8 }
9 
10 import std.typecons : Tuple;
11 
12 template Aes(Specs...)
13 {
14     import std.traits : Identity;
15     import std.typecons : isTuple;
16     import std.typetuple : staticMap, TypeTuple;
17     import std.range : isInputRange;
18 
19     // Parse (type,name) pairs (FieldSpecs) out of the specified
20     // arguments. Some fields would have name, others not.
21     template parseSpecs(Specs...)
22     {
23         static if (Specs.length == 0)
24         {
25             alias parseSpecs = TypeTuple!();
26         }
27         else static if (is(Specs[0]) && isInputRange!(Specs[0]))
28         {
29             static if (is(typeof(Specs[1]) : string))
30             {
31                 alias parseSpecs = TypeTuple!(FieldSpec!(Specs[0 .. 2]), parseSpecs!(Specs[2 .. $]));
32             }
33             else
34             {
35                 alias parseSpecs = TypeTuple!(FieldSpec!(Specs[0]), parseSpecs!(Specs[1 .. $]));
36             }
37         }
38         else
39         {
40             static assert(0,
41                 "Attempted to instantiate Tuple with an " ~ "invalid argument: " ~ Specs[0].stringof);
42         }
43     }
44 
45     template FieldSpec(T, string s = "")
46     {
47         alias Type = T;
48         alias name = s;
49     }
50 
51     alias fieldSpecs = parseSpecs!Specs;
52 
53     // Used with staticMap.
54     alias extractType(alias spec) = spec.Type;
55     alias extractName(alias spec) = spec.name;
56 
57     // Generates named fields as follows:
58     //    alias name_0 = Identity!(field[0]);
59     //    alias name_1 = Identity!(field[1]);
60     //      :
61     // NOTE: field[k] is an expression (which yields a symbol of a
62     //       variable) and can't be aliased directly.
63     string injectNamedFields()
64     {
65         string decl = "";
66 
67         foreach (i, name; staticMap!(extractName, fieldSpecs))
68         {
69             import std.format : format;
70 
71             decl ~= format("alias _%s = Identity!(field[%s]);", i, i);
72             if (name.length != 0)
73             {
74                 decl ~= format("alias %s = _%s;", name, i);
75             }
76         }
77         return decl;
78     }
79 
80     alias fieldNames = staticMap!(extractName, fieldSpecs);
81 
82     // TODO Also update default grouping if appropiate
83     alias defaultNames = TypeTuple!("label","colour", "size", "angle", "alpha", "mask", "fill");
84     alias defaultTypes = TypeTuple!(string,string, double, double, double, bool, double);
85     alias defaultValues = TypeTuple!(`""`, `"black"`, 10, 0, 1, true, 0.0);
86     string injectFront()
87     {
88         import std.format : format;
89 
90         string decl = "auto front() { import std.range : ElementType;";
91         decl ~= "import std.typecons : Tuple; import std.range : front;";
92 
93         string tupleType = "Tuple!(";
94         string values = "(";
95 
96         foreach (i, name; fieldNames)
97         {
98 
99             tupleType ~= format(q{typeof(%s.front),}, name);
100             tupleType ~= "q{" ~ name ~ "},";
101             values ~= format("this.%s.front,", name);
102         }
103 
104         foreach (i, name; defaultNames)
105         {
106             auto defined = false;
107             foreach (j, fName; fieldNames)
108             {
109                 if (name == fName)
110                     defined = true;
111             }
112             if (!defined)
113             {
114                 //tupleType ~= format( q{typeof(%s),}, defaultValues[i] );
115                 tupleType ~= format(q{%s,}, defaultTypes[i].stringof);
116                 tupleType ~= "q{" ~ name ~ "},";
117                 if (is(defaultTypes[i]==string))
118                     values ~= format("%s,", defaultValues[i]);
119                 else
120                     values ~= format("%s,", defaultValues[i].stringof);
121             }
122         }
123 
124         decl ~= "return " ~ tupleType[0 .. $ - 1] ~ ")" ~ values[0 .. $ - 1] ~ "); }";
125         //string decl2 = format("auto front() { import std.stdio; \"%s\".writeln; return 0.0; }", decl);
126         return decl;
127     }
128 
129     // Returns Specs for a subtuple this[from .. to] preserving field
130     // names if any.
131     alias sliceSpecs(size_t from, size_t to) = staticMap!(expandSpec, fieldSpecs[from .. to]);
132 
133     template expandSpec(alias spec)
134     {
135         static if (spec.name.length == 0)
136         {
137             alias expandSpec = TypeTuple!(spec.Type);
138         }
139         else
140         {
141             alias expandSpec = TypeTuple!(spec.Type, spec.name);
142         }
143     }
144 
145     enum areCompatibleTuples(Tup1, Tup2, string op) = isTuple!Tup2 && is(typeof({
146         Tup1 tup1 = void;
147         Tup2 tup2 = void;
148         static assert(tup1.field.length == tup2.field.length);
149         foreach (i, _;
150         Tup1.Types)
151         {
152             auto lhs = typeof(tup1.field[i]).init;
153             auto rhs = typeof(tup2.field[i]).init;
154             static if (op == "=")
155                 lhs = rhs;
156             else
157                 auto result = mixin("lhs " ~ op ~ " rhs");
158         }
159     }));
160 
161     enum areBuildCompatibleTuples(Tup1, Tup2) = isTuple!Tup2 && is(typeof({
162         static assert(Tup1.Types.length == Tup2.Types.length);
163         foreach (i, _;
164         Tup1.Types)
165         static assert(isBuildable!(Tup1.Types[i], Tup2.Types[i]));
166     }));
167 
168     /+ Returns $(D true) iff a $(D T) can be initialized from a $(D U). +/
169     enum isBuildable(T, U) = is(typeof({ U u = U.init; T t = u; }));
170     /+ Helper for partial instanciation +/
171     template isBuildableFrom(U)
172     {
173         enum isBuildableFrom(T) = isBuildable!(T, U);
174     }
175 
176     ///
177     struct Aes
178     {
179         /**
180          * The type of the tuple's components.
181          */
182         alias Types = staticMap!(extractType, fieldSpecs);
183 
184         /**
185          * The names of the tuple's components. Unnamed fields have empty names.
186          *
187          * Examples:
188          * ----
189          * alias Fields = Tuple!(int, "id", string, float);
190          * static assert(Fields.fieldNames == TypeTuple!("id", "", ""));
191          * ----
192          */
193         alias fieldNames = staticMap!(extractName, fieldSpecs);
194 
195         /**
196          * Use $(D t.expand) for a tuple $(D t) to expand it into its
197          * components. The result of $(D expand) acts as if the tuple components
198          * were listed as a list of values. (Ordinarily, a $(D Tuple) acts as a
199          * single value.)
200          *
201          * Examples:
202          * ----
203          * auto t = tuple(1, " hello ", 2.3);
204          * writeln(t);        // Tuple!(int, string, double)(1, " hello ", 2.3)
205          * writeln(t.expand); // 1 hello 2.3
206          * ----
207          */
208         Types expand;
209         mixin(injectNamedFields());
210 
211         // backwards compatibility
212         alias field = expand;
213 
214         /**
215          * Constructor taking one value for each field.
216          */
217         static if (Types.length > 0)
218         {
219             this(Types values)
220             {
221                 field[] = values[];
222             }
223         }
224 
225         /**
226          * Constructor taking a compatible array.
227          *
228          * Examples:
229          * ----
230          * int[2] ints;
231          * Tuple!(int, int) t = ints;
232          * ----
233          */
234         this(U, size_t n)(U[n] values) if (n == Types.length
235                 && allSatisfy!(isBuildableFrom!U, Types))
236         {
237             foreach (i, _; Types)
238             {
239                 field[i] = values[i];
240             }
241         }
242 
243         /**
244          * Constructor taking a compatible tuple.
245          */
246         this(U)(U another) if (areBuildCompatibleTuples!(typeof(this), U))
247         {
248             field[] = another.field[];
249         }
250 
251         mixin(injectFront());
252 
253         ///
254         void popFront()
255         {
256             import std.range : popFront;
257 
258             foreach (i, _; Types[0 .. $])
259             {
260                 field[i].popFront();
261             }
262         }
263 
264         ///
265         @property bool empty()
266         {
267             if (length == 0)
268                 return true;
269             return false;
270         }
271 
272         ///
273         size_t length()
274         {
275             import std.algorithm : min;
276             import std.range : walkLength, isInfinite;
277 
278             size_t l = size_t.max;
279             foreach (i, type; Types[0 .. $])
280             {
281                 static if (!isInfinite!type)
282                 {
283                     if (field[i].walkLength < l)
284                         l = field[i].walkLength;
285                 }
286             }
287             return l;
288         }
289 
290         /**
291          * Comparison for equality.
292          */
293         bool opEquals(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "=="))
294         {
295             return field[] == rhs.field[];
296         }
297 
298         /// ditto
299         bool opEquals(R)(R rhs) const if (areCompatibleTuples!(typeof(this), R, "=="))
300         {
301             return field[] == rhs.field[];
302         }
303 
304         /**
305          * Comparison for ordering.
306          */
307         int opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<"))
308         {
309             foreach (i, Unused; Types)
310             {
311                 if (field[i] != rhs.field[i])
312                 {
313                     return field[i] < rhs.field[i] ? -1 : 1;
314                 }
315             }
316             return 0;
317         }
318 
319         /// ditto
320         int opCmp(R)(R rhs) const if (areCompatibleTuples!(typeof(this), R, "<"))
321         {
322             foreach (i, Unused; Types)
323             {
324                 if (field[i] != rhs.field[i])
325                 {
326                     return field[i] < rhs.field[i] ? -1 : 1;
327                 }
328             }
329             return 0;
330         }
331 
332         /**
333          * Assignment from another tuple. Each element of the source must be
334          * implicitly assignable to the respective element of the target.
335          */
336         void opAssign(R)(auto ref R rhs) if (areCompatibleTuples!(typeof(this), R,
337                 "="))
338         {
339             import std.algorithm : swap;
340 
341             static if (is(R : Tuple!Types) && !__traits(isRef, rhs))
342             {
343                 if (__ctfe)
344                 {
345                     // Cannot use swap at compile time
346                     field[] = rhs.field[];
347                 }
348                 else
349                 {
350                     // Use swap-and-destroy to optimize rvalue assignment
351                     swap!(Tuple!Types)(this, rhs);
352                 }
353             }
354             else
355             {
356                 // Do not swap; opAssign should be called on the fields.
357                 field[] = rhs.field[];
358             }
359         }
360 
361         /**
362          * Takes a slice of the tuple.
363          *
364          * Examples:
365          * ----
366          * Tuple!(int, string, float, double) a;
367          * a[1] = "abc";
368          * a[2] = 4.5;
369          * auto s = a.slice!(1, 3);
370          * static assert(is(typeof(s) == Tuple!(string, float)));
371          * assert(s[0] == "abc" && s[1] == 4.5);
372          * ----
373          */
374         @property ref Tuple!(sliceSpecs!(from, to)) slice(size_t from, size_t to)() @trusted if (
375                 from <= to && to <= Types.length)
376         {
377             return *cast(typeof(return)*)&(field[from]);
378         }
379 
380         ///
381         size_t toHash() const nothrow @trusted
382         {
383             size_t h = 0;
384             foreach (i, T; Types)
385                 h += typeid(T).getHash(cast(const void*)&field[i]);
386             return h;
387         }
388 
389         /**
390          * Converts to string.
391          */
392         void toString(DG)(scope DG sink)
393         {
394             enum header = typeof(this).stringof ~ "(", footer = ")", separator = ", ";
395             sink(header);
396             foreach (i, Type; Types)
397             {
398                 static if (i > 0)
399                 {
400                     sink(separator);
401                 }
402                 // TODO: Change this once toString() works for shared objects.
403                 static if (is(Type == class) && is(typeof(Type.init) == shared))
404                 {
405                     sink(Type.stringof);
406                 }
407                 else
408                 {
409                     import std.format : FormatSpec, formatElement;
410 
411                     FormatSpec!char f;
412                     formatElement(sink, field[i], f);
413                 }
414             }
415             sink(footer);
416         }
417 
418         ///
419         string toString()()
420         {
421             import std.conv : to;
422 
423             return this.to!string;
424         }
425     }
426 }
427 
428 unittest
429 {
430     auto tup = Aes!(double[], "x", double[], "y", string[], "colour")([0, 1],
431         [2, 1], ["white", "white2"]);
432     auto tup2 = Aes!(double[], "x", double[], "y")([0, 1], [2, 1]);
433     assertEqual(tup.colour, ["white", "white2"]);
434     assertEqual(tup.length, 2);
435     assertEqual(tup2.length, 2);
436 
437     tup2.x ~= 0.0;
438     tup2.x ~= 0.0;
439     assertEqual(tup2.length, 2);
440     tup2.y ~= 0.0;
441     assertEqual(tup2.length, 3);
442 }
443 
444 unittest
445 {
446     auto tup = Aes!(double[], "x", double[], "y", string[], "colour")([0, 1],
447         [2, 1], ["white", "white2"]);
448 
449     tup.popFront;
450     assertEqual(tup.front.y, 1);
451     assertEqual(tup.front.colour, "white2");
452     assertEqual(tup.front.label, "");
453 
454     auto tup2 = Aes!(double[], "x", double[], "y")([0, 1], [2, 1]);
455     assertEqual(tup2.front.y, 2);
456     assertEqual(tup2.front.colour, "black");
457 
458     import std.range : repeat;
459 
460     auto xs = repeat(0);
461     auto tup3 = Aes!(typeof(xs), "x", double[], "y")(xs, [2, 1]);
462 
463     assertEqual(tup3.front.x, 0);
464     tup3.popFront;
465     tup3.popFront;
466     assertEqual(tup3.empty, true);
467 
468 }
469 
470 ///
471 template group(Specs...)
472 {
473     string buildExtractKey()
474     {
475         static if (Specs.length == 0)
476         {
477             import std.typecons : TypeTuple;
478             alias Specs = TypeTuple!("alpha","colour","label");
479         }
480         string types = "";
481         string values = "";
482         foreach( spec; Specs )
483         {
484             types ~= "typeof(a." ~ spec ~"),";
485             values ~= "a." ~ spec ~",";
486         }
487         string str = "auto extractKey(T)(T a) 
488             { return Tuple!(" ~ types[0..$-1] ~ ")(" ~ values[0..$-1] ~ "); }";
489         return str;
490     }
491     
492     auto group(AES)(AES aes)
493     {
494         import std.stdio;
495         mixin(buildExtractKey());
496         extractKey( aes.front );
497 
498         typeof(aes.front())[][typeof(extractKey(aes.front))] grouped;
499 
500         // Extract keys for aa and store in aa. Return the values of the aa
501         foreach( tup; aes )
502         {
503             auto key = extractKey(tup);
504             if (key in grouped)
505                 grouped[key] ~= tup;
506             else
507                 grouped[key] = [tup];
508         }
509         return grouped.values;
510     }
511 }
512 
513 unittest
514 {
515     import std.range : walkLength;
516     auto aes = Aes!(double[], "x", string[], "colour", double[], "alpha")
517         ([0,1,2,3], ["a","a","b","b"], [0,1,0,1]);
518 
519     assertEqual(group!("colour","alpha")(aes).walkLength,4);
520     assertEqual(group!("alpha")(aes).walkLength,2);
521 
522     assertEqual(group(aes).walkLength,4);
523 }
524 
525 unittest
526 {
527     auto aes = Aes!(double[], "x", double[], "y", string[], "colour")([1.0,
528         2.0, 1.1], [3.0, 1.5, 1.1], ["a", "b", "a"]);
529 
530     import std.range : walkLength, front, popFront;
531 
532     auto grouped = aes.group;
533     assertEqual(grouped.walkLength, 2);
534     assertEqual(grouped.front.walkLength, 2);
535     grouped.popFront;
536     assertEqual(grouped.front.walkLength, 1);
537 }
538 
539 ///
540 import std.range : isInputRange;
541 
542 ///
543 struct NumericLabel(T) if (isInputRange!T)
544 {
545     import std.range : ElementType;
546     import std.traits : isNumeric;
547 
548     alias E = ElementType!T;
549 
550     ///
551     this(T range)
552     {
553         original = range;
554     }
555 
556     ///
557     @property auto front()
558     {
559         import std.typecons : Tuple;
560         import std.range : front;
561         import std.conv : to;
562 
563         static if (isNumeric!E)
564             return Tuple!(double, string)(original.front.to!double, original.front.to!string);
565         else
566         {
567             if (original.front !in fromLabelMap)
568             {
569                 fromLabelMap[original.front] = fromLabelMap.length.to!double;
570                 //toLabelMap[fromLabelMap[original.front]] 
571                 //    = original.front;
572             }
573             return Tuple!(double, string)(fromLabelMap[original.front], original.front.to!string,
574                 );
575         }
576     }
577 
578     ///
579     void popFront()
580     {
581         import std.range : popFront;
582 
583         original.popFront;
584     }
585 
586     ///
587     @property bool empty()
588     {
589         import std.range : empty;
590 
591         return original.empty;
592     }
593 
594     ///
595     @property bool numeric()
596     {
597         static if (isNumeric!E)
598             return true;
599         else
600             return false;
601     }
602 
603 private:
604     T original;
605     //E[double] toLabelMap;
606     double[E] fromLabelMap;
607 }
608 
609 unittest
610 {
611     import std.stdio : writeln;
612     import std.array : array;
613     import std.algorithm : map;
614 
615     auto num = NumericLabel!(double[])([0.0, 0.1, 1.0, 0.0]);
616     assertEqual(num.map!((a) => a[0]).array, [0.0, 0.1, 1.0, 0.0]);
617     assertEqual(num.map!((a) => a[1]).array, ["0", "0.1", "1", "0"]);
618     auto strs = NumericLabel!(string[])(["a", "c", "b", "a"]);
619     assertEqual(strs.map!((a) => a[0]).array, [0, 1, 2.0, 0.0]);
620     assertEqual(strs.map!((a) => a[1]).array, ["a", "c", "b", "a"]);
621 }
622 
623 /++
624 Merge two Aes structs
625 
626 If it has similar named types, then it uses the second one.
627 
628 Returns a new struct, with combined types.
629 +/
630 template merge(T, U)
631 {
632     auto generateCode()
633     {
634         import std.string : split;
635         string typing = T.stringof.split("!")[0] ~ "!(";
636         string variables = "(";
637         foreach (i, t; U.fieldNames)
638         {
639             typing ~= U.Types[i].stringof ~ ",\"" ~ t ~ "\",";
640             variables ~= "other." ~ t ~ ",";
641         }
642 
643         foreach (i, t; T.fieldNames)
644         {
645             bool contains = false;
646             foreach (_, t2; U.fieldNames)
647             {
648                 if (t == t2)
649                     contains = true;
650             }
651             if (!contains)
652             {
653                 typing ~= T.Types[i].stringof ~ ",\"" ~ t ~ "\",";
654                 variables ~= "base." ~ t ~ ",";
655             }
656         }
657         return "return " ~ typing[0 .. $ - 1] ~ ")" ~ variables[0 .. $ - 1] ~ ");";
658     }
659 
660     auto merge(T base, U other)
661     {
662         mixin(generateCode());
663     }
664 }
665 
666 ///
667 unittest
668 {
669     import std.range : front;
670 
671     auto xs = ["a", "b"];
672     auto ys = ["c", "d"];
673     auto labels = ["e", "f"];
674     auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels);
675 
676     auto nlAes = merge(aes, Aes!(NumericLabel!(string[]), "x",
677         NumericLabel!(string[]), "y")(NumericLabel!(string[])(aes.x),
678         NumericLabel!(string[])(aes.y)));
679 
680     assertEqual(nlAes.x.front[0], 0);
681     assertEqual(nlAes.label.front, "e");
682 }