2017-09-11 23:40:37 +00:00
|
|
|
package hcl
|
2017-05-14 00:44:11 +00:00
|
|
|
|
|
|
|
import "fmt"
|
|
|
|
|
|
|
|
// Pos represents a single position in a source file, by addressing the
|
|
|
|
// start byte of a unicode character encoded in UTF-8.
|
|
|
|
//
|
|
|
|
// Pos is generally used only in the context of a Range, which then defines
|
|
|
|
// which source file the position is within.
|
|
|
|
type Pos struct {
|
|
|
|
// Line is the source code line where this position points. Lines are
|
|
|
|
// counted starting at 1 and incremented for each newline character
|
|
|
|
// encountered.
|
|
|
|
Line int
|
|
|
|
|
|
|
|
// Column is the source code column where this position points, in
|
|
|
|
// unicode characters, with counting starting at 1.
|
|
|
|
//
|
|
|
|
// Column counts characters as they appear visually, so for example a
|
|
|
|
// latin letter with a combining diacritic mark counts as one character.
|
|
|
|
// This is intended for rendering visual markers against source code in
|
|
|
|
// contexts where these diacritics would be rendered in a single character
|
|
|
|
// cell. Technically speaking, Column is counting grapheme clusters as
|
|
|
|
// used in unicode normalization.
|
|
|
|
Column int
|
|
|
|
|
|
|
|
// Byte is the byte offset into the file where the indicated character
|
|
|
|
// begins. This is a zero-based offset to the first byte of the first
|
|
|
|
// UTF-8 codepoint sequence in the character, and thus gives a position
|
|
|
|
// that can be resolved _without_ awareness of Unicode characters.
|
|
|
|
Byte int
|
|
|
|
}
|
|
|
|
|
2019-04-12 22:16:41 +00:00
|
|
|
// InitialPos is a suitable position to use to mark the start of a file.
|
|
|
|
var InitialPos = Pos{Byte: 0, Line: 1, Column: 1}
|
|
|
|
|
2017-05-14 00:44:11 +00:00
|
|
|
// Range represents a span of characters between two positions in a source
|
|
|
|
// file.
|
|
|
|
//
|
|
|
|
// This struct is usually used by value in types that represent AST nodes,
|
|
|
|
// but by pointer in types that refer to the positions of other objects,
|
|
|
|
// such as in diagnostics.
|
|
|
|
type Range struct {
|
|
|
|
// Filename is the name of the file into which this range's positions
|
|
|
|
// point.
|
|
|
|
Filename string
|
|
|
|
|
|
|
|
// Start and End represent the bounds of this range. Start is inclusive
|
|
|
|
// and End is exclusive.
|
|
|
|
Start, End Pos
|
|
|
|
}
|
|
|
|
|
|
|
|
// RangeBetween returns a new range that spans from the beginning of the
|
|
|
|
// start range to the end of the end range.
|
|
|
|
//
|
|
|
|
// The result is meaningless if the two ranges do not belong to the same
|
|
|
|
// source file or if the end range appears before the start range.
|
|
|
|
func RangeBetween(start, end Range) Range {
|
|
|
|
return Range{
|
|
|
|
Filename: start.Filename,
|
|
|
|
Start: start.Start,
|
|
|
|
End: end.End,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-14 19:33:23 +00:00
|
|
|
// RangeOver returns a new range that covers both of the given ranges and
|
|
|
|
// possibly additional content between them if the two ranges do not overlap.
|
|
|
|
//
|
|
|
|
// If either range is empty then it is ignored. The result is empty if both
|
|
|
|
// given ranges are empty.
|
|
|
|
//
|
|
|
|
// The result is meaningless if the two ranges to not belong to the same
|
|
|
|
// source file.
|
|
|
|
func RangeOver(a, b Range) Range {
|
|
|
|
if a.Empty() {
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
if b.Empty() {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
|
|
|
var start, end Pos
|
|
|
|
if a.Start.Byte < b.Start.Byte {
|
|
|
|
start = a.Start
|
|
|
|
} else {
|
|
|
|
start = b.Start
|
|
|
|
}
|
|
|
|
if a.End.Byte > b.End.Byte {
|
|
|
|
end = a.End
|
|
|
|
} else {
|
|
|
|
end = b.End
|
|
|
|
}
|
|
|
|
return Range{
|
|
|
|
Filename: a.Filename,
|
|
|
|
Start: start,
|
|
|
|
End: end,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-28 20:17:51 +00:00
|
|
|
// ContainsPos returns true if and only if the given position is contained within
|
|
|
|
// the receiving range.
|
|
|
|
//
|
|
|
|
// In the unlikely case that the line/column information disagree with the byte
|
|
|
|
// offset information in the given position or receiving range, the byte
|
|
|
|
// offsets are given priority.
|
|
|
|
func (r Range) ContainsPos(pos Pos) bool {
|
|
|
|
return r.ContainsOffset(pos.Byte)
|
|
|
|
}
|
|
|
|
|
2017-05-14 00:44:11 +00:00
|
|
|
// ContainsOffset returns true if and only if the given byte offset is within
|
|
|
|
// the receiving Range.
|
2017-06-04 16:37:37 +00:00
|
|
|
func (r Range) ContainsOffset(offset int) bool {
|
2017-05-14 00:44:11 +00:00
|
|
|
return offset >= r.Start.Byte && offset < r.End.Byte
|
|
|
|
}
|
|
|
|
|
2017-05-15 15:33:22 +00:00
|
|
|
// Ptr returns a pointer to a copy of the receiver. This is a convenience when
|
|
|
|
// ranges in places where pointers are required, such as in Diagnostic, but
|
|
|
|
// the range in question is returned from a method. Go would otherwise not
|
|
|
|
// allow one to take the address of a function call.
|
|
|
|
func (r Range) Ptr() *Range {
|
|
|
|
return &r
|
|
|
|
}
|
|
|
|
|
2017-05-14 00:44:11 +00:00
|
|
|
// String returns a compact string representation of the receiver.
|
|
|
|
// Callers should generally prefer to present a range more visually,
|
|
|
|
// e.g. via markers directly on the relevant portion of source code.
|
2017-06-11 15:37:28 +00:00
|
|
|
func (r Range) String() string {
|
2017-05-14 00:44:11 +00:00
|
|
|
if r.Start.Line == r.End.Line {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
"%s:%d,%d-%d",
|
|
|
|
r.Filename,
|
|
|
|
r.Start.Line, r.Start.Column,
|
|
|
|
r.End.Column,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
"%s:%d,%d-%d,%d",
|
|
|
|
r.Filename,
|
|
|
|
r.Start.Line, r.Start.Column,
|
|
|
|
r.End.Line, r.End.Column,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2018-01-14 18:08:39 +00:00
|
|
|
|
|
|
|
func (r Range) Empty() bool {
|
|
|
|
return r.Start.Byte == r.End.Byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// CanSliceBytes returns true if SliceBytes could return an accurate
|
|
|
|
// sub-slice of the given slice.
|
|
|
|
//
|
|
|
|
// This effectively tests whether the start and end offsets of the range
|
|
|
|
// are within the bounds of the slice, and thus whether SliceBytes can be
|
|
|
|
// trusted to produce an accurate start and end position within that slice.
|
|
|
|
func (r Range) CanSliceBytes(b []byte) bool {
|
|
|
|
switch {
|
|
|
|
case r.Start.Byte < 0 || r.Start.Byte > len(b):
|
|
|
|
return false
|
|
|
|
case r.End.Byte < 0 || r.End.Byte > len(b):
|
|
|
|
return false
|
|
|
|
case r.End.Byte < r.Start.Byte:
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SliceBytes returns a sub-slice of the given slice that is covered by the
|
|
|
|
// receiving range, assuming that the given slice is the source code of the
|
|
|
|
// file indicated by r.Filename.
|
|
|
|
//
|
|
|
|
// If the receiver refers to any byte offsets that are outside of the slice
|
|
|
|
// then the result is constrained to the overlapping portion only, to avoid
|
|
|
|
// a panic. Use CanSliceBytes to determine if the result is guaranteed to
|
|
|
|
// be an accurate span of the requested range.
|
|
|
|
func (r Range) SliceBytes(b []byte) []byte {
|
|
|
|
start := r.Start.Byte
|
|
|
|
end := r.End.Byte
|
|
|
|
if start < 0 {
|
|
|
|
start = 0
|
|
|
|
} else if start > len(b) {
|
|
|
|
start = len(b)
|
|
|
|
}
|
|
|
|
if end < 0 {
|
|
|
|
end = 0
|
|
|
|
} else if end > len(b) {
|
|
|
|
end = len(b)
|
|
|
|
}
|
|
|
|
if end < start {
|
|
|
|
end = start
|
|
|
|
}
|
|
|
|
return b[start:end]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Overlaps returns true if the receiver and the other given range share any
|
|
|
|
// characters in common.
|
|
|
|
func (r Range) Overlaps(other Range) bool {
|
|
|
|
switch {
|
|
|
|
case r.Filename != other.Filename:
|
|
|
|
// If the ranges are in different files then they can't possibly overlap
|
|
|
|
return false
|
|
|
|
case r.Empty() || other.Empty():
|
|
|
|
// Empty ranges can never overlap
|
|
|
|
return false
|
|
|
|
case r.ContainsOffset(other.Start.Byte) || r.ContainsOffset(other.End.Byte):
|
|
|
|
return true
|
|
|
|
case other.ContainsOffset(r.Start.Byte) || other.ContainsOffset(r.End.Byte):
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Overlap finds a range that is either identical to or a sub-range of both
|
|
|
|
// the receiver and the other given range. It returns an empty range
|
|
|
|
// within the receiver if there is no overlap between the two ranges.
|
|
|
|
//
|
|
|
|
// A non-empty result is either identical to or a subset of the receiver.
|
|
|
|
func (r Range) Overlap(other Range) Range {
|
|
|
|
if !r.Overlaps(other) {
|
|
|
|
// Start == End indicates an empty range
|
|
|
|
return Range{
|
|
|
|
Filename: r.Filename,
|
|
|
|
Start: r.Start,
|
|
|
|
End: r.Start,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var start, end Pos
|
|
|
|
if r.Start.Byte > other.Start.Byte {
|
|
|
|
start = r.Start
|
|
|
|
} else {
|
|
|
|
start = other.Start
|
|
|
|
}
|
|
|
|
if r.End.Byte < other.End.Byte {
|
|
|
|
end = r.End
|
|
|
|
} else {
|
|
|
|
end = other.End
|
|
|
|
}
|
|
|
|
|
|
|
|
return Range{
|
|
|
|
Filename: r.Filename,
|
|
|
|
Start: start,
|
|
|
|
End: end,
|
|
|
|
}
|
|
|
|
}
|
2018-01-14 19:51:05 +00:00
|
|
|
|
|
|
|
// PartitionAround finds the portion of the given range that overlaps with
|
|
|
|
// the reciever and returns three ranges: the portion of the reciever that
|
|
|
|
// precedes the overlap, the overlap itself, and then the portion of the
|
|
|
|
// reciever that comes after the overlap.
|
|
|
|
//
|
|
|
|
// If the two ranges do not overlap then all three returned ranges are empty.
|
|
|
|
//
|
|
|
|
// If the given range aligns with or extends beyond either extent of the
|
|
|
|
// reciever then the corresponding outer range will be empty.
|
|
|
|
func (r Range) PartitionAround(other Range) (before, overlap, after Range) {
|
|
|
|
overlap = r.Overlap(other)
|
|
|
|
if overlap.Empty() {
|
|
|
|
return overlap, overlap, overlap
|
|
|
|
}
|
|
|
|
|
|
|
|
before = Range{
|
|
|
|
Filename: r.Filename,
|
|
|
|
Start: r.Start,
|
|
|
|
End: overlap.Start,
|
|
|
|
}
|
|
|
|
after = Range{
|
|
|
|
Filename: r.Filename,
|
|
|
|
Start: overlap.End,
|
|
|
|
End: r.End,
|
|
|
|
}
|
|
|
|
|
|
|
|
return before, overlap, after
|
|
|
|
}
|