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
}
|