Dnspy/dnSpy/dnSpy.Contracts.DnSpy/Text/SpanDataCollection.cs
2021-09-20 18:20:01 +02:00

274 lines
8.8 KiB
C#

/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.VisualStudio.Text;
namespace dnSpy.Contracts.Text {
/// <summary>
/// Span and data collection, sorted by span, no overlaps, see also <see cref="SpanDataCollectionBuilder{TData}"/>
/// </summary>
/// <typeparam name="TData">Type of data</typeparam>
public sealed class SpanDataCollection<TData> : IEnumerable<SpanData<TData>> {
/// <summary>
/// Gets the empty instance
/// </summary>
public static readonly SpanDataCollection<TData> Empty = new SpanDataCollection<TData>(Array.Empty<SpanData<TData>>());
/// <summary>
/// Gets the number of elements
/// </summary>
public int Count => spanDataArray.Length;
/// <summary>
/// Gets the element at <paramref name="index"/>
/// </summary>
/// <param name="index">Index</param>
/// <returns></returns>
public SpanData<TData> this[int index] => spanDataArray[index];
readonly SpanData<TData>[] spanDataArray;
/// <summary>
/// Constructor, see also <see cref="SpanDataCollectionBuilder{TData}"/>
/// </summary>
/// <param name="spanDataArray">Span and data collection</param>
public SpanDataCollection(SpanData<TData>[] spanDataArray) {
if (spanDataArray is null)
throw new ArgumentNullException(nameof(spanDataArray));
#if DEBUG
for (int i = 1; i < spanDataArray.Length; i++) {
if (spanDataArray[i - 1].Span.Length == 0 || spanDataArray[i - 1].Span.End > spanDataArray[i].Span.Start)
throw new ArgumentException("Input array must be sorted and must not contain any overlapping or empty elements", nameof(spanDataArray));
}
if (spanDataArray.Length > 0 && spanDataArray[spanDataArray.Length - 1].Span.Length == 0)
throw new ArgumentException("Input array must be sorted and must not contain any overlapping or empty elements", nameof(spanDataArray));
#endif
this.spanDataArray = spanDataArray;
}
/// <summary>
/// Finds data or returns null if not found
/// </summary>
/// <param name="position">Position</param>
/// <param name="allowIntersection">true if references whose <see cref="Span.End"/> equals <paramref name="position"/> can be returned</param>
/// <returns></returns>
public SpanData<TData>? Find(int position, bool allowIntersection = true) {
var array = spanDataArray;
int lo = 0, hi = array.Length - 1;
while (lo <= hi) {
int index = (lo + hi) / 2;
var spanData = array[index];
if (position < spanData.Span.Start)
hi = index - 1;
else if (position >= spanData.Span.End)
lo = index + 1;
else
return spanData;
}
if (allowIntersection && (uint)hi < (uint)array.Length && array[hi].Span.End == position)
return array[hi];
return null;
}
/// <summary>
/// Finds data
/// </summary>
/// <param name="span">Span to search</param>
/// <returns></returns>
public IEnumerable<SpanData<TData>> Find(Span span) => Find(span.Start, span.Length);
/// <summary>
/// Finds data
/// </summary>
/// <param name="position">Start position</param>
/// <returns></returns>
public IEnumerable<SpanData<TData>> FindFrom(int position) {
if (position < 0)
throw new ArgumentOutOfRangeException(nameof(position));
var array = spanDataArray;
if (array.Length == 0)
return Array.Empty<SpanData<TData>>();
int lastPosition = array[array.Length - 1].Span.End;
int length = lastPosition - position;
if (length < 0)
return Array.Empty<SpanData<TData>>();
return Find(position, length);
}
/// <summary>
/// Finds data
/// </summary>
/// <param name="position">Start position</param>
/// <param name="length">Length</param>
/// <returns></returns>
public IEnumerable<SpanData<TData>> Find(int position, int length) {
if (position < 0)
throw new ArgumentOutOfRangeException(nameof(position));
if (length < 0)
throw new ArgumentOutOfRangeException(nameof(length));
int end = position + length;
if (end < 0)
throw new ArgumentOutOfRangeException(nameof(length));
int index = GetStartIndex(position);
if (index < 0)
yield break;
var array = spanDataArray;
while (index < array.Length) {
var spanData = array[index++];
if (end < spanData.Span.Start)
break;
Debug.Assert(spanData.Span.IntersectsWith(new Span(position, length)));
yield return spanData;
}
}
/// <summary>
/// Gets the index of the first element whose span is greater than or equal to <paramref name="position"/>.
/// -1 is returned if no such element exists.
/// </summary>
/// <param name="position">Position</param>
/// <returns></returns>
public int GetStartIndex(int position) {
var array = spanDataArray;
int lo = 0, hi = array.Length - 1;
while (lo <= hi) {
int index = (lo + hi) / 2;
var spanData = array[index];
if (position < spanData.Span.Start)
hi = index - 1;
else if (position >= spanData.Span.End)
lo = index + 1;
else {
if (index > 0 && array[index - 1].Span.End == position)
return index - 1;
return index;
}
}
if ((uint)hi < (uint)array.Length && array[hi].Span.End == position)
return hi;
return lo < array.Length ? lo : -1;
}
/// <summary>
/// Returns the first <see cref="SpanData{TData}"/> in the collection that satisfies a condition
/// or returns null if nothing was found
/// </summary>
/// <param name="predicate">Returns true if the element should be returned</param>
/// <returns></returns>
public SpanData<TData>? FirstOrNull(Func<SpanData<TData>, bool> predicate) {
foreach (var info in spanDataArray) {
if (predicate(info))
return info;
}
return null;
}
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<SpanData<TData>>)this).GetEnumerator();
IEnumerator<SpanData<TData>> IEnumerable<SpanData<TData>>.GetEnumerator() {
foreach (var info in spanDataArray)
yield return info;
}
}
/// <summary>
/// Span and data
/// </summary>
/// <typeparam name="TData">Type of data</typeparam>
public readonly struct SpanData<TData> {
/// <summary>
/// Gets the span
/// </summary>
public Span Span { get; }
/// <summary>
/// Gets the data
/// </summary>
public TData Data { get; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="span">Span</param>
/// <param name="data">Data</param>
public SpanData(Span span, TData data) {
Span = span;
Data = data;
}
/// <summary>
/// ToString()
/// </summary>
/// <returns></returns>
public override string ToString() => "[" + Span.ToString() + "]";
}
/// <summary>
/// Builds a <see cref="SpanDataCollection{TData}"/>
/// </summary>
/// <typeparam name="TData">Type of data</typeparam>
public readonly struct SpanDataCollectionBuilder<TData> {
readonly List<SpanData<TData>> list;
/// <summary>
/// Creates a <see cref="SpanDataCollectionBuilder{TData}"/>
/// </summary>
/// <returns></returns>
public static SpanDataCollectionBuilder<TData> CreateBuilder() => new SpanDataCollectionBuilder<TData>(true);
/// <summary>
/// Creates a <see cref="SpanDataCollectionBuilder{TData}"/>
/// </summary>
/// <param name="capacity">Capacity</param>
/// <returns></returns>
public static SpanDataCollectionBuilder<TData> CreateBuilder(int capacity) => new SpanDataCollectionBuilder<TData>(capacity);
SpanDataCollectionBuilder(bool unused) => list = new List<SpanData<TData>>();
SpanDataCollectionBuilder(int capacity) => list = new List<SpanData<TData>>(capacity);
/// <summary>
/// Clears the created list
/// </summary>
public void Clear() => list.Clear();
/// <summary>
/// Adds span and data. The span must be located after the previously added span
/// </summary>
/// <param name="span">Span</param>
/// <param name="data">Data</param>
public void Add(Span span, TData data) {
Debug.Assert(list.Count == 0 || list[list.Count - 1].Span.End <= span.Start);
if (!span.IsEmpty)
list.Add(new SpanData<TData>(span, data));
}
/// <summary>
/// Creates a <see cref="SpanDataCollection{TData}"/>
/// </summary>
/// <returns></returns>
public SpanDataCollection<TData> Create() => list.Count == 0 ? SpanDataCollection<TData>.Empty : new SpanDataCollection<TData>(list.ToArray());
}
}