[822] | 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 is variant of the stdlib package token with types FileSet and
|
---|
| 6 | // Token removed.
|
---|
| 7 | package token // import "modernc.org/token"
|
---|
| 8 |
|
---|
| 9 | import (
|
---|
| 10 | "fmt"
|
---|
| 11 | "sort"
|
---|
| 12 | )
|
---|
| 13 |
|
---|
| 14 | // -----------------------------------------------------------------------------
|
---|
| 15 | // Positions
|
---|
| 16 |
|
---|
| 17 | // Position describes an arbitrary source position
|
---|
| 18 | // including the file, line, and column location.
|
---|
| 19 | // A Position is valid if the line number is > 0.
|
---|
| 20 | //
|
---|
| 21 | type Position struct {
|
---|
| 22 | Filename string // filename, if any
|
---|
| 23 | Offset int // offset, starting at 0
|
---|
| 24 | Line int // line number, starting at 1
|
---|
| 25 | Column int // column number, starting at 1 (byte count)
|
---|
| 26 | }
|
---|
| 27 |
|
---|
| 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 | func (pos Position) String() string {
|
---|
| 39 | s := pos.Filename
|
---|
| 40 | if pos.IsValid() {
|
---|
| 41 | if s != "" {
|
---|
| 42 | s += ":"
|
---|
| 43 | }
|
---|
| 44 | s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
|
---|
| 45 | }
|
---|
| 46 | if s == "" {
|
---|
| 47 | s = "-"
|
---|
| 48 | }
|
---|
| 49 | return s
|
---|
| 50 | }
|
---|
| 51 |
|
---|
| 52 | // Pos is a compact encoding of a source position within a file set.
|
---|
| 53 | // It can be converted into a Position for a more convenient, but much
|
---|
| 54 | // larger, representation.
|
---|
| 55 | //
|
---|
| 56 | // The Pos value for a given file is a number in the range [base, base+size],
|
---|
| 57 | // where base and size are specified when adding the file to the file set via
|
---|
| 58 | // AddFile.
|
---|
| 59 | //
|
---|
| 60 | // To create the Pos value for a specific source offset (measured in bytes),
|
---|
| 61 | // first add the respective file to the current file set using FileSet.AddFile
|
---|
| 62 | // and then call File.Pos(offset) for that file. Given a Pos value p
|
---|
| 63 | // for a specific file set fset, the corresponding Position value is
|
---|
| 64 | // obtained by calling fset.Position(p).
|
---|
| 65 | //
|
---|
| 66 | // Pos values can be compared directly with the usual comparison operators:
|
---|
| 67 | // If two Pos values p and q are in the same file, comparing p and q is
|
---|
| 68 | // equivalent to comparing the respective source file offsets. If p and q
|
---|
| 69 | // are in different files, p < q is true if the file implied by p was added
|
---|
| 70 | // to the respective file set before the file implied by q.
|
---|
| 71 | //
|
---|
| 72 | type Pos int
|
---|
| 73 |
|
---|
| 74 | // NoPos is the zero value of Pos; there is no file and line information
|
---|
| 75 | // associated with it, and NoPos.IsValid() is false. NoPos is always
|
---|
| 76 | // smaller than any other Pos value. The corresponding Position value
|
---|
| 77 | // for NoPos is the zero value for Position.
|
---|
| 78 | //
|
---|
| 79 | const NoPos Pos = 0
|
---|
| 80 |
|
---|
| 81 | // IsValid reports whether the position is valid.
|
---|
| 82 | func (p Pos) IsValid() bool {
|
---|
| 83 | return p != NoPos
|
---|
| 84 | }
|
---|
| 85 |
|
---|
| 86 | // -----------------------------------------------------------------------------
|
---|
| 87 | // File
|
---|
| 88 |
|
---|
| 89 | // A File is a handle for a file. A File has a name, size, and line offset
|
---|
| 90 | // table.
|
---|
| 91 | //
|
---|
| 92 | type File struct {
|
---|
| 93 | name string // file name as provided to AddFile
|
---|
| 94 | base int // Pos value range for this file is [base...base+size]
|
---|
| 95 | size int // file size as provided to AddFile
|
---|
| 96 |
|
---|
| 97 | lines []int // lines contains the offset of the first character for each line (the first entry is always 0)
|
---|
| 98 | infos []lineInfo
|
---|
| 99 | }
|
---|
| 100 |
|
---|
| 101 | // NewFile returns a new file with a given filename and file size.
|
---|
| 102 | //
|
---|
| 103 | func NewFile(filename string, size int) *File {
|
---|
| 104 | return &File{name: filename, base: 1, size: size, lines: []int{0}}
|
---|
| 105 | }
|
---|
| 106 |
|
---|
| 107 | // Name returns the file name of file f as registered with AddFile.
|
---|
| 108 | func (f *File) Name() string {
|
---|
| 109 | return f.name
|
---|
| 110 | }
|
---|
| 111 |
|
---|
| 112 | // Base returns the base offset of file f as registered with AddFile.
|
---|
| 113 | func (f *File) Base() int {
|
---|
| 114 | return f.base
|
---|
| 115 | }
|
---|
| 116 |
|
---|
| 117 | // Size returns the size of file f as registered with AddFile.
|
---|
| 118 | func (f *File) Size() int {
|
---|
| 119 | return f.size
|
---|
| 120 | }
|
---|
| 121 |
|
---|
| 122 | // LineCount returns the number of lines in file f.
|
---|
| 123 | func (f *File) LineCount() int {
|
---|
| 124 | n := len(f.lines)
|
---|
| 125 | return n
|
---|
| 126 | }
|
---|
| 127 |
|
---|
| 128 | // AddLine adds the line offset for a new line.
|
---|
| 129 | // The line offset must be larger than the offset for the previous line
|
---|
| 130 | // and smaller than the file size; otherwise the line offset is ignored.
|
---|
| 131 | //
|
---|
| 132 | func (f *File) AddLine(offset int) {
|
---|
| 133 | if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size {
|
---|
| 134 | f.lines = append(f.lines, offset)
|
---|
| 135 | }
|
---|
| 136 | }
|
---|
| 137 |
|
---|
| 138 | // MergeLine merges a line with the following line. It is akin to replacing
|
---|
| 139 | // the newline character at the end of the line with a space (to not change the
|
---|
| 140 | // remaining offsets). To obtain the line number, consult e.g. Position.Line.
|
---|
| 141 | // MergeLine will panic if given an invalid line number.
|
---|
| 142 | //
|
---|
| 143 | func (f *File) MergeLine(line int) {
|
---|
| 144 | if line <= 0 {
|
---|
| 145 | panic("illegal line number (line numbering starts at 1)")
|
---|
| 146 | }
|
---|
| 147 | if line >= len(f.lines) {
|
---|
| 148 | panic("illegal line number")
|
---|
| 149 | }
|
---|
| 150 | // To merge the line numbered <line> with the line numbered <line+1>,
|
---|
| 151 | // we need to remove the entry in lines corresponding to the line
|
---|
| 152 | // numbered <line+1>. The entry in lines corresponding to the line
|
---|
| 153 | // numbered <line+1> is located at index <line>, since indices in lines
|
---|
| 154 | // are 0-based and line numbers are 1-based.
|
---|
| 155 | copy(f.lines[line:], f.lines[line+1:])
|
---|
| 156 | f.lines = f.lines[:len(f.lines)-1]
|
---|
| 157 | }
|
---|
| 158 |
|
---|
| 159 | // SetLines sets the line offsets for a file and reports whether it succeeded.
|
---|
| 160 | // The line offsets are the offsets of the first character of each line;
|
---|
| 161 | // for instance for the content "ab\nc\n" the line offsets are {0, 3}.
|
---|
| 162 | // An empty file has an empty line offset table.
|
---|
| 163 | // Each line offset must be larger than the offset for the previous line
|
---|
| 164 | // and smaller than the file size; otherwise SetLines fails and returns
|
---|
| 165 | // false.
|
---|
| 166 | // Callers must not mutate the provided slice after SetLines returns.
|
---|
| 167 | //
|
---|
| 168 | func (f *File) SetLines(lines []int) bool {
|
---|
| 169 | // verify validity of lines table
|
---|
| 170 | size := f.size
|
---|
| 171 | for i, offset := range lines {
|
---|
| 172 | if i > 0 && offset <= lines[i-1] || size <= offset {
|
---|
| 173 | return false
|
---|
| 174 | }
|
---|
| 175 | }
|
---|
| 176 |
|
---|
| 177 | // set lines table
|
---|
| 178 | f.lines = lines
|
---|
| 179 | return true
|
---|
| 180 | }
|
---|
| 181 |
|
---|
| 182 | // SetLinesForContent sets the line offsets for the given file content.
|
---|
| 183 | // It ignores position-altering //line comments.
|
---|
| 184 | func (f *File) SetLinesForContent(content []byte) {
|
---|
| 185 | var lines []int
|
---|
| 186 | line := 0
|
---|
| 187 | for offset, b := range content {
|
---|
| 188 | if line >= 0 {
|
---|
| 189 | lines = append(lines, line)
|
---|
| 190 | }
|
---|
| 191 | line = -1
|
---|
| 192 | if b == '\n' {
|
---|
| 193 | line = offset + 1
|
---|
| 194 | }
|
---|
| 195 | }
|
---|
| 196 |
|
---|
| 197 | // set lines table
|
---|
| 198 | f.lines = lines
|
---|
| 199 | }
|
---|
| 200 |
|
---|
| 201 | // A lineInfo object describes alternative file and line number
|
---|
| 202 | // information (such as provided via a //line comment in a .go
|
---|
| 203 | // file) for a given file offset.
|
---|
| 204 | type lineInfo struct {
|
---|
| 205 | // fields are exported to make them accessible to gob
|
---|
| 206 | Offset int
|
---|
| 207 | Filename string
|
---|
| 208 | Line int
|
---|
| 209 | }
|
---|
| 210 |
|
---|
| 211 | // AddLineInfo adds alternative file and line number information for
|
---|
| 212 | // a given file offset. The offset must be larger than the offset for
|
---|
| 213 | // the previously added alternative line info and smaller than the
|
---|
| 214 | // file size; otherwise the information is ignored.
|
---|
| 215 | //
|
---|
| 216 | // AddLineInfo is typically used to register alternative position
|
---|
| 217 | // information for //line filename:line comments in source files.
|
---|
| 218 | //
|
---|
| 219 | func (f *File) AddLineInfo(offset int, filename string, line int) {
|
---|
| 220 | if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size {
|
---|
| 221 | f.infos = append(f.infos, lineInfo{offset, filename, line})
|
---|
| 222 | }
|
---|
| 223 | }
|
---|
| 224 |
|
---|
| 225 | // Pos returns the Pos value for the given file offset;
|
---|
| 226 | // the offset must be <= f.Size().
|
---|
| 227 | // f.Pos(f.Offset(p)) == p.
|
---|
| 228 | //
|
---|
| 229 | func (f *File) Pos(offset int) Pos {
|
---|
| 230 | if offset > f.size {
|
---|
| 231 | panic("illegal file offset")
|
---|
| 232 | }
|
---|
| 233 | return Pos(f.base + offset)
|
---|
| 234 | }
|
---|
| 235 |
|
---|
| 236 | // Offset returns the offset for the given file position p;
|
---|
| 237 | // p must be a valid Pos value in that file.
|
---|
| 238 | // f.Offset(f.Pos(offset)) == offset.
|
---|
| 239 | //
|
---|
| 240 | func (f *File) Offset(p Pos) int {
|
---|
| 241 | if int(p) < f.base || int(p) > f.base+f.size {
|
---|
| 242 | panic("illegal Pos value")
|
---|
| 243 | }
|
---|
| 244 | return int(p) - f.base
|
---|
| 245 | }
|
---|
| 246 |
|
---|
| 247 | // Line returns the line number for the given file position p;
|
---|
| 248 | // p must be a Pos value in that file or NoPos.
|
---|
| 249 | //
|
---|
| 250 | func (f *File) Line(p Pos) int {
|
---|
| 251 | return f.Position(p).Line
|
---|
| 252 | }
|
---|
| 253 |
|
---|
| 254 | func searchLineInfos(a []lineInfo, x int) int {
|
---|
| 255 | return sort.Search(len(a), func(i int) bool { return a[i].Offset > x }) - 1
|
---|
| 256 | }
|
---|
| 257 |
|
---|
| 258 | // unpack returns the filename and line and column number for a file offset.
|
---|
| 259 | // If adjusted is set, unpack will return the filename and line information
|
---|
| 260 | // possibly adjusted by //line comments; otherwise those comments are ignored.
|
---|
| 261 | //
|
---|
| 262 | func (f *File) unpack(offset int, adjusted bool) (filename string, line, column int) {
|
---|
| 263 | filename = f.name
|
---|
| 264 | if i := searchInts(f.lines, offset); i >= 0 {
|
---|
| 265 | line, column = i+1, offset-f.lines[i]+1
|
---|
| 266 | }
|
---|
| 267 | if adjusted && len(f.infos) > 0 {
|
---|
| 268 | // almost no files have extra line infos
|
---|
| 269 | if i := searchLineInfos(f.infos, offset); i >= 0 {
|
---|
| 270 | alt := &f.infos[i]
|
---|
| 271 | filename = alt.Filename
|
---|
| 272 | if i := searchInts(f.lines, alt.Offset); i >= 0 {
|
---|
| 273 | line += alt.Line - i - 1
|
---|
| 274 | }
|
---|
| 275 | }
|
---|
| 276 | }
|
---|
| 277 | return
|
---|
| 278 | }
|
---|
| 279 |
|
---|
| 280 | func (f *File) position(p Pos, adjusted bool) (pos Position) {
|
---|
| 281 | offset := int(p) - f.base
|
---|
| 282 | pos.Offset = offset
|
---|
| 283 | pos.Filename, pos.Line, pos.Column = f.unpack(offset, adjusted)
|
---|
| 284 | return
|
---|
| 285 | }
|
---|
| 286 |
|
---|
| 287 | // PositionFor returns the Position value for the given file position p.
|
---|
| 288 | // If adjusted is set, the position may be adjusted by position-altering
|
---|
| 289 | // //line comments; otherwise those comments are ignored.
|
---|
| 290 | // p must be a Pos value in f or NoPos.
|
---|
| 291 | //
|
---|
| 292 | func (f *File) PositionFor(p Pos, adjusted bool) (pos Position) {
|
---|
| 293 | if p != NoPos {
|
---|
| 294 | if int(p) < f.base || int(p) > f.base+f.size {
|
---|
| 295 | panic("illegal Pos value")
|
---|
| 296 | }
|
---|
| 297 | pos = f.position(p, adjusted)
|
---|
| 298 | }
|
---|
| 299 | return
|
---|
| 300 | }
|
---|
| 301 |
|
---|
| 302 | // Position returns the Position value for the given file position p.
|
---|
| 303 | // Calling f.Position(p) is equivalent to calling f.PositionFor(p, true).
|
---|
| 304 | //
|
---|
| 305 | func (f *File) Position(p Pos) (pos Position) {
|
---|
| 306 | return f.PositionFor(p, true)
|
---|
| 307 | }
|
---|
| 308 |
|
---|
| 309 | // -----------------------------------------------------------------------------
|
---|
| 310 | // Helper functions
|
---|
| 311 |
|
---|
| 312 | func searchInts(a []int, x int) int {
|
---|
| 313 | // This function body is a manually inlined version of:
|
---|
| 314 | //
|
---|
| 315 | // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1
|
---|
| 316 | //
|
---|
| 317 | // With better compiler optimizations, this may not be needed in the
|
---|
| 318 | // future, but at the moment this change improves the go/printer
|
---|
| 319 | // benchmark performance by ~30%. This has a direct impact on the
|
---|
| 320 | // speed of gofmt and thus seems worthwhile (2011-04-29).
|
---|
| 321 | // TODO(gri): Remove this when compilers have caught up.
|
---|
| 322 | i, j := 0, len(a)
|
---|
| 323 | for i < j {
|
---|
| 324 | h := i + (j-i)/2 // avoid overflow when computing h
|
---|
| 325 | // i ≤ h < j
|
---|
| 326 | if a[h] <= x {
|
---|
| 327 | i = h + 1
|
---|
| 328 | } else {
|
---|
| 329 | j = h
|
---|
| 330 | }
|
---|
| 331 | }
|
---|
| 332 | return i - 1
|
---|
| 333 | }
|
---|