1
0
mirror of https://github.com/golang/go synced 2024-09-30 16:08:36 -06:00
go/internal/span/span.go
Ian Cottrell b40df0fb21 internal/span: adding a source location package to unify the pos conversions
This implements a standard way of describing source locations along with
printing and parsing of those locations and conversion to and from the token.Pos
forms.

Change-Id: Ibb3df0a282bc2effb0fa8bd3a51bb0d281b2ffb1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/163778
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-03-08 14:21:31 +00:00

203 lines
5.3 KiB
Go

// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package span
import (
"fmt"
)
// Span represents a source code range in standardized form.
type Span struct {
URI URI `json:"uri"`
Start Point `json:"start"`
End Point `json:"end"`
}
// Point represents a single point within a file.
// In general this should only be used as part of a Span, as on its own it
// does not carry enough information.
type Point struct {
Line int `json:"line"`
Column int `json:"column"`
Offset int `json:"offset"`
}
// Offsets is the interface to an object that can convert to offset
// from line:column forms for a single file.
type Offsets interface {
ToOffset(line, col int) int
}
// Coords is the interface to an object that can convert to line:column
// from offset forms for a single file.
type Coords interface {
ToCoord(offset int) (int, int)
}
// Converter is the interface to an object that can convert between line:column
// and offset forms for a single file.
type Converter interface {
Offsets
Coords
}
// Format implements fmt.Formatter to print the Location in a standard form.
// The format produced is one that can be read back in using Parse.
func (s Span) Format(f fmt.State, c rune) {
fullForm := f.Flag('+')
preferOffset := f.Flag('#')
// we should always have a uri, simplify if it is file format
//TODO: make sure the end of the uri is unambiguous
uri := string(s.URI)
if !fullForm {
if filename, err := s.URI.Filename(); err == nil {
uri = filename
}
}
fmt.Fprint(f, uri)
// see which bits of start to write
printOffset := fullForm || (s.Start.Offset > 0 && (preferOffset || s.Start.Line <= 0))
printLine := fullForm || (s.Start.Line > 0 && !printOffset)
printColumn := fullForm || (printLine && (s.Start.Column > 1 || s.End.Column > 1))
if !printLine && !printColumn && !printOffset {
return
}
fmt.Fprint(f, ":")
if printLine {
fmt.Fprintf(f, "%d", clamp(s.Start.Line))
}
if printColumn {
fmt.Fprintf(f, ":%d", clamp(s.Start.Column))
}
if printOffset {
fmt.Fprintf(f, "#%d", clamp(s.Start.Offset))
}
// start is written, do we need end?
printLine = fullForm || (printLine && s.End.Line > s.Start.Line)
isPoint := s.End.Line == s.Start.Line && s.End.Column == s.Start.Column
printColumn = fullForm || (printColumn && s.End.Column > 0 && !isPoint)
printOffset = fullForm || (printOffset && s.End.Offset > s.Start.Offset)
if !printLine && !printColumn && !printOffset {
return
}
fmt.Fprint(f, "-")
if printLine {
fmt.Fprintf(f, "%d", clamp(s.End.Line))
}
if printColumn {
if printLine {
fmt.Fprint(f, ":")
}
fmt.Fprintf(f, "%d", clamp(s.End.Column))
}
if printOffset {
fmt.Fprintf(f, "#%d", clamp(s.End.Offset))
}
}
// CleanOffset returns a copy of the Span with the Offset field updated.
// If the field is missing and Offsets is supplied it will be used to
// calculate it from the line and column.
// The value will then be adjusted to the canonical form.
func (s Span) CleanOffset(offsets Offsets) Span {
if offsets != nil {
if (s.Start.Line > 1 || s.Start.Column > 1) && s.Start.Offset == 0 {
s.Start.updateOffset(offsets)
}
if (s.End.Line > 1 || s.End.Column > 1) && s.End.Offset == 0 {
s.End.updateOffset(offsets)
}
}
if s.Start.Offset < 0 {
s.Start.Offset = 0
}
if s.End.Offset <= s.Start.Offset {
s.End.Offset = s.Start.Offset
}
return s
}
// CleanCoords returns a copy of the Span with the Line and Column fields
// cleaned.
// If the fields are missing and Coords is supplied it will be used to
// calculate them from the offset.
// The values will then be adjusted to the canonical form.
func (s Span) CleanCoords(coords Coords) Span {
if coords != nil {
if s.Start.Line == 0 && s.Start.Offset > 0 {
s.Start.Line, s.Start.Column = coords.ToCoord(s.Start.Offset)
}
if s.End.Line == 0 && s.End.Offset > 0 {
s.End.Line, s.End.Column = coords.ToCoord(s.End.Offset)
}
}
if s.Start.Line <= 0 {
s.Start.Line = 0
}
if s.Start.Line == 0 {
s.Start.Column = 0
} else if s.Start.Column <= 0 {
s.Start.Column = 0
}
if s.End.Line < s.Start.Line {
s.End.Line = s.Start.Line
}
if s.End.Column < s.Start.Column {
s.End.Column = s.Start.Column
}
if s.Start.Column <= 1 && s.End.Column <= 1 {
s.Start.Column = 0
s.End.Column = 0
}
if s.Start.Line <= 1 && s.End.Line <= 1 && s.Start.Column <= 1 && s.End.Column <= 1 {
s.Start.Line = 0
s.End.Line = 0
}
return s
}
// Clean returns a copy of the Span fully normalized.
// If passed a converter, it will use it to fill in any missing fields by
// converting between offset and line column fields.
// It does not attempt to validate that already filled fields have consistent
// values.
func (s Span) Clean(converter Converter) Span {
s = s.CleanOffset(converter)
s = s.CleanCoords(converter)
if s.End.Offset == 0 {
// in case CleanCoords adjusted the end position
s.End.Offset = s.Start.Offset
}
return s
}
// IsPoint returns true if the span represents a single point.
// It is only valid on spans that are "clean".
func (s Span) IsPoint() bool {
return s.Start == s.End
}
func (p *Point) updateOffset(offsets Offsets) {
p.Offset = 0
if p.Line <= 0 {
return
}
c := p.Column
if c < 1 {
c = 1
}
if p.Line == 1 && c == 1 {
return
}
p.Offset = offsets.ToOffset(p.Line, c)
}
func clamp(v int) int {
if v < 0 {
return 0
}
return v
}