diff --git a/ExtensibleParaser/ExtensibleParaser.csproj b/ExtensibleParaser/ExtensibleParaser.csproj
index 6219149..098146f 100644
--- a/ExtensibleParaser/ExtensibleParaser.csproj
+++ b/ExtensibleParaser/ExtensibleParaser.csproj
@@ -1,13 +1,28 @@
-
- net8.0
- enable
- enable
- preview
-
-
-
-
-
+
+ netstandard2.0
+ enable
+ enable
+ preview
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ExtensibleParaser/Parser.cs b/ExtensibleParaser/Parser.cs
index 143ee09..ad6504d 100644
--- a/ExtensibleParaser/Parser.cs
+++ b/ExtensibleParaser/Parser.cs
@@ -481,7 +481,7 @@ private Result ParseZeroOrMany(ZeroOrMany zeroOrMany, int startPos, string input
return Result.Success(new SeqNode(zeroOrMany.Kind ?? "ZeroOrMany", elements, startPos, currentPos), currentPos, maxFailPos);
}
- private static ReadOnlySpan Preview(string input, int pos, int len = 5) => pos >= input.Length
+ private static ChatRef Preview(string input, int pos, int len = 5) => pos >= input.Length
? "«»"
: $"«{input.AsSpan(pos, Math.Min(input.Length - pos, len))}»";
diff --git a/ExtensibleParaser/SyntaxTree.cs b/ExtensibleParaser/SyntaxTree.cs
index aacb448..c382972 100644
--- a/ExtensibleParaser/SyntaxTree.cs
+++ b/ExtensibleParaser/SyntaxTree.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics;
+
+using System.Diagnostics;
namespace ExtensibleParaser;
@@ -9,7 +10,7 @@ public interface ISyntaxNode
int EndPos { get; }
bool IsRecovery { get; }
void Accept(ISyntaxVisitor visitor);
- ReadOnlySpan AsSpan(string input);
+ ChatRef AsSpan(string input);
string ToString(string input);
}
@@ -26,7 +27,7 @@ public interface ISyntaxVisitor
public abstract record Node(string Kind, int StartPos, int EndPos, bool IsRecovery = false) : ISyntaxNode
{
public int Length => EndPos - StartPos;
- public virtual ReadOnlySpan AsSpan(string input) => input.AsSpan(StartPos, EndPos - StartPos);
+ public virtual ChatRef AsSpan(string input) => input.AsSpan(StartPos, EndPos - StartPos);
public virtual string ToString(string input) => input[StartPos..EndPos];
public abstract void Accept(ISyntaxVisitor visitor);
@@ -64,7 +65,7 @@ private sealed class DebugView(Node node)
public record TerminalNode(string Kind, int StartPos, int EndPos, int ContentLength, bool IsRecovery = false) : Node(Kind, StartPos, EndPos, IsRecovery)
{
- public override ReadOnlySpan AsSpan(string input) => input.AsSpan(StartPos, ContentLength);
+ public override ChatRef AsSpan(string input) => input.AsSpan(StartPos, ContentLength);
public override string ToString(string input) => input.Substring(StartPos, ContentLength);
public override void Accept(ISyntaxVisitor visitor) => visitor.Visit(this);
public override string ToString() => $"{Kind}Terminal([{StartPos},{StartPos + ContentLength}), «{DebugContent() ?? Kind}»)";
diff --git a/Parsers/DotParser/DotAst.cs b/Parsers/DotParser/DotAst.cs
index 1026d28..9ac7ea1 100644
--- a/Parsers/DotParser/DotAst.cs
+++ b/Parsers/DotParser/DotAst.cs
@@ -55,12 +55,12 @@ public record DotIdentifier(string Value, int StartPos, int EndPos) : DotTermina
public record DotQuotedString(string Value, string RawValue, int StartPos, int EndPos)
: DotTerminalNode(Kind: "QuotedString", StartPos: StartPos, EndPos: EndPos)
{
- public DotQuotedString(ReadOnlySpan span, int startPos, int endPos)
+ public DotQuotedString(ChatRef span, int startPos, int endPos)
: this(Value: ProcessQuotedString(span), RawValue: span[1..^1].ToString(), StartPos: startPos, EndPos: endPos)
{
}
- private static string ProcessQuotedString(ReadOnlySpan span)
+ private static string ProcessQuotedString(ChatRef span)
{
var content = span[1..^1];
var result = new StringBuilder();
diff --git a/Parsers/DotParser/DotParser.csproj b/Parsers/DotParser/DotParser.csproj
index 56516e6..3c1dd96 100644
--- a/Parsers/DotParser/DotParser.csproj
+++ b/Parsers/DotParser/DotParser.csproj
@@ -1,16 +1,25 @@
-
- net8.0
- enable
- enable
- Dot
-
+
+ netstandard2.0
+ enable
+ enable
+ preview
+ Dot
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Parsers/DotParser/DotVisitor.cs b/Parsers/DotParser/DotVisitor.cs
index 1ac446b..9eca975 100644
--- a/Parsers/DotParser/DotVisitor.cs
+++ b/Parsers/DotParser/DotVisitor.cs
@@ -74,7 +74,7 @@ public void Visit(SeqNode node)
return;
DotAttribute[] getAttributes(List children)
{
- if (children is [DotLiteral { Value: "[" }, .. var attributes, DotLiteral { Value: "]" }])
+ if (children.ToArray() is [DotLiteral { Value: "[" }, .. var attributes, DotLiteral { Value: "]" }])
return attributes.SelectMany(x => x switch
{
DotAttribute a => [a],
diff --git a/Regex/Regex/CompilerServices/Extensions.cs b/Regex/Regex/CompilerServices/Extensions.cs
deleted file mode 100644
index 9de7d50..0000000
--- a/Regex/Regex/CompilerServices/Extensions.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-#if NETSTANDARD
-
-namespace System.Runtime.CompilerServices;
-
-internal static class Extensions
-{
- public static void Deconstruct(
- this KeyValuePair pair,
- out TKey key,
- out TValue value)
- {
- key = pair.Key;
- value = pair.Value;
- }
-}
-
-[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
-internal sealed class CallerArgumentExpressionAttribute : Attribute
-{
- public CallerArgumentExpressionAttribute(string parameterName)
- {
- ParameterName = parameterName;
- }
-
- public string ParameterName { get; }
-}
-
-#endif
diff --git a/Regex/Regex/CompilerServices/Index.cs b/Regex/Regex/CompilerServices/Index.cs
deleted file mode 100644
index ce341f5..0000000
--- a/Regex/Regex/CompilerServices/Index.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-#if NETSTANDARD
-
-namespace System;
-
-public readonly struct Index : IEquatable
-{
- private readonly int _value;
-
- // Создаёт индекс из начала (isFromEnd = false) или конца (isFromEnd = true)
- public Index(int value, bool isFromEnd)
- {
- if (value < 0)
- throw new ArgumentOutOfRangeException(nameof(value), "Value must be non-negative.");
-
- _value = value;
- IsFromEnd = isFromEnd;
- }
-
- // Создаёт индекс относительно начала
- public static Index Start => new Index(0, false);
-
- // Создаёт индекс относительно конца
- public static Index End => new Index(0, true);
-
- // Получает значение индекса
- public int Value => _value;
-
- // Определяет, отсчитывается ли индекс от конца
- public bool IsFromEnd { get; }
-
- // Создаёт индекс из целого числа (от конца ^x)
- public static Index FromEnd(int value) => new Index(value, true);
-
- // Создаёт индекс от начала
- public static Index FromStart(int value) => new Index(value, false);
-
- public int GetOffset(int length) => IsFromEnd ? length - _value >= 0 ? length - _value : 0 : _value;
-
- // Реализация Equals
- public override bool Equals(object obj) =>
- obj is Index index && Equals(index);
-
- public bool Equals(Index other) =>
- _value == other._value && IsFromEnd == other.IsFromEnd;
-
- public override int GetHashCode() => (_value * 397) ^ IsFromEnd.GetHashCode();
-
- public override string ToString() =>
- IsFromEnd ? $"^{_value}" : _value.ToString();
-
- public static bool operator ==(Index left, Index right) => left.Equals(right);
- public static bool operator !=(Index left, Index right) => !left.Equals(right);
-}
-
-#endif
diff --git a/Regex/Regex/Regex.csproj b/Regex/Regex/Regex.csproj
index 4c54c07..2be4451 100644
--- a/Regex/Regex/Regex.csproj
+++ b/Regex/Regex/Regex.csproj
@@ -8,10 +8,19 @@
+
+
+
+
+
+
+
+
diff --git a/Shared/NetStandard2_0Support.cs b/Shared/NetStandard2_0Support.cs
new file mode 100644
index 0000000..472727c
--- /dev/null
+++ b/Shared/NetStandard2_0Support.cs
@@ -0,0 +1,522 @@
+#if NETSTANDARD2_0
+global using ChatRef = string;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+internal static class ReadOnlySpanExtensions
+{
+ public static ChatRef AsSpan(this string text, int start, int length) => text.Substring(start, length);
+ public static ChatRef AsSpan(this string text, int start) => text.Substring(start);
+
+}
+
+namespace System.Numerics.Hashing
+{
+ internal static class HashHelpers
+ {
+ public static readonly int RandomSeed = new Random().Next(int.MinValue, int.MaxValue);
+
+ public static int Combine(int h1, int h2)
+ {
+ // RyuJIT optimizes this to use the ROL instruction
+ // Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830
+ uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
+ return ((int)rol5 + h1) ^ h2;
+ }
+ }
+}
+
+namespace System.Runtime.CompilerServices
+{
+ internal static class IsExternalInit { }
+ internal sealed class IntrinsicAttribute : Attribute { }
+ internal static class Extensions
+ {
+ public static void Deconstruct(
+ this KeyValuePair pair,
+ out TKey key,
+ out TValue value)
+ {
+ key = pair.Key;
+ value = pair.Value;
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
+ internal sealed class CallerArgumentExpressionAttribute(string parameterName) : Attribute
+ {
+ public string ParameterName { get; } = parameterName;
+ }
+
+ internal static partial class RuntimeHelpers
+ {
+ // The special dll name to be used for DllImport of QCalls
+#if NATIVEAOT
+ internal const string QCall = "*";
+#else
+ internal const string QCall = "QCall";
+#endif
+
+ public delegate void TryCode(object? userData);
+
+ public delegate void CleanupCode(object? userData, bool exceptionThrown);
+
+ ///
+ /// Slices the specified array using the specified range.
+ ///
+ public static T[] GetSubArray(T[] array, Range range)
+ {
+ if (array == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(array));
+ }
+
+ (int offset, int length) = range.GetOffsetAndLength(array.Length);
+
+ T[] dest;
+
+ if (typeof(T[]) == array.GetType())
+ {
+ // We know the type of the array to be exactly T[].
+
+ if (length == 0)
+ {
+ return Array.Empty();
+ }
+
+ dest = new T[length];
+ }
+ else
+ {
+ // The array is actually a U[] where U:T. We'll make sure to create
+ // an array of the exact same backing type. The cast to T[] will
+ // never fail.
+
+ ;
+ dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), length);
+ }
+
+ // In either case, the newly-allocated array is the exact same type as the
+ // original incoming array. It's safe for us to SpanHelpers.Memmove the contents
+ // from the source array to the destination array, otherwise the contents
+ // wouldn't have been valid for the source array in the first place.
+
+ Array.Copy(
+ sourceArray: array,
+ sourceIndex: offset,
+ destinationArray: dest,
+ destinationIndex: 0,
+ length: length);
+
+ return dest;
+ }
+
+
+ // The following intrinsics return true if input is a compile-time constant
+ // Feel free to add more overloads on demand
+#pragma warning disable IDE0060
+ [Intrinsic]
+ internal static bool IsKnownConstant(Type? t) => false;
+
+ [Intrinsic]
+ internal static bool IsKnownConstant(string? t) => false;
+
+ [Intrinsic]
+ internal static bool IsKnownConstant(char t) => false;
+
+ [Intrinsic]
+ internal static bool IsKnownConstant(T t) where T : struct => false;
+#pragma warning restore IDE0060
+
+ }
+}
+
+namespace System.Diagnostics.CodeAnalysis
+{
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
+ internal sealed class MaybeNullWhenAttribute(bool returnValue) : Attribute
+ {
+ public bool ReturnValue { get; } = returnValue;
+ }
+
+ /// Applied to a method that will never return under any circumstance.
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ internal sealed class DoesNotReturnAttribute : Attribute { }
+
+ /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value.
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ internal sealed class DoesNotReturnIfAttribute : Attribute
+ {
+ /// Initializes the attribute with the specified parameter value.
+ ///
+ /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
+ /// the associated parameter matches this value.
+ ///
+ public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
+
+ /// Gets the condition parameter value.
+ public bool ParameterValue { get; }
+ }
+}
+
+namespace System
+{
+ using System.Numerics.Hashing;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Runtime.CompilerServices;
+
+ internal static class ThrowHelper
+ {
+ internal static void ThrowValueArgumentOutOfRange_NeedNonNegNumException() =>
+ throw new ArgumentOutOfRangeException();
+
+ [DoesNotReturn]
+ public static void ThrowArgumentNullException(string parameterName) => throw new ArgumentNullException(parameterName);
+ }
+
+ /// Represent a type can be used to index a collection either from the start or the end.
+ ///
+ /// Index is used by the C# compiler to support the new index syntax
+ ///
+ /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
+ /// int lastElement = someArray[^1]; // lastElement = 5
+ ///
+ ///
+ internal readonly struct Index : IEquatable
+ {
+ private readonly int _value;
+
+ /// Construct an Index using a value and indicating if the index is from the start or from the end.
+ /// The index value. it has to be zero or positive number.
+ /// Indicating if the index is from the start or from the end.
+ ///
+ /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Index(int value, bool fromEnd = false)
+ {
+ if (value < 0)
+ {
+ ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if (fromEnd)
+ _value = ~value;
+ else
+ _value = value;
+ }
+
+ // The following private constructors mainly created for perf reason to avoid the checks
+ private Index(int value)
+ {
+ _value = value;
+ }
+
+ /// Create an Index pointing at first element.
+ public static Index Start => new(0);
+
+ /// Create an Index pointing at beyond last element.
+ public static Index End => new(~0);
+
+ /// Create an Index from the start at the position indicated by the value.
+ /// The index value from the start.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Index FromStart(int value)
+ {
+ if (value < 0)
+ {
+ ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ return new Index(value);
+ }
+
+ /// Create an Index from the end at the position indicated by the value.
+ /// The index value from the end.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Index FromEnd(int value)
+ {
+ if (value < 0)
+ {
+ ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ return new Index(~value);
+ }
+
+ /// Returns the index value.
+ public int Value
+ {
+ get
+ {
+ if (_value < 0)
+ return ~_value;
+ else
+ return _value;
+ }
+ }
+
+ /// Indicates whether the index is from the start or the end.
+ public bool IsFromEnd => _value < 0;
+
+ /// Calculate the offset from the start using the giving collection length.
+ /// The length of the collection that the Index will be used with. length has to be a positive value
+ ///
+ /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
+ /// we don't validate either the returned offset is greater than the input length.
+ /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and
+ /// then used to index a collection will get out of range exception which will be same affect as the validation.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetOffset(int length)
+ {
+ int offset = _value;
+ if (IsFromEnd)
+ {
+ // offset = length - (~value)
+ // offset = length + (~(~value) + 1)
+ // offset = length + value + 1
+
+ offset += length + 1;
+ }
+ return offset;
+ }
+
+ /// Indicates whether the current Index object is equal to another object of the same type.
+ /// An object to compare with this object
+ public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value;
+
+ /// Indicates whether the current Index object is equal to another Index object.
+ /// An object to compare with this object
+ public bool Equals(Index other) => _value == other._value;
+
+ /// Returns the hash code for this instance.
+ public override int GetHashCode() => _value;
+
+ /// Converts integer number to an Index.
+ public static implicit operator Index(int value) => FromStart(value);
+
+ /// Converts the value of the current Index object to its equivalent string representation.
+ public override string ToString()
+ {
+ if (IsFromEnd)
+ return ToStringFromEnd();
+
+ return ((uint)Value).ToString();
+ }
+
+ private string ToStringFromEnd()
+ {
+#if !NETSTANDARD2_0
+ Span span = stackalloc char[11]; // 1 for ^ and 10 for longest possible uint value
+ bool formatted = ((uint)Value).TryFormat(span.Slice(1), out int charsWritten);
+ Debug.Assert(formatted);
+ span[0] = '^';
+ return new string(span.Slice(0, charsWritten + 1));
+#else
+ return '^' + Value.ToString();
+#endif
+ }
+ }
+ /// Represent a range has start and end indexes.
+ ///
+ /// Range is used by the C# compiler to support the range syntax.
+ ///
+ /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 };
+ /// int[] subArray1 = someArray[0..2]; // { 1, 2 }
+ /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 }
+ ///
+ ///
+ internal readonly struct Range : IEquatable
+ {
+ /// Represent the inclusive start index of the Range.
+ public Index Start { get; }
+
+ /// Represent the exclusive end index of the Range.
+ public Index End { get; }
+
+ /// Construct a Range object using the start and end indexes.
+ /// Represent the inclusive start index of the range.
+ /// Represent the exclusive end index of the range.
+ public Range(Index start, Index end)
+ {
+ Start = start;
+ End = end;
+ }
+
+ /// Indicates whether the current Range object is equal to another object of the same type.
+ /// An object to compare with this object
+ public override bool Equals(object? value) =>
+ value is Range r &&
+ r.Start.Equals(Start) &&
+ r.End.Equals(End);
+
+ /// Indicates whether the current Range object is equal to another Range object.
+ /// An object to compare with this object
+ public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End);
+
+ /// Returns the hash code for this instance.
+ public override int GetHashCode()
+ {
+#if !NETSTANDARD2_0
+ return HashCode.Combine(Start.GetHashCode(), End.GetHashCode());
+#else
+ return HashHelpers.Combine(Start.GetHashCode(), End.GetHashCode());
+#endif
+ }
+
+ /// Converts the value of the current Range object to its equivalent string representation.
+ public override string ToString()
+ {
+#if !NETSTANDARD2_0
+ Span span = stackalloc char[2 + (2 * 11)]; // 2 for "..", then for each index 1 for '^' and 10 for longest possible uint
+ int pos = 0;
+
+ if (Start.IsFromEnd)
+ {
+ span[0] = '^';
+ pos = 1;
+ }
+ bool formatted = ((uint)Start.Value).TryFormat(span.Slice(pos), out int charsWritten);
+ Debug.Assert(formatted);
+ pos += charsWritten;
+
+ span[pos++] = '.';
+ span[pos++] = '.';
+
+ if (End.IsFromEnd)
+ {
+ span[pos++] = '^';
+ }
+ formatted = ((uint)End.Value).TryFormat(span.Slice(pos), out charsWritten);
+ Debug.Assert(formatted);
+ pos += charsWritten;
+
+ return new string(span.Slice(0, pos));
+#else
+ return Start.ToString() + ".." + End.ToString();
+#endif
+ }
+
+ /// Create a Range object starting from start index to the end of the collection.
+ public static Range StartAt(Index start) => new Range(start, Index.End);
+
+ /// Create a Range object starting from first element in the collection to the end Index.
+ public static Range EndAt(Index end) => new Range(Index.Start, end);
+
+ /// Create a Range object starting from first element to the end.
+ public static Range All => new Range(Index.Start, Index.End);
+
+ /// Calculate the start offset and length of range object using a collection length.
+ /// The length of the collection that the range will be used with. length has to be a positive value.
+ ///
+ /// For performance reason, we don't validate the input length parameter against negative values.
+ /// It is expected Range will be used with collections which always have non negative length/count.
+ /// We validate the range is inside the length scope though.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public (int Offset, int Length) GetOffsetAndLength(int length)
+ {
+ int start;
+ Index startIndex = Start;
+ if (startIndex.IsFromEnd)
+ start = length - startIndex.Value;
+ else
+ start = startIndex.Value;
+
+ int end;
+ Index endIndex = End;
+ if (endIndex.IsFromEnd)
+ end = length - endIndex.Value;
+ else
+ end = endIndex.Value;
+
+ if ((uint)end > (uint)length || (uint)start > (uint)end)
+ throw new ArgumentOutOfRangeException(nameof(length));
+
+ return (start, end - start);
+ }
+ }
+}
+
+
+namespace System.Collections.Generic
+{
+ using System.Collections.ObjectModel;
+ using System.Diagnostics.CodeAnalysis;
+ internal static class CollectionExtensions
+ {
+ public static TValue? GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key) =>
+ dictionary.GetValueOrDefault(key, default!);
+
+ public static TValue GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key, TValue defaultValue)
+ {
+ if (dictionary is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(dictionary));
+ }
+
+ return dictionary.TryGetValue(key, out TValue? value) ? value : defaultValue;
+ }
+
+ public static bool TryAdd(this IDictionary dictionary, TKey key, TValue value)
+ {
+ if (dictionary is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(dictionary));
+ }
+
+ if (!dictionary.ContainsKey(key))
+ {
+ dictionary.Add(key, value);
+ return true;
+ }
+
+ return false;
+ }
+
+ public static bool Remove(this IDictionary dictionary, TKey key, [MaybeNullWhen(false)] out TValue value)
+ {
+ if (dictionary is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(dictionary));
+ }
+
+ if (dictionary.TryGetValue(key, out value))
+ {
+ dictionary.Remove(key);
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ ///
+ /// Returns a read-only wrapper
+ /// for the specified list.
+ ///
+ /// The type of elements in the collection.
+ /// The list to wrap.
+ /// An object that acts as a read-only wrapper around the current .
+ /// is null.
+ public static ReadOnlyCollection AsReadOnly(this IList list) =>
+ new ReadOnlyCollection(list);
+
+ ///
+ /// Returns a read-only wrapper
+ /// for the current dictionary.
+ ///
+ /// The type of keys in the dictionary.
+ /// The type of values in the dictionary.
+ /// The dictionary to wrap.
+ /// An object that acts as a read-only wrapper around the current .
+ /// is null.
+ public static ReadOnlyDictionary AsReadOnly(this IDictionary dictionary) where TKey : notnull =>
+ new ReadOnlyDictionary(dictionary);
+ }
+}
+
+#else
+global using ChatRef = System.ReadOnlySpan;
+#endif
diff --git a/Tests/Extensions/StringExtensions.cs b/Shared/StringExtensions.cs
similarity index 76%
rename from Tests/Extensions/StringExtensions.cs
rename to Shared/StringExtensions.cs
index 1c2ccbc..fde03df 100644
--- a/Tests/Extensions/StringExtensions.cs
+++ b/Shared/StringExtensions.cs
@@ -1,6 +1,6 @@
namespace Tests.Extensions;
-public static class StringExtensions
+internal static class StringExtensions
{
public static string NormalizeEol(this string text) => text.Replace("\r\n", "\n").Replace("\r", "\n");
}
diff --git a/Tests/ParaserTests/ParaserTests.csproj b/Tests/ParaserTests/ParaserTests.csproj
index 5468b36..a58a03d 100644
--- a/Tests/ParaserTests/ParaserTests.csproj
+++ b/Tests/ParaserTests/ParaserTests.csproj
@@ -10,13 +10,16 @@
+
+
+
-
+
@@ -30,8 +33,7 @@
-
-
+
diff --git a/Tests/RegexTests/RegexTests.csproj b/Tests/RegexTests/RegexTests.csproj
index bd859ac..c076953 100644
--- a/Tests/RegexTests/RegexTests.csproj
+++ b/Tests/RegexTests/RegexTests.csproj
@@ -1,17 +1,17 @@
-
- net8.0
- latest
- enable
- enable
- true
- Regex
-
+
+ net8.0
+ preview
+ enable
+ enable
+ true
+ Regex
+
-
-
-
+
+
+
@@ -20,8 +20,7 @@
-
-
+