github.com/zebozhuang/go@v0.0.0-20200207033046-f8a98f6f5c5d/src/go/token/position.go (about) 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 // 标记一个二进制文件源码位置,包括文件,行,列位置;一个位置是合法当行数>0 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 // 判断Position是否合法 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 // 返回位置的字符串格式:[filename:]line:column 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 // Pos is a compact encoding of a source position within a file set. 54 // It can be converted into a Position for a more convenient, but much 55 // larger, representation. 56 // Pos是个源码文件集的位置压缩编码. 可以转化为更方便的Position对象,也更大,中间表达式 57 // The Pos value for a given file is a number in the range [base, base+size], 58 // where base and size are specified when adding the file to the file set via 59 // AddFile. 60 // Pos对象在给定的文件范围是[base, base+size],base 和 size 是特定指定的当通过调用AddFile添加文件集 61 // To create the Pos value for a specific source offset (measured in bytes), 62 // first add the respective file to the current file set using FileSet.AddFile 63 // and then call File.Pos(offset) for that file. Given a Pos value p 64 // for a specific file set fset, the corresponding Position value is 65 // obtained by calling fset.Position(p). 66 // 创建一个Pos值给一个特定的距离位置,首先调用FileSet.AddFile,然后调用File.Pos(offset) 67 // Pos values can be compared directly with the usual comparison operators: 68 // If two Pos values p and q are in the same file, comparing p and q is 69 // equivalent to comparing the respective source file offsets. If p and q 70 // are in different files, p < q is true if the file implied by p was added 71 // to the respective file set before the file implied by q. 72 // 73 // Pos 对象 74 type Pos int 75 76 // The zero value for Pos is NoPos; there is no file and line information 77 // associated with it, and NoPos.IsValid() is false. NoPos is always 78 // smaller than any other Pos value. The corresponding Position value 79 // for NoPos is the zero value for Position. 80 // 81 // 没有Pos值 82 const NoPos Pos = 0 83 84 // IsValid reports whether the position is valid. 85 func (p Pos) IsValid() bool { 86 return p != NoPos 87 } 88 89 // ----------------------------------------------------------------------------- 90 // File 91 92 // A File is a handle for a file belonging to a FileSet. 93 // A File has a name, size, and line offset table. 94 // 文件对象 95 type File struct { 96 set *FileSet // 文件集? 97 name string // file name as provided to AddFile 文件名字 98 base int // Pos value range for this file is [base...base+size] 如何计算? 99 size int // file size as provided to AddFile // 文件大小 100 101 // lines and infos are protected by mutex 102 mutex sync.Mutex // 锁 103 lines []int // lines contains the offset of the first character for each line (the first entry is always 0), 每行第一个字符位置 104 infos []lineInfo // ? 105 } 106 107 // Name returns the file name of file f as registered with AddFile. 108 // 文件名 109 func (f *File) Name() string { 110 return f.name 111 } 112 113 // Base returns the base offset of file f as registered with AddFile. 114 // 通过Addfile计算得到的base 115 func (f *File) Base() int { 116 return f.base 117 } 118 119 // Size returns the size of file f as registered with AddFile. 120 // 返回文件大小 121 func (f *File) Size() int { 122 return f.size 123 } 124 125 // LineCount returns the number of lines in file f. 126 // 文件行数 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 // 添加行, offset一定比上个offset大,但一定比文件大小小 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 // 合并行 152 func (f *File) MergeLine(line int) { 153 if line <= 0 { 154 panic("illegal line number (line numbering starts at 1)") 155 } 156 f.mutex.Lock() 157 defer f.mutex.Unlock() 158 if line >= len(f.lines) { 159 panic("illegal line number") 160 } 161 // To merge the line numbered <line> with the line numbered <line+1>, 162 // we need to remove the entry in lines corresponding to the line 163 // numbered <line+1>. The entry in lines corresponding to the line 164 // numbered <line+1> is located at index <line>, since indices in lines 165 // are 0-based and line numbers are 1-based. 166 copy(f.lines[line:], f.lines[line+1:]) 167 f.lines = f.lines[:len(f.lines)-1] 168 } 169 170 // SetLines sets the line offsets for a file and reports whether it succeeded. 171 // The line offsets are the offsets of the first character of each line; 172 // for instance for the content "ab\nc\n" the line offsets are {0, 3}. 173 // An empty file has an empty line offset table. 174 // Each line offset must be larger than the offset for the previous line 175 // and smaller than the file size; otherwise SetLines fails and returns 176 // false. 177 // Callers must not mutate the provided slice after SetLines returns. 178 // 179 // 设置行 180 func (f *File) SetLines(lines []int) bool { 181 // verify validity of lines table 182 size := f.size 183 for i, offset := range lines { 184 if i > 0 && offset <= lines[i-1] || size <= offset { 185 return false 186 } 187 } 188 189 // set lines table 190 f.mutex.Lock() 191 f.lines = lines 192 f.mutex.Unlock() 193 return true 194 } 195 196 // SetLinesForContent sets the line offsets for the given file content. 197 // It ignores position-altering //line comments. 198 // 设置内容 199 func (f *File) SetLinesForContent(content []byte) { 200 var lines []int 201 line := 0 202 for offset, b := range content { 203 if line >= 0 { 204 lines = append(lines, line) 205 } 206 line = -1 207 if b == '\n' { 208 line = offset + 1 209 } 210 } 211 212 // set lines table 213 f.mutex.Lock() 214 f.lines = lines 215 f.mutex.Unlock() 216 } 217 218 // A lineInfo object describes alternative file and line number 219 // information (such as provided via a //line comment in a .go 220 // file) for a given file offset. 221 type lineInfo struct { 222 // fields are exported to make them accessible to gob 223 Offset int 224 Filename string 225 Line int 226 } 227 228 // AddLineInfo adds alternative file and line number information for 229 // a given file offset. The offset must be larger than the offset for 230 // the previously added alternative line info and smaller than the 231 // file size; otherwise the information is ignored. 232 // 233 // AddLineInfo is typically used to register alternative position 234 // information for //line filename:line comments in source files. 235 // 236 func (f *File) AddLineInfo(offset int, filename string, line int) { 237 f.mutex.Lock() 238 if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size { 239 f.infos = append(f.infos, lineInfo{offset, filename, line}) 240 } 241 f.mutex.Unlock() 242 } 243 244 // Pos returns the Pos value for the given file offset; 245 // the offset must be <= f.Size(). 246 // f.Pos(f.Offset(p)) == p. 247 // 248 func (f *File) Pos(offset int) Pos { 249 if offset > f.size { 250 panic("illegal file offset") 251 } 252 return Pos(f.base + offset) 253 } 254 255 // Offset returns the offset for the given file position p; 256 // p must be a valid Pos value in that file. 257 // f.Offset(f.Pos(offset)) == offset. 258 // 获取offset 259 func (f *File) Offset(p Pos) int { 260 if int(p) < f.base || int(p) > f.base+f.size { 261 panic("illegal Pos value") 262 } 263 return int(p) - f.base 264 } 265 266 // Line returns the line number for the given file position p; 267 // p must be a Pos value in that file or NoPos. 268 // 根据P获取行 269 func (f *File) Line(p Pos) int { 270 return f.Position(p).Line 271 } 272 273 func searchLineInfos(a []lineInfo, x int) int { 274 return sort.Search(len(a), func(i int) bool { return a[i].Offset > x }) - 1 275 } 276 277 // unpack returns the filename and line and column number for a file offset. 278 // If adjusted is set, unpack will return the filename and line information 279 // possibly adjusted by //line comments; otherwise those comments are ignored. 280 // 解压 281 func (f *File) unpack(offset int, adjusted bool) (filename string, line, column int) { 282 f.mutex.Lock() 283 defer f.mutex.Unlock() 284 filename = f.name 285 if i := searchInts(f.lines, offset); i >= 0 { 286 line, column = i+1, offset-f.lines[i]+1 287 } 288 if adjusted && len(f.infos) > 0 { 289 // almost no files have extra line infos 290 if i := searchLineInfos(f.infos, offset); i >= 0 { 291 alt := &f.infos[i] 292 filename = alt.Filename 293 if i := searchInts(f.lines, alt.Offset); i >= 0 { 294 line += alt.Line - i - 1 295 } 296 } 297 } 298 return 299 } 300 301 func (f *File) position(p Pos, adjusted bool) (pos Position) { 302 offset := int(p) - f.base 303 pos.Offset = offset 304 pos.Filename, pos.Line, pos.Column = f.unpack(offset, adjusted) 305 return 306 } 307 308 // PositionFor returns the Position value for the given file position p. 309 // If adjusted is set, the position may be adjusted by position-altering 310 // //line comments; otherwise those comments are ignored. 311 // p must be a Pos value in f or NoPos. 312 // 313 func (f *File) PositionFor(p Pos, adjusted bool) (pos Position) { 314 if p != NoPos { 315 if int(p) < f.base || int(p) > f.base+f.size { 316 panic("illegal Pos value") 317 } 318 pos = f.position(p, adjusted) 319 } 320 return 321 } 322 323 // Position returns the Position value for the given file position p. 324 // Calling f.Position(p) is equivalent to calling f.PositionFor(p, true). 325 // 326 func (f *File) Position(p Pos) (pos Position) { 327 return f.PositionFor(p, true) 328 } 329 330 // ----------------------------------------------------------------------------- 331 // FileSet 332 333 // A FileSet represents a set of source files. 334 // Methods of file sets are synchronized; multiple goroutines 335 // may invoke them concurrently. 336 // 337 type FileSet struct { 338 mutex sync.RWMutex // protects the file set 339 base int // base offset for the next file 340 files []*File // list of files in the order added to the set 341 last *File // cache of last file looked up 342 } 343 344 // NewFileSet creates a new file set. 345 func NewFileSet() *FileSet { 346 return &FileSet{ 347 base: 1, // 0 == NoPos 348 } 349 } 350 351 // Base returns the minimum base offset that must be provided to 352 // AddFile when adding the next file. 353 // 354 func (s *FileSet) Base() int { 355 s.mutex.RLock() 356 b := s.base 357 s.mutex.RUnlock() 358 return b 359 360 } 361 362 // AddFile adds a new file with a given filename, base offset, and file size 363 // to the file set s and returns the file. Multiple files may have the same 364 // name. The base offset must not be smaller than the FileSet's Base(), and 365 // size must not be negative. As a special case, if a negative base is provided, 366 // the current value of the FileSet's Base() is used instead. 367 // 368 // Adding the file will set the file set's Base() value to base + size + 1 369 // as the minimum base value for the next file. The following relationship 370 // exists between a Pos value p for a given file offset offs: 371 // 372 // int(p) = base + offs 373 // 374 // with offs in the range [0, size] and thus p in the range [base, base+size]. 375 // For convenience, File.Pos may be used to create file-specific position 376 // values from a file offset. 377 // 378 // 添加文件 379 func (s *FileSet) AddFile(filename string, base, size int) *File { 380 s.mutex.Lock() 381 defer s.mutex.Unlock() 382 if base < 0 { 383 base = s.base 384 } 385 if base < s.base || size < 0 { 386 panic("illegal base or size") 387 } 388 // base >= s.base && size >= 0 389 f := &File{set: s, name: filename, base: base, size: size, lines: []int{0}} 390 base += size + 1 // +1 because EOF also has a position 391 if base < 0 { 392 panic("token.Pos offset overflow (> 2G of source code in file set)") 393 } 394 // add the file to the file set 395 s.base = base 396 s.files = append(s.files, f) 397 s.last = f 398 return f 399 } 400 401 // Iterate calls f for the files in the file set in the order they were added 402 // until f returns false. 403 // 404 func (s *FileSet) Iterate(f func(*File) bool) { 405 for i := 0; ; i++ { 406 var file *File 407 s.mutex.RLock() 408 if i < len(s.files) { 409 file = s.files[i] 410 } 411 s.mutex.RUnlock() 412 if file == nil || !f(file) { 413 break 414 } 415 } 416 } 417 418 func searchFiles(a []*File, x int) int { 419 return sort.Search(len(a), func(i int) bool { return a[i].base > x }) - 1 420 } 421 422 func (s *FileSet) file(p Pos) *File { 423 s.mutex.RLock() 424 // common case: p is in last file 425 if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size { 426 s.mutex.RUnlock() 427 return f 428 } 429 // p is not in last file - search all files 430 if i := searchFiles(s.files, int(p)); i >= 0 { 431 f := s.files[i] 432 // f.base <= int(p) by definition of searchFiles 433 if int(p) <= f.base+f.size { 434 s.mutex.RUnlock() 435 s.mutex.Lock() 436 s.last = f // race is ok - s.last is only a cache 437 s.mutex.Unlock() 438 return f 439 } 440 } 441 s.mutex.RUnlock() 442 return nil 443 } 444 445 // File returns the file that contains the position p. 446 // If no such file is found (for instance for p == NoPos), 447 // the result is nil. 448 // 449 func (s *FileSet) File(p Pos) (f *File) { 450 if p != NoPos { 451 f = s.file(p) 452 } 453 return 454 } 455 456 // PositionFor converts a Pos p in the fileset into a Position value. 457 // If adjusted is set, the position may be adjusted by position-altering 458 // //line comments; otherwise those comments are ignored. 459 // p must be a Pos value in s or NoPos. 460 // 461 func (s *FileSet) PositionFor(p Pos, adjusted bool) (pos Position) { 462 if p != NoPos { 463 if f := s.file(p); f != nil { 464 return f.position(p, adjusted) 465 } 466 } 467 return 468 } 469 470 // Position converts a Pos p in the fileset into a Position value. 471 // Calling s.Position(p) is equivalent to calling s.PositionFor(p, true). 472 // 473 func (s *FileSet) Position(p Pos) (pos Position) { 474 return s.PositionFor(p, true) 475 } 476 477 // ----------------------------------------------------------------------------- 478 // Helper functions 479 480 func searchInts(a []int, x int) int { 481 // This function body is a manually inlined version of: 482 // 483 // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1 484 // 485 // With better compiler optimizations, this may not be needed in the 486 // future, but at the moment this change improves the go/printer 487 // benchmark performance by ~30%. This has a direct impact on the 488 // speed of gofmt and thus seems worthwhile (2011-04-29). 489 // TODO(gri): Remove this when compilers have caught up. 490 i, j := 0, len(a) 491 for i < j { 492 h := i + (j-i)/2 // avoid overflow when computing h 493 // i ≤ h < j 494 if a[h] <= x { 495 i = h + 1 496 } else { 497 j = h 498 } 499 } 500 return i - 1 501 }