Home | History | Annotate | Download | only in src
      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 := &copy
    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 }
    272