1 module dud.semver.versionrange;
2 
3 import std.array : empty, split, join;
4 import std.conv : to;
5 import std.exception : enforce;
6 import std.format : format;
7 import std.stdio;
8 import std..string : indexOfAny;
9 import std.typecons : nullable, Nullable;
10 
11 import dud.semver.semver;
12 import dud.semver.parse;
13 import dud.semver.comparision;
14 
15 @safe pure:
16 
17 enum Inclusive : bool {
18 	no = false,
19 	yes = true
20 }
21 
22 struct VersionRange {
23 @safe:
24 	string branch;
25 	Inclusive inclusiveLow;
26 	Inclusive inclusiveHigh;
27 	SemVer low;
28 	SemVer high;
29 
30 	this(string branch) nothrow @nogc pure {
31 		this.branch = branch;
32 	}
33 
34 	this(const(SemVer) low, const(Inclusive) incLow, const(SemVer) high,
35 			const(Inclusive) incHigh) pure
36 	{
37 		enforce(low <= high, format("low %s must be lower equal to high %s",
38 			low, high));
39 		//enforce(low < high || (incLow == incHigh && incLow == Inclusive.yes),
40 		//	format("tried to construct an empty range with incLow '%s', "
41 		//		~ "low '%s', incHigh '%s', high '%s'", incLow, low, incHigh,
42 		//		high));
43 		this.low = low.dup();
44 		this.inclusiveLow = incLow;
45 		this.high = high.dup();
46 		this.inclusiveHigh = incHigh;
47 	}
48 
49 	bool isBranch() const pure @safe nothrow @nogc {
50 		return !this.branch.empty;
51 	}
52 
53 	bool opEquals(const VersionRange o) const pure @safe {
54 		return this.isBranch() != o.isBranch()
55 				? false
56 				: this.isBranch()
57 					? this.branch == o.branch
58 					: o.inclusiveLow == this.inclusiveLow
59 						&& o.inclusiveHigh == this.inclusiveHigh
60 						&& o.low == this.low
61 						&& o.high == this.high
62 						&& o.branch == this.branch;
63 	}
64 
65 	/// ditto
66 	int opCmp(const VersionRange o) const pure @safe {
67 		import std.algorithm.comparison : cmp;
68 		if(this.isBranch() && o.isBranch()) {
69 			return cmp(this.branch, o.branch);
70 		} else if(this.isBranch() && !o.isBranch()) {
71 			return -1;
72 		} else if(!this.isBranch() && o.isBranch()) {
73 			return 1;
74 		}
75 
76 		const int lowCmp = compare(this.low, o.low);
77 		if(lowCmp == -1 || lowCmp == 1) {
78 			return lowCmp;
79 		} else {
80 			return this.inclusiveLow && o.inclusiveLow
81 				? 0
82 				: this.inclusiveLow && !o.inclusiveLow
83 					? 1
84 					: -1;
85 		}
86 	}
87 
88 	/// ditto
89 	size_t toHash() const nothrow @trusted @nogc pure {
90 		size_t hash = 0;
91 		hash = this.inclusiveLow.hashOf(hash);
92 		hash = this.low.hashOf(hash);
93 		hash = this.inclusiveHigh.hashOf(hash);
94 		hash = this.high.hashOf(hash);
95 		hash = this.branch.hashOf(hash);
96 		return hash;
97 	}
98 
99 	@property VersionRange dup() const pure {
100 		return this.isBranch
101 			? VersionRange(this.branch)
102 			: VersionRange(this.low.dup(), this.inclusiveLow, this.high.dup(),
103 				this.inclusiveHigh);
104 	}
105 
106 	string toString() const @safe pure {
107 		import std.array : appender;
108 		import std.format : format;
109 		if(!this.branch.empty) {
110 			return format("%s", this.branch);
111 		}
112 
113 		string ret = format(">%s%s", this.inclusiveLow ? "=" : "", this.low);
114 		if(this.high != SemVer.max()) {
115 			ret ~= format(" <%s%s", this.inclusiveHigh ? "=" : "", this.high);
116 		}
117 		return ret;
118 	}
119 }
120 
121 unittest {
122 	Nullable!VersionRange snn = parseVersionRange("1.0.0");
123 	assert(!snn.isNull);
124 	VersionRange s = snn.get();
125 	assert(s == s);
126 	assert(s.toHash() != 0);
127 
128 	snn = parseVersionRange("~master");
129 	assert(!snn.isNull);
130 	s = snn.get();
131 	assert(s == s);
132 	assert(s.toHash() != 0);
133 }
134 
135 /** Sets/gets the matching version range as a specification string.
136 
137 	The acceptable forms for this string are as follows:
138 
139 	$(UL
140 		$(LI `"1.0.0"` - a single version in SemVer format)
141 		$(LI `"==1.0.0"` - alternative single version notation)
142 		$(LI `">1.0.0"` - version range with a single bound)
143 		$(LI `">1.0.0 <2.0.0"` - version range with two bounds)
144 		$(LI `"~>1.0.0"` - a fuzzy version range)
145 		$(LI `"~>1.0"` - a fuzzy version range with partial version)
146 		$(LI `"^1.0.0"` - semver compatible version range (same version if 0.x.y, ==major >=minor.patch if x.y.z))
147 		$(LI `"^1.0"` - same as ^1.0.0)
148 		$(LI `"~master"` - a branch name)
149 		$(LI `"*" - match any version (see also `any`))
150 	)
151 
152 	Apart from "$(LT)" and "$(GT)", "$(GT)=" and "$(LT)=" are also valid
153 	comparators.
154 */
155 Nullable!VersionRange parseVersionRange(string ves) pure {
156 	import std..string;
157 	import std.algorithm.searching : startsWith;
158 	import std.format : format;
159 
160 	enforce(ves.length > 0, "Can not process empty version specifier");
161 	const string orig = ves;
162 
163 	VersionRange ret;
164 
165 	if(orig.empty) {
166 		return Nullable!(VersionRange).init;
167 	}
168 
169 	ves = ves == "*"
170 		// Any version is good.
171 		? ves = ">=0.0.0"
172 		: ves;
173 
174 	if (ves.startsWith("~>")) {
175 		// Shortcut: "~>x.y.z" variant. Last non-zero number will indicate
176 		// the base for this so something like this: ">=x.y.z <x.(y+1).z"
177 		ret.inclusiveLow = Inclusive.yes;
178 		ret.inclusiveHigh = Inclusive.no;
179 		ves = ves[2..$];
180 		ret.low = parseSemVer(expandVersion(ves));
181 		ret.high = parseSemVer(bumpVersion(ves) ~ "-0");
182 	} else if (ves.startsWith("^")) {
183 		// Shortcut: "^x.y.z" variant. "Semver compatible" - no breaking changes.
184 		// if 0.x.y, ==0.x.y
185 		// if x.y.z, >=x.y.z <(x+1).0.0-0
186 		// ^x.y is equivalent to ^x.y.0.
187 		ret.inclusiveLow = Inclusive.yes;
188 		ret.inclusiveHigh = Inclusive.no;
189 		ves = ves[1..$].expandVersion;
190 		ret.low = parseSemVer(ves);
191 		ret.high = parseSemVer(bumpIncompatibleVersion(ves) ~ "-0");
192 	} else if (ves[0] == '~') {
193 		ret.inclusiveLow = Inclusive.yes;
194 		ret.inclusiveHigh = Inclusive.yes;
195 		ret.branch = ves;
196 	} else if (indexOf("><=", ves[0]) == -1) {
197 		ret.inclusiveLow = Inclusive.yes;
198 		ret.inclusiveHigh = Inclusive.yes;
199 		ret.low = ret.high = parseSemVer(ves);
200 	} else {
201 		auto cmpa = skipComp(ves);
202 		size_t idx2 = indexOf(ves, " ");
203 		if (idx2 == -1) {
204 			if (cmpa == "<=" || cmpa == "<") {
205 				ret.low = SemVer.MinRelease.dup;
206 				ret.inclusiveLow = Inclusive.yes;
207 				ret.high = parseSemVer(ves);
208 				ret.inclusiveHigh = cast(Inclusive)(cmpa == "<=");
209 			} else if (cmpa == ">=" || cmpa == ">") {
210 				ret.low = parseSemVer(ves);
211 				ret.inclusiveLow = cast(Inclusive)(cmpa == ">=");
212 				ret.high = SemVer.MaxRelease.dup;
213 				ret.inclusiveHigh = Inclusive.yes;
214 			} else {
215 				// Converts "==" to ">=a&&<=a", which makes merging easier
216 				ret.low = ret.high = parseSemVer(ves);
217 				ret.inclusiveLow = ret.inclusiveHigh = Inclusive.yes;
218 			}
219 		} else {
220 			enforce(cmpa == ">" || cmpa == ">=",
221 				"First comparison operator expected to be either > or >=, not "
222 				~ cmpa);
223 			assert(ves[idx2] == ' ');
224 			ret.low = parseSemVer(ves[0..idx2]);
225 			ret.inclusiveLow = cast(Inclusive)(cmpa == ">=");
226 			string v2 = ves[idx2+1..$];
227 			auto cmpb = skipComp(v2);
228 			enforce(cmpb == "<" || cmpb == "<=",
229 				"Second comparison operator expected to be either < or <=, not "
230 				~ cmpb);
231 			ret.high = parseSemVer(v2);
232 			ret.inclusiveHigh = cast(Inclusive)(cmpb == "<=");
233 
234 			enforce(ret.low <= ret.high,
235 				"First version must not be greater than the second one.");
236 		}
237 	}
238 
239 	return nullable(ret);
240 }
241 
242 private string expandVersion(string ver) pure {
243 	auto mi = ver.indexOfAny("+-");
244 	auto sub = "";
245 	if (mi > 0) {
246 		sub = ver[mi..$];
247 		ver = ver[0..mi];
248 	}
249 	//auto splitted = () @trusted { return split(ver, "."); } (); // DMD 2.065.0
250 	auto splitted = split(ver, ".");
251 	assert(splitted.length > 0 && splitted.length <= 3, "Version corrupt: " ~ ver);
252 	while (splitted.length < 3) splitted ~= "0";
253 	return splitted.join(".") ~ sub;
254 }
255 
256 unittest {
257 	assert("1.0.0" == expandVersion("1"));
258 	assert("1.0.0" == expandVersion("1.0"));
259 	assert("1.0.0" == expandVersion("1.0.0"));
260 	// These are rather excotic variants...
261 	assert("1.0.0-pre.release" == expandVersion("1-pre.release"));
262 	assert("1.0.0+meta" == expandVersion("1+meta"));
263 	assert("1.0.0-pre.release+meta" == expandVersion("1-pre.release+meta"));
264 }
265 
266 /**
267 	Increments a given (partial) version number to the next higher version.
268 
269 	Prerelease and build metadata information is ignored. The given version
270 	can skip the minor and patch digits. If no digits are skipped, the next
271 	minor version will be selected. If the patch or minor versions are skipped,
272 	the next major version will be selected.
273 
274 	This function corresponds to the semantivs of the "~>" comparison operator's
275 	upper bound.
276 
277 	The semantics of this are the same as for the "approximate" version
278 	specifier from rubygems.
279 	(https://github.com/rubygems/rubygems/tree/81d806d818baeb5dcb6398ca631d772a003d078e/lib/rubygems/version.rb)
280 
281 	See_Also: `expandVersion`
282 */
283 private string bumpVersion(string ver) {
284 	// Cut off metadata and prerelease information.
285 	auto mi = ver.indexOfAny("+-");
286 	if (mi > 0) ver = ver[0..mi];
287 	// Increment next to last version from a[.b[.c]].
288 	auto splitted =  split(ver, ".");
289 	assert(splitted.length > 0 && splitted.length <= 3, "Version corrupt: " ~ ver);
290 	auto to_inc = splitted.length == 3 ? 1 : 0;
291 	splitted = splitted[0 .. to_inc+1];
292 	splitted[to_inc] = to!string(to!int(splitted[to_inc]) + 1);
293 	// Fill up to three compontents to make valid SemVer version.
294 	while (splitted.length < 3) splitted ~= "0";
295 	return splitted.join(".");
296 }
297 
298 ///
299 unittest {
300 	assert("1.0.0" == bumpVersion("0"));
301 	assert("1.0.0" == bumpVersion("0.0"));
302 	assert("0.1.0" == bumpVersion("0.0.0"));
303 	assert("1.3.0" == bumpVersion("1.2.3"));
304 	assert("1.3.0" == bumpVersion("1.2.3+metadata"));
305 	assert("1.3.0" == bumpVersion("1.2.3-pre.release"));
306 	assert("1.3.0" == bumpVersion("1.2.3-pre.release+metadata"));
307 }
308 
309 /**
310 	Increments a given version number to the next incompatible version.
311 
312 	Prerelease and build metadata information is removed.
313 
314 	This implements the "^" comparison operator, which represents "nonbreaking semver compatibility."
315 	With 0.x.y releases, any release can break.
316 	With x.y.z releases, only major releases can break.
317 */
318 string bumpIncompatibleVersion(string ver) {
319 	// Cut off metadata and prerelease information.
320 	auto mi = ver.indexOfAny("+-");
321 	if (mi > 0) ver = ver[0..mi];
322 	// Increment next to last version from a[.b[.c]].
323 	auto splitted = split(ver, ".");
324 	assert(splitted.length == 3, "Version corrupt: " ~ ver);
325 	if(splitted[0] == "0") {
326 		splitted[2] = to!string(to!int(splitted[2]) + 1);
327 	} else {
328 		splitted = [ to!string(to!int(splitted[0]) + 1), "0", "0" ];
329 	}
330 	return splitted.join(".");
331 }
332 ///
333 unittest {
334 	assert(bumpIncompatibleVersion("0.0.0") == "0.0.1");
335 	assert(bumpIncompatibleVersion("0.1.2") == "0.1.3");
336 	assert(bumpIncompatibleVersion("1.0.0") == "2.0.0");
337 	assert(bumpIncompatibleVersion("1.2.3") == "2.0.0");
338 	assert(bumpIncompatibleVersion("1.2.3+metadata") == "2.0.0");
339 	assert(bumpIncompatibleVersion("1.2.3-pre.release") == "2.0.0");
340 	assert(bumpIncompatibleVersion("1.2.3-pre.release+metadata") == "2.0.0");
341 }
342 
343 private string skipComp(ref string c) pure {
344 	import std.ascii : isDigit;
345 	size_t idx = 0;
346 	while(idx < c.length && !isDigit(c[idx]) && c[idx] != '~') {
347 		idx++;
348 	}
349 	enforce(idx < c.length, "Expected version number in version spec: "~c);
350 	string cmp = idx == c.length - 1 || idx == 0 ? ">=" : c[0..idx];
351 	c = c[idx..$];
352 
353 	switch(cmp) {
354 		default:
355 			enforce(false, "No/Unknown comparison specified: '"~cmp~"'");
356 			return ">=";
357 		case ">=": goto case; case ">": goto case;
358 		case "<=": goto case; case "<": goto case;
359 		case "==": return cmp;
360 	}
361 }
362 
363 pure unittest {
364 	string tt = ">=1.0.0";
365 	auto v = parseVersionRange(tt);
366 }
367 
368 bool isInRange(const(VersionRange) range, const(SemVer) v) pure {
369 	import dud.semver.comparision : compare;
370 
371 	const int low = compare(v, range.low);
372 	const int high = compare(range.high, v);
373 
374 	if(low < 0 || (low == 0 && !range.inclusiveLow)) {
375 		return false;
376 	}
377 
378 	if(high < 0 || (high == 0 && !range.inclusiveHigh)) {
379 		return false;
380 	}
381 
382 	return true;
383 }
384 
385 pure unittest {
386 	Nullable!VersionRange r1N = parseVersionRange("^1.0.0");
387 	assert(!r1N.isNull());
388 	VersionRange r1 = r1N.get();
389 	SemVer v1 = parseSemVer("1.0.0");
390 	SemVer v2 = parseSemVer("2.0.0");
391 	SemVer v3 = parseSemVer("2.0.1");
392 	SemVer v4 = parseSemVer("0.999.999");
393 	SemVer v5 = parseSemVer("1.999.999");
394 	SemVer v6 = parseSemVer("89.0.1");
395 
396 	assert( isInRange(r1, v1));
397 	assert(!isInRange(r1, v2));
398 	assert(!isInRange(r1, v3));
399 	assert(!isInRange(r1, v4));
400 	assert( isInRange(r1, v5));
401 	assert(!isInRange(r1, v6));
402 }
403 
404 pure unittest {
405 	Nullable!VersionRange r1N = parseVersionRange("*");
406 	assert(!r1N.isNull());
407 	VersionRange r1 = r1N.get();
408 	SemVer v1 = parseSemVer("1.0.0");
409 	SemVer v2 = parseSemVer("2.0.0");
410 	SemVer v3 = parseSemVer("2.0.1");
411 	SemVer v4 = parseSemVer("0.999.999");
412 	SemVer v5 = parseSemVer("1.999.999");
413 	SemVer v6 = parseSemVer("89.0.1");
414 
415 	assert( isInRange(r1, v1));
416 	assert( isInRange(r1, v2));
417 	assert( isInRange(r1, v3));
418 	assert( isInRange(r1, v4));
419 	assert( isInRange(r1, v5));
420 	assert( isInRange(r1, v6));
421 }
422 
423 pure unittest {
424 	Nullable!VersionRange r1N = parseVersionRange("~master");
425 	assert(!r1N.isNull());
426 	VersionRange r1 = r1N.get();
427 	SemVer v1 = parseSemVer("1.0.0");
428 	SemVer v2 = parseSemVer("2.0.0");
429 	SemVer v3 = parseSemVer("2.0.1");
430 	SemVer v4 = parseSemVer("0.999.999");
431 	SemVer v5 = parseSemVer("1.999.999");
432 	SemVer v6 = parseSemVer("89.0.1");
433 
434 	assert(!isInRange(r1, v1));
435 	assert(!isInRange(r1, v2));
436 	assert(!isInRange(r1, v3));
437 	assert(!isInRange(r1, v4));
438 	assert(!isInRange(r1, v5));
439 	assert(!isInRange(r1, v6));
440 }
441 
442 ///
443 enum BoundRelation {
444 	less,
445 	equal,
446 	unequal,
447 	more
448 }
449 
450 /** Return whether a is less than, equal, or greater than b
451 */
452 BoundRelation relation(const(SemVer) a, const Inclusive aInclusive,
453 		const(SemVer) b, const Inclusive bInclusive) pure
454 {
455 	import dud.semver.comparision : compare;
456 	const int cmp = compare(a, b);
457 	if(cmp < 0) {
458 		return BoundRelation.less;
459 	} else if(cmp > 0) {
460 		return BoundRelation.more;
461 	} else if(cmp == 0 && aInclusive == Inclusive.yes
462 			&& aInclusive == bInclusive)
463 	{
464 		return BoundRelation.equal;
465 	} else if(cmp == 0 && aInclusive == Inclusive.no
466 			&& aInclusive == bInclusive)
467 	{
468 		return BoundRelation.unequal;
469 	} else if(cmp == 0 && aInclusive == Inclusive.no
470 			&& bInclusive == Inclusive.yes)
471 	{
472 		return BoundRelation.unequal;
473 	} else if(cmp == 0 && aInclusive == Inclusive.yes
474 			&& bInclusive == Inclusive.no)
475 	{
476 		return BoundRelation.unequal;
477 	}
478 	assert(false, format(
479 		"invalid state a '%s', aInclusive '%s', b '%s', bInclusive '%s'",
480 		a, aInclusive, b, bInclusive));
481 }
482 
483 unittest {
484 	SemVer a = parseSemVer("1.0.0");
485 
486 	BoundRelation aa = relation(a, Inclusive.yes, a, Inclusive.yes);
487 	assert(aa == BoundRelation.equal, format("%s", aa));
488 
489 	aa = relation(a, Inclusive.yes, a, Inclusive.no);
490 	assert(aa == BoundRelation.unequal, format("%s", aa));
491 
492 	aa = relation(a, Inclusive.no, a, Inclusive.yes);
493 	assert(aa == BoundRelation.unequal, format("%s", aa));
494 
495 	aa = relation(a, Inclusive.yes, a, Inclusive.yes);
496 	assert(aa == BoundRelation.equal, format("%s", aa));
497 }
498 
499 unittest {
500 	SemVer a = parseSemVer("1.0.0");
501 	SemVer b = parseSemVer("2.0.0");
502 
503 	BoundRelation ab = relation(a, Inclusive.yes, b, Inclusive.yes);
504 	assert(ab == BoundRelation.less, format("%s", ab));
505 
506 	ab = relation(a, Inclusive.no, b, Inclusive.yes);
507 	assert(ab == BoundRelation.less, format("%s", ab));
508 
509 	ab = relation(a, Inclusive.yes, b, Inclusive.no);
510 	assert(ab == BoundRelation.less, format("%s", ab));
511 
512 	ab = relation(a, Inclusive.no, b, Inclusive.no);
513 	assert(ab == BoundRelation.less, format("%s", ab));
514 
515 	ab = relation(a, Inclusive.no, b, Inclusive.no);
516 	assert(ab == BoundRelation.less, format("%s", ab));
517 
518 	ab = relation(a, Inclusive.no, b, Inclusive.no);
519 	assert(ab == BoundRelation.less, format("%s", ab));
520 
521 	ab = relation(b, Inclusive.yes, a, Inclusive.yes);
522 	assert(ab == BoundRelation.more, format("%s", ab));
523 
524 	ab = relation(b, Inclusive.no, a, Inclusive.yes);
525 	assert(ab == BoundRelation.more, format("%s", ab));
526 
527 	ab = relation(b, Inclusive.no, a, Inclusive.no);
528 	assert(ab == BoundRelation.more, format("%s", ab));
529 
530 	ab = relation(b, Inclusive.yes, a, Inclusive.no);
531 	assert(ab == BoundRelation.more, format("%s", ab));
532 }
533 
534 pure unittest {
535 	SemVer[] sv =
536 		[ parseSemVer("1.0.0"), parseSemVer("2.0.0")
537 		, parseSemVer("3.0.0")
538 		];
539 	Inclusive[] b = [Inclusive.yes, Inclusive.no];
540 
541 	BoundRelation[] brs = [ BoundRelation.more, BoundRelation.less,
542 		BoundRelation.equal];
543 
544 	relation(sv[0], Inclusive.yes, sv[0], Inclusive.no);
545 	foreach(sa; sv) {
546 		foreach(sb; sv) {
547 			foreach(ba; b) {
548 				foreach(bb; b) {
549 					foreach(br; brs) {
550 						foreach(bl; brs) {
551 							relation(sa, ba, sb, bb);
552 						}
553 					}
554 				}
555 			}
556 		}
557 	}
558 }
559 
560 enum SetRelation : int {
561 	/// The second set contains all elements of the first, as well as possibly
562 	/// more.
563 	subset = 0,
564 
565 	/// Neither set contains any elements of the other.
566 	disjoint = 1,
567 
568 	/// The sets have elements in common, but the first is not a superset of the
569 	/// second.
570 	///
571 	/// This is also used when the first set is a superset of the second
572 	overlapping = 2
573 }
574 
575 /** Tests the relation between a and b.
576 A and b can be overlapping or disjoint and a can be a subset of b.
577 */
578 SetRelation relation(const(VersionRange) a, const(VersionRange) b)
579 		pure
580 {
581 	const BoundRelation lowLow = relation(
582 			a.low, a.inclusiveLow,
583 			b.low, b.inclusiveLow);
584 	const BoundRelation lowHigh = relation(
585 			a.low, a.inclusiveLow,
586 			b.high, b.inclusiveHigh);
587 	const BoundRelation highHigh = relation(
588 			a.high, a.inclusiveHigh,
589 			b.high, b.inclusiveHigh);
590 	const BoundRelation highLow = relation(
591 			a.high, a.inclusiveHigh,
592 			b.low, b.inclusiveLow);
593 
594 	//debug writefln(
595 	//	"\na: %s\nb: %s\n\tlowLow %s\n\tlowHigh %s\n\thighLow %s\n\thighHigh %s",
596 	//	a, b, lowLow, lowHigh, highLow, highHigh);
597 
598 
599 	// a: | . | . . . . . . . .
600 	// b: . . . | . . . | . . .
601 
602 	// a: | . . ) . . . . . . .
603 	// b: . . . | . . . | . . .
604 
605 	// a: | . . | . . . . . . .
606 	// b: . . . ( . . . | . . .
607 	if(highLow == BoundRelation.less
608 			|| (highLow == BoundRelation.unequal
609 				&& (!a.inclusiveHigh || !b.inclusiveLow)))
610 	{
611 		return SetRelation.disjoint;
612 	}
613 
614 	// a: . . . . . . . . | . |
615 	// b: . . . | . . . | . . .
616 
617 	// a: . . . . . . . ( . . |
618 	// b: . . . | . . . | . . .
619 
620 	// a: . . . . . . . | . . |
621 	// b: . . . | . . . ) . . .
622 	if(lowHigh == BoundRelation.more
623 			|| (lowHigh == BoundRelation.unequal
624 				&& (!a.inclusiveLow || !b.inclusiveHigh)))
625 	{
626 		return SetRelation.disjoint;
627 	}
628 
629 	// a: . . . | . . . | . . .
630 	// b: . . . | . . . | . . .
631 
632 	// a: . . . | . . . | . . .
633 	// b: . . . [ . . . ] . . .
634 
635 	// a: . . . . | . | . . . .
636 	// b: . . . | . . . | . . .
637 	if(((lowLow == BoundRelation.equal)
638 			|| (lowLow == BoundRelation.more)
639 			|| (lowLow == BoundRelation.unequal && b.inclusiveLow)
640 			|| (lowLow == BoundRelation.unequal
641 				&& a.inclusiveLow == b.inclusiveLow)
642 		)
643 		&&
644 		((highHigh == BoundRelation.equal)
645 			|| (highHigh == BoundRelation.less)
646 			|| (highHigh == BoundRelation.unequal && b.inclusiveHigh)
647 			|| (highHigh == BoundRelation.unequal
648 				&& a.inclusiveHigh == b.inclusiveHigh)
649 		)
650 	)
651 	{
652 		return SetRelation.subset;
653 	}
654 
655 	// a: . | . | . . . . . . .
656 	// b: . . . | . . . | . . .
657 
658 	// a: . . . . . . . | . | .
659 	// b: . . . | . . . | . . .
660 	if(lowHigh == BoundRelation.equal
661 			|| highLow == BoundRelation.equal)
662 	{
663 		return SetRelation.overlapping;
664 	}
665 
666 	// a: . . . . . . | . . | .
667 	// b: . . . | . . . | . . .
668 
669 	// a: . . . | . . . . . | .
670 	// b: . . . | . . . | . . .
671 	if((lowLow == BoundRelation.more || lowLow == BoundRelation.equal)
672 			&& lowHigh == BoundRelation.less
673 			&& ((highHigh == BoundRelation.more)
674 				|| (highHigh == BoundRelation.unequal
675 					&& a.inclusiveHigh
676 					&& !b.inclusiveHigh)
677 				)
678 		)
679 	{
680 		return SetRelation.overlapping;
681 	}
682 
683 	// a: . . . | . . . | . . .
684 	// b: . . . . . . | . . | .
685 
686 	// a: . . . | . . . . . | .
687 	// b: . . . . . . | . . | .
688 
689 	// a: . . . [ . . . . . | .
690 	// b: . . . ( . . . . . | .
691 	if(highLow == BoundRelation.more
692 			&& (highHigh == BoundRelation.less
693 				|| highHigh == BoundRelation.equal)
694 			&& ((lowLow == BoundRelation.less)
695 				|| (lowLow == BoundRelation.unequal
696 					&& a.inclusiveLow
697 					&& !b.inclusiveLow))
698 		)
699 	{
700 		return SetRelation.overlapping;
701 	}
702 
703 	if(lowLow == BoundRelation.less && highLow != BoundRelation.less) {
704 		return SetRelation.overlapping;
705 	}
706 
707 	if(highHigh == BoundRelation.more && lowHigh != BoundRelation.more) {
708 		return SetRelation.overlapping;
709 	}
710 
711 	// a: . . | . . . | . . . .
712 	// b: . . | . . . | . . . .
713 
714 	if(lowLow == BoundRelation.unequal && a.inclusiveLow && !b.inclusiveLow
715 		&& (highHigh == BoundRelation.unequal
716 			|| highHigh == BoundRelation.more
717 			)
718 	)
719 	{
720 		return SetRelation.overlapping;
721 	}
722 
723 	if((lowLow == BoundRelation.unequal && highHigh == BoundRelation.unequal)
724 			&& ((a.inclusiveLow && !b.inclusiveLow)
725 				|| (a.inclusiveHigh && !b.inclusiveHigh)))
726 	{
727 		return SetRelation.overlapping;
728 	}
729 
730 	assert(false, format(
731 		"\na:%s\nb:%s\nlowLow:%s\nlowHigh:%s\nhighLow:%s\nhighHigh:%s", a, b,
732 		lowLow, lowHigh, highLow, highHigh));
733 }