// RichTextKit // Copyright © 2019-2020 Topten Software. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this product except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; namespace Topten.RichTextKit.Utils { /// /// A growable array of elements of type `T` /// /// The buffer element type [DebuggerDisplay("Length = {Length}")] public class Buffer : IEnumerable, IEnumerable { /// /// Constructs a new buffer. /// public Buffer() { // Create initial array _data = new T[32]; } /// /// The data held by this buffer /// T[] _data; /// /// The data held by this buffer /// public T[] Underlying => _data; /// /// The used length of the buffer /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] int _length; /// /// Gets or sets the length of the buffer /// /// /// The internal buffer will be grown if the new length is larger /// than the current buffer size. /// public int Length { get => _length; set { // Now grow buffer if (!GrowBuffer(value)) { // If the length is increasing, but we didn't re-size the buffer // then we need to clear the new elements. if (value > _length) { Array.Clear(_data, _length, value - _length); } } // Store new length _length = value; } } /// /// Ensures the buffer has sufficient capacity /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] bool GrowBuffer(int requiredLength) { if (requiredLength <= _data.Length) return false; // Work out new length int newLength; if (_data.Length < 1048576) { // Below 1MB, grow by doubling... newLength = _data.Length * 2; } else { // otherwise grow by 1mb at a time... newLength = _data.Length + 1048576; } // Make sure we're allocating enough if (newLength < requiredLength) newLength = requiredLength; // Allocate new buffer, only copying _length, not Data.Length var newData = new T[requiredLength]; Array.Copy(_data, 0, newData, 0, _length); _data = newData; return true; } /// /// Clears the buffer, keeping the internally allocated array. /// public void Clear() { _length = 0; } /// /// Inserts room into the buffer /// /// The position to insert at /// The length to insert /// Whether to clear the inserted part of the buffer /// The new buffer area as a slice public Slice Insert(int position, int length, bool clear = true) { // Grow internal buffer? GrowBuffer(_length + length); // Shuffle existing to make room for inserted data if (position < _length) { Array.Copy(_data, position, _data, position + length, _length - position); } // Update the length _length += length; // Clear it? if (clear) Array.Clear(_data, position, length); // Return slice return SubSlice(position, length); } /// /// Insert a slice of data into this buffer /// /// The position to insert at /// The data to insert /// The newly inserted data as a slice public Slice Insert(int position, Slice data) { // Make room var slice = Insert(position, data.Length, false); // Copy in the source slice Array.Copy(data.Underlying, data.Start, _data, position, data.Length); // Return slice return slice; } /// /// Adds to the buffer, returning a slice of requested size /// /// Number of elements to add /// True to clear the content; otherwise false /// A slice representing the allocated elements. public Slice Add(int length, bool clear = true) { return Insert(_length, length, clear); } /// /// Add a slice of data to this buffer. /// /// The slice to be added /// A slice representing the added elements. public Slice Add(Slice slice) { var pos = _length; // Grow internal buffer? GrowBuffer(_length + slice.Length); _length += slice.Length; // Copy in the slice Array.Copy(slice.Underlying, slice.Start, _data, pos, slice.Length); // Return position return SubSlice(pos, slice.Length); } /// /// Delete a section of the buffer /// /// The position to delete from /// The length to of the deletion public void Delete(int from, int length) { // Clamp to buffer size if (from >= _length) return; if (from + length >= _length) length = _length - from; // Shuffle trailing data if (from + length < _length) { Array.Copy(_data, from + length, _data, from, _length - (from + length)); } // Update length _length -= length; } /// /// Gets a reference to an element in the buffer /// /// The element index /// A reference to the element value. public ref T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (index < 0 || index >= _length) throw new ArgumentOutOfRangeException(nameof(index)); return ref _data[index]; } } /// /// Returns a range within this buffer as a /// /// Start offset of the slice /// Length of the slice /// A slice for the specified sub-range public Slice SubSlice(int start, int length) { if (start < 0 || start + length > _length) { throw new ArgumentOutOfRangeException($"Invalid buffer slice range ({start},{length}) with buffer length of {_length}"); } return new Slice(_data, start, length); } /// /// Returns the entire buffer contents as a /// /// A Slice public Slice AsSlice() { return SubSlice(0, _length); } /// /// Split the utf32 buffer on a codepoint type /// /// The delimiter /// An enumeration of slices public IEnumerable> Split(T delim) { int start = 0; for (int i = 0; i < Length; i++) { if (_data[i].Equals(delim)) { yield return SubSlice(start, i - start); start = i + 1; } } yield return SubSlice(start, Length - start); } /// /// Split the utf32 buffer on a codepoint type /// /// The delimiter to split on /// An enumeration of offset/length for each range public IEnumerable<(int Offset, int Length)> GetRanges(T delim) { int start = 0; for (int i = 0; i < Length; i++) { if (_data[i].Equals(delim)) { yield return (start, i - start); start = i + 1; } } yield return (start, Length - start); } /// /// Replaces all instances of a value in the buffer with another value /// /// The value to replace /// The new value /// The number of replacements made public int Replace(T oldValue, T newValue) { int count = 0; for (int i = 0; i < Length; i++) { if (_data[i].Equals(oldValue)) { _data[i] = newValue; count++; } } return count; } IEnumerator IEnumerable.GetEnumerator() { return new ArraySliceEnumerator(_data, 0, _length); } IEnumerator IEnumerable.GetEnumerator() { return new ArraySliceEnumerator(_data, 0, _length); } } }