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 }