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 := &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 }