本站源代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

436 lines
12KB

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