1 // Copyright 2018 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 package syntax
6 7 import "fmt"
8 9 // PosMax is the largest line or column value that can be represented without loss.
10 // Incoming values (arguments) larger than PosMax will be set to PosMax.
11 //
12 // Keep this consistent with maxLineCol in go/scanner.
13 const PosMax = 1 << 30
14 15 // A Pos represents an absolute (line, col) source position
16 // with a reference to position base for computing relative
17 // (to a file, or line directive) position information.
18 // Pos values are intentionally light-weight so that they
19 // can be created without too much concern about space use.
20 type Pos struct {
21 base *PosBase
22 line, col uint32
23 }
24 25 // MakePos returns a new Pos for the given PosBase, line and column.
26 func MakePos(base *PosBase, line, col uint32) Pos { return Pos{base, sat32(line), sat32(col)} }
27 28 // TODO(gri) IsKnown makes an assumption about Linebase < 1.
29 // Maybe we should check for Base() != nil instead.
30 31 func (pos Pos) Pos() Pos { return pos }
32 func (pos Pos) IsKnown() bool { return pos.line > 0 }
33 func (pos Pos) Base() *PosBase { return pos.base }
34 func (pos Pos) Line() uint32 { return pos.line }
35 func (pos Pos) Col() uint32 { return pos.col }
36 37 // FileBase returns the PosBase of the file containing pos,
38 // skipping over intermediate PosBases from //line directives.
39 // The result is nil if pos doesn't have a file base.
40 func (pos Pos) FileBase() *PosBase {
41 b := pos.base
42 for b != nil && b != b.pos.base {
43 b = b.pos.base
44 }
45 // b == nil || b == b.pos.base
46 return b
47 }
48 49 func (pos Pos) RelFilename() string { return pos.base.Filename() }
50 51 func (pos Pos) RelLine() uint32 {
52 b := pos.base
53 if b.Line() == 0 {
54 // base line is unknown => relative line is unknown
55 return 0
56 }
57 return b.Line() + (pos.Line() - b.Pos().Line())
58 }
59 60 func (pos Pos) RelCol() uint32 {
61 b := pos.base
62 if b.Col() == 0 {
63 // base column is unknown => relative column is unknown
64 // (the current specification for line directives requires
65 // this to apply until the next PosBase/line directive,
66 // not just until the new newline)
67 return 0
68 }
69 if pos.Line() == b.Pos().Line() {
70 // pos on same line as pos base => column is relative to pos base
71 return b.Col() + (pos.Col() - b.Pos().Col())
72 }
73 return pos.Col()
74 }
75 76 // Cmp compares the positions p and q and returns a result r as follows:
77 //
78 // r < 0: p is before q
79 // r == 0: p and q are the same position (but may not be identical)
80 // r > 0: p is after q
81 //
82 // If p and q are in different files, p is before q if the filename
83 // of p sorts lexicographically before the filename of q.
84 func (p Pos) Cmp(q Pos) int {
85 pname := p.RelFilename()
86 qname := q.RelFilename()
87 switch {
88 case pname < qname:
89 return -1
90 case pname > qname:
91 return +1
92 }
93 94 pline := p.Line()
95 qline := q.Line()
96 switch {
97 case pline < qline:
98 return -1
99 case pline > qline:
100 return +1
101 }
102 103 pcol := p.Col()
104 qcol := q.Col()
105 switch {
106 case pcol < qcol:
107 return -1
108 case pcol > qcol:
109 return +1
110 }
111 112 return 0
113 }
114 115 func (pos Pos) String() string {
116 rel := position_{pos.RelFilename(), pos.RelLine(), pos.RelCol()}
117 abs := position_{pos.Base().Pos().RelFilename(), pos.Line(), pos.Col()}
118 s := rel.String()
119 if rel != abs {
120 s += "[" + abs.String() + "]"
121 }
122 return s
123 }
124 125 // TODO(gri) cleanup: find better name, avoid conflict with position in error_test.go
126 type position_ struct {
127 filename string
128 line, col uint32
129 }
130 131 func (p position_) String() string {
132 if p.line == 0 {
133 if p.filename == "" {
134 return "<unknown position>"
135 }
136 return p.filename
137 }
138 if p.col == 0 {
139 return fmt.Sprintf("%s:%d", p.filename, p.line)
140 }
141 return fmt.Sprintf("%s:%d:%d", p.filename, p.line, p.col)
142 }
143 144 // A PosBase represents the base for relative position information:
145 // At position pos, the relative position is filename:line:col.
146 type PosBase struct {
147 pos Pos
148 filename string
149 line, col uint32
150 trimmed bool // whether -trimpath has been applied
151 }
152 153 // NewFileBase returns a new PosBase for the given filename.
154 // A file PosBase's position is relative to itself, with the
155 // position being filename:1:1.
156 func NewFileBase(filename string) *PosBase {
157 return NewTrimmedFileBase(filename, false)
158 }
159 160 // NewTrimmedFileBase is like NewFileBase, but allows specifying Trimmed.
161 func NewTrimmedFileBase(filename string, trimmed bool) *PosBase {
162 base := &PosBase{MakePos(nil, Linebase, Colbase), filename, Linebase, Colbase, trimmed}
163 base.pos.base = base
164 return base
165 }
166 167 // NewLineBase returns a new PosBase for a line directive "line filename:line:col"
168 // relative to pos, which is the position of the character immediately following
169 // the comment containing the line directive. For a directive in a line comment,
170 // that position is the beginning of the next line (i.e., the newline character
171 // belongs to the line comment).
172 func NewLineBase(pos Pos, filename string, trimmed bool, line, col uint32) *PosBase {
173 return &PosBase{pos, filename, sat32(line), sat32(col), trimmed}
174 }
175 176 func (base *PosBase) IsFileBase() bool {
177 if base == nil {
178 return false
179 }
180 return base.pos.base == base
181 }
182 183 func (base *PosBase) Pos() (_ Pos) {
184 if base == nil {
185 return
186 }
187 return base.pos
188 }
189 190 func (base *PosBase) Filename() string {
191 if base == nil {
192 return ""
193 }
194 return base.filename
195 }
196 197 func (base *PosBase) Line() uint32 {
198 if base == nil {
199 return 0
200 }
201 return base.line
202 }
203 204 func (base *PosBase) Col() uint32 {
205 if base == nil {
206 return 0
207 }
208 return base.col
209 }
210 211 func (base *PosBase) Trimmed() bool {
212 if base == nil {
213 return false
214 }
215 return base.trimmed
216 }
217 218 func sat32(x uint32) uint32 {
219 if x > PosMax {
220 return PosMax
221 }
222 return uint32(x)
223 }
224