From 035f988382084493afc82c3075d1c011d3b392f7 Mon Sep 17 00:00:00 2001 From: VladD2 Date: Wed, 21 May 2025 18:34:31 +0300 Subject: [PATCH 1/2] WIM --- Parsers/DotParser/DotParser.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsers/DotParser/DotParser.csproj b/Parsers/DotParser/DotParser.csproj index 56516e6..391f3b0 100644 --- a/Parsers/DotParser/DotParser.csproj +++ b/Parsers/DotParser/DotParser.csproj @@ -1,7 +1,7 @@  - net8.0 + netstandard2.0 enable enable Dot From 02d618ec31277f2a1862b37d03a3f5df3fb135d8 Mon Sep 17 00:00:00 2001 From: VladD2 Date: Wed, 21 May 2025 21:57:24 +0300 Subject: [PATCH 2/2] Move core projects to netstandard2.0 to support internal source generators --- ExtensibleParaser/ExtensibleParaser.csproj | 35 +- ExtensibleParaser/Parser.cs | 2 +- ExtensibleParaser/SyntaxTree.cs | 9 +- Parsers/DotParser/DotAst.cs | 4 +- Parsers/DotParser/DotParser.csproj | 31 +- Parsers/DotParser/DotVisitor.cs | 2 +- Regex/Regex/CompilerServices/Extensions.cs | 28 - Regex/Regex/CompilerServices/Index.cs | 55 -- Regex/Regex/Regex.csproj | 9 + Shared/NetStandard2_0Support.cs | 522 ++++++++++++++++++ .../Extensions => Shared}/StringExtensions.cs | 2 +- Tests/ParaserTests/ParaserTests.csproj | 8 +- Tests/RegexTests/RegexTests.csproj | 25 +- 13 files changed, 603 insertions(+), 129 deletions(-) delete mode 100644 Regex/Regex/CompilerServices/Extensions.cs delete mode 100644 Regex/Regex/CompilerServices/Index.cs create mode 100644 Shared/NetStandard2_0Support.cs rename {Tests/Extensions => Shared}/StringExtensions.cs (76%) 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 391f3b0..3c1dd96 100644 --- a/Parsers/DotParser/DotParser.csproj +++ b/Parsers/DotParser/DotParser.csproj @@ -1,16 +1,25 @@  - - netstandard2.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 @@ - - +