/* 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()); } }