1 module dud.resolve.toolchain;
2 
3 import std.array : array, front;
4 import std.algorithm.iteration : filter, map;
5 import std.algorithm.searching : all, find;
6 import std.typecons : nullable, Nullable;
7 import std.exception : enforce;
8 import std.format : format;
9 
10 import dud.pkgdescription : Toolchain;
11 import dud.resolve.versionconfigurationtoolchain;
12 import dud.semver.setoperation;
13 import dud.semver.versionunion;
14 import dud.semver.versionrange : SetRelation;
15 
16 @safe pure:
17 
18 struct ToolchainVersionUnion {
19 	Toolchain tool;
20 	bool no;
21 	VersionUnion version_;
22 }
23 
24 ToolchainVersionUnion dup(const(ToolchainVersionUnion) old) {
25 	return ToolchainVersionUnion(old.tool, old.no
26 			, old.version_.dup());
27 }
28 
29 ToolchainVersionUnion invert(const(ToolchainVersionUnion) old) {
30 	return ToolchainVersionUnion(cast()old.tool, !old.no
31 			, dud.semver.setoperation.invert(old.version_));
32 }
33 
34 private void testToolchainEqual(const(ToolchainVersionUnion) a
35 		, const(ToolchainVersionUnion) b, string file = __FILE__
36 		, int line = __LINE__)
37 {
38 	import std.exception : enforce;
39 
40 	enforce(a.tool == b.tool, format("Both tools must be the same, but got "
41 				~ "a.tool %s, b.tool %s", a.tool, b.tool), file, line);
42 }
43 
44 /** Tests if all elements in bs are allowed by an element in `as`.
45 * Allowed also means that no entry is present in `as`
46 */
47 bool allowsAny(const(ToolchainVersionUnion)[] as
48 		, const(ToolchainVersionUnion)[] bs)
49 {
50 	return bs.map!(b => {
51 				auto other = as.find!(it => it.tool == b.tool);
52 				return other.empty
53 					? true
54 					: allowsAny(other.front, b);
55 			}())
56 			.all;
57 }
58 
59 bool allowsAny(const(ToolchainVersionUnion) a, const(ToolchainVersionUnion) b) {
60 	testToolchainEqual(a, b);
61 
62 	return a.no
63 		? false
64 		: dud.resolve.versionconfigurationtoolchain.allowsAny(a.version_
65 				, b.version_);
66 }
67 
68 /** Tests if all elements in bs are allowed by an element in `as`.
69 * Allowed also means that no entry is present in `as`
70 */
71 bool allowsAll(const(ToolchainVersionUnion)[] as
72 		, const(ToolchainVersionUnion)[] bs)
73 {
74 	return bs.map!(b => {
75 				auto other = as.find!(it => it.tool == b.tool);
76 				return other.empty
77 					? true
78 					: allowsAll(other.front, b);
79 			}())
80 			.all;
81 }
82 
83 bool allowsAll(const(ToolchainVersionUnion) a, const(ToolchainVersionUnion) b) {
84 	testToolchainEqual(a, b);
85 
86 	return a.no
87 		? false
88 		: dud.resolve.versionconfigurationtoolchain.allowsAll(a.version_
89 				, b.version_);
90 }
91 
92 ToolchainVersionUnion intersectionOf(const(ToolchainVersionUnion) a,
93 		const(ToolchainVersionUnion) b)
94 {
95 	testToolchainEqual(a, b);
96 
97 	return a.no || b.no
98 		? ToolchainVersionUnion(a.tool, true)
99 		: ToolchainVersionUnion(a.tool, false
100 				, dud.semver.setoperation.intersectionOf(a.version_
101 					, b.version_));
102 }
103 
104 ToolchainVersionUnion[] intersectionOf(const(ToolchainVersionUnion)[] as
105 		, const(ToolchainVersionUnion)[] bs)
106 {
107 	return as.map!(a => {
108 				auto other = bs.find!(it => it.tool == a.tool);
109 				return other.empty
110 					? Nullable!(ToolchainVersionUnion).init
111 					: nullable(intersectionOf(a, other.front));
112 			}())
113 			.filter!(it => !it.isNull())
114 			.map!(it => it.get())
115 			.array;
116 }
117 
118 /** Return if `a` is a subset of `b`, or if `a` and `b` are disjoint, or
119 if `a` and `b` overlap
120 */
121 SetRelation relation(const(ToolchainVersionUnion) a
122 		, const(ToolchainVersionUnion) b)
123 {
124 	testToolchainEqual(a, b);
125 
126 	if(b.no) {
127 		return SetRelation.disjoint;
128 	}
129 
130 	return dud.resolve.versionconfigurationtoolchain.allowsAll(b.version_
131 			, a.version_)
132 		? SetRelation.subset
133 		: dud.resolve.versionconfigurationtoolchain.allowsAny(b.version_
134 				, a.version_)
135 			? SetRelation.overlapping
136 			: SetRelation.disjoint;
137 }
138 
139 SetRelation relation(const(ToolchainVersionUnion)[] as
140 		, const(ToolchainVersionUnion)[] bs)
141 {
142 	import std.algorithm.iteration : reduce;
143 	import std.algorithm.comparison : min;
144 
145 	return reduce!((a, b) => min(a, b))(SetRelation.overlapping,
146 		as.map!(a => {
147 			auto b = bs.find!(it => it.tool == a.tool);
148 			return b.empty
149 				? SetRelation.subset
150 				: relation(a, b.front);
151 		}()));
152 }