]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package hcl |
2 | ||
3 | import "fmt" | |
4 | ||
5 | // Pos represents a single position in a source file, by addressing the | |
6 | // start byte of a unicode character encoded in UTF-8. | |
7 | // | |
8 | // Pos is generally used only in the context of a Range, which then defines | |
9 | // which source file the position is within. | |
10 | type Pos struct { | |
11 | // Line is the source code line where this position points. Lines are | |
12 | // counted starting at 1 and incremented for each newline character | |
13 | // encountered. | |
14 | Line int | |
15 | ||
16 | // Column is the source code column where this position points, in | |
17 | // unicode characters, with counting starting at 1. | |
18 | // | |
19 | // Column counts characters as they appear visually, so for example a | |
20 | // latin letter with a combining diacritic mark counts as one character. | |
21 | // This is intended for rendering visual markers against source code in | |
22 | // contexts where these diacritics would be rendered in a single character | |
23 | // cell. Technically speaking, Column is counting grapheme clusters as | |
24 | // used in unicode normalization. | |
25 | Column int | |
26 | ||
27 | // Byte is the byte offset into the file where the indicated character | |
28 | // begins. This is a zero-based offset to the first byte of the first | |
29 | // UTF-8 codepoint sequence in the character, and thus gives a position | |
30 | // that can be resolved _without_ awareness of Unicode characters. | |
31 | Byte int | |
32 | } | |
33 | ||
107c1cdb ND |
34 | // InitialPos is a suitable position to use to mark the start of a file. |
35 | var InitialPos = Pos{Byte: 0, Line: 1, Column: 1} | |
36 | ||
15c0b25d AP |
37 | // Range represents a span of characters between two positions in a source |
38 | // file. | |
39 | // | |
40 | // This struct is usually used by value in types that represent AST nodes, | |
41 | // but by pointer in types that refer to the positions of other objects, | |
42 | // such as in diagnostics. | |
43 | type Range struct { | |
44 | // Filename is the name of the file into which this range's positions | |
45 | // point. | |
46 | Filename string | |
47 | ||
48 | // Start and End represent the bounds of this range. Start is inclusive | |
49 | // and End is exclusive. | |
50 | Start, End Pos | |
51 | } | |
52 | ||
53 | // RangeBetween returns a new range that spans from the beginning of the | |
54 | // start range to the end of the end range. | |
55 | // | |
56 | // The result is meaningless if the two ranges do not belong to the same | |
57 | // source file or if the end range appears before the start range. | |
58 | func RangeBetween(start, end Range) Range { | |
59 | return Range{ | |
60 | Filename: start.Filename, | |
61 | Start: start.Start, | |
62 | End: end.End, | |
63 | } | |
64 | } | |
65 | ||
66 | // RangeOver returns a new range that covers both of the given ranges and | |
67 | // possibly additional content between them if the two ranges do not overlap. | |
68 | // | |
69 | // If either range is empty then it is ignored. The result is empty if both | |
70 | // given ranges are empty. | |
71 | // | |
72 | // The result is meaningless if the two ranges to not belong to the same | |
73 | // source file. | |
74 | func RangeOver(a, b Range) Range { | |
75 | if a.Empty() { | |
76 | return b | |
77 | } | |
78 | if b.Empty() { | |
79 | return a | |
80 | } | |
81 | ||
82 | var start, end Pos | |
83 | if a.Start.Byte < b.Start.Byte { | |
84 | start = a.Start | |
85 | } else { | |
86 | start = b.Start | |
87 | } | |
88 | if a.End.Byte > b.End.Byte { | |
89 | end = a.End | |
90 | } else { | |
91 | end = b.End | |
92 | } | |
93 | return Range{ | |
94 | Filename: a.Filename, | |
95 | Start: start, | |
96 | End: end, | |
97 | } | |
98 | } | |
99 | ||
107c1cdb ND |
100 | // ContainsPos returns true if and only if the given position is contained within |
101 | // the receiving range. | |
102 | // | |
103 | // In the unlikely case that the line/column information disagree with the byte | |
104 | // offset information in the given position or receiving range, the byte | |
105 | // offsets are given priority. | |
106 | func (r Range) ContainsPos(pos Pos) bool { | |
107 | return r.ContainsOffset(pos.Byte) | |
108 | } | |
109 | ||
15c0b25d AP |
110 | // ContainsOffset returns true if and only if the given byte offset is within |
111 | // the receiving Range. | |
112 | func (r Range) ContainsOffset(offset int) bool { | |
113 | return offset >= r.Start.Byte && offset < r.End.Byte | |
114 | } | |
115 | ||
116 | // Ptr returns a pointer to a copy of the receiver. This is a convenience when | |
117 | // ranges in places where pointers are required, such as in Diagnostic, but | |
118 | // the range in question is returned from a method. Go would otherwise not | |
119 | // allow one to take the address of a function call. | |
120 | func (r Range) Ptr() *Range { | |
121 | return &r | |
122 | } | |
123 | ||
124 | // String returns a compact string representation of the receiver. | |
125 | // Callers should generally prefer to present a range more visually, | |
126 | // e.g. via markers directly on the relevant portion of source code. | |
127 | func (r Range) String() string { | |
128 | if r.Start.Line == r.End.Line { | |
129 | return fmt.Sprintf( | |
130 | "%s:%d,%d-%d", | |
131 | r.Filename, | |
132 | r.Start.Line, r.Start.Column, | |
133 | r.End.Column, | |
134 | ) | |
135 | } else { | |
136 | return fmt.Sprintf( | |
137 | "%s:%d,%d-%d,%d", | |
138 | r.Filename, | |
139 | r.Start.Line, r.Start.Column, | |
140 | r.End.Line, r.End.Column, | |
141 | ) | |
142 | } | |
143 | } | |
144 | ||
145 | func (r Range) Empty() bool { | |
146 | return r.Start.Byte == r.End.Byte | |
147 | } | |
148 | ||
149 | // CanSliceBytes returns true if SliceBytes could return an accurate | |
150 | // sub-slice of the given slice. | |
151 | // | |
152 | // This effectively tests whether the start and end offsets of the range | |
153 | // are within the bounds of the slice, and thus whether SliceBytes can be | |
154 | // trusted to produce an accurate start and end position within that slice. | |
155 | func (r Range) CanSliceBytes(b []byte) bool { | |
156 | switch { | |
157 | case r.Start.Byte < 0 || r.Start.Byte > len(b): | |
158 | return false | |
159 | case r.End.Byte < 0 || r.End.Byte > len(b): | |
160 | return false | |
161 | case r.End.Byte < r.Start.Byte: | |
162 | return false | |
163 | default: | |
164 | return true | |
165 | } | |
166 | } | |
167 | ||
168 | // SliceBytes returns a sub-slice of the given slice that is covered by the | |
169 | // receiving range, assuming that the given slice is the source code of the | |
170 | // file indicated by r.Filename. | |
171 | // | |
172 | // If the receiver refers to any byte offsets that are outside of the slice | |
173 | // then the result is constrained to the overlapping portion only, to avoid | |
174 | // a panic. Use CanSliceBytes to determine if the result is guaranteed to | |
175 | // be an accurate span of the requested range. | |
176 | func (r Range) SliceBytes(b []byte) []byte { | |
177 | start := r.Start.Byte | |
178 | end := r.End.Byte | |
179 | if start < 0 { | |
180 | start = 0 | |
181 | } else if start > len(b) { | |
182 | start = len(b) | |
183 | } | |
184 | if end < 0 { | |
185 | end = 0 | |
186 | } else if end > len(b) { | |
187 | end = len(b) | |
188 | } | |
189 | if end < start { | |
190 | end = start | |
191 | } | |
192 | return b[start:end] | |
193 | } | |
194 | ||
195 | // Overlaps returns true if the receiver and the other given range share any | |
196 | // characters in common. | |
197 | func (r Range) Overlaps(other Range) bool { | |
198 | switch { | |
199 | case r.Filename != other.Filename: | |
200 | // If the ranges are in different files then they can't possibly overlap | |
201 | return false | |
202 | case r.Empty() || other.Empty(): | |
203 | // Empty ranges can never overlap | |
204 | return false | |
205 | case r.ContainsOffset(other.Start.Byte) || r.ContainsOffset(other.End.Byte): | |
206 | return true | |
207 | case other.ContainsOffset(r.Start.Byte) || other.ContainsOffset(r.End.Byte): | |
208 | return true | |
209 | default: | |
210 | return false | |
211 | } | |
212 | } | |
213 | ||
214 | // Overlap finds a range that is either identical to or a sub-range of both | |
215 | // the receiver and the other given range. It returns an empty range | |
216 | // within the receiver if there is no overlap between the two ranges. | |
217 | // | |
218 | // A non-empty result is either identical to or a subset of the receiver. | |
219 | func (r Range) Overlap(other Range) Range { | |
220 | if !r.Overlaps(other) { | |
221 | // Start == End indicates an empty range | |
222 | return Range{ | |
223 | Filename: r.Filename, | |
224 | Start: r.Start, | |
225 | End: r.Start, | |
226 | } | |
227 | } | |
228 | ||
229 | var start, end Pos | |
230 | if r.Start.Byte > other.Start.Byte { | |
231 | start = r.Start | |
232 | } else { | |
233 | start = other.Start | |
234 | } | |
235 | if r.End.Byte < other.End.Byte { | |
236 | end = r.End | |
237 | } else { | |
238 | end = other.End | |
239 | } | |
240 | ||
241 | return Range{ | |
242 | Filename: r.Filename, | |
243 | Start: start, | |
244 | End: end, | |
245 | } | |
246 | } | |
247 | ||
248 | // PartitionAround finds the portion of the given range that overlaps with | |
249 | // the reciever and returns three ranges: the portion of the reciever that | |
250 | // precedes the overlap, the overlap itself, and then the portion of the | |
251 | // reciever that comes after the overlap. | |
252 | // | |
253 | // If the two ranges do not overlap then all three returned ranges are empty. | |
254 | // | |
255 | // If the given range aligns with or extends beyond either extent of the | |
256 | // reciever then the corresponding outer range will be empty. | |
257 | func (r Range) PartitionAround(other Range) (before, overlap, after Range) { | |
258 | overlap = r.Overlap(other) | |
259 | if overlap.Empty() { | |
260 | return overlap, overlap, overlap | |
261 | } | |
262 | ||
263 | before = Range{ | |
264 | Filename: r.Filename, | |
265 | Start: r.Start, | |
266 | End: overlap.Start, | |
267 | } | |
268 | after = Range{ | |
269 | Filename: r.Filename, | |
270 | Start: overlap.End, | |
271 | End: r.End, | |
272 | } | |
273 | ||
274 | return before, overlap, after | |
275 | } |