diff options
author | vnugent <public@vaughnnugent.com> | 2023-09-22 12:29:32 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-09-22 12:29:32 -0400 |
commit | 04c8fa8017a04da05cd8e1d104cfcd0a9aba17f5 (patch) | |
tree | 771243c218e880346fc7d826fa25ac2d639b01be /lib/Utils/src/Extensions | |
parent | 0b4e18b9a7d8e0aea23aef7efd3707674f223b2b (diff) |
Structure refactor & unused feature pruning, more http2 prep
Diffstat (limited to 'lib/Utils/src/Extensions')
-rw-r--r-- | lib/Utils/src/Extensions/StringExtensions.cs | 117 |
1 files changed, 89 insertions, 28 deletions
diff --git a/lib/Utils/src/Extensions/StringExtensions.cs b/lib/Utils/src/Extensions/StringExtensions.cs index 09d6517..f211b73 100644 --- a/lib/Utils/src/Extensions/StringExtensions.cs +++ b/lib/Utils/src/Extensions/StringExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -32,6 +32,10 @@ using VNLib.Utils.Memory; namespace VNLib.Utils.Extensions { + /// <summary> + /// Delegate for a stateless span action + /// </summary> + /// <param name="line">The line of data to process</param> public delegate void StatelessSpanAction(ReadOnlySpan<char> line); /// <summary> @@ -51,6 +55,7 @@ namespace VNLib.Utils.Extensions { Split(value, splitter.AsSpan(), output, options); } + /// <summary> /// Split a string based on split value and insert into the specified list /// </summary> @@ -66,6 +71,7 @@ namespace VNLib.Utils.Extensions //Call the split function on the span Split(value, cs, output, options); } + /// <summary> /// Split a string based on split value and insert into the specified list /// </summary> @@ -79,6 +85,7 @@ namespace VNLib.Utils.Extensions { Split(value.AsSpan(), splitter, output, options); } + /// <summary> /// Split a string based on split value and insert into the specified list /// </summary> @@ -88,13 +95,14 @@ namespace VNLib.Utils.Extensions /// <param name="options">String split options</param> /// <exception cref="ArgumentNullException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Split<T>(this in ReadOnlySpan<char> value, char splitter, T output, StringSplitOptions options) where T : ICollection<string> + public static void Split<T>(this ReadOnlySpan<char> value, char splitter, T output, StringSplitOptions options) where T : ICollection<string> { //Create span from char pointer ReadOnlySpan<char> cs = MemoryMarshal.CreateReadOnlySpan(ref splitter, 1); //Call the split function on the span - Split(in value, cs, output, options); + Split(value, cs, output, options); } + /// <summary> /// Split a <see cref="ReadOnlySpan{T}"/> based on split value and insert into the specified list /// </summary> @@ -104,13 +112,15 @@ namespace VNLib.Utils.Extensions /// <param name="options">String split options</param> /// <exception cref="ArgumentNullException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Split<T>(this in ReadOnlySpan<char> value, ReadOnlySpan<char> splitter, T output, StringSplitOptions options) where T : ICollection<string> + public static void Split<T>(this ReadOnlySpan<char> value, ReadOnlySpan<char> splitter, T output, StringSplitOptions options) where T : ICollection<string> { //Create a local function that adds the split strings to the list static void SplitFound(ReadOnlySpan<char> split, T output) => output.Add(split.ToString()); + //Invoke the split function with the local callback method - Split(in value, splitter, options, SplitFound, output); + Split(value, splitter, options, SplitFound, output); } + /// <summary> /// Split a <see cref="ReadOnlySpan{T}"/> based on split value and pass it to the split delegate handler /// </summary> @@ -120,11 +130,13 @@ namespace VNLib.Utils.Extensions /// <param name="splitCb">The action to invoke when a split segment has been found</param> /// <param name="state">The state to pass to the callback handler</param> /// <exception cref="ArgumentNullException"></exception> - public static void Split<T>(this in ReadOnlySpan<char> value, ReadOnlySpan<char> splitter, StringSplitOptions options, ReadOnlySpanAction<char, T> splitCb, T state) + public static void Split<T>(this ReadOnlySpan<char> value, ReadOnlySpan<char> splitter, StringSplitOptions options, ReadOnlySpanAction<char, T> splitCb, T state) { _ = splitCb ?? throw new ArgumentNullException(nameof(splitCb)); + //Get span over string ForwardOnlyReader<char> reader = new(value); + //No string options if (options == 0) { @@ -132,41 +144,49 @@ namespace VNLib.Utils.Extensions { //Find index of the splitter int start = reader.Window.IndexOf(splitter); + //guard if (start == -1) { break; } + //Trim and add it regardless of length splitCb(reader.Window[..start], state); + //shift window reader.Advance(start + splitter.Length); } while (true); + //Trim remaining and add it regardless of length splitCb(reader.Window, state); } //Trim but do not remove empties - else if ((options & StringSplitOptions.RemoveEmptyEntries) == 0) + else if ((options & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries) { do { //Find index of the splitter int start = reader.Window.IndexOf(splitter); + //guard if (start == -1) { break; } + //Trim and add it regardless of length splitCb(reader.Window[..start].Trim(), state); + //shift window reader.Advance(start + splitter.Length); } while (true); + //Trim remaining and add it regardless of length splitCb(reader.Window.Trim(), state); } //Remove empty entires but do not trim them - else if ((options & StringSplitOptions.TrimEntries) == 0) + else if ((options & StringSplitOptions.RemoveEmptyEntries) == StringSplitOptions.RemoveEmptyEntries) { //Get data before splitter and trim it ReadOnlySpan<char> data; @@ -186,9 +206,11 @@ namespace VNLib.Utils.Extensions { splitCb(data, state); } - //shift window - reader.Advance(start + splitter.Length); + + reader.Advance(start + splitter.Length); + } while (true); + //Add if not empty if (reader.WindowSize > 0) { @@ -204,23 +226,29 @@ namespace VNLib.Utils.Extensions { //Find index of the splitter int start = reader.Window.IndexOf(splitter); + //guard if (start == -1) { break; } + //Get data before splitter and trim it data = reader.Window[..start].Trim(); + //If its not empty, then add it to the list if (!data.IsEmpty) { splitCb(data, state); } - //shift window + reader.Advance(start + splitter.Length); + } while (true); + //Trim remaining data = reader.Window.Trim(); + //Add if not empty if (!data.IsEmpty) { @@ -238,13 +266,14 @@ namespace VNLib.Utils.Extensions /// <param name="splitCb">The action to invoke when a split segment has been found</param> /// <param name="state"></param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Split<T>(this in ReadOnlySpan<char> value, char splitter, StringSplitOptions options, ReadOnlySpanAction<char, T> splitCb, T state) + public static void Split<T>(this ReadOnlySpan<char> value, char splitter, StringSplitOptions options, ReadOnlySpanAction<char, T> splitCb, T state) { //Alloc a span for char ReadOnlySpan<char> cs = MemoryMarshal.CreateReadOnlySpan(ref splitter, 1); //Call the split function on the span - Split(in value, cs, options, splitCb, state); + Split(value, cs, options, splitCb, state); } + /// <summary> /// Split a <see cref="ReadOnlySpan{T}"/> based on split value and pass it to the split delegate handler /// </summary> @@ -254,13 +283,14 @@ namespace VNLib.Utils.Extensions /// <param name="splitCb">The action to invoke when a split segment has been found</param> /// <exception cref="ArgumentNullException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Split(this in ReadOnlySpan<char> value, ReadOnlySpan<char> splitter, StringSplitOptions options, StatelessSpanAction splitCb) + public static void Split(this ReadOnlySpan<char> value, ReadOnlySpan<char> splitter, StringSplitOptions options, StatelessSpanAction splitCb) { //Create a SpanSplitDelegate with the non-typed delegate as the state argument static void ssplitcb(ReadOnlySpan<char> param, StatelessSpanAction callback) => callback(param); //Call split with the new callback delegate - Split(in value, splitter, options, ssplitcb, splitCb); + Split(value, splitter, options, ssplitcb, splitCb); } + /// <summary> /// Split a <see cref="ReadOnlySpan{T}"/> based on split value and pass it to the split delegate handler /// </summary> @@ -270,12 +300,12 @@ namespace VNLib.Utils.Extensions /// <param name="splitCb">The action to invoke when a split segment has been found</param> /// <exception cref="ArgumentNullException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Split(this in ReadOnlySpan<char> value, char splitter, StringSplitOptions options, StatelessSpanAction splitCb) + public static void Split(this ReadOnlySpan<char> value, char splitter, StringSplitOptions options, StatelessSpanAction splitCb) { //Create a SpanSplitDelegate with the non-typed delegate as the state argument static void ssplitcb(ReadOnlySpan<char> param, StatelessSpanAction callback) => callback(param); //Call split with the new callback delegate - Split(in value, splitter, options, ssplitcb, splitCb); + Split(value, splitter, options, ssplitcb, splitCb); } /// <summary> @@ -285,11 +315,12 @@ namespace VNLib.Utils.Extensions /// <param name="search">Sequence to search for within the current sequence</param> /// <returns>the index of the end of the sequenc</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int EndOf(this in ReadOnlySpan<char> data, ReadOnlySpan<char> search) + public static int EndOf(this ReadOnlySpan<char> data, ReadOnlySpan<char> search) { int index = data.IndexOf(search); return index > -1 ? index + search.Length : -1; } + /// <summary> /// Gets the index of the end of the found character /// </summary> @@ -297,15 +328,18 @@ namespace VNLib.Utils.Extensions /// <param name="search">Character to search for within the current sequence</param> /// <returns>the index of the end of the sequence</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int EndOf(this in ReadOnlySpan<char> data, char search) + public static int EndOf(this ReadOnlySpan<char> data, char search) { int index = data.IndexOf(search); return index > -1 ? index + 1 : -1; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOf(this in Memory<byte> data, byte search) => data.Span.IndexOf(search); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOf(this in Memory<byte> data, ReadOnlySpan<byte> search) => data.Span.IndexOf(search); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOf(this in Memory<byte> data, ReadOnlyMemory<byte> search) => IndexOf(data, search.Span); @@ -317,13 +351,14 @@ namespace VNLib.Utils.Extensions /// <param name="search">The delimiting character</param> /// <returns>The segment of data before the search character, or the entire segment if not found</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan<char> SliceBeforeParam(this in ReadOnlySpan<char> data, char search) + public static ReadOnlySpan<char> SliceBeforeParam(this ReadOnlySpan<char> data, char search) { //Find the index of the specified data int index = data.IndexOf(search); //Return the slice of data before the index, or an empty span if it was not found return index > -1 ? data[..index] : data; } + /// <summary> /// Slices the current span from the begining of the segment to the first occurrance of the specified character sequence. /// If the character sequence is not found, the entire segment is returned @@ -332,13 +367,14 @@ namespace VNLib.Utils.Extensions /// <param name="search">The delimiting character sequence</param> /// <returns>The segment of data before the search character, or the entire <paramref name="data"/> if the seach sequence is not found</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan<char> SliceBeforeParam(this in ReadOnlySpan<char> data, ReadOnlySpan<char> search) + public static ReadOnlySpan<char> SliceBeforeParam(this ReadOnlySpan<char> data, ReadOnlySpan<char> search) { //Find the index of the specified data int index = data.IndexOf(search); //Return the slice of data before the index, or an empty span if it was not found return index > -1 ? data[..index] : data; } + /// <summary> /// Gets the remaining segment of data after the specified search character or <see cref="ReadOnlySpan{T}.Empty"/> /// if the search character is not found within the current segment @@ -347,13 +383,15 @@ namespace VNLib.Utils.Extensions /// <param name="search">The character to search for within the segment</param> /// <returns>The segment of data after the search character or <see cref="ReadOnlySpan{T}.Empty"/> if not found</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan<char> SliceAfterParam(this in ReadOnlySpan<char> data, char search) + public static ReadOnlySpan<char> SliceAfterParam(this ReadOnlySpan<char> data, char search) { //Find the index of the specified data - int index = EndOf(in data, search); + int index = EndOf(data, search); + //Return the slice of data after the index, or an empty span if it was not found return index > -1 ? data[index..] : ReadOnlySpan<char>.Empty; } + /// <summary> /// Gets the remaining segment of data after the specified search sequence or <see cref="ReadOnlySpan{T}.Empty"/> /// if the search sequence is not found within the current segment @@ -362,42 +400,53 @@ namespace VNLib.Utils.Extensions /// <param name="search">The sequence to search for within the segment</param> /// <returns>The segment of data after the search sequence or <see cref="ReadOnlySpan{T}.Empty"/> if not found</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan<char> SliceAfterParam(this in ReadOnlySpan<char> data, ReadOnlySpan<char> search) + public static ReadOnlySpan<char> SliceAfterParam(this ReadOnlySpan<char> data, ReadOnlySpan<char> search) { //Find the index of the specified data int index = EndOf(data, search); + //Return the slice of data after the index, or an empty span if it was not found return index > -1 ? data[index..] : ReadOnlySpan<char>.Empty; } + /// <summary> /// Trims any leading or trailing <c>'\r'|'\n'|' '</c>(whitespace) characters from the segment /// </summary> /// <returns>The trimmed segment</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan<char> TrimCRLF(this in ReadOnlySpan<char> data) + public static ReadOnlySpan<char> TrimCRLF(this ReadOnlySpan<char> data) { int start = 0, end = data.Length; + //trim leading \r\n chars while(start < end) { char t = data[start]; + //If character \r or \n slice it off - if (t != '\r' && t != '\n' && t != ' ') { + if (t != '\r' && t != '\n' && t != ' ') + { break; } + //Shift start++; } + //remove trailing crlf characters while (end > start) { char t = data[end - 1]; + //If character \r or \n slice it off - if (t != '\r' && t != '\n' && t != ' ') { + if (t != '\r' && t != '\n' && t != ' ') + { break; } + end--; } + return data[start..end]; } @@ -408,7 +457,7 @@ namespace VNLib.Utils.Extensions /// <param name="search">The sequence to search for</param> /// <param name="replace">The sequence to write in the place of the search parameter</param> /// <exception cref="OutOfMemoryException"></exception> - public static int Replace(this ref Span<char> buffer, ReadOnlySpan<char> search, ReadOnlySpan<char> replace) + public static int Replace(this Span<char> buffer, ReadOnlySpan<char> search, ReadOnlySpan<char> replace) { ForwardOnlyWriter<char> writer = new (buffer); writer.Replace(search, replace); @@ -425,20 +474,25 @@ namespace VNLib.Utils.Extensions public static void Replace(this ref ForwardOnlyWriter<char> writer, ReadOnlySpan<char> search, ReadOnlySpan<char> replace) { Span<char> buffer = writer.AsSpan(); + //If the search and replacment parameters are the same length if (search.Length == replace.Length) { buffer.ReplaceInPlace(search, replace); return; } + //Search and replace are not the same length int searchLen = search.Length, start = buffer.IndexOf(search); + if(start == -1) { return; } + //Replacment might be empty writer.Reset(); + do { //Append the data before the split character @@ -449,10 +503,13 @@ namespace VNLib.Utils.Extensions buffer = buffer[(start + searchLen)..]; //search for next index start = buffer.IndexOf(search); + } while (start > -1); + //Write remaining data writer.Append(replace); } + /// <summary> /// Replaces very ocurrance of character sequence within a buffer with another sequence of the same length /// </summary> @@ -466,13 +523,17 @@ namespace VNLib.Utils.Extensions { throw new ArgumentException("Search parameter and replacment parameter must be the same length"); } + int start = buffer.IndexOf(search); + while(start > -1) { //Shift the buffer to the begining of the search parameter buffer = buffer[start..]; + //Overwite the search parameter replace.CopyTo(buffer); + //Search for next index of the search character start = buffer.IndexOf(search); } |