github.com/ltltlt/go-source-code@v0.0.0-20190830023027-95be009773aa/cmd/internal/src/pos.go (about) 1 // Copyright 2016 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 // This file implements the encoding of source positions. 6 7 package src 8 9 import "strconv" 10 11 // A Pos encodes a source position consisting of a (line, column) number pair 12 // and a position base. A zero Pos is a ready to use "unknown" position (nil 13 // position base and zero line number). 14 // 15 // The (line, column) values refer to a position in a file independent of any 16 // position base ("absolute" file position). 17 // 18 // The position base is used to determine the "relative" position, that is the 19 // filename and line number relative to the position base. If the base refers 20 // to the current file, there is no difference between absolute and relative 21 // positions. If it refers to a //line pragma, a relative position is relative 22 // to that pragma. A position base in turn contains the position at which it 23 // was introduced in the current file. 24 type Pos struct { 25 base *PosBase 26 lico 27 } 28 29 // NoPos is a valid unknown position. 30 var NoPos Pos 31 32 // MakePos creates a new Pos value with the given base, and (file-absolute) 33 // line and column. 34 func MakePos(base *PosBase, line, col uint) Pos { 35 return Pos{base, makeLico(line, col)} 36 } 37 38 // IsKnown reports whether the position p is known. 39 // A position is known if it either has a non-nil 40 // position base, or a non-zero line number. 41 func (p Pos) IsKnown() bool { 42 return p.base != nil || p.Line() != 0 43 } 44 45 // Before reports whether the position p comes before q in the source. 46 // For positions in different files, ordering is by filename. 47 func (p Pos) Before(q Pos) bool { 48 n, m := p.Filename(), q.Filename() 49 return n < m || n == m && p.lico < q.lico 50 } 51 52 // After reports whether the position p comes after q in the source. 53 // For positions in different files, ordering is by filename. 54 func (p Pos) After(q Pos) bool { 55 n, m := p.Filename(), q.Filename() 56 return n > m || n == m && p.lico > q.lico 57 } 58 59 // Filename returns the name of the actual file containing this position. 60 func (p Pos) Filename() string { return p.base.Pos().RelFilename() } 61 62 // Base returns the position base. 63 func (p Pos) Base() *PosBase { return p.base } 64 65 // SetBase sets the position base. 66 func (p *Pos) SetBase(base *PosBase) { p.base = base } 67 68 // RelFilename returns the filename recorded with the position's base. 69 func (p Pos) RelFilename() string { return p.base.Filename() } 70 71 // RelLine returns the line number relative to the positions's base. 72 func (p Pos) RelLine() uint { b := p.base; return b.Line() + p.Line() - b.Pos().Line() } 73 74 // AbsFilename() returns the absolute filename recorded with the position's base. 75 func (p Pos) AbsFilename() string { return p.base.AbsFilename() } 76 77 // SymFilename() returns the absolute filename recorded with the position's base, 78 // prefixed by FileSymPrefix to make it appropriate for use as a linker symbol. 79 func (p Pos) SymFilename() string { return p.base.SymFilename() } 80 81 func (p Pos) String() string { 82 return p.Format(true, true) 83 } 84 85 // Format formats a position as "filename:line" or "filename:line:column", 86 // controlled by the showCol flag. A position relative to a line directive 87 // is always formatted without column information. In that case, if showOrig 88 // is set, the original position (again controlled by showCol) is appended 89 // in square brackets: "filename:line[origfile:origline:origcolumn]". 90 func (p Pos) Format(showCol, showOrig bool) string { 91 if !p.IsKnown() { 92 return "<unknown line number>" 93 } 94 95 if b := p.base; b == b.Pos().base { 96 // base is file base (incl. nil) 97 return format(p.Filename(), p.Line(), p.Col(), showCol) 98 } 99 100 // base is relative 101 // Print the column only for the original position since the 102 // relative position's column information may be bogus (it's 103 // typically generated code and we can't say much about the 104 // original source at that point but for the file:line info 105 // that's provided via a line directive). 106 // TODO(gri) This may not be true if we have an inlining base. 107 // We may want to differentiate at some point. 108 s := format(p.RelFilename(), p.RelLine(), 0, false) 109 if showOrig { 110 s += "[" + format(p.Filename(), p.Line(), p.Col(), showCol) + "]" 111 } 112 return s 113 } 114 115 // format formats a (filename, line, col) tuple as "filename:line" (showCol 116 // is false) or "filename:line:column" (showCol is true). 117 func format(filename string, line, col uint, showCol bool) string { 118 s := filename + ":" + strconv.FormatUint(uint64(line), 10) 119 // col == colMax is interpreted as unknown column value 120 if showCol && col < colMax { 121 s += ":" + strconv.FormatUint(uint64(col), 10) 122 } 123 return s 124 } 125 126 // ---------------------------------------------------------------------------- 127 // PosBase 128 129 // A PosBase encodes a filename and base line number. 130 // Typically, each file and line pragma introduce a PosBase. 131 // A nil *PosBase is a ready to use file PosBase for an unnamed 132 // file with line numbers starting at 1. 133 type PosBase struct { 134 pos Pos 135 filename string // file name used to open source file, for error messages 136 absFilename string // absolute file name, for PC-Line tables 137 symFilename string // cached symbol file name, to avoid repeated string concatenation 138 line uint // relative line number at pos 139 inl int // inlining index (see cmd/internal/obj/inl.go) 140 } 141 142 // NewFileBase returns a new *PosBase for a file with the given (relative and 143 // absolute) filenames. 144 func NewFileBase(filename, absFilename string) *PosBase { 145 if filename != "" { 146 base := &PosBase{ 147 filename: filename, 148 absFilename: absFilename, 149 symFilename: FileSymPrefix + absFilename, 150 inl: -1, 151 } 152 base.pos = MakePos(base, 0, 0) 153 return base 154 } 155 return nil 156 } 157 158 // NewLinePragmaBase returns a new *PosBase for a line pragma of the form 159 // //line filename:line 160 // at position pos. 161 func NewLinePragmaBase(pos Pos, filename, absFilename string, line uint) *PosBase { 162 return &PosBase{pos, filename, absFilename, FileSymPrefix + absFilename, line - 1, -1} 163 } 164 165 // NewInliningBase returns a copy of the old PosBase with the given inlining 166 // index. If old == nil, the resulting PosBase has no filename. 167 func NewInliningBase(old *PosBase, inlTreeIndex int) *PosBase { 168 if old == nil { 169 base := &PosBase{inl: inlTreeIndex} 170 base.pos = MakePos(base, 0, 0) 171 return base 172 } 173 copy := *old 174 base := © 175 base.inl = inlTreeIndex 176 if old == old.pos.base { 177 base.pos.base = base 178 } 179 return base 180 } 181 182 var noPos Pos 183 184 // Pos returns the position at which base is located. 185 // If b == nil, the result is the zero position. 186 func (b *PosBase) Pos() *Pos { 187 if b != nil { 188 return &b.pos 189 } 190 return &noPos 191 } 192 193 // Filename returns the filename recorded with the base. 194 // If b == nil, the result is the empty string. 195 func (b *PosBase) Filename() string { 196 if b != nil { 197 return b.filename 198 } 199 return "" 200 } 201 202 // AbsFilename returns the absolute filename recorded with the base. 203 // If b == nil, the result is the empty string. 204 func (b *PosBase) AbsFilename() string { 205 if b != nil { 206 return b.absFilename 207 } 208 return "" 209 } 210 211 const FileSymPrefix = "gofile.." 212 213 // SymFilename returns the absolute filename recorded with the base, 214 // prefixed by FileSymPrefix to make it appropriate for use as a linker symbol. 215 // If b is nil, SymFilename returns FileSymPrefix + "??". 216 func (b *PosBase) SymFilename() string { 217 if b != nil { 218 return b.symFilename 219 } 220 return FileSymPrefix + "??" 221 } 222 223 // Line returns the line number recorded with the base. 224 // If b == nil, the result is 0. 225 func (b *PosBase) Line() uint { 226 if b != nil { 227 return b.line 228 } 229 return 0 230 } 231 232 // InliningIndex returns the index into the global inlining 233 // tree recorded with the base. If b == nil or the base has 234 // not been inlined, the result is < 0. 235 func (b *PosBase) InliningIndex() int { 236 if b != nil { 237 return b.inl 238 } 239 return -1 240 } 241 242 // ---------------------------------------------------------------------------- 243 // lico 244 245 // A lico is a compact encoding of a LIne and COlumn number. 246 type lico uint32 247 248 // Layout constants: 24 bits for line, 8 bits for column. 249 // (If this is too tight, we can either make lico 64b wide, 250 // or we can introduce a tiered encoding where we remove column 251 // information as line numbers grow bigger; similar to what gcc 252 // does.) 253 const ( 254 lineBits, lineMax = 24, 1<<lineBits - 1 255 colBits, colMax = 32 - lineBits, 1<<colBits - 1 256 ) 257 258 func makeLico(line, col uint) lico { 259 if line > lineMax { 260 // cannot represent line, use max. line so we have some information 261 line = lineMax 262 } 263 if col > colMax { 264 // cannot represent column, use max. column so we have some information 265 col = colMax 266 } 267 return lico(line<<colBits | col) 268 } 269 270 func (x lico) Line() uint { return uint(x) >> colBits } 271 func (x lico) Col() uint { return uint(x) & colMax }