From e9ddee84f10c90ba0dff9518a8926496d7165664 Mon Sep 17 00:00:00 2001 From: trumpmcdonaldz Date: Fri, 12 Jan 2024 11:03:37 +0800 Subject: [PATCH 1/6] Fixed and optimized CodeBlock.cs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug Fixes: • "```````" is no longer treated as codeblock • "```csharp```" now treat "csharp" as code, not formatter. This may fail if strictMode is set to false Performance optimization: • Reduced code size by merging return paths • Greatly optimized comparision of characters Test: https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEUCuA7AHwAEAmABgFgAoUgRmqLIAIjaA6AEQEsBDAczwQAzhi5ghbAMIQAJjACCeHgBsAnkK5CA3NWoA3HlCZCcYMDCFCmAXibS5AIWUQwAazYAVKKoAKhoTAAFABEAAbh4gAWhgAOADp44aHBaEwQOBhMBkZgwACUOlQMtACcgQAkwQDKpuaWIEwA3iZmFkIAvkwETABi0AC2PBgYMFANjblsfVCDw6Od3fYw45NL7cEFulQtdVa2S04u7l6+/kFhEULRUPF4CUkpaRlMuZtFNKUV1bVt4zttC16AyGIzGTUm01moMBSxWwCkshg6ze1H+lhsdkRhzcnm8figARCSSisQSAA0Hql0plXoVimVKjVWvUmmiOl0gTMQaM4VNgXMoDDEby1hs6dsfuj9ljnDiTvjCRdQvdwo9qS98uLWAzvsyhH9JezupDuWCJvCTQKhXIRYjkeK2RiDrLjnizkSkqqqc9aVttV8mbsDXr7X6AMwsEiYxwu6iNahMRMsCOsZhLJpMPgwDBaJjtBNJogp2hkAD8nKhowzWZzea2SeT0Zg2NcgVTL0RqVT5YAZvzQRi8DhlMo8gXE/GqA2G+nbJA5IVp0nLQPbH2uQLF0n8+8G0WWLQkExgBAIMomPL3QAlGA8GQAeTwaiqMR4eAAPGBrgA+JgjBAYKkADaAByEAYCBw7KAA6pEMB4IEGC4DAeQALpPJkzpHOW87Ni6qQnmexhIWIGAALKIhiSE4Ch45NHRDa4S2g5QeKS6JtkTCaFhbhUTAAFsFUGCGBgQjQVwGCRB6yR5EwABkcl/vxGBsAAongMhiRJUlKhs8mKf+KkADLwXwklMN+thKiqyRsCZeBmZEW5LgxSZcD2TCBAAhNxMpHGOU7sZO7HTnw4EQEwN45q5iY7iFMUHmWTDrpWRi2EOI7OdOhl8QBQFhmwbAAHphqhWUNpxPZcASEEwAA7kZXB4DAuUqQAkhp/H3j2gQAOQJL1bwhQ2CXuZ5VU1SB9WNc1TBebYcC0AFIWNMN7EJQ2N53o+z6vh+X6GL+KWmi+b6tUBhUTSIU0NU1MBlRt62BWt663l+nmcWAXF4Ml/ajKdeDLWt9HPcDHGGFxQjyEIYBcFw8jKDE0SQf0oxiBiB1QGwbVQzDcMmQK95QNwfASYEYBDWDT1U25HmBJo0Ow/DiPIzgqNQOjBDdF91i2L1ADUvVdNzNh83Ag2PUuwU09OkB4KIQ4wOVwNxTLI2g2DYUYBFgZtMrS6q2tktKQB52BMdAoA3ZpnmfzTBLYVD0a9TwNjd5Igc2A5GUVzc2GdjYmRBJMCneYgR5EDa3SzTFurr9G6glbHgQEJHMOeH+vTjuACQufZ8bMDKAExvR1TOVrn9UAA5nDaG1nCUJbr9QJUxLqDvViHKZ4KckeneSpLHoyUy5zuJlFIAJUQADskM8a4WU7nR+6sEec9MAqQRbQ+T6qADn4/ibGCRyDIVu5eBJBIZ3qZJ9fluBHxvTx2MZHJnhfF6P2WRFAEB1UwzV/0tKpBA5gYiiAgAhSokg3yCEyK+C+TBeqNCUKjCAPU555HaL1NgYo6KL1BvuCAehRgczkIlC8Pc058HDjYX8lQkiNBXPMBIjQ1gPEKO0IAA --- NetCord/CodeBlock.cs | 57 ++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/NetCord/CodeBlock.cs b/NetCord/CodeBlock.cs index 09b0170e6..0ccb92e96 100644 --- a/NetCord/CodeBlock.cs +++ b/NetCord/CodeBlock.cs @@ -13,34 +13,55 @@ public CodeBlock(string code, string? formatter = null) Formatter = formatter; } - public static bool TryParse(ReadOnlySpan text, [NotNullWhen(true)] out CodeBlock? codeBlock) + public static bool TryParse(ReadOnlySpan text, [NotNullWhen(true)] out CodeBlock? codeBlock, bool strictMode = true) { - if (text.StartsWith("```") && text.EndsWith("```")) + codeBlock = null; + + var isCodeBlock = text.StartsWith("```") && text.EndsWith("```") && text.Length >= "```\n```".Length; + + if (!isCodeBlock) { - text = text[3..^3]; - int i = text.IndexOf('\n'); - if (i != -1) + goto Ret; + } + + string? formatter = null; + text = text[3..^3]; + var firstNewLine = text.IndexOf('\n'); + + if (firstNewLine != -1) + { + ReadOnlySpan formatterSpan = text[..firstNewLine]; + + foreach (var c in formatterSpan) { - ReadOnlySpan formatter = text[..i]; - foreach (var c in formatter) + var isAsciiAlphaNumeric = char.IsAsciiLetterOrDigit(c); + + if (isAsciiAlphaNumeric || c == '+' || c == '-') { - if (c is not ((>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or (>= '0' and <= '9') or '+' or '-')) - { - codeBlock = new(text.ToString()); - return true; - } + continue; } - codeBlock = new(text[(formatter.Length + 1)..].ToString(), formatter.ToString()); - return true; + + goto Success; } + + text = text[(formatterSpan.Length + 1)..]; + + if (!strictMode || !text.IsWhiteSpace()) + { + formatter = formatterSpan.ToString(); + } + else { - codeBlock = new(text.ToString()); - return true; + text = formatterSpan; } } - codeBlock = null; - return false; + + Success: + codeBlock = new(text.ToString(), formatter); + + Ret: + return isCodeBlock; } public static CodeBlock Parse(ReadOnlySpan text) From acbe340e7d8d8aadb5810611ee0c80803e950117 Mon Sep 17 00:00:00 2001 From: trumpmcdonaldz Date: Fri, 12 Jan 2024 11:13:57 +0800 Subject: [PATCH 2/6] Inline so that strictMode branches can be eliminated if it is a constant. --- NetCord/CodeBlock.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NetCord/CodeBlock.cs b/NetCord/CodeBlock.cs index 0ccb92e96..d476cc7fe 100644 --- a/NetCord/CodeBlock.cs +++ b/NetCord/CodeBlock.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace NetCord; @@ -13,6 +14,7 @@ public CodeBlock(string code, string? formatter = null) Formatter = formatter; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] // Inline so that strictMode branches can be eliminated if it is a constant public static bool TryParse(ReadOnlySpan text, [NotNullWhen(true)] out CodeBlock? codeBlock, bool strictMode = true) { codeBlock = null; From b5e5d5b5c7d3530dddff5e06e4fed8e5ef3ec253 Mon Sep 17 00:00:00 2001 From: trumpmcdonaldz Date: Fri, 12 Jan 2024 11:39:54 +0800 Subject: [PATCH 3/6] Make good use of const. --- NetCord/CodeBlock.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/NetCord/CodeBlock.cs b/NetCord/CodeBlock.cs index d476cc7fe..086979ac6 100644 --- a/NetCord/CodeBlock.cs +++ b/NetCord/CodeBlock.cs @@ -17,9 +17,11 @@ public CodeBlock(string code, string? formatter = null) [MethodImpl(MethodImplOptions.AggressiveInlining)] // Inline so that strictMode branches can be eliminated if it is a constant public static bool TryParse(ReadOnlySpan text, [NotNullWhen(true)] out CodeBlock? codeBlock, bool strictMode = true) { + const string prefixSuffix = "```"; + codeBlock = null; - - var isCodeBlock = text.StartsWith("```") && text.EndsWith("```") && text.Length >= "```\n```".Length; + + var isCodeBlock = text.StartsWith(prefixSuffix) && text.EndsWith(prefixSuffix) && text.Length >= "```\n```".Length; if (!isCodeBlock) { @@ -27,7 +29,7 @@ public static bool TryParse(ReadOnlySpan text, [NotNullWhen(true)] out Cod } string? formatter = null; - text = text[3..^3]; + text = text[prefixSuffix.Length..^prefixSuffix.Length]; var firstNewLine = text.IndexOf('\n'); if (firstNewLine != -1) From aaa9e71af04fc0db5aedc60063bca9bbbe557262 Mon Sep 17 00:00:00 2001 From: trumpmcdonaldz Date: Fri, 12 Jan 2024 11:50:40 +0800 Subject: [PATCH 4/6] Make good use of const II. --- NetCord/CodeBlock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetCord/CodeBlock.cs b/NetCord/CodeBlock.cs index 086979ac6..e50e2a18b 100644 --- a/NetCord/CodeBlock.cs +++ b/NetCord/CodeBlock.cs @@ -21,7 +21,7 @@ public static bool TryParse(ReadOnlySpan text, [NotNullWhen(true)] out Cod codeBlock = null; - var isCodeBlock = text.StartsWith(prefixSuffix) && text.EndsWith(prefixSuffix) && text.Length >= "```\n```".Length; + var isCodeBlock = text.StartsWith(prefixSuffix) && text.EndsWith(prefixSuffix) && text.Length >= $"{prefixSuffix}\n{prefixSuffix}".Length; if (!isCodeBlock) { From 0e3ea4e313667140a98d73b55f972e22ca4697f1 Mon Sep 17 00:00:00 2001 From: trumpmcdonaldz Date: Fri, 12 Jan 2024 11:53:01 +0800 Subject: [PATCH 5/6] Prioritize faster test --- NetCord/CodeBlock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetCord/CodeBlock.cs b/NetCord/CodeBlock.cs index e50e2a18b..27e89039b 100644 --- a/NetCord/CodeBlock.cs +++ b/NetCord/CodeBlock.cs @@ -21,7 +21,7 @@ public static bool TryParse(ReadOnlySpan text, [NotNullWhen(true)] out Cod codeBlock = null; - var isCodeBlock = text.StartsWith(prefixSuffix) && text.EndsWith(prefixSuffix) && text.Length >= $"{prefixSuffix}\n{prefixSuffix}".Length; + var isCodeBlock = text.Length >= $"{prefixSuffix}\n{prefixSuffix}".Length && text.StartsWith(prefixSuffix) && text.EndsWith(prefixSuffix); if (!isCodeBlock) { From 151777a73f78dfbb9aaa108578a4aadef5b43b6d Mon Sep 17 00:00:00 2001 From: KubaZ2 Date: Wed, 17 Jan 2024 12:37:37 +0100 Subject: [PATCH 6/6] Resolve merge conflicts and refactor and fix a minor bug --- NetCord/CodeBlock.cs | 125 ++++++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 43 deletions(-) diff --git a/NetCord/CodeBlock.cs b/NetCord/CodeBlock.cs index 27e89039b..f97257ef0 100644 --- a/NetCord/CodeBlock.cs +++ b/NetCord/CodeBlock.cs @@ -3,7 +3,7 @@ namespace NetCord; -public class CodeBlock +public class CodeBlock : ISpanFormattable, ISpanParsable { public string Code { get; } public string? Formatter { get; } @@ -14,67 +14,106 @@ public CodeBlock(string code, string? formatter = null) Formatter = formatter; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] // Inline so that strictMode branches can be eliminated if it is a constant - public static bool TryParse(ReadOnlySpan text, [NotNullWhen(true)] out CodeBlock? codeBlock, bool strictMode = true) + public override string ToString() => $"```{Formatter}\n{Code}```"; + + public string ToString(string? format, IFormatProvider? formatProvider) => ToString(); + + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) { - const string prefixSuffix = "```"; - - codeBlock = null; - - var isCodeBlock = text.Length >= $"{prefixSuffix}\n{prefixSuffix}".Length && text.StartsWith(prefixSuffix) && text.EndsWith(prefixSuffix); - + var code = Code; + var formatter = Formatter; + + int requiredLength = formatter is null ? (code.Length + 7) : (code.Length + formatter.Length + 7); + if (destination.Length < requiredLength) + { + charsWritten = 0; + return false; + } + + int written = 0; + + "```".CopyTo(destination); + written += 3; + + if (formatter is not null) + { + formatter.CopyTo(destination[written..]); + written += formatter.Length; + } + + destination[written++] = '\n'; + + code.CopyTo(destination[written..]); + written += code.Length; + + "```".CopyTo(destination[written..]); + + charsWritten = requiredLength; + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] // Inline so that 'strictMode' branches can be eliminated if it is a constant + public static bool TryParse(ReadOnlySpan s, bool strictMode, [MaybeNullWhen(false)] out CodeBlock result) + { + // It needs to start and end with 3 backticks and have at least 1 character in between + // 3 + 1 + 3 = 7 + var isCodeBlock = s.Length >= 7 && s.StartsWith("```") && s.EndsWith("```"); + if (!isCodeBlock) { + result = null; goto Ret; } - + string? formatter = null; - text = text[prefixSuffix.Length..^prefixSuffix.Length]; - var firstNewLine = text.IndexOf('\n'); - - if (firstNewLine != -1) - { - ReadOnlySpan formatterSpan = text[..firstNewLine]; - + s = s[3..^3]; + var firstNewLine = s.IndexOf('\n'); + + if (firstNewLine > 0) + { + var formatterSpan = s[..firstNewLine]; + foreach (var c in formatterSpan) { - var isAsciiAlphaNumeric = char.IsAsciiLetterOrDigit(c); - - if (isAsciiAlphaNumeric || c == '+' || c == '-') - { + if (char.IsAsciiLetterOrDigit(c) || c == '+' || c == '-') continue; - } - + goto Success; } - - text = text[(formatterSpan.Length + 1)..]; - - if (!strictMode || !text.IsWhiteSpace()) - { - formatter = formatterSpan.ToString(); - } - + + s = s[(firstNewLine + 1)..]; + + if (strictMode && s.IsWhiteSpace()) + s = formatterSpan; else - { - text = formatterSpan; - } + formatter = formatterSpan.ToString(); } - + Success: - codeBlock = new(text.ToString(), formatter); - + result = new(s.ToString(), formatter); + Ret: return isCodeBlock; } - public static CodeBlock Parse(ReadOnlySpan text) + public static bool TryParse(ReadOnlySpan s, [MaybeNullWhen(false)] out CodeBlock result) => TryParse(s, true, out result); + + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out CodeBlock result) => TryParse(s, true, out result); + + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out CodeBlock result) => TryParse(s.AsSpan(), true, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] // Inline so that 'strictMode' branches can be eliminated if it is a constant + public static CodeBlock Parse(ReadOnlySpan s, bool strictMode) { - if (TryParse(text, out var codeBlock)) - return codeBlock; - else - throw new FormatException($"Cannot parse '{nameof(CodeBlock)}'."); + if (TryParse(s, strictMode, out var result)) + return result; + + throw new FormatException($"Cannot parse '{nameof(CodeBlock)}'."); } - public override string ToString() => $"```{Formatter}\n{Code}```"; + public static CodeBlock Parse(ReadOnlySpan s) => Parse(s, true); + + public static CodeBlock Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, true); + + public static CodeBlock Parse(string s, IFormatProvider? provider) => Parse(s.AsSpan(), true); }