source_file.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. package parser
  2. import (
  3. "fmt"
  4. "sort"
  5. )
  6. // SourceFilePos represents a position information in the file.
  7. type SourceFilePos struct {
  8. Filename string // filename, if any
  9. Offset int // offset, starting at 0
  10. Line int // line number, starting at 1
  11. Column int // column number, starting at 1 (byte count)
  12. }
  13. // IsValid returns true if the position is valid.
  14. func (p SourceFilePos) IsValid() bool {
  15. return p.Line > 0
  16. }
  17. // String returns a string in one of several forms:
  18. //
  19. // file:line:column valid position with file name
  20. // file:line valid position with file name but no column (column == 0)
  21. // line:column valid position without file name
  22. // line valid position without file name and no column (column == 0)
  23. // file invalid position with file name
  24. // - invalid position without file name
  25. //
  26. func (p SourceFilePos) String() string {
  27. s := p.Filename
  28. if p.IsValid() {
  29. if s != "" {
  30. s += ":"
  31. }
  32. s += fmt.Sprintf("%d", p.Line)
  33. if p.Column != 0 {
  34. s += fmt.Sprintf(":%d", p.Column)
  35. }
  36. }
  37. if s == "" {
  38. s = "-"
  39. }
  40. return s
  41. }
  42. // SourceFileSet represents a set of source files.
  43. type SourceFileSet struct {
  44. Base int // base offset for the next file
  45. Files []*SourceFile // list of files in the order added to the set
  46. LastFile *SourceFile // cache of last file looked up
  47. }
  48. // NewFileSet creates a new file set.
  49. func NewFileSet() *SourceFileSet {
  50. return &SourceFileSet{
  51. Base: 1, // 0 == NoPos
  52. }
  53. }
  54. // AddFile adds a new file in the file set.
  55. func (s *SourceFileSet) AddFile(filename string, base, size int) *SourceFile {
  56. if base < 0 {
  57. base = s.Base
  58. }
  59. if base < s.Base || size < 0 {
  60. panic("illegal base or size")
  61. }
  62. f := &SourceFile{
  63. set: s,
  64. Name: filename,
  65. Base: base,
  66. Size: size,
  67. Lines: []int{0},
  68. }
  69. base += size + 1 // +1 because EOF also has a position
  70. if base < 0 {
  71. panic("offset overflow (> 2G of source code in file set)")
  72. }
  73. // add the file to the file set
  74. s.Base = base
  75. s.Files = append(s.Files, f)
  76. s.LastFile = f
  77. return f
  78. }
  79. // File returns the file that contains the position p. If no such file is
  80. // found (for instance for p == NoPos), the result is nil.
  81. func (s *SourceFileSet) File(p Pos) (f *SourceFile) {
  82. if p != NoPos {
  83. f = s.file(p)
  84. }
  85. return
  86. }
  87. // Position converts a SourcePos p in the fileset into a SourceFilePos value.
  88. func (s *SourceFileSet) Position(p Pos) (pos SourceFilePos) {
  89. if p != NoPos {
  90. if f := s.file(p); f != nil {
  91. return f.position(p)
  92. }
  93. }
  94. return
  95. }
  96. func (s *SourceFileSet) file(p Pos) *SourceFile {
  97. // common case: p is in last file
  98. f := s.LastFile
  99. if f != nil && f.Base <= int(p) && int(p) <= f.Base+f.Size {
  100. return f
  101. }
  102. // p is not in last file - search all files
  103. if i := searchFiles(s.Files, int(p)); i >= 0 {
  104. f := s.Files[i]
  105. // f.base <= int(p) by definition of searchFiles
  106. if int(p) <= f.Base+f.Size {
  107. s.LastFile = f // race is ok - s.last is only a cache
  108. return f
  109. }
  110. }
  111. return nil
  112. }
  113. func searchFiles(a []*SourceFile, x int) int {
  114. return sort.Search(len(a), func(i int) bool { return a[i].Base > x }) - 1
  115. }
  116. // SourceFile represents a source file.
  117. type SourceFile struct {
  118. // SourceFile set for the file
  119. set *SourceFileSet
  120. // SourceFile name as provided to AddFile
  121. Name string
  122. // SourcePos value range for this file is [base...base+size]
  123. Base int
  124. // SourceFile size as provided to AddFile
  125. Size int
  126. // Lines contains the offset of the first character for each line
  127. // (the first entry is always 0)
  128. Lines []int
  129. }
  130. // Set returns SourceFileSet.
  131. func (f *SourceFile) Set() *SourceFileSet {
  132. return f.set
  133. }
  134. // LineCount returns the current number of lines.
  135. func (f *SourceFile) LineCount() int {
  136. return len(f.Lines)
  137. }
  138. // AddLine adds a new line.
  139. func (f *SourceFile) AddLine(offset int) {
  140. i := len(f.Lines)
  141. if (i == 0 || f.Lines[i-1] < offset) && offset < f.Size {
  142. f.Lines = append(f.Lines, offset)
  143. }
  144. }
  145. // LineStart returns the position of the first character in the line.
  146. func (f *SourceFile) LineStart(line int) Pos {
  147. if line < 1 {
  148. panic("illegal line number (line numbering starts at 1)")
  149. }
  150. if line > len(f.Lines) {
  151. panic("illegal line number")
  152. }
  153. return Pos(f.Base + f.Lines[line-1])
  154. }
  155. // FileSetPos returns the position in the file set.
  156. func (f *SourceFile) FileSetPos(offset int) Pos {
  157. if offset > f.Size {
  158. panic("illegal file offset")
  159. }
  160. return Pos(f.Base + offset)
  161. }
  162. // Offset translates the file set position into the file offset.
  163. func (f *SourceFile) Offset(p Pos) int {
  164. if int(p) < f.Base || int(p) > f.Base+f.Size {
  165. panic("illegal SourcePos value")
  166. }
  167. return int(p) - f.Base
  168. }
  169. // Position translates the file set position into the file position.
  170. func (f *SourceFile) Position(p Pos) (pos SourceFilePos) {
  171. if p != NoPos {
  172. if int(p) < f.Base || int(p) > f.Base+f.Size {
  173. panic("illegal SourcePos value")
  174. }
  175. pos = f.position(p)
  176. }
  177. return
  178. }
  179. func (f *SourceFile) position(p Pos) (pos SourceFilePos) {
  180. offset := int(p) - f.Base
  181. pos.Offset = offset
  182. pos.Filename, pos.Line, pos.Column = f.unpack(offset)
  183. return
  184. }
  185. func (f *SourceFile) unpack(offset int) (filename string, line, column int) {
  186. filename = f.Name
  187. if i := searchInts(f.Lines, offset); i >= 0 {
  188. line, column = i+1, offset-f.Lines[i]+1
  189. }
  190. return
  191. }
  192. func searchInts(a []int, x int) int {
  193. // This function body is a manually inlined version of:
  194. // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1
  195. i, j := 0, len(a)
  196. for i < j {
  197. h := i + (j-i)/2 // avoid overflow when computing h
  198. // i ≤ h < j
  199. if a[h] <= x {
  200. i = h + 1
  201. } else {
  202. j = h
  203. }
  204. }
  205. return i - 1
  206. }