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
7package src
8
9import "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.
24type Pos struct {
25	base *PosBase
26	lico
27}
28
29// NoPos is a valid unknown position.
30var NoPos Pos
31
32// MakePos creates a new Pos value with the given base, and (file-absolute)
33// line and column.
34func 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.
41func (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.
47func (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.
54func (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.
60func (p Pos) Filename() string { return p.base.Pos().RelFilename() }
61
62// Base returns the position base.
63func (p Pos) Base() *PosBase { return p.base }
64
65// SetBase sets the position base.
66func (p *Pos) SetBase(base *PosBase) { p.base = base }
67
68// RelFilename returns the filename recorded with the position's base.
69func (p Pos) RelFilename() string { return p.base.Filename() }
70
71// RelLine returns the line number relative to the positions's base.
72func (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.
75func (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.
79func (p Pos) SymFilename() string { return p.base.SymFilename() }
80
81func (p Pos) String() string {
82	return p.Format(true)
83}
84
85// Format formats a position as "filename:line" or "filename:line:column",
86// controlled by the showCol flag.
87// If the position is relative to a line directive, the original position
88// is appended in square brackets without column (since the column doesn't
89// change).
90func (p Pos) Format(showCol 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	return format(p.RelFilename(), p.RelLine(), 0, false) +
109		"[" + format(p.Filename(), p.Line(), p.Col(), showCol) + "]"
110}
111
112// format formats a (filename, line, col) tuple as "filename:line" (showCol
113// is false) or "filename:line:column" (showCol is true).
114func format(filename string, line, col uint, showCol bool) string {
115	s := filename + ":" + strconv.FormatUint(uint64(line), 10)
116	// col == colMax is interpreted as unknown column value
117	if showCol && col < colMax {
118		s += ":" + strconv.FormatUint(uint64(col), 10)
119	}
120	return s
121}
122
123// ----------------------------------------------------------------------------
124// PosBase
125
126// A PosBase encodes a filename and base line number.
127// Typically, each file and line pragma introduce a PosBase.
128// A nil *PosBase is a ready to use file PosBase for an unnamed
129// file with line numbers starting at 1.
130type PosBase struct {
131	pos         Pos
132	filename    string // file name used to open source file, for error messages
133	absFilename string // absolute file name, for PC-Line tables
134	symFilename string // cached symbol file name, to avoid repeated string concatenation
135	line        uint   // relative line number at pos
136	inl         int    // inlining index (see cmd/internal/obj/inl.go)
137}
138
139// NewFileBase returns a new *PosBase for a file with the given (relative and
140// absolute) filenames.
141func NewFileBase(filename, absFilename string) *PosBase {
142	if filename != "" {
143		base := &PosBase{
144			filename:    filename,
145			absFilename: absFilename,
146			symFilename: FileSymPrefix + absFilename,
147			inl:         -1,
148		}
149		base.pos = MakePos(base, 0, 0)
150		return base
151	}
152	return nil
153}
154
155// NewLinePragmaBase returns a new *PosBase for a line pragma of the form
156//      //line filename:line
157// at position pos.
158func NewLinePragmaBase(pos Pos, filename string, line uint) *PosBase {
159	return &PosBase{pos, filename, filename, FileSymPrefix + filename, line - 1, -1}
160}
161
162// NewInliningBase returns a copy of the old PosBase with the given inlining
163// index. If old == nil, the resulting PosBase has no filename.
164func NewInliningBase(old *PosBase, inlTreeIndex int) *PosBase {
165	if old == nil {
166		base := &PosBase{inl: inlTreeIndex}
167		base.pos = MakePos(base, 0, 0)
168		return base
169	}
170	copy := *old
171	base := &copy
172	base.inl = inlTreeIndex
173	if old == old.pos.base {
174		base.pos.base = base
175	}
176	return base
177}
178
179var noPos Pos
180
181// Pos returns the position at which base is located.
182// If b == nil, the result is the zero position.
183func (b *PosBase) Pos() *Pos {
184	if b != nil {
185		return &b.pos
186	}
187	return &noPos
188}
189
190// Filename returns the filename recorded with the base.
191// If b == nil, the result is the empty string.
192func (b *PosBase) Filename() string {
193	if b != nil {
194		return b.filename
195	}
196	return ""
197}
198
199// AbsFilename returns the absolute filename recorded with the base.
200// If b == nil, the result is the empty string.
201func (b *PosBase) AbsFilename() string {
202	if b != nil {
203		return b.absFilename
204	}
205	return ""
206}
207
208const FileSymPrefix = "gofile.."
209
210// SymFilename returns the absolute filename recorded with the base,
211// prefixed by FileSymPrefix to make it appropriate for use as a linker symbol.
212// If b is nil, SymFilename returns FileSymPrefix + "??".
213func (b *PosBase) SymFilename() string {
214	if b != nil {
215		return b.symFilename
216	}
217	return FileSymPrefix + "??"
218}
219
220// Line returns the line number recorded with the base.
221// If b == nil, the result is 0.
222func (b *PosBase) Line() uint {
223	if b != nil {
224		return b.line
225	}
226	return 0
227}
228
229// InliningIndex returns the index into the global inlining
230// tree recorded with the base. If b == nil or the base has
231// not been inlined, the result is < 0.
232func (b *PosBase) InliningIndex() int {
233	if b != nil {
234		return b.inl
235	}
236	return -1
237}
238
239// ----------------------------------------------------------------------------
240// lico
241
242// A lico is a compact encoding of a LIne and COlumn number.
243type lico uint32
244
245// Layout constants: 24 bits for line, 8 bits for column.
246// (If this is too tight, we can either make lico 64b wide,
247// or we can introduce a tiered encoding where we remove column
248// information as line numbers grow bigger; similar to what gcc
249// does.)
250const (
251	lineBits, lineMax = 24, 1<<lineBits - 1
252	colBits, colMax   = 32 - lineBits, 1<<colBits - 1
253)
254
255func makeLico(line, col uint) lico {
256	if line > lineMax {
257		// cannot represent line, use max. line so we have some information
258		line = lineMax
259	}
260	if col > colMax {
261		// cannot represent column, use max. column so we have some information
262		col = colMax
263	}
264	return lico(line<<colBits | col)
265}
266
267func (x lico) Line() uint { return uint(x) >> colBits }
268func (x lico) Col() uint  { return uint(x) & colMax }
269