Source File
position.go
Belonging Package
go/token
// Copyright 2010 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package tokenimport ()// If debug is set, invalid offset and position values cause a panic// (go.dev/issue/57490).const debug = false// -----------------------------------------------------------------------------// Positions// Position describes an arbitrary source position// including the file, line, and column location.// A Position is valid if the line number is > 0.type Position struct {Filename string // filename, if anyOffset int // offset, starting at 0Line int // line number, starting at 1Column int // column number, starting at 1 (byte count)}// IsValid reports whether the position is valid.func ( *Position) () bool { return .Line > 0 }// String returns a string in one of several forms://// file:line:column valid position with file name// file:line valid position with file name but no column (column == 0)// line:column valid position without file name// line valid position without file name and no column (column == 0)// file invalid position with file name// - invalid position without file namefunc ( Position) () string {:= .Filenameif .IsValid() {if != "" {+= ":"}+= strconv.Itoa(.Line)if .Column != 0 {+= fmt.Sprintf(":%d", .Column)}}if == "" {= "-"}return}// Pos is a compact encoding of a source position within a file set.// It can be converted into a [Position] for a more convenient, but much// larger, representation.//// The Pos value for a given file is a number in the range [base, base+size],// where base and size are specified when a file is added to the file set.// The difference between a Pos value and the corresponding file base// corresponds to the byte offset of that position (represented by the Pos value)// from the beginning of the file. Thus, the file base offset is the Pos value// representing the first byte in the file.//// To create the Pos value for a specific source offset (measured in bytes),// first add the respective file to the current file set using [FileSet.AddFile]// and then call [File.Pos](offset) for that file. Given a Pos value p// for a specific file set fset, the corresponding [Position] value is// obtained by calling fset.Position(p).//// Pos values can be compared directly with the usual comparison operators:// If two Pos values p and q are in the same file, comparing p and q is// equivalent to comparing the respective source file offsets. If p and q// are in different files, p < q is true if the file implied by p was added// to the respective file set before the file implied by q.type Pos int// The zero value for [Pos] is NoPos; there is no file and line information// associated with it, and NoPos.IsValid() is false. NoPos is always// smaller than any other [Pos] value. The corresponding [Position] value// for NoPos is the zero value for [Position].const NoPos Pos = 0// IsValid reports whether the position is valid.func ( Pos) () bool {return != NoPos}// -----------------------------------------------------------------------------// File// A File is a handle for a file belonging to a [FileSet].// A File has a name, size, and line offset table.//// Use [FileSet.AddFile] to create a File.// A File may belong to more than one FileSet; see [FileSet.AddExistingFiles].type File struct {name string // file name as provided to AddFilebase int // Pos value range for this file is [base...base+size]size int // file size as provided to AddFile// lines and infos are protected by mutexmutex sync.Mutexlines []int // lines contains the offset of the first character for each line (the first entry is always 0)infos []lineInfo}// Name returns the file name of file f as registered with AddFile.func ( *File) () string {return .name}// Base returns the base offset of file f as registered with AddFile.func ( *File) () int {return .base}// Size returns the size of file f as registered with AddFile.func ( *File) () int {return .size}// LineCount returns the number of lines in file f.func ( *File) () int {.mutex.Lock():= len(.lines).mutex.Unlock()return}// AddLine adds the line offset for a new line.// The line offset must be larger than the offset for the previous line// and smaller than the file size; otherwise the line offset is ignored.func ( *File) ( int) {.mutex.Lock()if := len(.lines); ( == 0 || .lines[-1] < ) && < .size {.lines = append(.lines, )}.mutex.Unlock()}// MergeLine merges a line with the following line. It is akin to replacing// the newline character at the end of the line with a space (to not change the// remaining offsets). To obtain the line number, consult e.g. [Position.Line].// MergeLine will panic if given an invalid line number.func ( *File) ( int) {if < 1 {panic(fmt.Sprintf("invalid line number %d (should be >= 1)", ))}.mutex.Lock()defer .mutex.Unlock()if >= len(.lines) {panic(fmt.Sprintf("invalid line number %d (should be < %d)", , len(.lines)))}// To merge the line numbered <line> with the line numbered <line+1>,// we need to remove the entry in lines corresponding to the line// numbered <line+1>. The entry in lines corresponding to the line// numbered <line+1> is located at index <line>, since indices in lines// are 0-based and line numbers are 1-based.copy(.lines[:], .lines[+1:]).lines = .lines[:len(.lines)-1]}// Lines returns the effective line offset table of the form described by [File.SetLines].// Callers must not mutate the result.func ( *File) () []int {.mutex.Lock():= .lines.mutex.Unlock()return}// SetLines sets the line offsets for a file and reports whether it succeeded.// The line offsets are the offsets of the first character of each line;// for instance for the content "ab\nc\n" the line offsets are {0, 3}.// An empty file has an empty line offset table.// Each line offset must be larger than the offset for the previous line// and smaller than the file size; otherwise SetLines fails and returns// false.// Callers must not mutate the provided slice after SetLines returns.func ( *File) ( []int) bool {// verify validity of lines table:= .sizefor , := range {if > 0 && <= [-1] || <= {return false}}// set lines table.mutex.Lock().lines =.mutex.Unlock()return true}// SetLinesForContent sets the line offsets for the given file content.// It ignores position-altering //line comments.func ( *File) ( []byte) {var []int:= 0for , := range {if >= 0 {= append(, )}= -1if == '\n' {= + 1}}// set lines table.mutex.Lock().lines =.mutex.Unlock()}// LineStart returns the [Pos] value of the start of the specified line.// It ignores any alternative positions set using [File.AddLineColumnInfo].// LineStart panics if the 1-based line number is invalid.func ( *File) ( int) Pos {if < 1 {panic(fmt.Sprintf("invalid line number %d (should be >= 1)", ))}.mutex.Lock()defer .mutex.Unlock()if > len(.lines) {panic(fmt.Sprintf("invalid line number %d (should be < %d)", , len(.lines)))}return Pos(.base + .lines[-1])}// A lineInfo object describes alternative file, line, and column// number information (such as provided via a //line directive)// for a given file offset.type lineInfo struct {// fields are exported to make them accessible to gobOffset intFilename stringLine, Column int}// AddLineInfo is like [File.AddLineColumnInfo] with a column = 1 argument.// It is here for backward-compatibility for code prior to Go 1.11.func ( *File) ( int, string, int) {.AddLineColumnInfo(, , , 1)}// AddLineColumnInfo adds alternative file, line, and column number// information for a given file offset. The offset must be larger// than the offset for the previously added alternative line info// and smaller than the file size; otherwise the information is// ignored.//// AddLineColumnInfo is typically used to register alternative position// information for line directives such as //line filename:line:column.func ( *File) ( int, string, , int) {.mutex.Lock()if := len(.infos); ( == 0 || .infos[-1].Offset < ) && < .size {.infos = append(.infos, lineInfo{, , , })}.mutex.Unlock()}// fixOffset fixes an out-of-bounds offset such that 0 <= offset <= f.size.func ( *File) ( int) int {switch {case < 0:if !debug {return 0}case > .size:if !debug {return .size}default:return}// only generate this code if neededif debug {panic(fmt.Sprintf("offset %d out of bounds [%d, %d] (position %d out of bounds [%d, %d])",0 /* for symmetry */, , .size,.base+, .base, .base+.size))}return 0}// Pos returns the Pos value for the given file offset.//// If offset is negative, the result is the file's start// position; if the offset is too large, the result is// the file's end position (see also go.dev/issue/57490).//// The following invariant, though not true for Pos values// in general, holds for the result p:// f.Pos(f.Offset(p)) == p.func ( *File) ( int) Pos {return Pos(.base + .fixOffset())}// Offset returns the offset for the given file position p.//// If p is before the file's start position (or if p is NoPos),// the result is 0; if p is past the file's end position,// the result is the file size (see also go.dev/issue/57490).//// The following invariant, though not true for offset values// in general, holds for the result offset:// f.Offset(f.Pos(offset)) == offsetfunc ( *File) ( Pos) int {return .fixOffset(int() - .base)}// Line returns the line number for the given file position p;// p must be a [Pos] value in that file or [NoPos].func ( *File) ( Pos) int {return .Position().Line}func searchLineInfos( []lineInfo, int) int {, := slices.BinarySearchFunc(, , func( lineInfo, int) int {return cmp.Compare(.Offset, )})if ! {// We want the lineInfo containing x, but if we didn't// find x then i is the next one.--}return}// unpack returns the filename and line and column number for a file offset.// If adjusted is set, unpack will return the filename and line information// possibly adjusted by //line comments; otherwise those comments are ignored.func ( *File) ( int, bool) ( string, , int) {.mutex.Lock()= .nameif := searchInts(.lines, ); >= 0 {, = +1, -.lines[]+1}if && len(.infos) > 0 {// few files have extra line infosif := searchLineInfos(.infos, ); >= 0 {:= &.infos[]= .Filenameif := searchInts(.lines, .Offset); >= 0 {// i+1 is the line at which the alternative position was recorded:= - ( + 1) // line distance from alternative position base= .Line +if .Column == 0 {// alternative column is unknown => relative column is unknown// (the current specification for line directives requires// this to apply until the next PosBase/line directive,// not just until the new newline)= 0} else if == 0 {// the alternative position base is on the current line// => column is relative to alternative column= .Column + ( - .Offset)}}}}// TODO(mvdan): move Unlock back under Lock with a defer statement once// https://go.dev/issue/38471 is fixed to remove the performance penalty..mutex.Unlock()return}func ( *File) ( Pos, bool) ( Position) {:= .fixOffset(int() - .base).Offset =.Filename, .Line, .Column = .unpack(, )return}// PositionFor returns the Position value for the given file position p.// If p is out of bounds, it is adjusted to match the File.Offset behavior.// If adjusted is set, the position may be adjusted by position-altering// //line comments; otherwise those comments are ignored.// p must be a Pos value in f or NoPos.func ( *File) ( Pos, bool) ( Position) {if != NoPos {= .position(, )}return}// Position returns the Position value for the given file position p.// If p is out of bounds, it is adjusted to match the File.Offset behavior.// Calling f.Position(p) is equivalent to calling f.PositionFor(p, true).func ( *File) ( Pos) ( Position) {return .PositionFor(, true)}// -----------------------------------------------------------------------------// FileSet// A FileSet represents a set of source files.// Methods of file sets are synchronized; multiple goroutines// may invoke them concurrently.//// The byte offsets for each file in a file set are mapped into// distinct (integer) intervals, one interval [base, base+size]// per file. [FileSet.Base] represents the first byte in the file, and size// is the corresponding file size. A [Pos] value is a value in such// an interval. By determining the interval a [Pos] value belongs// to, the file, its file base, and thus the byte offset (position)// the [Pos] value is representing can be computed.//// When adding a new file, a file base must be provided. That can// be any integer value that is past the end of any interval of any// file already in the file set. For convenience, [FileSet.Base] provides// such a value, which is simply the end of the Pos interval of the most// recently added file, plus one. Unless there is a need to extend an// interval later, using the [FileSet.Base] should be used as argument// for [FileSet.AddFile].//// A [File] may be removed from a FileSet when it is no longer needed.// This may reduce memory usage in a long-running application.type FileSet struct {mutex sync.RWMutex // protects the file setbase int // base offset for the next filetree tree // tree of files in ascending base orderlast atomic.Pointer[File] // cache of last file looked up}// NewFileSet creates a new file set.func () *FileSet {return &FileSet{base: 1, // 0 == NoPos}}// Base returns the minimum base offset that must be provided to// [FileSet.AddFile] when adding the next file.func ( *FileSet) () int {.mutex.RLock():= .base.mutex.RUnlock()return}// AddFile adds a new file with a given filename, base offset, and file size// to the file set s and returns the file. Multiple files may have the same// name. The base offset must not be smaller than the [FileSet.Base], and// size must not be negative. As a special case, if a negative base is provided,// the current value of the [FileSet.Base] is used instead.//// Adding the file will set the file set's [FileSet.Base] value to base + size + 1// as the minimum base value for the next file. The following relationship// exists between a [Pos] value p for a given file offset offs://// int(p) = base + offs//// with offs in the range [0, size] and thus p in the range [base, base+size].// For convenience, [File.Pos] may be used to create file-specific position// values from a file offset.func ( *FileSet) ( string, , int) *File {// Allocate f outside the critical section.:= &File{name: , size: , lines: []int{0}}.mutex.Lock()defer .mutex.Unlock()if < 0 {= .base}if < .base {panic(fmt.Sprintf("invalid base %d (should be >= %d)", , .base))}.base =if < 0 {panic(fmt.Sprintf("invalid size %d (should be >= 0)", ))}// base >= s.base && size >= 0+= + 1 // +1 because EOF also has a positionif < 0 {panic("token.Pos offset overflow (> 2G of source code in file set)")}// add the file to the file set.base =.tree.add().last.Store()return}// AddExistingFiles adds the specified files to the// FileSet if they are not already present.// The caller must ensure that no pair of Files that// would appear in the resulting FileSet overlap.func ( *FileSet) ( ...*File) {// This function cannot be implemented as://// for _, file := range files {// if prev := fset.File(token.Pos(file.Base())); prev != nil {// if prev != file {// panic("FileSet contains a different file at the same base")// }// continue// }// file2 := fset.AddFile(file.Name(), file.Base(), file.Size())// file2.SetLines(file.Lines())// }//// because all calls to AddFile must be in increasing order.// AddExistingFilesFiles lets us augment an existing FileSet// sequentially, so long as all sets of files have disjoint ranges.// This approach also does not preserve line directives..mutex.Lock()defer .mutex.Unlock()for , := range {.tree.add().base = max(.base, .Base()+.Size()+1)}}// RemoveFile removes a file from the [FileSet] so that subsequent// queries for its [Pos] interval yield a negative result.// This reduces the memory usage of a long-lived [FileSet] that// encounters an unbounded stream of files.//// Removing a file that does not belong to the set has no effect.func ( *FileSet) ( *File) {.last.CompareAndSwap(, nil) // clear last file cache.mutex.Lock()defer .mutex.Unlock(), := .tree.locate(.key())if * != nil && (*).file == {.tree.delete()}}// Iterate calls yield for the files in the file set in ascending Base// order until yield returns false.func ( *FileSet) ( func(*File) bool) {.mutex.RLock()defer .mutex.RUnlock()// Unlock around user code.// The iterator is robust to modification by yield.// Avoid range here, so we can use defer..tree.all()(func( *File) bool {.mutex.RUnlock()defer .mutex.RLock()return ()})}func ( *FileSet) ( Pos) *File {// common case: p is in last file.if := .last.Load(); != nil && .base <= int() && int() <= .base+.size {return}.mutex.RLock()defer .mutex.RUnlock(), := .tree.locate(key{int(), int()})if := *; != nil {// Update cache of last file. A race is ok,// but an exclusive lock causes heavy contention..last.Store(.file)return .file}return nil}// File returns the file that contains the position p.// If no such file is found (for instance for p == [NoPos]),// the result is nil.func ( *FileSet) ( Pos) ( *File) {if != NoPos {= .file()}return}// PositionFor converts a [Pos] p in the fileset into a [Position] value.// If adjusted is set, the position may be adjusted by position-altering// //line comments; otherwise those comments are ignored.// p must be a [Pos] value in s or [NoPos].func ( *FileSet) ( Pos, bool) ( Position) {if != NoPos {if := .file(); != nil {return .position(, )}}return}// Position converts a [Pos] p in the fileset into a Position value.// Calling s.Position(p) is equivalent to calling s.PositionFor(p, true).func ( *FileSet) ( Pos) ( Position) {return .PositionFor(, true)}// -----------------------------------------------------------------------------// Helper functionsfunc searchInts( []int, int) int {// This function body is a manually inlined version of://// return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1//// With better compiler optimizations, this may not be needed in the// future, but at the moment this change improves the go/printer// benchmark performance by ~30%. This has a direct impact on the// speed of gofmt and thus seems worthwhile (2011-04-29).// TODO(gri): Remove this when compilers have caught up., := 0, len()for < {:= int(uint(+) >> 1) // avoid overflow when computing h// i ≤ h < jif [] <= {= + 1} else {=}}return - 1}
![]() |
The pages are generated with Golds v0.7.9-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |