OSDN Git Service

Update to current version of Go library.
[pf3gnuchains/gcc-fork.git] / libgo / go / go / token / position.go
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 // TODO(gri) consider making this a separate package outside the go directory.
6
7 package token
8
9 import (
10         "fmt"
11         "sort"
12         "sync"
13 )
14
15
16 // Position describes an arbitrary source position
17 // including the file, line, and column location.
18 // A Position is valid if the line number is > 0.
19 //
20 type Position struct {
21         Filename string // filename, if any
22         Offset   int    // offset, starting at 0
23         Line     int    // line number, starting at 1
24         Column   int    // column number, starting at 1 (character count)
25 }
26
27
28 // IsValid returns true if the position is valid.
29 func (pos *Position) IsValid() bool { return pos.Line > 0 }
30
31
32 // String returns a string in one of several forms:
33 //
34 //      file:line:column    valid position with file name
35 //      line:column         valid position without file name
36 //      file                invalid position with file name
37 //      -                   invalid position without file name
38 //
39 func (pos Position) String() string {
40         s := pos.Filename
41         if pos.IsValid() {
42                 if s != "" {
43                         s += ":"
44                 }
45                 s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
46         }
47         if s == "" {
48                 s = "-"
49         }
50         return s
51 }
52
53
54 // Pos is a compact encoding of a source position within a file set.
55 // It can be converted into a Position for a more convenient, but much
56 // larger, representation.
57 //
58 // The Pos value for a given file is a number in the range [base, base+size],
59 // where base and size are specified when adding the file to the file set via
60 // AddFile.
61 //
62 // To create the Pos value for a specific source offset, first add
63 // the respective file to the current file set (via FileSet.AddFile)
64 // and then call File.Pos(offset) for that file. Given a Pos value p
65 // for a specific file set fset, the corresponding Position value is
66 // obtained by calling fset.Position(p).
67 //
68 // Pos values can be compared directly with the usual comparison operators:
69 // If two Pos values p and q are in the same file, comparing p and q is
70 // equivalent to comparing the respective source file offsets. If p and q
71 // are in different files, p < q is true if the file implied by p was added
72 // to the respective file set before the file implied by q.
73 //
74 type Pos int
75
76
77 // The zero value for Pos is NoPos; there is no file and line information
78 // associated with it, and NoPos().IsValid() is false. NoPos is always
79 // smaller than any other Pos value. The corresponding Position value
80 // for NoPos is the zero value for Position.
81 // 
82 const NoPos Pos = 0
83
84
85 // IsValid returns true if the position is valid.
86 func (p Pos) IsValid() bool {
87         return p != NoPos
88 }
89
90
91 func searchFiles(a []*File, x int) int {
92         return sort.Search(len(a), func(i int) bool { return a[i].base > x }) - 1
93 }
94
95
96 func (s *FileSet) file(p Pos) *File {
97         if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size {
98                 return f
99         }
100         if i := searchFiles(s.files, int(p)); i >= 0 {
101                 f := s.files[i]
102                 // f.base <= int(p) by definition of searchFiles
103                 if int(p) <= f.base+f.size {
104                         s.last = f
105                         return f
106                 }
107         }
108         return nil
109 }
110
111
112 // File returns the file which contains the position p.
113 // If no such file is found (for instance for p == NoPos),
114 // the result is nil.
115 //
116 func (s *FileSet) File(p Pos) (f *File) {
117         if p != NoPos {
118                 s.mutex.RLock()
119                 f = s.file(p)
120                 s.mutex.RUnlock()
121         }
122         return
123 }
124
125
126 func (f *File) position(p Pos) (pos Position) {
127         offset := int(p) - f.base
128         pos.Offset = offset
129         pos.Filename, pos.Line, pos.Column = f.info(offset)
130         return
131 }
132
133
134 // Position converts a Pos in the fileset into a general Position.
135 func (s *FileSet) Position(p Pos) (pos Position) {
136         if p != NoPos {
137                 // TODO(gri) consider optimizing the case where p
138                 //           is in the last file addded, or perhaps
139                 //           looked at - will eliminate one level
140                 //           of search
141                 s.mutex.RLock()
142                 if f := s.file(p); f != nil {
143                         pos = f.position(p)
144                 }
145                 s.mutex.RUnlock()
146         }
147         return
148 }
149
150
151 type lineInfo struct {
152         offset   int
153         filename string
154         line     int
155 }
156
157
158 // AddLineInfo adds alternative file and line number information for
159 // a given file offset. The offset must be larger than the offset for
160 // the previously added alternative line info and smaller than the
161 // file size; otherwise the information is ignored.
162 //
163 // AddLineInfo is typically used to register alternative position
164 // information for //line filename:line comments in source files.
165 //
166 func (f *File) AddLineInfo(offset int, filename string, line int) {
167         f.set.mutex.Lock()
168         if i := len(f.infos); i == 0 || f.infos[i-1].offset < offset && offset < f.size {
169                 f.infos = append(f.infos, lineInfo{offset, filename, line})
170         }
171         f.set.mutex.Unlock()
172 }
173
174
175 // A File is a handle for a file belonging to a FileSet.
176 // A File has a name, size, and line offset table.
177 //
178 type File struct {
179         set  *FileSet
180         name string // file name as provided to AddFile
181         base int    // Pos value range for this file is [base...base+size]
182         size int    // file size as provided to AddFile
183
184         // lines and infos are protected by set.mutex
185         lines []int
186         infos []lineInfo
187 }
188
189
190 // Name returns the file name of file f as registered with AddFile.
191 func (f *File) Name() string {
192         return f.name
193 }
194
195
196 // Base returns the base offset of file f as registered with AddFile.
197 func (f *File) Base() int {
198         return f.base
199 }
200
201
202 // Size returns the size of file f as registered with AddFile.
203 func (f *File) Size() int {
204         return f.size
205 }
206
207
208 // LineCount returns the number of lines in file f.
209 func (f *File) LineCount() int {
210         f.set.mutex.RLock()
211         n := len(f.lines)
212         f.set.mutex.RUnlock()
213         return n
214 }
215
216
217 // AddLine adds the line offset for a new line.
218 // The line offset must be larger than the offset for the previous line
219 // and smaller than the file size; otherwise the line offset is ignored.
220 //
221 func (f *File) AddLine(offset int) {
222         f.set.mutex.Lock()
223         if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size {
224                 f.lines = append(f.lines, offset)
225         }
226         f.set.mutex.Unlock()
227 }
228
229
230 // SetLines sets the line offsets for a file and returns true if successful.
231 // The line offsets are the offsets of the first character of each line;
232 // for instance for the content "ab\nc\n" the line offsets are {0, 3}.
233 // An empty file has an empty line offset table.
234 // Each line offset must be larger than the offset for the previous line
235 // and smaller than the file size; otherwise SetLines fails and returns
236 // false.
237 //
238 func (f *File) SetLines(lines []int) bool {
239         // verify validity of lines table
240         size := f.size
241         for i, offset := range lines {
242                 if i > 0 && offset <= lines[i-1] || size <= offset {
243                         return false
244                 }
245         }
246
247         // set lines table
248         f.set.mutex.Lock()
249         f.lines = lines
250         f.set.mutex.Unlock()
251         return true
252 }
253
254
255 // SetLinesForContent sets the line offsets for the given file content.
256 func (f *File) SetLinesForContent(content []byte) {
257         var lines []int
258         line := 0
259         for offset, b := range content {
260                 if line >= 0 {
261                         lines = append(lines, line)
262                 }
263                 line = -1
264                 if b == '\n' {
265                         line = offset + 1
266                 }
267         }
268
269         // set lines table
270         f.set.mutex.Lock()
271         f.lines = lines
272         f.set.mutex.Unlock()
273 }
274
275
276 // Pos returns the Pos value for the given file offset;
277 // the offset must be <= f.Size().
278 // f.Pos(f.Offset(p)) == p.
279 //
280 func (f *File) Pos(offset int) Pos {
281         if offset > f.size {
282                 panic("illegal file offset")
283         }
284         return Pos(f.base + offset)
285 }
286
287
288 // Offset returns the offset for the given file position p;
289 // p must be a valid Pos value in that file.
290 // f.Offset(f.Pos(offset)) == offset.
291 //
292 func (f *File) Offset(p Pos) int {
293         if int(p) < f.base || int(p) > f.base+f.size {
294                 panic("illegal Pos value")
295         }
296         return int(p) - f.base
297 }
298
299
300 // Line returns the line number for the given file position p;
301 // p must be a Pos value in that file or NoPos.
302 //
303 func (f *File) Line(p Pos) int {
304         // TODO(gri) this can be implemented much more efficiently
305         return f.Position(p).Line
306 }
307
308
309 // Position returns the Position value for the given file position p;
310 // p must be a Pos value in that file or NoPos.
311 //
312 func (f *File) Position(p Pos) (pos Position) {
313         if p != NoPos {
314                 if int(p) < f.base || int(p) > f.base+f.size {
315                         panic("illegal Pos value")
316                 }
317                 pos = f.position(p)
318         }
319         return
320 }
321
322
323 func searchInts(a []int, x int) int {
324         // This function body is a manually inlined version of:
325         //
326         //   return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1
327         //
328         // With better compiler optimizations, this may not be needed in the
329         // future, but at the moment this change improves the go/printer
330         // benchmark performance by ~30%. This has a direct impact on the
331         // speed of gofmt and thus seems worthwhile (2011-04-29).
332         i, j := 0, len(a)
333         for i < j {
334                 h := i + (j-i)/2 // avoid overflow when computing h
335                 // i ≤ h < j
336                 if a[h] <= x {
337                         i = h + 1
338                 } else {
339                         j = h
340                 }
341         }
342         return i - 1
343 }
344
345
346 func searchLineInfos(a []lineInfo, x int) int {
347         return sort.Search(len(a), func(i int) bool { return a[i].offset > x }) - 1
348 }
349
350
351 // info returns the file name, line, and column number for a file offset.
352 func (f *File) info(offset int) (filename string, line, column int) {
353         filename = f.name
354         if i := searchInts(f.lines, offset); i >= 0 {
355                 line, column = i+1, offset-f.lines[i]+1
356         }
357         if len(f.infos) > 0 {
358                 // almost no files have extra line infos
359                 if i := searchLineInfos(f.infos, offset); i >= 0 {
360                         alt := &f.infos[i]
361                         filename = alt.filename
362                         if i := searchInts(f.lines, alt.offset); i >= 0 {
363                                 line += alt.line - i - 1
364                         }
365                 }
366         }
367         return
368 }
369
370
371 // A FileSet represents a set of source files.
372 // Methods of file sets are synchronized; multiple goroutines
373 // may invoke them concurrently.
374 //
375 type FileSet struct {
376         mutex sync.RWMutex // protects the file set
377         base  int          // base offset for the next file
378         files []*File      // list of files in the order added to the set
379         last  *File        // cache of last file looked up
380 }
381
382
383 // NewFileSet creates a new file set.
384 func NewFileSet() *FileSet {
385         s := new(FileSet)
386         s.base = 1 // 0 == NoPos
387         return s
388 }
389
390
391 // Base returns the minimum base offset that must be provided to
392 // AddFile when adding the next file.
393 //
394 func (s *FileSet) Base() int {
395         s.mutex.RLock()
396         b := s.base
397         s.mutex.RUnlock()
398         return b
399
400 }
401
402
403 // AddFile adds a new file with a given filename, base offset, and file size
404 // to the file set s and returns the file. Multiple files may have the same
405 // name. The base offset must not be smaller than the FileSet's Base(), and
406 // size must not be negative.
407 //
408 // Adding the file will set the file set's Base() value to base + size + 1
409 // as the minimum base value for the next file. The following relationship
410 // exists between a Pos value p for a given file offset offs:
411 //
412 //      int(p) = base + offs
413 //
414 // with offs in the range [0, size] and thus p in the range [base, base+size].
415 // For convenience, File.Pos may be used to create file-specific position
416 // values from a file offset.
417 //
418 func (s *FileSet) AddFile(filename string, base, size int) *File {
419         s.mutex.Lock()
420         defer s.mutex.Unlock()
421         if base < s.base || size < 0 {
422                 panic("illegal base or size")
423         }
424         // base >= s.base && size >= 0
425         f := &File{s, filename, base, size, []int{0}, nil}
426         base += size + 1 // +1 because EOF also has a position
427         if base < 0 {
428                 panic("token.Pos offset overflow (> 2G of source code in file set)")
429         }
430         // add the file to the file set
431         s.base = base
432         s.files = append(s.files, f)
433         s.last = f
434         return f
435 }
436
437
438 // Files returns the files added to the file set.
439 func (s *FileSet) Files() <-chan *File {
440         ch := make(chan *File)
441         go func() {
442                 for i := 0; ; i++ {
443                         var f *File
444                         s.mutex.RLock()
445                         if i < len(s.files) {
446                                 f = s.files[i]
447                         }
448                         s.mutex.RUnlock()
449                         if f == nil {
450                                 break
451                         }
452                         ch <- f
453                 }
454                 close(ch)
455         }()
456         return ch
457 }