1 | // Copyright 2010 The Go Authors. All rights reserved.
|
---|
2 | // Use of this source code is governed by a BSD-style
|
---|
3 | // license that can be found in the LICENSE file.
|
---|
4 |
|
---|
5 | // Package token is variant of the stdlib package token with types FileSet and
|
---|
6 | // Token removed.
|
---|
7 | package token // import "modernc.org/token"
|
---|
8 |
|
---|
9 | import (
|
---|
10 | "fmt"
|
---|
11 | "sort"
|
---|
12 | )
|
---|
13 |
|
---|
14 | // -----------------------------------------------------------------------------
|
---|
15 | // Positions
|
---|
16 |
|
---|
17 | // Position describes an arbitrary source position
|
---|
18 | // including the file, line, and column location.
|
---|
19 | // A Position is valid if the line number is > 0.
|
---|
20 | //
|
---|
21 | type Position struct {
|
---|
22 | Filename string // filename, if any
|
---|
23 | Offset int // offset, starting at 0
|
---|
24 | Line int // line number, starting at 1
|
---|
25 | Column int // column number, starting at 1 (byte count)
|
---|
26 | }
|
---|
27 |
|
---|
28 | // IsValid reports whether the position is valid.
|
---|
29 | func (pos *Position) IsValid() bool { return pos.Line > 0 }
|
---|
30 |
|
---|
31 | // String returns a string in one of several forms:
|
---|
32 | //
|
---|
33 | // file:line:column valid position with file name
|
---|
34 | // line:column valid position without file name
|
---|
35 | // file invalid position with file name
|
---|
36 | // - invalid position without file name
|
---|
37 | //
|
---|
38 | func (pos Position) String() string {
|
---|
39 | s := pos.Filename
|
---|
40 | if pos.IsValid() {
|
---|
41 | if s != "" {
|
---|
42 | s += ":"
|
---|
43 | }
|
---|
44 | s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
|
---|
45 | }
|
---|
46 | if s == "" {
|
---|
47 | s = "-"
|
---|
48 | }
|
---|
49 | return s
|
---|
50 | }
|
---|
51 |
|
---|
52 | // Pos is a compact encoding of a source position within a file set.
|
---|
53 | // It can be converted into a Position for a more convenient, but much
|
---|
54 | // larger, representation.
|
---|
55 | //
|
---|
56 | // The Pos value for a given file is a number in the range [base, base+size],
|
---|
57 | // where base and size are specified when adding the file to the file set via
|
---|
58 | // AddFile.
|
---|
59 | //
|
---|
60 | // To create the Pos value for a specific source offset (measured in bytes),
|
---|
61 | // first add the respective file to the current file set using FileSet.AddFile
|
---|
62 | // and then call File.Pos(offset) for that file. Given a Pos value p
|
---|
63 | // for a specific file set fset, the corresponding Position value is
|
---|
64 | // obtained by calling fset.Position(p).
|
---|
65 | //
|
---|
66 | // Pos values can be compared directly with the usual comparison operators:
|
---|
67 | // If two Pos values p and q are in the same file, comparing p and q is
|
---|
68 | // equivalent to comparing the respective source file offsets. If p and q
|
---|
69 | // are in different files, p < q is true if the file implied by p was added
|
---|
70 | // to the respective file set before the file implied by q.
|
---|
71 | //
|
---|
72 | type Pos int
|
---|
73 |
|
---|
74 | // NoPos is the zero value of Pos; there is no file and line information
|
---|
75 | // associated with it, and NoPos.IsValid() is false. NoPos is always
|
---|
76 | // smaller than any other Pos value. The corresponding Position value
|
---|
77 | // for NoPos is the zero value for Position.
|
---|
78 | //
|
---|
79 | const NoPos Pos = 0
|
---|
80 |
|
---|
81 | // IsValid reports whether the position is valid.
|
---|
82 | func (p Pos) IsValid() bool {
|
---|
83 | return p != NoPos
|
---|
84 | }
|
---|
85 |
|
---|
86 | // -----------------------------------------------------------------------------
|
---|
87 | // File
|
---|
88 |
|
---|
89 | // A File is a handle for a file. A File has a name, size, and line offset
|
---|
90 | // table.
|
---|
91 | //
|
---|
92 | type File struct {
|
---|
93 | name string // file name as provided to AddFile
|
---|
94 | base int // Pos value range for this file is [base...base+size]
|
---|
95 | size int // file size as provided to AddFile
|
---|
96 |
|
---|
97 | lines []int // lines contains the offset of the first character for each line (the first entry is always 0)
|
---|
98 | infos []lineInfo
|
---|
99 | }
|
---|
100 |
|
---|
101 | // NewFile returns a new file with a given filename and file size.
|
---|
102 | //
|
---|
103 | func NewFile(filename string, size int) *File {
|
---|
104 | return &File{name: filename, base: 1, size: size, lines: []int{0}}
|
---|
105 | }
|
---|
106 |
|
---|
107 | // Name returns the file name of file f as registered with AddFile.
|
---|
108 | func (f *File) Name() string {
|
---|
109 | return f.name
|
---|
110 | }
|
---|
111 |
|
---|
112 | // Base returns the base offset of file f as registered with AddFile.
|
---|
113 | func (f *File) Base() int {
|
---|
114 | return f.base
|
---|
115 | }
|
---|
116 |
|
---|
117 | // Size returns the size of file f as registered with AddFile.
|
---|
118 | func (f *File) Size() int {
|
---|
119 | return f.size
|
---|
120 | }
|
---|
121 |
|
---|
122 | // LineCount returns the number of lines in file f.
|
---|
123 | func (f *File) LineCount() int {
|
---|
124 | n := len(f.lines)
|
---|
125 | return n
|
---|
126 | }
|
---|
127 |
|
---|
128 | // AddLine adds the line offset for a new line.
|
---|
129 | // The line offset must be larger than the offset for the previous line
|
---|
130 | // and smaller than the file size; otherwise the line offset is ignored.
|
---|
131 | //
|
---|
132 | func (f *File) AddLine(offset int) {
|
---|
133 | if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size {
|
---|
134 | f.lines = append(f.lines, offset)
|
---|
135 | }
|
---|
136 | }
|
---|
137 |
|
---|
138 | // MergeLine merges a line with the following line. It is akin to replacing
|
---|
139 | // the newline character at the end of the line with a space (to not change the
|
---|
140 | // remaining offsets). To obtain the line number, consult e.g. Position.Line.
|
---|
141 | // MergeLine will panic if given an invalid line number.
|
---|
142 | //
|
---|
143 | func (f *File) MergeLine(line int) {
|
---|
144 | if line <= 0 {
|
---|
145 | panic("illegal line number (line numbering starts at 1)")
|
---|
146 | }
|
---|
147 | if line >= len(f.lines) {
|
---|
148 | panic("illegal line number")
|
---|
149 | }
|
---|
150 | // To merge the line numbered <line> with the line numbered <line+1>,
|
---|
151 | // we need to remove the entry in lines corresponding to the line
|
---|
152 | // numbered <line+1>. The entry in lines corresponding to the line
|
---|
153 | // numbered <line+1> is located at index <line>, since indices in lines
|
---|
154 | // are 0-based and line numbers are 1-based.
|
---|
155 | copy(f.lines[line:], f.lines[line+1:])
|
---|
156 | f.lines = f.lines[:len(f.lines)-1]
|
---|
157 | }
|
---|
158 |
|
---|
159 | // SetLines sets the line offsets for a file and reports whether it succeeded.
|
---|
160 | // The line offsets are the offsets of the first character of each line;
|
---|
161 | // for instance for the content "ab\nc\n" the line offsets are {0, 3}.
|
---|
162 | // An empty file has an empty line offset table.
|
---|
163 | // Each line offset must be larger than the offset for the previous line
|
---|
164 | // and smaller than the file size; otherwise SetLines fails and returns
|
---|
165 | // false.
|
---|
166 | // Callers must not mutate the provided slice after SetLines returns.
|
---|
167 | //
|
---|
168 | func (f *File) SetLines(lines []int) bool {
|
---|
169 | // verify validity of lines table
|
---|
170 | size := f.size
|
---|
171 | for i, offset := range lines {
|
---|
172 | if i > 0 && offset <= lines[i-1] || size <= offset {
|
---|
173 | return false
|
---|
174 | }
|
---|
175 | }
|
---|
176 |
|
---|
177 | // set lines table
|
---|
178 | f.lines = lines
|
---|
179 | return true
|
---|
180 | }
|
---|
181 |
|
---|
182 | // SetLinesForContent sets the line offsets for the given file content.
|
---|
183 | // It ignores position-altering //line comments.
|
---|
184 | func (f *File) SetLinesForContent(content []byte) {
|
---|
185 | var lines []int
|
---|
186 | line := 0
|
---|
187 | for offset, b := range content {
|
---|
188 | if line >= 0 {
|
---|
189 | lines = append(lines, line)
|
---|
190 | }
|
---|
191 | line = -1
|
---|
192 | if b == '\n' {
|
---|
193 | line = offset + 1
|
---|
194 | }
|
---|
195 | }
|
---|
196 |
|
---|
197 | // set lines table
|
---|
198 | f.lines = lines
|
---|
199 | }
|
---|
200 |
|
---|
201 | // A lineInfo object describes alternative file and line number
|
---|
202 | // information (such as provided via a //line comment in a .go
|
---|
203 | // file) for a given file offset.
|
---|
204 | type lineInfo struct {
|
---|
205 | // fields are exported to make them accessible to gob
|
---|
206 | Offset int
|
---|
207 | Filename string
|
---|
208 | Line int
|
---|
209 | }
|
---|
210 |
|
---|
211 | // AddLineInfo adds alternative file and line number information for
|
---|
212 | // a given file offset. The offset must be larger than the offset for
|
---|
213 | // the previously added alternative line info and smaller than the
|
---|
214 | // file size; otherwise the information is ignored.
|
---|
215 | //
|
---|
216 | // AddLineInfo is typically used to register alternative position
|
---|
217 | // information for //line filename:line comments in source files.
|
---|
218 | //
|
---|
219 | func (f *File) AddLineInfo(offset int, filename string, line int) {
|
---|
220 | if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size {
|
---|
221 | f.infos = append(f.infos, lineInfo{offset, filename, line})
|
---|
222 | }
|
---|
223 | }
|
---|
224 |
|
---|
225 | // Pos returns the Pos value for the given file offset;
|
---|
226 | // the offset must be <= f.Size().
|
---|
227 | // f.Pos(f.Offset(p)) == p.
|
---|
228 | //
|
---|
229 | func (f *File) Pos(offset int) Pos {
|
---|
230 | if offset > f.size {
|
---|
231 | panic("illegal file offset")
|
---|
232 | }
|
---|
233 | return Pos(f.base + offset)
|
---|
234 | }
|
---|
235 |
|
---|
236 | // Offset returns the offset for the given file position p;
|
---|
237 | // p must be a valid Pos value in that file.
|
---|
238 | // f.Offset(f.Pos(offset)) == offset.
|
---|
239 | //
|
---|
240 | func (f *File) Offset(p Pos) int {
|
---|
241 | if int(p) < f.base || int(p) > f.base+f.size {
|
---|
242 | panic("illegal Pos value")
|
---|
243 | }
|
---|
244 | return int(p) - f.base
|
---|
245 | }
|
---|
246 |
|
---|
247 | // Line returns the line number for the given file position p;
|
---|
248 | // p must be a Pos value in that file or NoPos.
|
---|
249 | //
|
---|
250 | func (f *File) Line(p Pos) int {
|
---|
251 | return f.Position(p).Line
|
---|
252 | }
|
---|
253 |
|
---|
254 | func searchLineInfos(a []lineInfo, x int) int {
|
---|
255 | return sort.Search(len(a), func(i int) bool { return a[i].Offset > x }) - 1
|
---|
256 | }
|
---|
257 |
|
---|
258 | // unpack returns the filename and line and column number for a file offset.
|
---|
259 | // If adjusted is set, unpack will return the filename and line information
|
---|
260 | // possibly adjusted by //line comments; otherwise those comments are ignored.
|
---|
261 | //
|
---|
262 | func (f *File) unpack(offset int, adjusted bool) (filename string, line, column int) {
|
---|
263 | filename = f.name
|
---|
264 | if i := searchInts(f.lines, offset); i >= 0 {
|
---|
265 | line, column = i+1, offset-f.lines[i]+1
|
---|
266 | }
|
---|
267 | if adjusted && len(f.infos) > 0 {
|
---|
268 | // almost no files have extra line infos
|
---|
269 | if i := searchLineInfos(f.infos, offset); i >= 0 {
|
---|
270 | alt := &f.infos[i]
|
---|
271 | filename = alt.Filename
|
---|
272 | if i := searchInts(f.lines, alt.Offset); i >= 0 {
|
---|
273 | line += alt.Line - i - 1
|
---|
274 | }
|
---|
275 | }
|
---|
276 | }
|
---|
277 | return
|
---|
278 | }
|
---|
279 |
|
---|
280 | func (f *File) position(p Pos, adjusted bool) (pos Position) {
|
---|
281 | offset := int(p) - f.base
|
---|
282 | pos.Offset = offset
|
---|
283 | pos.Filename, pos.Line, pos.Column = f.unpack(offset, adjusted)
|
---|
284 | return
|
---|
285 | }
|
---|
286 |
|
---|
287 | // PositionFor returns the Position value for the given file position p.
|
---|
288 | // If adjusted is set, the position may be adjusted by position-altering
|
---|
289 | // //line comments; otherwise those comments are ignored.
|
---|
290 | // p must be a Pos value in f or NoPos.
|
---|
291 | //
|
---|
292 | func (f *File) PositionFor(p Pos, adjusted bool) (pos Position) {
|
---|
293 | if p != NoPos {
|
---|
294 | if int(p) < f.base || int(p) > f.base+f.size {
|
---|
295 | panic("illegal Pos value")
|
---|
296 | }
|
---|
297 | pos = f.position(p, adjusted)
|
---|
298 | }
|
---|
299 | return
|
---|
300 | }
|
---|
301 |
|
---|
302 | // Position returns the Position value for the given file position p.
|
---|
303 | // Calling f.Position(p) is equivalent to calling f.PositionFor(p, true).
|
---|
304 | //
|
---|
305 | func (f *File) Position(p Pos) (pos Position) {
|
---|
306 | return f.PositionFor(p, true)
|
---|
307 | }
|
---|
308 |
|
---|
309 | // -----------------------------------------------------------------------------
|
---|
310 | // Helper functions
|
---|
311 |
|
---|
312 | func searchInts(a []int, x int) int {
|
---|
313 | // This function body is a manually inlined version of:
|
---|
314 | //
|
---|
315 | // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1
|
---|
316 | //
|
---|
317 | // With better compiler optimizations, this may not be needed in the
|
---|
318 | // future, but at the moment this change improves the go/printer
|
---|
319 | // benchmark performance by ~30%. This has a direct impact on the
|
---|
320 | // speed of gofmt and thus seems worthwhile (2011-04-29).
|
---|
321 | // TODO(gri): Remove this when compilers have caught up.
|
---|
322 | i, j := 0, len(a)
|
---|
323 | for i < j {
|
---|
324 | h := i + (j-i)/2 // avoid overflow when computing h
|
---|
325 | // i ≤ h < j
|
---|
326 | if a[h] <= x {
|
---|
327 | i = h + 1
|
---|
328 | } else {
|
---|
329 | j = h
|
---|
330 | }
|
---|
331 | }
|
---|
332 | return i - 1
|
---|
333 | }
|
---|