1 module reggae.range;
2 
3 import reggae.build;
4 import std.range;
5 import std.algorithm;
6 import std.conv;
7 import std.exception;
8 
9 @safe:
10 
11 enum isTargetLike(T) = is(typeof(() {
12     auto target = T.init;
13     auto deps = target.dependencies;
14     static assert(is(Unqual!(typeof(deps[0])) == Unqual!T));
15     auto imps = target.implicits;
16     static assert(is(Unqual!(typeof(imps[0])) == Unqual!T));
17     if(target.isLeaf) {}
18     string cmd = target.expandCommand;
19     cmd = target.expandCommand("");
20 }));
21 
22 
23 static assert(isTargetLike!Target);
24 
25 struct DepthFirst(T) if(isTargetLike!T) {
26     const(T)[] targets;
27 
28     this(in T target) pure {
29         this.targets = depthFirstTargets(target);
30     }
31 
32     const(T)[] depthFirstTargets(in T target) pure {
33         //if leaf, return
34         if(target.isLeaf) return target.expandCommand is null ? [] : [target];
35 
36         //if not, add ourselves to the end to get depth-first
37         return reduce!((a, b) => a ~ depthFirstTargets(b))(typeof(return).init, target.dependencies) ~
38             reduce!((a, b) => a ~ depthFirstTargets(b))(typeof(return).init, target.implicits) ~
39             target;
40     }
41 
42     T front() pure nothrow {
43         return targets.front;
44     }
45 
46     void popFront() pure nothrow {
47         targets.popFront;
48     }
49 
50     bool empty() pure nothrow {
51         return targets.empty;
52     }
53 
54     static assert(isInputRange!DepthFirst);
55 }
56 
57 auto depthFirst(T)(in T target) pure {
58     return DepthFirst!T(target);
59 }
60 
61 struct ByDepthLevel {
62     const(Target)[][] targets;
63 
64     this(in Target target) pure nothrow {
65         this.targets = sortTargets(target);
66     }
67 
68     auto front() pure nothrow {
69         return targets.front;
70     }
71 
72     void popFront() pure nothrow {
73         targets.popFront;
74     }
75 
76     bool empty() pure nothrow {
77         return targets.empty;
78     }
79 
80     private const(Target)[][] sortTargets(in Target target) pure nothrow {
81         if(target.isLeaf) return [];
82 
83         const(Target)[][] targets = [[target]];
84         rec(0, [target], targets);
85         return targets.retro.array;
86     }
87 
88     private void rec(int level, in Target[] targets, ref const(Target)[][] soFar) @trusted pure nothrow {
89         const notLeaves = targets.
90             map!(a => chain(a.dependencies, a.implicits)). //get all dependencies
91             flatten. //flatten into a regular range
92             filter!(a => !a.isLeaf). //don't care about leaves
93             array;
94         if(notLeaves.empty) return;
95 
96         soFar ~= notLeaves;
97         rec(level + 1, notLeaves, soFar);
98     }
99 
100     static assert(isInputRange!ByDepthLevel);
101 }
102 
103 struct Leaves {
104     this(in Target target) pure nothrow {
105         recurse(target);
106     }
107 
108     Target front() pure nothrow {
109         return targets.front;
110     }
111 
112     void popFront() pure nothrow {
113         targets.popFront;
114     }
115 
116     bool empty() pure nothrow {
117         return targets.empty;
118     }
119 
120 
121 private:
122 
123     const(Target)[] targets;
124 
125     void recurse(in Target target) pure nothrow {
126         if(target.isLeaf) {
127             targets ~= target;
128             return;
129         }
130 
131         foreach(dep; target.dependencies ~ target.implicits) {
132             if(dep.isLeaf) {
133                 targets ~= dep;
134             } else {
135                 recurse(dep);
136             }
137         }
138     }
139 
140     static assert(isInputRange!Leaves);
141 }
142 
143 
144 //TODO: a non-allocating version with no arrays
145 auto flatten(R)(R range) @trusted pure {
146     alias rangeType = ElementType!R;
147     alias T = ElementType!rangeType;
148     T[] res;
149     foreach(x; range) res ~= x.array;
150     return res;
151 }
152 
153 //TODO: a non-allocating version with no arrays
154 auto noSortUniq(R)(R range) if(isInputRange!R) {
155     ElementType!R[] ret;
156     foreach(elt; range) {
157         if(!ret.canFind(elt)) ret ~= elt;
158     }
159     return ret;
160 }
161 
162 //removes duplicate targets from the build, presents a depth-first interface
163 //per top-level target
164 struct UniqueDepthFirst {
165     Build build;
166     private const(Target)[] _targets;
167 
168     this(in Build build) pure @trusted {
169         _targets = build.targets.
170             map!(a => depthFirst(a)).
171             flatten.
172             noSortUniq.
173             array;
174     }
175 
176     Target front() pure nothrow {
177         return _targets.front;
178     }
179 
180     void popFront() pure nothrow {
181         _targets.popFront;
182     }
183 
184     bool empty() pure nothrow {
185         return _targets.empty;
186     }
187 
188     static assert(isInputRange!UniqueDepthFirst);
189 }