pos.go raw

   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