...

Source file src/go/token/position.go

Documentation: go/token

		 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
		 6  
		 7  import (
		 8  	"fmt"
		 9  	"sort"
		10  	"sync"
		11  )
		12  
		13  // -----------------------------------------------------------------------------
		14  // Positions
		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 (byte count)
		25  }
		26  
		27  // IsValid reports whether the position is valid.
		28  func (pos *Position) IsValid() bool { return pos.Line > 0 }
		29  
		30  // String returns a string in one of several forms:
		31  //
		32  //	file:line:column		valid position with file name
		33  //	file:line					 valid position with file name but no column (column == 0)
		34  //	line:column				 valid position without file name
		35  //	line								valid position without file name and no column (column == 0)
		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", pos.Line)
		46  		if pos.Column != 0 {
		47  			s += fmt.Sprintf(":%d", pos.Column)
		48  		}
		49  	}
		50  	if s == "" {
		51  		s = "-"
		52  	}
		53  	return s
		54  }
		55  
		56  // Pos is a compact encoding of a source position within a file set.
		57  // It can be converted into a Position for a more convenient, but much
		58  // larger, representation.
		59  //
		60  // The Pos value for a given file is a number in the range [base, base+size],
		61  // where base and size are specified when a file is added to the file set.
		62  // The difference between a Pos value and the corresponding file base
		63  // corresponds to the byte offset of that position (represented by the Pos value)
		64  // from the beginning of the file. Thus, the file base offset is the Pos value
		65  // representing the first byte in the file.
		66  //
		67  // To create the Pos value for a specific source offset (measured in bytes),
		68  // first add the respective file to the current file set using FileSet.AddFile
		69  // and then call File.Pos(offset) for that file. Given a Pos value p
		70  // for a specific file set fset, the corresponding Position value is
		71  // obtained by calling fset.Position(p).
		72  //
		73  // Pos values can be compared directly with the usual comparison operators:
		74  // If two Pos values p and q are in the same file, comparing p and q is
		75  // equivalent to comparing the respective source file offsets. If p and q
		76  // are in different files, p < q is true if the file implied by p was added
		77  // to the respective file set before the file implied by q.
		78  //
		79  type Pos int
		80  
		81  // The zero value for Pos is NoPos; there is no file and line information
		82  // associated with it, and NoPos.IsValid() is false. NoPos is always
		83  // smaller than any other Pos value. The corresponding Position value
		84  // for NoPos is the zero value for Position.
		85  //
		86  const NoPos Pos = 0
		87  
		88  // IsValid reports whether the position is valid.
		89  func (p Pos) IsValid() bool {
		90  	return p != NoPos
		91  }
		92  
		93  // -----------------------------------------------------------------------------
		94  // File
		95  
		96  // A File is a handle for a file belonging to a FileSet.
		97  // A File has a name, size, and line offset table.
		98  //
		99  type File struct {
	 100  	set	*FileSet
	 101  	name string // file name as provided to AddFile
	 102  	base int		// Pos value range for this file is [base...base+size]
	 103  	size int		// file size as provided to AddFile
	 104  
	 105  	// lines and infos are protected by mutex
	 106  	mutex sync.Mutex
	 107  	lines []int // lines contains the offset of the first character for each line (the first entry is always 0)
	 108  	infos []lineInfo
	 109  }
	 110  
	 111  // Name returns the file name of file f as registered with AddFile.
	 112  func (f *File) Name() string {
	 113  	return f.name
	 114  }
	 115  
	 116  // Base returns the base offset of file f as registered with AddFile.
	 117  func (f *File) Base() int {
	 118  	return f.base
	 119  }
	 120  
	 121  // Size returns the size of file f as registered with AddFile.
	 122  func (f *File) Size() int {
	 123  	return f.size
	 124  }
	 125  
	 126  // LineCount returns the number of lines in file f.
	 127  func (f *File) LineCount() int {
	 128  	f.mutex.Lock()
	 129  	n := len(f.lines)
	 130  	f.mutex.Unlock()
	 131  	return n
	 132  }
	 133  
	 134  // AddLine adds the line offset for a new line.
	 135  // The line offset must be larger than the offset for the previous line
	 136  // and smaller than the file size; otherwise the line offset is ignored.
	 137  //
	 138  func (f *File) AddLine(offset int) {
	 139  	f.mutex.Lock()
	 140  	if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size {
	 141  		f.lines = append(f.lines, offset)
	 142  	}
	 143  	f.mutex.Unlock()
	 144  }
	 145  
	 146  // MergeLine merges a line with the following line. It is akin to replacing
	 147  // the newline character at the end of the line with a space (to not change the
	 148  // remaining offsets). To obtain the line number, consult e.g. Position.Line.
	 149  // MergeLine will panic if given an invalid line number.
	 150  //
	 151  func (f *File) MergeLine(line int) {
	 152  	if line < 1 {
	 153  		panic(fmt.Sprintf("invalid line number %d (should be >= 1)", line))
	 154  	}
	 155  	f.mutex.Lock()
	 156  	defer f.mutex.Unlock()
	 157  	if line >= len(f.lines) {
	 158  		panic(fmt.Sprintf("invalid line number %d (should be < %d)", line, len(f.lines)))
	 159  	}
	 160  	// To merge the line numbered <line> with the line numbered <line+1>,
	 161  	// we need to remove the entry in lines corresponding to the line
	 162  	// numbered <line+1>. The entry in lines corresponding to the line
	 163  	// numbered <line+1> is located at index <line>, since indices in lines
	 164  	// are 0-based and line numbers are 1-based.
	 165  	copy(f.lines[line:], f.lines[line+1:])
	 166  	f.lines = f.lines[:len(f.lines)-1]
	 167  }
	 168  
	 169  // SetLines sets the line offsets for a file and reports whether it succeeded.
	 170  // The line offsets are the offsets of the first character of each line;
	 171  // for instance for the content "ab\nc\n" the line offsets are {0, 3}.
	 172  // An empty file has an empty line offset table.
	 173  // Each line offset must be larger than the offset for the previous line
	 174  // and smaller than the file size; otherwise SetLines fails and returns
	 175  // false.
	 176  // Callers must not mutate the provided slice after SetLines returns.
	 177  //
	 178  func (f *File) SetLines(lines []int) bool {
	 179  	// verify validity of lines table
	 180  	size := f.size
	 181  	for i, offset := range lines {
	 182  		if i > 0 && offset <= lines[i-1] || size <= offset {
	 183  			return false
	 184  		}
	 185  	}
	 186  
	 187  	// set lines table
	 188  	f.mutex.Lock()
	 189  	f.lines = lines
	 190  	f.mutex.Unlock()
	 191  	return true
	 192  }
	 193  
	 194  // SetLinesForContent sets the line offsets for the given file content.
	 195  // It ignores position-altering //line comments.
	 196  func (f *File) SetLinesForContent(content []byte) {
	 197  	var lines []int
	 198  	line := 0
	 199  	for offset, b := range content {
	 200  		if line >= 0 {
	 201  			lines = append(lines, line)
	 202  		}
	 203  		line = -1
	 204  		if b == '\n' {
	 205  			line = offset + 1
	 206  		}
	 207  	}
	 208  
	 209  	// set lines table
	 210  	f.mutex.Lock()
	 211  	f.lines = lines
	 212  	f.mutex.Unlock()
	 213  }
	 214  
	 215  // LineStart returns the Pos value of the start of the specified line.
	 216  // It ignores any alternative positions set using AddLineColumnInfo.
	 217  // LineStart panics if the 1-based line number is invalid.
	 218  func (f *File) LineStart(line int) Pos {
	 219  	if line < 1 {
	 220  		panic(fmt.Sprintf("invalid line number %d (should be >= 1)", line))
	 221  	}
	 222  	f.mutex.Lock()
	 223  	defer f.mutex.Unlock()
	 224  	if line > len(f.lines) {
	 225  		panic(fmt.Sprintf("invalid line number %d (should be < %d)", line, len(f.lines)))
	 226  	}
	 227  	return Pos(f.base + f.lines[line-1])
	 228  }
	 229  
	 230  // A lineInfo object describes alternative file, line, and column
	 231  // number information (such as provided via a //line directive)
	 232  // for a given file offset.
	 233  type lineInfo struct {
	 234  	// fields are exported to make them accessible to gob
	 235  	Offset			 int
	 236  	Filename		 string
	 237  	Line, Column int
	 238  }
	 239  
	 240  // AddLineInfo is like AddLineColumnInfo with a column = 1 argument.
	 241  // It is here for backward-compatibility for code prior to Go 1.11.
	 242  //
	 243  func (f *File) AddLineInfo(offset int, filename string, line int) {
	 244  	f.AddLineColumnInfo(offset, filename, line, 1)
	 245  }
	 246  
	 247  // AddLineColumnInfo adds alternative file, line, and column number
	 248  // information for a given file offset. The offset must be larger
	 249  // than the offset for the previously added alternative line info
	 250  // and smaller than the file size; otherwise the information is
	 251  // ignored.
	 252  //
	 253  // AddLineColumnInfo is typically used to register alternative position
	 254  // information for line directives such as //line filename:line:column.
	 255  //
	 256  func (f *File) AddLineColumnInfo(offset int, filename string, line, column int) {
	 257  	f.mutex.Lock()
	 258  	if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size {
	 259  		f.infos = append(f.infos, lineInfo{offset, filename, line, column})
	 260  	}
	 261  	f.mutex.Unlock()
	 262  }
	 263  
	 264  // Pos returns the Pos value for the given file offset;
	 265  // the offset must be <= f.Size().
	 266  // f.Pos(f.Offset(p)) == p.
	 267  //
	 268  func (f *File) Pos(offset int) Pos {
	 269  	if offset > f.size {
	 270  		panic(fmt.Sprintf("invalid file offset %d (should be <= %d)", offset, f.size))
	 271  	}
	 272  	return Pos(f.base + offset)
	 273  }
	 274  
	 275  // Offset returns the offset for the given file position p;
	 276  // p must be a valid Pos value in that file.
	 277  // f.Offset(f.Pos(offset)) == offset.
	 278  //
	 279  func (f *File) Offset(p Pos) int {
	 280  	if int(p) < f.base || int(p) > f.base+f.size {
	 281  		panic(fmt.Sprintf("invalid Pos value %d (should be in [%d, %d])", p, f.base, f.base+f.size))
	 282  	}
	 283  	return int(p) - f.base
	 284  }
	 285  
	 286  // Line returns the line number for the given file position p;
	 287  // p must be a Pos value in that file or NoPos.
	 288  //
	 289  func (f *File) Line(p Pos) int {
	 290  	return f.Position(p).Line
	 291  }
	 292  
	 293  func searchLineInfos(a []lineInfo, x int) int {
	 294  	return sort.Search(len(a), func(i int) bool { return a[i].Offset > x }) - 1
	 295  }
	 296  
	 297  // unpack returns the filename and line and column number for a file offset.
	 298  // If adjusted is set, unpack will return the filename and line information
	 299  // possibly adjusted by //line comments; otherwise those comments are ignored.
	 300  //
	 301  func (f *File) unpack(offset int, adjusted bool) (filename string, line, column int) {
	 302  	f.mutex.Lock()
	 303  	defer f.mutex.Unlock()
	 304  	filename = f.name
	 305  	if i := searchInts(f.lines, offset); i >= 0 {
	 306  		line, column = i+1, offset-f.lines[i]+1
	 307  	}
	 308  	if adjusted && len(f.infos) > 0 {
	 309  		// few files have extra line infos
	 310  		if i := searchLineInfos(f.infos, offset); i >= 0 {
	 311  			alt := &f.infos[i]
	 312  			filename = alt.Filename
	 313  			if i := searchInts(f.lines, alt.Offset); i >= 0 {
	 314  				// i+1 is the line at which the alternative position was recorded
	 315  				d := line - (i + 1) // line distance from alternative position base
	 316  				line = alt.Line + d
	 317  				if alt.Column == 0 {
	 318  					// alternative column is unknown => relative column is unknown
	 319  					// (the current specification for line directives requires
	 320  					// this to apply until the next PosBase/line directive,
	 321  					// not just until the new newline)
	 322  					column = 0
	 323  				} else if d == 0 {
	 324  					// the alternative position base is on the current line
	 325  					// => column is relative to alternative column
	 326  					column = alt.Column + (offset - alt.Offset)
	 327  				}
	 328  			}
	 329  		}
	 330  	}
	 331  	return
	 332  }
	 333  
	 334  func (f *File) position(p Pos, adjusted bool) (pos Position) {
	 335  	offset := int(p) - f.base
	 336  	pos.Offset = offset
	 337  	pos.Filename, pos.Line, pos.Column = f.unpack(offset, adjusted)
	 338  	return
	 339  }
	 340  
	 341  // PositionFor returns the Position value for the given file position p.
	 342  // If adjusted is set, the position may be adjusted by position-altering
	 343  // //line comments; otherwise those comments are ignored.
	 344  // p must be a Pos value in f or NoPos.
	 345  //
	 346  func (f *File) PositionFor(p Pos, adjusted bool) (pos Position) {
	 347  	if p != NoPos {
	 348  		if int(p) < f.base || int(p) > f.base+f.size {
	 349  			panic(fmt.Sprintf("invalid Pos value %d (should be in [%d, %d])", p, f.base, f.base+f.size))
	 350  		}
	 351  		pos = f.position(p, adjusted)
	 352  	}
	 353  	return
	 354  }
	 355  
	 356  // Position returns the Position value for the given file position p.
	 357  // Calling f.Position(p) is equivalent to calling f.PositionFor(p, true).
	 358  //
	 359  func (f *File) Position(p Pos) (pos Position) {
	 360  	return f.PositionFor(p, true)
	 361  }
	 362  
	 363  // -----------------------------------------------------------------------------
	 364  // FileSet
	 365  
	 366  // A FileSet represents a set of source files.
	 367  // Methods of file sets are synchronized; multiple goroutines
	 368  // may invoke them concurrently.
	 369  //
	 370  // The byte offsets for each file in a file set are mapped into
	 371  // distinct (integer) intervals, one interval [base, base+size]
	 372  // per file. Base represents the first byte in the file, and size
	 373  // is the corresponding file size. A Pos value is a value in such
	 374  // an interval. By determining the interval a Pos value belongs
	 375  // to, the file, its file base, and thus the byte offset (position)
	 376  // the Pos value is representing can be computed.
	 377  //
	 378  // When adding a new file, a file base must be provided. That can
	 379  // be any integer value that is past the end of any interval of any
	 380  // file already in the file set. For convenience, FileSet.Base provides
	 381  // such a value, which is simply the end of the Pos interval of the most
	 382  // recently added file, plus one. Unless there is a need to extend an
	 383  // interval later, using the FileSet.Base should be used as argument
	 384  // for FileSet.AddFile.
	 385  //
	 386  type FileSet struct {
	 387  	mutex sync.RWMutex // protects the file set
	 388  	base	int					// base offset for the next file
	 389  	files []*File			// list of files in the order added to the set
	 390  	last	*File				// cache of last file looked up
	 391  }
	 392  
	 393  // NewFileSet creates a new file set.
	 394  func NewFileSet() *FileSet {
	 395  	return &FileSet{
	 396  		base: 1, // 0 == NoPos
	 397  	}
	 398  }
	 399  
	 400  // Base returns the minimum base offset that must be provided to
	 401  // AddFile when adding the next file.
	 402  //
	 403  func (s *FileSet) Base() int {
	 404  	s.mutex.RLock()
	 405  	b := s.base
	 406  	s.mutex.RUnlock()
	 407  	return b
	 408  
	 409  }
	 410  
	 411  // AddFile adds a new file with a given filename, base offset, and file size
	 412  // to the file set s and returns the file. Multiple files may have the same
	 413  // name. The base offset must not be smaller than the FileSet's Base(), and
	 414  // size must not be negative. As a special case, if a negative base is provided,
	 415  // the current value of the FileSet's Base() is used instead.
	 416  //
	 417  // Adding the file will set the file set's Base() value to base + size + 1
	 418  // as the minimum base value for the next file. The following relationship
	 419  // exists between a Pos value p for a given file offset offs:
	 420  //
	 421  //	int(p) = base + offs
	 422  //
	 423  // with offs in the range [0, size] and thus p in the range [base, base+size].
	 424  // For convenience, File.Pos may be used to create file-specific position
	 425  // values from a file offset.
	 426  //
	 427  func (s *FileSet) AddFile(filename string, base, size int) *File {
	 428  	s.mutex.Lock()
	 429  	defer s.mutex.Unlock()
	 430  	if base < 0 {
	 431  		base = s.base
	 432  	}
	 433  	if base < s.base {
	 434  		panic(fmt.Sprintf("invalid base %d (should be >= %d)", base, s.base))
	 435  	}
	 436  	if size < 0 {
	 437  		panic(fmt.Sprintf("invalid size %d (should be >= 0)", size))
	 438  	}
	 439  	// base >= s.base && size >= 0
	 440  	f := &File{set: s, name: filename, base: base, size: size, lines: []int{0}}
	 441  	base += size + 1 // +1 because EOF also has a position
	 442  	if base < 0 {
	 443  		panic("token.Pos offset overflow (> 2G of source code in file set)")
	 444  	}
	 445  	// add the file to the file set
	 446  	s.base = base
	 447  	s.files = append(s.files, f)
	 448  	s.last = f
	 449  	return f
	 450  }
	 451  
	 452  // Iterate calls f for the files in the file set in the order they were added
	 453  // until f returns false.
	 454  //
	 455  func (s *FileSet) Iterate(f func(*File) bool) {
	 456  	for i := 0; ; i++ {
	 457  		var file *File
	 458  		s.mutex.RLock()
	 459  		if i < len(s.files) {
	 460  			file = s.files[i]
	 461  		}
	 462  		s.mutex.RUnlock()
	 463  		if file == nil || !f(file) {
	 464  			break
	 465  		}
	 466  	}
	 467  }
	 468  
	 469  func searchFiles(a []*File, x int) int {
	 470  	return sort.Search(len(a), func(i int) bool { return a[i].base > x }) - 1
	 471  }
	 472  
	 473  func (s *FileSet) file(p Pos) *File {
	 474  	s.mutex.RLock()
	 475  	// common case: p is in last file
	 476  	if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size {
	 477  		s.mutex.RUnlock()
	 478  		return f
	 479  	}
	 480  	// p is not in last file - search all files
	 481  	if i := searchFiles(s.files, int(p)); i >= 0 {
	 482  		f := s.files[i]
	 483  		// f.base <= int(p) by definition of searchFiles
	 484  		if int(p) <= f.base+f.size {
	 485  			s.mutex.RUnlock()
	 486  			s.mutex.Lock()
	 487  			s.last = f // race is ok - s.last is only a cache
	 488  			s.mutex.Unlock()
	 489  			return f
	 490  		}
	 491  	}
	 492  	s.mutex.RUnlock()
	 493  	return nil
	 494  }
	 495  
	 496  // File returns the file that contains the position p.
	 497  // If no such file is found (for instance for p == NoPos),
	 498  // the result is nil.
	 499  //
	 500  func (s *FileSet) File(p Pos) (f *File) {
	 501  	if p != NoPos {
	 502  		f = s.file(p)
	 503  	}
	 504  	return
	 505  }
	 506  
	 507  // PositionFor converts a Pos p in the fileset into a Position value.
	 508  // If adjusted is set, the position may be adjusted by position-altering
	 509  // //line comments; otherwise those comments are ignored.
	 510  // p must be a Pos value in s or NoPos.
	 511  //
	 512  func (s *FileSet) PositionFor(p Pos, adjusted bool) (pos Position) {
	 513  	if p != NoPos {
	 514  		if f := s.file(p); f != nil {
	 515  			return f.position(p, adjusted)
	 516  		}
	 517  	}
	 518  	return
	 519  }
	 520  
	 521  // Position converts a Pos p in the fileset into a Position value.
	 522  // Calling s.Position(p) is equivalent to calling s.PositionFor(p, true).
	 523  //
	 524  func (s *FileSet) Position(p Pos) (pos Position) {
	 525  	return s.PositionFor(p, true)
	 526  }
	 527  
	 528  // -----------------------------------------------------------------------------
	 529  // Helper functions
	 530  
	 531  func searchInts(a []int, x int) int {
	 532  	// This function body is a manually inlined version of:
	 533  	//
	 534  	//	 return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1
	 535  	//
	 536  	// With better compiler optimizations, this may not be needed in the
	 537  	// future, but at the moment this change improves the go/printer
	 538  	// benchmark performance by ~30%. This has a direct impact on the
	 539  	// speed of gofmt and thus seems worthwhile (2011-04-29).
	 540  	// TODO(gri): Remove this when compilers have caught up.
	 541  	i, j := 0, len(a)
	 542  	for i < j {
	 543  		h := i + (j-i)>>1 // avoid overflow when computing h
	 544  		// i ≤ h < j
	 545  		if a[h] <= x {
	 546  			i = h + 1
	 547  		} else {
	 548  			j = h
	 549  		}
	 550  	}
	 551  	return i - 1
	 552  }
	 553  

View as plain text