Trim/TrimStart/TrimEnd methods for Memory and Span (#22798)
authorGrant <grant@jesanna.com>
Tue, 19 Mar 2019 21:20:00 +0000 (14:20 -0700)
committerStephen Toub <stoub@microsoft.com>
Tue, 19 Mar 2019 21:20:00 +0000 (17:20 -0400)
* Span.Trim

* Add another helper

* Remove redundant comments

* Optimize Trim(ReadOnlySpan<char>)

* Suggested overload for byte

* CR fixes

* CR fix

* Add Span overloads

* Add Memory overloads

* CR

* Rename method

* Optimization

* Tweaks

* Refactor

* Opt

* Use 2 helpers

* CR fixes

* CR fixes

* Nits

* CR fixes

* remove in params

* Bug

* Bug

* Move Trim methods to their own dedicated file

* Add char overloads

* Use Contains

* CR fixes

* Optmize ClampNull

* Nit

* Revert optimization

* Make existing implementations consistent with new

* Revert existing methods

* Move helpers

* Span, Memory and RoM should use consistent pattern

* POC for changing RoS

* Revert POC

* Use consistent idiom

* More inlining of explaining vars

* Use expression body for existing code

* CR fixes

* Nit

* Grammar

* CR fixes

* Fix perf bug in existing code

* Perf fix

* Force CI build

* Force CI

src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
src/System.Private.CoreLib/shared/System/MemoryExtensions.Trim.cs [new file with mode: 0644]
src/System.Private.CoreLib/shared/System/MemoryExtensions.cs

index b1c9da0..4b5bd02 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)System\MemoryDebugView.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\MemoryExtensions.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\MemoryExtensions.Fast.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\MemoryExtensions.Trim.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\MethodAccessException.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\MidpointRounding.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\MissingFieldException.cs" />
diff --git a/src/System.Private.CoreLib/shared/System/MemoryExtensions.Trim.cs b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Trim.cs
new file mode 100644 (file)
index 0000000..be98bb1
--- /dev/null
@@ -0,0 +1,866 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System
+{
+    public static partial class MemoryExtensions
+    {
+        /// <summary>
+        /// Removes all leading and trailing occurrences of a specified element from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static Memory<T> Trim<T>(this Memory<T> memory, T trimElement)
+            where T : IEquatable<T>
+        {
+            ReadOnlySpan<T> span = memory.Span;
+            int start = ClampStart(span, trimElement);
+            int length = ClampEnd(span, start + 1, trimElement);
+            return memory.Slice(start, length);
+        }
+
+        /// <summary>
+        /// Removes all leading occurrences of a specified element from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static Memory<T> TrimStart<T>(this Memory<T> memory, T trimElement)
+            where T : IEquatable<T>
+            => memory.Slice(ClampStart(memory.Span, trimElement));
+
+        /// <summary>
+        /// Removes all trailing occurrences of a specified element from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static Memory<T> TrimEnd<T>(this Memory<T> memory, T trimElement)
+            where T : IEquatable<T>
+            => memory.Slice(0, ClampEnd(memory.Span, 0, trimElement));
+
+        /// <summary>
+        /// Removes all leading and trailing occurrences of a specified element from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static ReadOnlyMemory<T> Trim<T>(this ReadOnlyMemory<T> memory, T trimElement)
+            where T : IEquatable<T>
+        {
+            ReadOnlySpan<T> span = memory.Span;
+            int start = ClampStart(span, trimElement);
+            int length = ClampEnd(span, start + 1, trimElement);
+            return memory.Slice(start, length);
+        }
+
+        /// <summary>
+        /// Removes all leading occurrences of a specified element from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static ReadOnlyMemory<T> TrimStart<T>(this ReadOnlyMemory<T> memory, T trimElement)
+            where T : IEquatable<T>
+            => memory.Slice(ClampStart(memory.Span, trimElement));
+
+        /// <summary>
+        /// Removes all trailing occurrences of a specified element from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static ReadOnlyMemory<T> TrimEnd<T>(this ReadOnlyMemory<T> memory, T trimElement)
+            where T : IEquatable<T>
+            => memory.Slice(0, ClampEnd(memory.Span, 0, trimElement));
+
+        /// <summary>
+        /// Removes all leading and trailing occurrences of a specified element from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static Span<T> Trim<T>(this Span<T> span, T trimElement)
+            where T : IEquatable<T>
+        {
+            int start = ClampStart(span, trimElement);
+            int length = ClampEnd(span, start + 1, trimElement);
+            return span.Slice(start, length);
+        }
+
+        /// <summary>
+        /// Removes all leading occurrences of a specified element from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static Span<T> TrimStart<T>(this Span<T> span, T trimElement)
+            where T : IEquatable<T>
+            => span.Slice(ClampStart(span, trimElement));
+
+        /// <summary>
+        /// Removes all trailing occurrences of a specified element from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static Span<T> TrimEnd<T>(this Span<T> span, T trimElement)
+            where T : IEquatable<T>
+            => span.Slice(0, ClampEnd(span, 0, trimElement));
+
+        /// <summary>
+        /// Removes all leading and trailing occurrences of a specified element from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static ReadOnlySpan<T> Trim<T>(this ReadOnlySpan<T> span, T trimElement)
+            where T : IEquatable<T>
+        {
+            int start = ClampStart(span, trimElement);
+            int length = ClampEnd(span, start + 1, trimElement);
+            return span.Slice(start, length);
+        }
+
+        /// <summary>
+        /// Removes all leading occurrences of a specified element from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static ReadOnlySpan<T> TrimStart<T>(this ReadOnlySpan<T> span, T trimElement)
+            where T : IEquatable<T>
+            => span.Slice(ClampStart(span, trimElement));
+
+        /// <summary>
+        /// Removes all trailing occurrences of a specified element from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        public static ReadOnlySpan<T> TrimEnd<T>(this ReadOnlySpan<T> span, T trimElement)
+            where T : IEquatable<T>
+            => span.Slice(0, ClampEnd(span, 0, trimElement));
+
+        /// <summary>
+        /// Delimits all leading occurrences of a specified element from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the element is removed.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        private static int ClampStart<T>(ReadOnlySpan<T> span, T trimElement)
+            where T : IEquatable<T>
+        {
+            int start = 0;
+
+            if (trimElement != null)
+            {
+                for (; start < span.Length; start++)
+                {
+                    if (!trimElement.Equals(span[start]))
+                    {
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                for (; start < span.Length; start++)
+                {
+                    if (span[start] != null)
+                    {
+                        break;
+                    }
+                }
+            }
+
+            return start;
+        }
+
+        /// <summary>
+        /// Delimits all trailing occurrences of a specified element from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the element is removed.</param>
+        /// <param name="start">The start index from which to being searching.</param>
+        /// <param name="trimElement">The specified element to look for and remove.</param>
+        private static int ClampEnd<T>(ReadOnlySpan<T> span, int start, T trimElement)
+            where T : IEquatable<T>
+        {
+            // Initially, start==len==0. If ClampStart trims all, start==len
+            Debug.Assert((uint)start <= span.Length);
+
+            int end = span.Length - 1;
+
+            if (trimElement != null)
+            {
+                for (; end >= start; end--)
+                {
+                    if (!trimElement.Equals(span[end]))
+                    {
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                for (; end >= start; end--)
+                {
+                    if (span[end] != null)
+                    {
+                        break;
+                    }
+                }
+            }
+
+            return end - start + 1;
+        }
+
+        /// <summary>
+        /// Removes all leading and trailing occurrences of a set of elements specified
+        /// in a readonly span from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the memory is returned unaltered.</remarks>
+        public static Memory<T> Trim<T>(this Memory<T> memory, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                ReadOnlySpan<T> span = memory.Span;
+                int start = ClampStart(span, trimElements);
+                int length = ClampEnd(span, start + 1, trimElements);
+                return memory.Slice(start, length);
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return Trim(memory, trimElements[0]);
+            }
+
+            return memory;
+        }
+
+        /// <summary>
+        /// Removes all leading occurrences of a set of elements specified
+        /// in a readonly span from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the memory is returned unaltered.</remarks>
+        public static Memory<T> TrimStart<T>(this Memory<T> memory, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                return memory.Slice(ClampStart(memory.Span, trimElements));
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return TrimStart(memory, trimElements[0]);
+            }
+
+            return memory;
+        }
+
+        /// <summary>
+        /// Removes all trailing occurrences of a set of elements specified
+        /// in a readonly span from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the memory is returned unaltered.</remarks>
+        public static Memory<T> TrimEnd<T>(this Memory<T> memory, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                return memory.Slice(0, ClampEnd(memory.Span, 0, trimElements));
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return TrimEnd(memory, trimElements[0]);
+            }
+
+            return memory;
+        }
+
+        /// <summary>
+        /// Removes all leading and trailing occurrences of a set of elements specified
+        /// in a readonly span from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the memory is returned unaltered.</remarks>
+        public static ReadOnlyMemory<T> Trim<T>(this ReadOnlyMemory<T> memory, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                ReadOnlySpan<T> span = memory.Span;
+                int start = ClampStart(span, trimElements);
+                int length = ClampEnd(span, start + 1, trimElements);
+                return memory.Slice(start, length);
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return Trim(memory, trimElements[0]);
+            }
+
+            return memory;
+        }
+
+        /// <summary>
+        /// Removes all leading occurrences of a set of elements specified
+        /// in a readonly span from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the memory is returned unaltered.</remarks>
+        public static ReadOnlyMemory<T> TrimStart<T>(this ReadOnlyMemory<T> memory, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                return memory.Slice(ClampStart(memory.Span, trimElements));
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return TrimStart(memory, trimElements[0]);
+            }
+
+            return memory;
+        }
+
+        /// <summary>
+        /// Removes all trailing occurrences of a set of elements specified
+        /// in a readonly span from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the memory is returned unaltered.</remarks>
+        public static ReadOnlyMemory<T> TrimEnd<T>(this ReadOnlyMemory<T> memory, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                return memory.Slice(0, ClampEnd(memory.Span, 0, trimElements));
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return TrimEnd(memory, trimElements[0]);
+            }
+
+            return memory;
+
+        }
+
+        /// <summary>
+        /// Removes all leading and trailing occurrences of a set of elements specified
+        /// in a readonly span from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the span is returned unaltered.</remarks>
+        public static Span<T> Trim<T>(this Span<T> span, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                int start = ClampStart(span, trimElements);
+                int length = ClampEnd(span, start + 1, trimElements);
+                return span.Slice(start, length);
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return Trim(span, trimElements[0]);
+            }
+
+            return span;
+        }
+
+        /// <summary>
+        /// Removes all leading occurrences of a set of elements specified
+        /// in a readonly span from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the span is returned unaltered.</remarks>
+        public static Span<T> TrimStart<T>(this Span<T> span, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                return span.Slice(ClampStart(span, trimElements));
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return TrimStart(span, trimElements[0]);
+            }
+
+            return span;
+        }
+
+        /// <summary>
+        /// Removes all trailing occurrences of a set of elements specified
+        /// in a readonly span from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the span is returned unaltered.</remarks>
+        public static Span<T> TrimEnd<T>(this Span<T> span, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                return span.Slice(0, ClampEnd(span, 0, trimElements));
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return TrimEnd(span, trimElements[0]);
+            }
+
+            return span;
+        }
+
+        /// <summary>
+        /// Removes all leading and trailing occurrences of a set of elements specified
+        /// in a readonly span from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the span is returned unaltered.</remarks>
+        public static ReadOnlySpan<T> Trim<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                int start = ClampStart(span, trimElements);
+                int length = ClampEnd(span, start + 1, trimElements);
+                return span.Slice(start, length);
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return Trim(span, trimElements[0]);
+            }
+
+            return span;
+        }
+
+        /// <summary>
+        /// Removes all leading occurrences of a set of elements specified
+        /// in a readonly span from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the span is returned unaltered.</remarks>
+        public static ReadOnlySpan<T> TrimStart<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                return span.Slice(ClampStart(span, trimElements));
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return TrimStart(span, trimElements[0]);
+            }
+
+            return span;
+        }
+
+        /// <summary>
+        /// Removes all trailing occurrences of a set of elements specified
+        /// in a readonly span from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        /// <remarks>If <paramref name="trimElements"/> is empty, the span is returned unaltered.</remarks>
+        public static ReadOnlySpan<T> TrimEnd<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            if (trimElements.Length > 1)
+            {
+                return span.Slice(0, ClampEnd(span, 0, trimElements));
+            }
+
+            if (trimElements.Length == 1)
+            {
+                return TrimEnd(span, trimElements[0]);
+            }
+
+            return span;
+        }
+
+        /// <summary>
+        /// Delimits all leading occurrences of a set of elements specified
+        /// in a readonly span from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the elements are removed.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        private static int ClampStart<T>(ReadOnlySpan<T> span, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            int start = 0;
+            for (; start < span.Length; start++)
+            {
+                if (!trimElements.Contains(span[start]))
+                {
+                    break;
+                }
+            }
+
+            return start;
+        }
+
+        /// <summary>
+        /// Delimits all trailing occurrences of a set of elements specified
+        /// in a readonly span from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the elements are removed.</param>
+        /// <param name="start">The start index from which to being searching.</param>
+        /// <param name="trimElements">The span which contains the set of elements to remove.</param>
+        private static int ClampEnd<T>(ReadOnlySpan<T> span, int start, ReadOnlySpan<T> trimElements)
+            where T : IEquatable<T>
+        {
+            // Initially, start==len==0. If ClampStart trims all, start==len
+            Debug.Assert((uint)start <= span.Length);
+
+            int end = span.Length - 1;
+            for (; end >= start; end--)
+            {
+                if (!trimElements.Contains(span[end]))
+                {
+                    break;
+                }
+            }
+
+            return end - start + 1;
+        }
+
+        /// <summary>
+        /// Removes all leading and trailing white-space characters from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the characters are removed.</param>
+        public static Memory<char> Trim(this Memory<char> memory)
+        {
+            ReadOnlySpan<char> span = memory.Span;
+            int start = ClampStart(span);
+            int length = ClampEnd(span, start + 1);
+            return memory.Slice(start, length);
+        }
+
+        /// <summary>
+        /// Removes all leading white-space characters from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the characters are removed.</param>
+        public static Memory<char> TrimStart(this Memory<char> memory)
+            => memory.Slice(ClampStart(memory.Span));
+
+        /// <summary>
+        /// Removes all trailing white-space characters from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the characters are removed.</param>
+        public static Memory<char> TrimEnd(this Memory<char> memory)
+            => memory.Slice(0, ClampEnd(memory.Span, 0));
+
+        /// <summary>
+        /// Removes all leading and trailing white-space characters from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the characters are removed.</param>
+        public static ReadOnlyMemory<char> Trim(this ReadOnlyMemory<char> memory)
+        {
+            ReadOnlySpan<char> span = memory.Span;
+            int start = ClampStart(span);
+            int length = ClampEnd(span, start + 1);
+            return memory.Slice(start, length);
+        }
+
+        /// <summary>
+        /// Removes all leading white-space characters from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the characters are removed.</param>
+        public static ReadOnlyMemory<char> TrimStart(this ReadOnlyMemory<char> memory)
+            => memory.Slice(ClampStart(memory.Span));
+
+        /// <summary>
+        /// Removes all trailing white-space characters from the memory.
+        /// </summary>
+        /// <param name="memory">The source memory from which the characters are removed.</param>
+        public static ReadOnlyMemory<char> TrimEnd(this ReadOnlyMemory<char> memory)
+            => memory.Slice(0, ClampEnd(memory.Span, 0));
+
+        /// <summary>
+        /// Removes all leading and trailing white-space characters from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the characters are removed.</param>
+        public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span)
+        {
+            int start = 0;
+            for (; start < span.Length; start++)
+            {
+                if (!char.IsWhiteSpace(span[start]))
+                {
+                    break;
+                }
+            }
+
+            int end = span.Length - 1;
+            for (; end > start; end--)
+            {
+                if (!char.IsWhiteSpace(span[end]))
+                {
+                    break;
+                }
+            }
+
+            return span.Slice(start, end - start + 1);
+        }
+
+        /// <summary>
+        /// Removes all leading white-space characters from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the characters are removed.</param>
+        public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span)
+        {
+            int start = 0;
+            for (; start < span.Length; start++)
+            {
+                if (!char.IsWhiteSpace(span[start]))
+                {
+                    break;
+                }
+            }
+
+            return span.Slice(start);
+        }
+
+        /// <summary>
+        /// Removes all trailing white-space characters from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the characters are removed.</param>
+        public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span)
+        {
+            int end = span.Length - 1;
+            for (; end >= 0; end--)
+            {
+                if (!char.IsWhiteSpace(span[end]))
+                {
+                    break;
+                }
+            }
+
+            return span.Slice(0, end + 1);
+        }
+
+        /// <summary>
+        /// Removes all leading and trailing occurrences of a specified character from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the character is removed.</param>
+        /// <param name="trimChar">The specified character to look for and remove.</param>
+        public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span, char trimChar)
+        {
+            int start = 0;
+            for (; start < span.Length; start++)
+            {
+                if (span[start] != trimChar)
+                {
+                    break;
+                }
+            }
+
+            int end = span.Length - 1;
+            for (; end > start; end--)
+            {
+                if (span[end] != trimChar)
+                {
+                    break;
+                }
+            }
+
+            return span.Slice(start, end - start + 1);
+        }
+
+        /// <summary>
+        /// Removes all leading occurrences of a specified character from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the character is removed.</param>
+        /// <param name="trimChar">The specified character to look for and remove.</param>
+        public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span, char trimChar)
+        {
+            int start = 0;
+            for (; start < span.Length; start++)
+            {
+                if (span[start] != trimChar)
+                {
+                    break;
+                }
+            }
+
+            return span.Slice(start);
+        }
+
+        /// <summary>
+        /// Removes all trailing occurrences of a specified character from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the character is removed.</param>
+        /// <param name="trimChar">The specified character to look for and remove.</param>
+        public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span, char trimChar)
+        {
+            int end = span.Length - 1;
+            for (; end >= 0; end--)
+            {
+                if (span[end] != trimChar)
+                {
+                    break;
+                }
+            }
+
+            return span.Slice(0, end + 1);
+        }
+
+        /// <summary>
+        /// Removes all leading and trailing occurrences of a set of characters specified
+        /// in a readonly span from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the characters are removed.</param>
+        /// <param name="trimChars">The span which contains the set of characters to remove.</param>
+        /// <remarks>If <paramref name="trimChars"/> is empty, white-space characters are removed instead.</remarks>
+        public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
+            => span.TrimStart(trimChars).TrimEnd(trimChars);
+
+        /// <summary>
+        /// Removes all leading occurrences of a set of characters specified
+        /// in a readonly span from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the characters are removed.</param>
+        /// <param name="trimChars">The span which contains the set of characters to remove.</param>
+        /// <remarks>If <paramref name="trimChars"/> is empty, white-space characters are removed instead.</remarks>
+        public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
+        {
+            if (trimChars.IsEmpty)
+            {
+                return span.TrimStart();
+            }
+
+            int start = 0;
+            for (; start < span.Length; start++)
+            {
+                for (int i = 0; i < trimChars.Length; i++)
+                {
+                    if (span[start] == trimChars[i])
+                    {
+                        goto Next;
+                    }
+                }
+
+                break;
+            Next:
+                ;
+            }
+
+            return span.Slice(start);
+        }
+
+        /// <summary>
+        /// Removes all trailing occurrences of a set of characters specified
+        /// in a readonly span from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the characters are removed.</param>
+        /// <param name="trimChars">The span which contains the set of characters to remove.</param>
+        /// <remarks>If <paramref name="trimChars"/> is empty, white-space characters are removed instead.</remarks>
+        public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
+        {
+            if (trimChars.IsEmpty)
+            {
+                return span.TrimEnd();
+            }
+
+            int end = span.Length - 1;
+            for (; end >= 0; end--)
+            {
+                for (int i = 0; i < trimChars.Length; i++)
+                {
+                    if (span[end] == trimChars[i])
+                    {
+                        goto Next;
+                    }
+                }
+
+                break;
+            Next:
+                ;
+            }
+
+            return span.Slice(0, end + 1);
+        }
+
+        /// <summary>
+        /// Removes all leading and trailing white-space characters from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the characters are removed.</param>
+        public static Span<char> Trim(this Span<char> span)
+        {
+            int start = ClampStart(span);
+            int length = ClampEnd(span, start + 1);
+            return span.Slice(start, length);
+        }
+
+        /// <summary>
+        /// Removes all leading white-space characters from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the characters are removed.</param>
+        public static Span<char> TrimStart(this Span<char> span)
+            => span.Slice(ClampStart(span));
+
+        /// <summary>
+        /// Removes all trailing white-space characters from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the characters are removed.</param>
+        public static Span<char> TrimEnd(this Span<char> span)
+            => span.Slice(0, ClampEnd(span, 0));
+
+        /// <summary>
+        /// Delimits all leading occurrences of whitespace charecters from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the characters are removed.</param>
+        private static int ClampStart(ReadOnlySpan<char> span)
+        {
+            int start = 0;
+
+            for (; start < span.Length; start++)
+            {
+                if (!char.IsWhiteSpace(span[start]))
+                {
+                    break;
+                }
+            }
+
+            return start;
+        }
+
+        /// <summary>
+        /// Delimits all trailing occurrences of whitespace charecters from the span.
+        /// </summary>
+        /// <param name="span">The source span from which the characters are removed.</param>
+        /// <param name="start">The start index from which to being searching.</param>
+        private static int ClampEnd(ReadOnlySpan<char> span, int start)
+        {
+            // Initially, start==len==0. If ClampStart trims all, start==len
+            Debug.Assert((uint)start <= span.Length);
+
+            int end = span.Length - 1;
+
+            for (; end >= start; end--)
+            {
+                if (!char.IsWhiteSpace(span[end]))
+                {
+                    break;
+                }
+            }
+
+            return end - start + 1;
+        }
+    }
+}
index 151ee32..2b7861f 100644 (file)
@@ -23,178 +23,6 @@ namespace System
     public static partial class MemoryExtensions
     {
         /// <summary>
-        /// Removes all leading and trailing white-space characters from the span.
-        /// </summary>
-        public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span)
-        {
-            int start = 0;
-            for (; start < span.Length; start++)
-            {
-                if (!char.IsWhiteSpace(span[start]))
-                    break;
-            }
-            int end = span.Length - 1;
-            for (; end >= start; end--)
-            {
-                if (!char.IsWhiteSpace(span[end]))
-                    break;
-            }
-            return span.Slice(start, end - start + 1);
-        }
-
-        /// <summary>
-        /// Removes all leading white-space characters from the span.
-        /// </summary>
-        public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span)
-        {
-            int start = 0;
-            for (; start < span.Length; start++)
-            {
-                if (!char.IsWhiteSpace(span[start]))
-                    break;
-            }
-            return span.Slice(start);
-        }
-
-        /// <summary>
-        /// Removes all trailing white-space characters from the span.
-        /// </summary>
-        public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span)
-        {
-            int end = span.Length - 1;
-            for (; end >= 0; end--)
-            {
-                if (!char.IsWhiteSpace(span[end]))
-                    break;
-            }
-            return span.Slice(0, end + 1);
-        }
-
-        /// <summary>
-        /// Removes all leading and trailing occurrences of a specified character.
-        /// </summary>
-        /// <param name="span">The source span from which the character is removed.</param>
-        /// <param name="trimChar">The specified character to look for and remove.</param>
-        public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span, char trimChar)
-        {
-            int start = 0;
-            for (; start < span.Length; start++)
-            {
-                if (span[start] != trimChar)
-                    break;
-            }
-            int end = span.Length - 1;
-            for (; end >= start; end--)
-            {
-                if (span[end] != trimChar)
-                    break;
-            }
-            return span.Slice(start, end - start + 1);
-        }
-
-        /// <summary>
-        /// Removes all leading occurrences of a specified character.
-        /// </summary>
-        /// <param name="span">The source span from which the character is removed.</param>
-        /// <param name="trimChar">The specified character to look for and remove.</param>
-        public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span, char trimChar)
-        {
-            int start = 0;
-            for (; start < span.Length; start++)
-            {
-                if (span[start] != trimChar)
-                    break;
-            }
-            return span.Slice(start);
-        }
-
-        /// <summary>
-        /// Removes all trailing occurrences of a specified character.
-        /// </summary>
-        /// <param name="span">The source span from which the character is removed.</param>
-        /// <param name="trimChar">The specified character to look for and remove.</param>
-        public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span, char trimChar)
-        {
-            int end = span.Length - 1;
-            for (; end >= 0; end--)
-            {
-                if (span[end] != trimChar)
-                    break;
-            }
-            return span.Slice(0, end + 1);
-        }
-
-        /// <summary>
-        /// Removes all leading and trailing occurrences of a set of characters specified
-        /// in a readonly span from the span.
-        /// </summary>
-        /// <param name="span">The source span from which the characters are removed.</param>
-        /// <param name="trimChars">The span which contains the set of characters to remove.</param>
-        /// <remarks>If <paramref name="trimChars"/> is empty, white-space characters are removed instead.</remarks>
-        public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
-        {
-            return span.TrimStart(trimChars).TrimEnd(trimChars);
-        }
-
-        /// <summary>
-        /// Removes all leading occurrences of a set of characters specified
-        /// in a readonly span from the span.
-        /// </summary>
-        /// <param name="span">The source span from which the characters are removed.</param>
-        /// <param name="trimChars">The span which contains the set of characters to remove.</param>
-        /// <remarks>If <paramref name="trimChars"/> is empty, white-space characters are removed instead.</remarks>
-        public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
-        {
-            if (trimChars.IsEmpty)
-            {
-                return span.TrimStart();
-            }
-
-            int start = 0;
-            for (; start < span.Length; start++)
-            {
-                for (int i = 0; i < trimChars.Length; i++)
-                {
-                    if (span[start] == trimChars[i])
-                        goto Next;
-                }
-                break;
-            Next:
-                ;
-            }
-            return span.Slice(start);
-        }
-
-        /// <summary>
-        /// Removes all trailing occurrences of a set of characters specified
-        /// in a readonly span from the span.
-        /// </summary>
-        /// <param name="span">The source span from which the characters are removed.</param>
-        /// <param name="trimChars">The span which contains the set of characters to remove.</param>
-        /// <remarks>If <paramref name="trimChars"/> is empty, white-space characters are removed instead.</remarks>
-        public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
-        {
-            if (trimChars.IsEmpty)
-            {
-                return span.TrimEnd();
-            }
-
-            int end = span.Length - 1;
-            for (; end >= 0; end--)
-            {
-                for (int i = 0; i < trimChars.Length; i++)
-                {
-                    if (span[end] == trimChars[i])
-                        goto Next;
-                }
-                break;
-            Next:
-                ;
-            }
-            return span.Slice(0, end + 1);
-        }
-
-        /// <summary>
         /// Indicates whether the specified span contains only white-space characters.
         /// </summary>
         public static bool IsWhiteSpace(this ReadOnlySpan<char> span)