hcl: SourceRange.PartitionAround

This is a convenience wrapper around SourceRange.Overlap that also
calculates the ranges in the receiver that _aren't_ overlapping with the
given range.

This is useful when, for example, partitioning a portion of source code
to insert markers to highlight the location of an error, as we do when
printing code snippets as part of diagnostic output.
This commit is contained in:
Martin Atkins 2018-01-14 11:51:05 -08:00
parent 1365a2cfe5
commit 368a3f81c0
2 changed files with 161 additions and 0 deletions

@ -231,3 +231,32 @@ func (r Range) Overlap(other Range) Range {
End: end,
}
}
// 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
}

@ -307,6 +307,138 @@ func TestPosOverlap(t *testing.T) {
}
}
func TestRangePartitionAround(t *testing.T) {
tests := []struct {
Outer Range
Inner Range
WantBefore Range
WantOverlap Range
WantAfter Range
}{
{
Range{ // ##
Start: Pos{Byte: 2, Line: 1, Column: 3},
End: Pos{Byte: 4, Line: 1, Column: 5},
},
Range{ // ####
Start: Pos{Byte: 1, Line: 1, Column: 2},
End: Pos{Byte: 5, Line: 1, Column: 6},
},
Range{ // (empty)
Start: Pos{Byte: 2, Line: 1, Column: 3},
End: Pos{Byte: 2, Line: 1, Column: 3},
},
Range{ // ##
Start: Pos{Byte: 2, Line: 1, Column: 3},
End: Pos{Byte: 4, Line: 1, Column: 5},
},
Range{ // (empty)
Start: Pos{Byte: 4, Line: 1, Column: 5},
End: Pos{Byte: 4, Line: 1, Column: 5},
},
},
{
Range{ // ####
Start: Pos{Byte: 0, Line: 1, Column: 1},
End: Pos{Byte: 4, Line: 1, Column: 5},
},
Range{ // ####
Start: Pos{Byte: 1, Line: 1, Column: 2},
End: Pos{Byte: 5, Line: 1, Column: 6},
},
Range{ // #
Start: Pos{Byte: 0, Line: 1, Column: 1},
End: Pos{Byte: 1, Line: 1, Column: 2},
},
Range{ // ###
Start: Pos{Byte: 1, Line: 1, Column: 2},
End: Pos{Byte: 4, Line: 1, Column: 5},
},
Range{ // (empty)
Start: Pos{Byte: 4, Line: 1, Column: 5},
End: Pos{Byte: 4, Line: 1, Column: 5},
},
},
{
Range{ // ####
Start: Pos{Byte: 2, Line: 1, Column: 3},
End: Pos{Byte: 5, Line: 1, Column: 6},
},
Range{ // ####
Start: Pos{Byte: 1, Line: 1, Column: 2},
End: Pos{Byte: 5, Line: 1, Column: 6},
},
Range{ // (empty)
Start: Pos{Byte: 2, Line: 1, Column: 3},
End: Pos{Byte: 2, Line: 1, Column: 3},
},
Range{ // ###
Start: Pos{Byte: 2, Line: 1, Column: 3},
End: Pos{Byte: 5, Line: 1, Column: 6},
},
Range{ // #
Start: Pos{Byte: 5, Line: 1, Column: 6},
End: Pos{Byte: 5, Line: 1, Column: 6},
},
},
{
Range{ // ####
Start: Pos{Byte: 1, Line: 1, Column: 2},
End: Pos{Byte: 5, Line: 1, Column: 6},
},
Range{ // ##
Start: Pos{Byte: 2, Line: 1, Column: 3},
End: Pos{Byte: 4, Line: 1, Column: 5},
},
Range{ // #
Start: Pos{Byte: 1, Line: 1, Column: 2},
End: Pos{Byte: 2, Line: 1, Column: 3},
},
Range{ // ##
Start: Pos{Byte: 2, Line: 1, Column: 3},
End: Pos{Byte: 4, Line: 1, Column: 5},
},
Range{ // #
Start: Pos{Byte: 4, Line: 1, Column: 5},
End: Pos{Byte: 5, Line: 1, Column: 6},
},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s around %s", test.Outer, test.Inner), func(t *testing.T) {
gotBefore, gotOverlap, gotAfter := test.Outer.PartitionAround(test.Inner)
if !reflect.DeepEqual(gotBefore, test.WantBefore) {
t.Errorf(
"wrong before\nA : %-10s %s\nB : %-10s %s\ngot : %-10s %s\nwant: %-10s %s",
visRangeOffsets(test.Outer), test.Outer,
visRangeOffsets(test.Inner), test.Inner,
visRangeOffsets(gotBefore), gotBefore,
visRangeOffsets(test.WantBefore), test.WantBefore,
)
}
if !reflect.DeepEqual(gotOverlap, test.WantOverlap) {
t.Errorf(
"wrong overlap\nA : %-10s %s\nB : %-10s %s\ngot : %-10s %s\nwant: %-10s %s",
visRangeOffsets(test.Outer), test.Outer,
visRangeOffsets(test.Inner), test.Inner,
visRangeOffsets(gotOverlap), gotOverlap,
visRangeOffsets(test.WantOverlap), test.WantOverlap,
)
}
if !reflect.DeepEqual(gotAfter, test.WantAfter) {
t.Errorf(
"wrong after\nA : %-10s %s\nB : %-10s %s\ngot : %-10s %s\nwant: %-10s %s",
visRangeOffsets(test.Outer), test.Outer,
visRangeOffsets(test.Inner), test.Inner,
visRangeOffsets(gotAfter), gotAfter,
visRangeOffsets(test.WantAfter), test.WantAfter,
)
}
})
}
}
// visRangeOffsets is a helper that produces a visual representation of the
// start and end byte offsets of the given range, which can then be stacked
// with the same for other ranges to more easily see how the ranges relate