11package scip
22
3- // Range represents a range between two offset positions.
3+ import "fmt"
4+
5+ // Range represents [start, end) between two offset positions.
6+ //
47// NOTE: the github.com/sourcegraph/sourcegraph/lib/codeintel/lsif/protocol package
58// contains similarly shaped structs but this one exists primarily to make it
69// easier to work with SCIP encoded positions, which have the type []int32
@@ -16,18 +19,109 @@ type Position struct {
1619 Character int32
1720}
1821
19- // NewRange converts an SCIP range into `Range`
20- func NewRange (scipRange []int32 ) * Range {
21- var endLine int32
22- var endCharacter int32
23- if len (scipRange ) == 3 { // single line
24- endLine = scipRange [0 ]
25- endCharacter = scipRange [2 ]
26- } else if len (scipRange ) == 4 { // multi-line
22+ func (p Position ) Compare (other Position ) int {
23+ if p .Line < other .Line {
24+ return - 1
25+ }
26+ if p .Line > other .Line {
27+ return 1
28+ }
29+ if p .Character < other .Character {
30+ return - 1
31+ }
32+ if p .Character > other .Character {
33+ return 1
34+ }
35+ return 0
36+ }
37+
38+ func (p Position ) Less (other Position ) bool {
39+ if p .Line < other .Line {
40+ return true
41+ }
42+ if p .Line > other .Line {
43+ return false
44+ }
45+ return p .Character < other .Character
46+ }
47+
48+ //go:noinline
49+ func makeNewRangeError (startLine , endLine , startChar , endChar int32 ) (Range , error ) {
50+ if startLine < 0 || endLine < 0 || startChar < 0 || endChar < 0 {
51+ return Range {}, NegativeOffsetsRangeError
52+ }
53+ if startLine > endLine || (startLine == endLine && startChar > endChar ) {
54+ return Range {}, EndBeforeStartRangeError
55+ }
56+ panic ("unreachable" )
57+ }
58+
59+ // NewRange constructs a Range while checking if the input is valid.
60+ func NewRange (scipRange []int32 ) (Range , error ) {
61+ // N.B. This function is kept small so that it can be inlined easily.
62+ // See also: https://github.com/golang/go/issues/17566
63+ var startLine , endLine , startChar , endChar int32
64+ switch len (scipRange ) {
65+ case 3 :
66+ startLine = scipRange [0 ]
67+ endLine = startLine
68+ startChar = scipRange [1 ]
69+ endChar = scipRange [2 ]
70+ if startLine >= 0 && startChar >= 0 && endChar >= startChar {
71+ break
72+ }
73+ return makeNewRangeError (startLine , endLine , startChar , endChar )
74+ case 4 :
75+ startLine = scipRange [0 ]
76+ startChar = scipRange [1 ]
2777 endLine = scipRange [2 ]
78+ endChar = scipRange [3 ]
79+ if startLine >= 0 && startChar >= 0 &&
80+ ((endLine > startLine && endChar >= 0 ) || (endLine == startLine && endChar >= startChar )) {
81+ break
82+ }
83+ return makeNewRangeError (startLine , endLine , startChar , endChar )
84+ default :
85+ return Range {}, IncorrectLengthRangeError
86+ }
87+ return Range {Start : Position {Line : startLine , Character : startChar }, End : Position {Line : endLine , Character : endChar }}, nil
88+ }
89+
90+ type RangeError int32
91+
92+ const (
93+ IncorrectLengthRangeError RangeError = iota
94+ NegativeOffsetsRangeError
95+ EndBeforeStartRangeError
96+ )
97+
98+ var _ error = RangeError (0 )
99+
100+ func (e RangeError ) Error () string {
101+ switch e {
102+ case IncorrectLengthRangeError :
103+ return "incorrect length"
104+ case NegativeOffsetsRangeError :
105+ return "negative offsets"
106+ case EndBeforeStartRangeError :
107+ return "end before start"
108+ }
109+ panic ("unhandled range error" )
110+ }
111+
112+ // NewRangeUnchecked converts an SCIP range into `Range`
113+ //
114+ // Pre-condition: The input slice must follow the SCIP range encoding.
115+ // https://sourcegraph.com/github.com/sourcegraph/scip/-/blob/scip.proto?L646:18-646:23
116+ func NewRangeUnchecked (scipRange []int32 ) Range {
117+ // Single-line case is most common
118+ endCharacter := scipRange [2 ]
119+ endLine := scipRange [0 ]
120+ if len (scipRange ) == 4 { // multi-line
28121 endCharacter = scipRange [3 ]
122+ endLine = scipRange [2 ]
29123 }
30- return & Range {
124+ return Range {
31125 Start : Position {
32126 Line : scipRange [0 ],
33127 Character : scipRange [1 ],
@@ -49,3 +143,57 @@ func (r Range) SCIPRange() []int32 {
49143 }
50144 return []int32 {r .Start .Line , r .Start .Character , r .End .Line , r .End .Character }
51145}
146+
147+ // Contains checks if position is within the range
148+ func (r Range ) Contains (position Position ) bool {
149+ return ! position .Less (r .Start ) && position .Less (r .End )
150+ }
151+
152+ // Intersects checks if two ranges intersect.
153+ //
154+ // case 1: r1.Start >= other.Start && r1.Start < other.End
155+ // case 2: r2.Start >= r1.Start && r2.Start < r1.End
156+ func (r Range ) Intersects (other Range ) bool {
157+ return r .Start .Less (other .End ) && other .Start .Less (r .End )
158+ }
159+
160+ // Compare compares two ranges.
161+ //
162+ // Returns 0 if the ranges intersect (not just if they're equal).
163+ func (r Range ) Compare (other Range ) int {
164+ if r .Intersects (other ) {
165+ return 0
166+ }
167+ return r .Start .Compare (other .Start )
168+ }
169+
170+ // Less compares two ranges, consistent with Compare.
171+ //
172+ // r.Compare(other) < 0 iff r.Less(other).
173+ func (r Range ) Less (other Range ) bool {
174+ return r .End .Compare (other .Start ) <= 0
175+ }
176+
177+ // CompareStrict compares two ranges.
178+ //
179+ // Returns 0 iff the ranges are exactly equal.
180+ func (r Range ) CompareStrict (other Range ) int {
181+ if ret := r .Start .Compare (other .Start ); ret != 0 {
182+ return ret
183+ }
184+ return r .End .Compare (other .End )
185+ }
186+
187+ // LessStrict compares two ranges, consistent with CompareStrict.
188+ //
189+ // r.CompareStrict(other) < 0 iff r.LessStrict(other).
190+ func (r Range ) LessStrict (other Range ) bool {
191+ if ret := r .Start .Compare (other .Start ); ret != 0 {
192+ return ret < 0
193+ }
194+ return r .End .Less (other .End )
195+ }
196+
197+ func (r Range ) String () string {
198+ return fmt .Sprintf ("%d:%d-%d:%d" , r .Start .Line , r .Start .Character , r .End .Line , r .End .Character )
199+ }
0 commit comments