// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Language.Syntax
{
///
/// Immutable abstract representation of a span of text. For example, in an error diagnostic that reports a
/// location, it could come from a parsed string, text from a tool editor buffer, etc.
///
internal readonly struct TextSpan : IEquatable, IComparable
{
///
/// Creates a TextSpan instance beginning with the position Start and having the Length
/// specified with .
///
public TextSpan(int start, int length)
{
if (start < 0)
{
throw new ArgumentOutOfRangeException(nameof(start));
}
if (start + length < start)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
Start = start;
Length = length;
}
///
/// Start point of the span.
///
public int Start { get; }
///
/// End of the span.
///
public int End => Start + Length;
///
/// Length of the span.
///
public int Length { get; }
///
/// Determines whether or not the span is empty.
///
public bool IsEmpty => Length == 0;
///
/// Determines whether the position lies within the span.
///
///
/// The position to check.
///
///
/// true if the position is greater than or equal to Start and strictly less
/// than End, otherwise false.
///
public bool Contains(int position)
{
return unchecked((uint)(position - Start) < (uint)Length);
}
///
/// Determines whether falls completely within this span.
///
///
/// The span to check.
///
///
/// true if the specified span falls completely within this span, otherwise false.
///
public bool Contains(TextSpan span)
{
return span.Start >= Start && span.End <= End;
}
///
/// Determines whether overlaps this span. Two spans are considered to overlap
/// if they have positions in common and neither is empty. Empty spans do not overlap with any
/// other span.
///
///
/// The span to check.
///
///
/// true if the spans overlap, otherwise false.
///
public bool OverlapsWith(TextSpan span)
{
var overlapStart = Math.Max(Start, span.Start);
var overlapEnd = Math.Min(End, span.End);
return overlapStart < overlapEnd;
}
///
/// Returns the overlap with the given span, or null if there is no overlap.
///
///
/// The span to check.
///
///
/// The overlap of the spans, or null if the overlap is empty.
///
public TextSpan? Overlap(TextSpan span)
{
var overlapStart = Math.Max(Start, span.Start);
var overlapEnd = Math.Min(End, span.End);
return overlapStart < overlapEnd
? FromBounds(overlapStart, overlapEnd)
: (TextSpan?)null;
}
///
/// Determines whether intersects this span. Two spans are considered to
/// intersect if they have positions in common or the end of one span
/// coincides with the start of the other span.
///
///
/// The span to check.
///
///
/// true if the spans intersect, otherwise false.
///
public bool IntersectsWith(TextSpan span)
{
return span.Start <= End && span.End >= Start;
}
///
/// Determines whether intersects this span.
/// A position is considered to intersect if it is between the start and
/// end positions (inclusive) of this span.
///
///
/// The position to check.
///
///
/// true if the position intersects, otherwise false.
///
public bool IntersectsWith(int position)
{
return unchecked((uint)(position - Start) <= (uint)Length);
}
///
/// Returns the intersection with the given span, or null if there is no intersection.
///
///
/// The span to check.
///
///
/// The intersection of the spans, or null if the intersection is empty.
///
public TextSpan? Intersection(TextSpan span)
{
var intersectStart = Math.Max(Start, span.Start);
var intersectEnd = Math.Min(End, span.End);
return intersectStart <= intersectEnd
? FromBounds(intersectStart, intersectEnd)
: (TextSpan?)null;
}
///
/// Creates a new from and positions as opposed to a position and length.
///
/// The returned TextSpan contains the range with inclusive,
/// and exclusive.
///
public static TextSpan FromBounds(int start, int end)
{
if (start < 0)
{
throw new ArgumentOutOfRangeException(nameof(start), "start must not be negative");
}
if (end < start)
{
throw new ArgumentOutOfRangeException(nameof(end), "end must not be less than start");
}
return new TextSpan(start, end - start);
}
///
/// Determines if two instances of are the same.
///
public static bool operator ==(TextSpan left, TextSpan right)
{
return left.Equals(right);
}
///
/// Determines if two instances of are different.
///
public static bool operator !=(TextSpan left, TextSpan right)
{
return !left.Equals(right);
}
///
/// Determines if current instance of is equal to another.
///
public bool Equals(TextSpan other)
{
return Start == other.Start && Length == other.Length;
}
///
/// Determines if current instance of is equal to another.
///
public override bool Equals(object obj)
{
return obj is TextSpan && Equals((TextSpan)obj);
}
///
/// Produces a hash code for .
///
public override int GetHashCode()
{
var combiner = new HashCodeCombiner();
combiner.Add(Start);
combiner.Add(Length);
return combiner.CombinedHash;
}
///
/// Provides a string representation for .
///
public override string ToString()
{
return $"[{Start}..{End})";
}
///
/// Compares current instance of with another.
///
public int CompareTo(TextSpan other)
{
var diff = Start - other.Start;
if (diff != 0)
{
return diff;
}
return Length - other.Length;
}
}
}