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