1<?php
2
3declare(strict_types=1);
4
5namespace Antlr\Antlr4\Runtime;
6
7use Antlr\Antlr4\Runtime\Comparison\Equatable;
8
9/**
10 * An immutable inclusive interval start..stop (both start and stop included).
11 */
12final class Interval implements Equatable
13{
14    /** @var int */
15    public $start;
16
17    /** @var int */
18    public $stop;
19
20    public function __construct(int $start, int $stop)
21    {
22        $this->start = $start;
23        $this->stop = $stop;
24    }
25
26    public static function invalid() : self
27    {
28        static $invalid;
29
30        return $invalid ?? $invalid = new Interval(-1, -2);
31    }
32
33    public function contains(int $item) : bool
34    {
35        return $item >= $this->start && $item <= $this->stop;
36    }
37
38    public function getLength() : int
39    {
40        return $this->stop - $this->start + 1;
41    }
42
43    public function equals(object $other) : bool
44    {
45        if ($this === $other) {
46            return true;
47        }
48
49        return $other instanceof self
50            && $this->start === $other->start
51            && $this->stop === $other->stop;
52    }
53
54    /**
55     * Does this start completely before other? Disjoint.
56     */
57    public function startsBeforeDisjoint(Interval $other) : bool
58    {
59        return $this->start < $other->start && $this->stop < $other->start;
60    }
61
62    /**
63     * Does this start at or before other? Nondisjoint.
64     */
65    public function startsBeforeNonDisjoint(Interval $other) : bool
66    {
67        return $this->start <= $other->start && $this->stop >= $other->start;
68    }
69
70    /**
71     * Does this.a start after other.b? May or may not be disjoint.
72     */
73    public function startsAfter(Interval $other) : bool
74    {
75        return $this->start > $other->start;
76    }
77
78    /**
79     * Does this start completely after other? Disjoint.
80     */
81    public function startsAfterDisjoint(Interval $other) : bool
82    {
83        return $this->start > $other->stop;
84    }
85
86    /**
87     * Does this start after other? NonDisjoint
88     */
89    public function startsAfterNonDisjoint(Interval $other) : bool
90    {
91        // this.b >= other.b implied
92        return $this->start > $other->start && $this->start <= $other->stop;
93    }
94
95    /**
96     * Are both ranges disjoint? I.e., no overlap?
97     */
98    public function disjoint(Interval $other) : bool
99    {
100        return $this->startsBeforeDisjoint($other) || $this->startsAfterDisjoint($other);
101    }
102
103    /**
104     * Are two intervals adjacent such as 0..41 and 42..42?
105     */
106    public function adjacent(Interval $other) : bool
107    {
108        return $this->start === $other->stop + 1 || $this->stop === $other->start - 1;
109    }
110
111    /**
112     * Return the interval computed from combining this and other
113     */
114    public function union(Interval $other) : self
115    {
116        return new self(\min($this->start, $other->start), \max($this->stop, $other->stop));
117    }
118
119    /**
120     * Return the interval in common between this and o
121     */
122    public function intersection(Interval $other) : self
123    {
124        return new self(\max($this->start, $other->start), \min($this->stop, $other->stop));
125    }
126
127    public function __toString() : string
128    {
129        if ($this->start === $this->stop) {
130            return (string) $this->start;
131        }
132
133        return $this->start . '..' . $this->stop;
134    }
135}
136