/*
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 .
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.VisualStudio.Text;
namespace dnSpy.Contracts.Text {
///
/// Span and data collection, sorted by span, no overlaps, see also
///
/// Type of data
public sealed class SpanDataCollection : IEnumerable> {
///
/// Gets the empty instance
///
public static readonly SpanDataCollection Empty = new SpanDataCollection(Array.Empty>());
///
/// Gets the number of elements
///
public int Count => spanDataArray.Length;
///
/// Gets the element at
///
/// Index
///
public SpanData this[int index] => spanDataArray[index];
readonly SpanData[] spanDataArray;
///
/// Constructor, see also
///
/// Span and data collection
public SpanDataCollection(SpanData[] 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;
}
///
/// Finds data or returns null if not found
///
/// Position
/// true if references whose equals can be returned
///
public SpanData? 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;
}
///
/// Finds data
///
/// Span to search
///
public IEnumerable> Find(Span span) => Find(span.Start, span.Length);
///
/// Finds data
///
/// Start position
///
public IEnumerable> FindFrom(int position) {
if (position < 0)
throw new ArgumentOutOfRangeException(nameof(position));
var array = spanDataArray;
if (array.Length == 0)
return Array.Empty>();
int lastPosition = array[array.Length - 1].Span.End;
int length = lastPosition - position;
if (length < 0)
return Array.Empty>();
return Find(position, length);
}
///
/// Finds data
///
/// Start position
/// Length
///
public IEnumerable> 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;
}
}
///
/// Gets the index of the first element whose span is greater than or equal to .
/// -1 is returned if no such element exists.
///
/// Position
///
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;
}
///
/// Returns the first in the collection that satisfies a condition
/// or returns null if nothing was found
///
/// Returns true if the element should be returned
///
public SpanData? FirstOrNull(Func, bool> predicate) {
foreach (var info in spanDataArray) {
if (predicate(info))
return info;
}
return null;
}
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator();
IEnumerator> IEnumerable>.GetEnumerator() {
foreach (var info in spanDataArray)
yield return info;
}
}
///
/// Span and data
///
/// Type of data
public readonly struct SpanData {
///
/// Gets the span
///
public Span Span { get; }
///
/// Gets the data
///
public TData Data { get; }
///
/// Constructor
///
/// Span
/// Data
public SpanData(Span span, TData data) {
Span = span;
Data = data;
}
///
/// ToString()
///
///
public override string ToString() => "[" + Span.ToString() + "]";
}
///
/// Builds a
///
/// Type of data
public readonly struct SpanDataCollectionBuilder {
readonly List> list;
///
/// Creates a
///
///
public static SpanDataCollectionBuilder CreateBuilder() => new SpanDataCollectionBuilder(true);
///
/// Creates a
///
/// Capacity
///
public static SpanDataCollectionBuilder CreateBuilder(int capacity) => new SpanDataCollectionBuilder(capacity);
SpanDataCollectionBuilder(bool unused) => list = new List>();
SpanDataCollectionBuilder(int capacity) => list = new List>(capacity);
///
/// Clears the created list
///
public void Clear() => list.Clear();
///
/// Adds span and data. The span must be located after the previously added span
///
/// Span
/// Data
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(span, data));
}
///
/// Creates a
///
///
public SpanDataCollection Create() => list.Count == 0 ? SpanDataCollection.Empty : new SpanDataCollection(list.ToArray());
}
}