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 5package syntax 6 7import "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. 13const 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. 20type Pos struct { 21 base *PosBase 22 line, col uint32 23} 24 25// MakePos returns a new Pos for the given PosBase, line and column. 26func MakePos(base *PosBase, line, col uint) 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 31func (pos Pos) Pos() Pos { return pos } 32func (pos Pos) IsKnown() bool { return pos.line > 0 } 33func (pos Pos) Base() *PosBase { return pos.base } 34func (pos Pos) Line() uint { return uint(pos.line) } 35func (pos Pos) Col() uint { return uint(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. 40func (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 49func (pos Pos) RelFilename() string { return pos.base.Filename() } 50 51func (pos Pos) RelLine() uint { 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 60func (pos Pos) RelCol() uint { 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. 84func (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 115func (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 126type position_ struct { 127 filename string 128 line, col uint 129} 130 131func (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. 146type 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. 156func NewFileBase(filename string) *PosBase { 157 return NewTrimmedFileBase(filename, false) 158} 159 160// NewTrimmedFileBase is like NewFileBase, but allows specifying Trimmed. 161func 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). 172func NewLineBase(pos Pos, filename string, trimmed bool, line, col uint) *PosBase { 173 return &PosBase{pos, filename, sat32(line), sat32(col), trimmed} 174} 175 176func (base *PosBase) IsFileBase() bool { 177 if base == nil { 178 return false 179 } 180 return base.pos.base == base 181} 182 183func (base *PosBase) Pos() (_ Pos) { 184 if base == nil { 185 return 186 } 187 return base.pos 188} 189 190func (base *PosBase) Filename() string { 191 if base == nil { 192 return "" 193 } 194 return base.filename 195} 196 197func (base *PosBase) Line() uint { 198 if base == nil { 199 return 0 200 } 201 return uint(base.line) 202} 203 204func (base *PosBase) Col() uint { 205 if base == nil { 206 return 0 207 } 208 return uint(base.col) 209} 210 211func (base *PosBase) Trimmed() bool { 212 if base == nil { 213 return false 214 } 215 return base.trimmed 216} 217 218func sat32(x uint) uint32 { 219 if x > PosMax { 220 return PosMax 221 } 222 return uint32(x) 223} 224