diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 38d98637..f4de8d36 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -39,12 +39,11 @@ categories: label: 'Type/Housekeeping' # Extra line before $BODY or markdown on the first line of $BODY will be interpreted as plain text change-template: | -
$TITLE (#$NUMBER) @$AUTHOR - +
`$TITLE` (#$NUMBER) @$AUTHOR
+ $BODY - +
no-changes-template: '* (No changes)' replacers: - # We don't support nested and
so we add a (?!
|) - - search: '/
((?:(?!
|).)+?)<\/summary>\s*<\/details>/g' + - search: '/
(.+?)<\/summary>
\s*<\/td><\/table><\/details>/g' replace: '- $1' diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 42948d45..080e6f6b 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -18,5 +18,5 @@ jobs: uses: warrenbuckley/Setup-MSBuild@v1 - name: Restore NuGet Packages run: nuget restore CSharpMath.sln - - name: Build CI artifacts + - name: Build Everything run: msbuild CSharpMath.sln /p:Configuration=Release diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index 23e721a1..cd316108 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -10,12 +10,6 @@ jobs: uses: release-drafter/release-drafter@v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: cardinalby/git-get-release-action@v1 - id: release_info - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - releaseId: ${{ steps.release_drafter.outputs.id }} - uses: actions/checkout@v2 with: submodules: 'recursive' @@ -35,9 +29,9 @@ jobs: - name: Build and Test env: RELEASE_NOTES: | - # ${{ steps.release_info.outputs.name }} + # ${{ steps.release_drafter.outputs.name }} - ${{ steps.release_info.outputs.body }} + ${{ steps.release_drafter.outputs.body }} # run: dotnet test CSharpMath.CrossPlatform.slnf run: | # https://github.com/dotnet/sdk/issues/10409, https://github.com/dotnet/sdk/issues/11417 # .NET Core MSBuild cannot parse , and ; correctly so we replace them with substitutions: https://github.com/dotnet/msbuild/issues/471#issuecomment-366268743 @@ -60,16 +54,17 @@ jobs: dotnet add "$p" package Microsoft.NET.Test.Sdk # Update is required for GitHubActionsTestLogger to print anything dotnet add "$p" package GitHubActionsTestLogger # -r for restore - dotnet msbuild -r -p:Configuration=Release -p:PackageVersion=${{ steps.release_drafter.outputs.tag_name }}-ci-${{ github.sha }} -p:PackageReleaseNotes="$RELEASE_NOTES" "$p" + dotnet msbuild -r -p:Configuration=Release -p:PackageVersion=${{ steps.release_drafter.outputs.tag_name || format('{0}-pr', github.event.number) }}-ci-${{ github.sha }} -p:PackageReleaseNotes="$RELEASE_NOTES" "$p" # --no-build because building again will produce additional NuGet packages without the PackageVersion setting above dotnet test "$p" --no-build -c Release -l GitHubActions --blame done - - uses: actions/upload-artifact@v2 + - name: Upload CSharpMath.Rendering.Tests results as CI artifacts + uses: actions/upload-artifact@v2 if: always() # Run even when a previous step failed: https://stackoverflow.com/a/58859404/5429648 with: name: CSharpMath.Rendering.Tests results path: CSharpMath.Rendering.Tests/*/*.png - - name: Upload CI artifacts + - name: Upload NuGet packages as CI artifacts uses: actions/upload-artifact@v2 if: always() with: diff --git a/CSharpMath.Evaluation.Tests/EvaluationTests.cs b/CSharpMath.Evaluation.Tests/EvaluationTests.cs index 0c32c188..b98e20f7 100644 --- a/CSharpMath.Evaluation.Tests/EvaluationTests.cs +++ b/CSharpMath.Evaluation.Tests/EvaluationTests.cs @@ -3,12 +3,12 @@ using Xunit; using AngouriMath; -namespace CSharpMath { +namespace CSharpMath.EvaluationTests { using Atom; - public partial class EvluationTests { - MathList ParseLaTeX(string latex) => + public class EvaluationTests { + internal static MathList ParseLaTeX(string latex) => LaTeXParser.MathListFromLaTeX(latex).Match(list => list, e => throw new Xunit.Sdk.XunitException(e)); - Evaluation.MathItem ParseMath(string latex) => + static Evaluation.MathItem ParseMath(string latex) => Evaluation.Evaluate(ParseLaTeX(latex)).Match(entity => entity, e => throw new Xunit.Sdk.XunitException(e)); void Test(string input, string converted, string? result) { void Test(string input) { @@ -424,7 +424,10 @@ public partial class EvluationTests { [InlineData(@"\sin^a\ \; (x)^2(x)", @"\sin \left( x\right) ^{a\times 2}\times x", @"\sin \left( x\right) ^{2\times a}\times x")] [InlineData(@"\sin (\frac\pi2)", @"\sin \left( \frac{\pi }{2}\right) ", @"1")] [InlineData(@"\sin (\frac\pi2)+1", @"\sin \left( \frac{\pi }{2}\right) +1", @"2")] - public void Parentheses(string latex, string converted, string result) => Test(latex, converted, result); + public void Parentheses(string latex, string converted, string result) { + Test(latex, converted, result); + Test(latex.Replace('(', '[').Replace(')', ']'), converted, result); + } [Theory] [InlineData(@"\begin{matrix}1\end{matrix}", @"\left( \begin{matrix}1\end{matrix}\right) ", @"\left( \begin{matrix}1\end{matrix}\right) ")] [InlineData(@"\begin{pmatrix}1\end{pmatrix}", @"\left( \begin{matrix}1\end{matrix}\right) ", @"\left( \begin{matrix}1\end{matrix}\right) ")] @@ -580,11 +583,12 @@ public partial class EvluationTests { [InlineData(@"\left(1+\right]", "Missing right operand for +")] [InlineData(@"\left(2,3\right]^\square", "Placeholders should be filled")] [InlineData(@"(2,3]^\square", "Placeholders should be filled")] - [InlineData(@"[]", "Unrecognized bracket pair [ ]")] - [InlineData(@"[x]", "Unrecognized bracket pair [ ]")] - [InlineData(@"[[x]", "Unrecognized bracket pair [ ]")] - [InlineData(@"[x]]", "Unrecognized bracket pair [ ]")] - [InlineData(@"\left[\right]", "Unrecognized bracket pair [ ]")] + [InlineData(@"[]", "Missing math inside [ ]")] + [InlineData(@"[[x]", "Missing ]")] + [InlineData(@"[x]]", "Missing [")] + [InlineData(@"[_\square x]", "Subscripts are unsupported for Open [")] + [InlineData(@"[x]_\square", "Subscripts are unsupported for Close ]")] + [InlineData(@"\left[\right]", "Missing math inside [ ]")] [InlineData(@"\left[1+\right]", "Missing right operand for +")] [InlineData(@"\left[2,3\right]^\square", "Placeholders should be filled")] [InlineData(@"[2,3]^\square", "Placeholders should be filled")] @@ -633,7 +637,7 @@ public partial class EvluationTests { [InlineData(@"+(3,4]", "Set cannot be right operand for +")] [InlineData(@"[5,6)\times", "Set cannot be left operand for ×")] [InlineData(@"\frac{[7,8]}{9}", "Set cannot be numerator")] - [InlineData(@"\sqrt[{[]}]{}", "Unrecognized bracket pair [ ]")] + [InlineData(@"\sqrt[{[)}]{}", "Unrecognized bracket pair [ )")] [InlineData(@"\sqrt[{[a,b]}]{}", "Set cannot be degree")] [InlineData(@"\{\{\}\}", "Set cannot be set element")] [InlineData(@"\cap", "Unsupported Unary Operator ∩")] diff --git a/CSharpMath.Evaluation.Tests/InterpretTests.cs b/CSharpMath.Evaluation.Tests/InterpretTests.cs new file mode 100644 index 00000000..10a24b5a --- /dev/null +++ b/CSharpMath.Evaluation.Tests/InterpretTests.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; +using Xunit; +namespace CSharpMath.EvaluationTests { + public class InterpretTests { + [Theory] + [InlineData(@"", @"\color{red}\text{There is nothing to evaluate}")] + [InlineData(@"1", @"\underline\mathrm{Input}\\1\\\\\underline\mathrm{Simplified}\\1\\\\\underline\mathrm{Value\ (100\ digits)}\\1")] + [InlineData(@"1+", @"\color{red}\text{Missing right operand for +}")] + [InlineData(@"1+2", @"\underline\mathrm{Input}\\1+2\\\\\underline\mathrm{Simplified}\\3\\\\\underline\mathrm{Value\ (100\ digits)}\\3")] + [InlineData(@"1+\sqrt", @"\color{red}\text{Missing radicand}")] + [InlineData(@"1+\sqrt2", @"\underline\mathrm{Input}\\1+\sqrt{2}\\\\\underline\mathrm{Simplified}\\1+\sqrt{2}\\\\\underline\mathrm{Value\ (100\ digits)}\\2.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573")] + [InlineData(@"1+\sqrt{2x}", @"\underline\mathrm{Input}\\1+\sqrt{2\times x_{}}\\\\\underline\mathrm{Simplified}\\1+\sqrt{2\times x_{}}\\\\\underline\mathrm{Expanded}\\1+\sqrt{2\times x_{}}\\\\\underline\mathrm{Factorized}\\1+\sqrt{2\times x_{}}")] + [InlineData(@"1+\sqrt{2xy}", @"\underline\mathrm{Input}\\1+\sqrt{2\times x_{}\times y_{}}\\\\\underline\mathrm{Simplified}\\1+\sqrt{2\times x_{}\times y_{}}\\\\\underline\mathrm{Expanded}\\1+\sqrt{2\times x_{}\times y_{}}\\\\\underline\mathrm{Factorized}\\1+\sqrt{2\times x_{}\times y_{}}")] + [InlineData(@"=1+\sqrt{2xy}", @"\color{red}\text{Missing left side of equation}")] + [InlineData(@"1+\sqrt{2xy}=", @"\color{red}\text{Missing right side of equation}")] + [InlineData(@"1+\sqrt{2xy}=3", @"\underline\mathrm{Input}\\1+\sqrt{2\times x_{}\times y_{}}=3\\\\\underline\mathrm{Solutions}\\x_{}\in \left\{\frac{--\frac{4}{y_{}}}{2}\right\}\\y_{}\in \left\{\frac{4}{2\times x_{}}\right\}\\")] + [InlineData(@"1=3", @"\underline\mathrm{Input}\\1=3\\\\\underline\mathrm{Result}\\\text{False}")] + [InlineData(@"1=1", @"\underline\mathrm{Input}\\1=1\\\\\underline\mathrm{Result}\\\text{True}")] + public void Interpret(string input, string expected) { + var actual = Evaluation.Interpret(EvaluationTests.ParseLaTeX(input)); + Assert.Equal(expected, actual); + Assert.NotEmpty(EvaluationTests.ParseLaTeX(actual)); + } + } +} \ No newline at end of file diff --git a/CSharpMath.Evaluation.Tests/PredictTests.cs b/CSharpMath.Evaluation.Tests/PredictTests.cs index 71e7bd6d..58f549c0 100644 --- a/CSharpMath.Evaluation.Tests/PredictTests.cs +++ b/CSharpMath.Evaluation.Tests/PredictTests.cs @@ -1,62 +1,60 @@ using System; using System.Linq; using Xunit; -namespace CSharpMath { - public partial class EvaluationTests { - public class PredictTests { - [Theory] - [InlineData(1, 1, 1)] - [InlineData(2, 2, 2)] - [InlineData(1, 2, 3, 4)] - [InlineData(3, 4, 5, 6)] - [InlineData(1, 2, 3, 4, 5)] - [InlineData(3, 4, 5, 6, 7)] - [InlineData(1, 3, 5, 7)] - [InlineData(8, 10, 12, 14)] - [InlineData(1, 3, 5, 7, 9)] - [InlineData(8, 10, 12, 14, 16)] - [InlineData(1, 4, 7, 10)] - [InlineData(1, 4, 7, 10, 13)] - [InlineData(24, 31, 38, 45)] - [InlineData(24, 31, 38, 45, 52)] - [InlineData(17, 21, 25, 29)] - [InlineData(17, 21, 25, 29, 33)] - [InlineData(1, 1, 1, 1)] - [InlineData(1, 1, 1, 1, 1)] - [InlineData(1, 3, 1, 3, 1)] - [InlineData(1, 3, 1, 3, 1, 3)] - [InlineData(1, 2, 4, 8)] - [InlineData(1, 2, 4, 8, 16)] - [InlineData(1, 3, 9, 27)] - [InlineData(1, 3, 9, 27, 81)] - [InlineData(1, -2, 4, -8)] - [InlineData(-1, 2, -4, 8)] - [InlineData(30, 31, 40, 41, 50, 51)] - [InlineData(61, 62, 63, 71, 72, 73, 81, 82)] - [InlineData(2, 4, 8, 24, 48, 96, 288, 576, 1152)] - [InlineData(0, 0, 0, 1, 0, 0, 0, 1, 0)] - [InlineData(39, 34, 27, 22, 15, 10)] - [InlineData(39, 34, 27, 22, 15, 10, 3)] - [InlineData(31, 30, 22, 21, 13, 12)] - [InlineData(31, 30, 22, 21, 13, 12, 4)] - // Use int instead of double because https://github.com/xunit/xunit/issues/1670#issuecomment-373566797 - public void Integer(params int[] input) { - static void Test(System.Collections.Generic.IEnumerable seq) => - Assert.Equal(seq.Last(), Assert.IsType(Evaluation.Predict(seq.Select(x => (double)x).SkipLast(1).ToArray())), 12); - Test(input); - Test(input.Reverse()); - } - [Theory] - [InlineData(new int[0])] - [InlineData(1)] - [InlineData(1, 2)] - [InlineData(2, 1)] - [InlineData(1, 2, 1)] - [InlineData(1, 2, 1, 3)] - [InlineData(39, 34, 27, 22)] - [InlineData(31, 30, 22, 21)] - public void IntegerFail(params int[] input) => - Assert.Null(Evaluation.Predict(input.Select(x => (double)x).ToArray())); +namespace CSharpMath.EvaluationTests { + public class PredictTests { + [Theory] + [InlineData(1, 1, 1)] + [InlineData(2, 2, 2)] + [InlineData(1, 2, 3, 4)] + [InlineData(3, 4, 5, 6)] + [InlineData(1, 2, 3, 4, 5)] + [InlineData(3, 4, 5, 6, 7)] + [InlineData(1, 3, 5, 7)] + [InlineData(8, 10, 12, 14)] + [InlineData(1, 3, 5, 7, 9)] + [InlineData(8, 10, 12, 14, 16)] + [InlineData(1, 4, 7, 10)] + [InlineData(1, 4, 7, 10, 13)] + [InlineData(24, 31, 38, 45)] + [InlineData(24, 31, 38, 45, 52)] + [InlineData(17, 21, 25, 29)] + [InlineData(17, 21, 25, 29, 33)] + [InlineData(1, 1, 1, 1)] + [InlineData(1, 1, 1, 1, 1)] + [InlineData(1, 3, 1, 3, 1)] + [InlineData(1, 3, 1, 3, 1, 3)] + [InlineData(1, 2, 4, 8)] + [InlineData(1, 2, 4, 8, 16)] + [InlineData(1, 3, 9, 27)] + [InlineData(1, 3, 9, 27, 81)] + [InlineData(1, -2, 4, -8)] + [InlineData(-1, 2, -4, 8)] + [InlineData(30, 31, 40, 41, 50, 51)] + [InlineData(61, 62, 63, 71, 72, 73, 81, 82)] + [InlineData(2, 4, 8, 24, 48, 96, 288, 576, 1152)] + [InlineData(0, 0, 0, 1, 0, 0, 0, 1, 0)] + [InlineData(39, 34, 27, 22, 15, 10)] + [InlineData(39, 34, 27, 22, 15, 10, 3)] + [InlineData(31, 30, 22, 21, 13, 12)] + [InlineData(31, 30, 22, 21, 13, 12, 4)] + // Use int instead of double because https://github.com/xunit/xunit/issues/1670#issuecomment-373566797 + public void Integer(params int[] input) { + static void Test(System.Collections.Generic.IEnumerable seq) => + Assert.Equal(seq.Last(), Assert.IsType(Evaluation.Predict(seq.Select(x => (double)x).SkipLast(1).ToArray())), 12); + Test(input); + Test(input.Reverse()); } + [Theory] + [InlineData(new int[0])] + [InlineData(1)] + [InlineData(1, 2)] + [InlineData(2, 1)] + [InlineData(1, 2, 1)] + [InlineData(1, 2, 1, 3)] + [InlineData(39, 34, 27, 22)] + [InlineData(31, 30, 22, 21)] + public void IntegerFail(params int[] input) => + Assert.Null(Evaluation.Predict(input.Select(x => (double)x).ToArray())); } } \ No newline at end of file diff --git a/CSharpMath.Evaluation/Evaluation.cs b/CSharpMath.Evaluation/Evaluation.cs index e9cb3e7c..6d8d21e1 100644 --- a/CSharpMath.Evaluation/Evaluation.cs +++ b/CSharpMath.Evaluation/Evaluation.cs @@ -167,8 +167,9 @@ public sealed class Set : MathItem { _ => "Unrecognized bracket pair ( ]" } }, { ("[", "]"), item => item switch { + null => "Missing math inside [ ]", MathItem.Comma c => TryMakeSet(c, true, true), - _ => "Unrecognized bracket pair [ ]" + _ => item } }, { ("{", "}"), item => item.AsEntities("set element").Bind(entities => (MathItem)MathS.Sets.Finite(entities)) } }; @@ -392,14 +393,18 @@ public sealed class Set : MathItem { handlePrecendence = Precedence.SetOperation; handleBinarySet = (l, r) => l - r; goto handleBinarySet; - // Until C# allows declaring variables under "or pattern"s... - case Atoms.Inner { LeftBoundary:{ Nucleus:"[" }, InnerList:{ Count:1 } inner, RightBoundary:{ Nucleus:"]" } } - when inner[0] is Atoms.Table { Environment: "matrix" } table: - var matrix = table; - goto handleMatrix; - case Atoms.Table { Environment: "matrix" } table: - matrix = table; - goto handleMatrix; + case Atoms.Table { Environment: "matrix" } matrix: + var (rows, cols, cells) = (matrix.NRows, matrix.NColumns, matrix.Cells); + var matrixElements = new Entity[rows * cols]; + for (var row = 0; row < rows; row++) + for (var col = 0; col < cols; col++) { + if (cells[row].Count <= col) + return $"There are empty slots in the {rows}×{cols} matrix"; + (matrixElements[row * cols + col], error) = Transform(cells[row][col]).ExpectEntity("matrix element"); + if (error != null) return error; + } + @this = MathS.Matrices.Matrix(rows, cols, matrixElements); + goto handleThis; case Atoms.Open { Nucleus: var opening }: if (!OpenBracketInfo.TryGetValue(opening, out var bracketInfo)) return "Unsupported opening bracket " + opening; @@ -463,19 +468,6 @@ public sealed class Set : MathItem { return $"Unsupported table environment {table.Environment}"; default: return $"Unsupported {atom.TypeName} {atom.Nucleus}"; - - handleMatrix: - var (rows, cols, cells) = (matrix.NRows, matrix.NColumns, matrix.Cells); - var matrixElements = new Entity[rows * cols]; - for (var row = 0; row < rows; row++) - for (var col = 0; col < cols; col++) { - if (cells[row].Count <= col) - return $"There are empty slots in the {rows}×{cols} matrix"; - (matrixElements[row * cols + col], error) = Transform(cells[row][col]).ExpectEntity("matrix element"); - if (error != null) return error; - } - @this = MathS.Matrices.Matrix(rows, cols, matrixElements); - goto handleThis; #pragma warning disable CS0162 // Unreachable code detected #pragma warning disable CS0164 // This label has not been referenced handleFunction: diff --git a/CSharpMath.Evaluation/Interpret.cs b/CSharpMath.Evaluation/Interpret.cs index fac7b4f0..8ada8f4c 100644 --- a/CSharpMath.Evaluation/Interpret.cs +++ b/CSharpMath.Evaluation/Interpret.cs @@ -53,10 +53,6 @@ static partial class Evaluation { latex.AppendLaTeXHeader("Expanded").AppendLaTeX(entity.Expand()); latex.AppendLaTeXHeader("Factorized").AppendLaTeX(entity.Collapse()); } - foreach (AngouriMath.VariableEntity variable in AngouriMath.MathS.Utils.GetUniqueVariables(entity).FiniteSet()) - latex.AppendLaTeXHeader(@"\mathnormal\frac\partial{\partial " + variable.Latexise() + @"}") - .AppendLaTeX(entity.Derive(variable).Simplify()); - // TryOutput("Alternate forms", () => entity.Alternate(5)); break; case MathItem.Set _: case MathItem.Comma _: diff --git a/CSharpMath/Extensions.cs b/CSharpMath/Extensions.cs new file mode 100644 index 00000000..fdb57f39 --- /dev/null +++ b/CSharpMath/Extensions.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace CSharpMath { + using Display; + using Display.FrontEnd; + public static partial class Extensions { + public static string ToStringInvariant(this T value) where T : IConvertible => + value.ToString(CultureInfo.InvariantCulture); + public static string ToStringInvariant(this T value, string? format = null) + where T : IFormattable => value.ToString(format, CultureInfo.InvariantCulture); + public static bool IsEmpty(this IEnumerable enumerable) { + foreach (var _ in enumerable) return false; + return true; + } + public static bool IsNonEmpty(this IEnumerable enumerable) => !enumerable.IsEmpty(); + public static IEnumerable Zip( + this IEnumerable first, IEnumerable second, IEnumerable third, + Func resultSelector) { + return ZipIterator(); + IEnumerable ZipIterator() { + using var enum1 = first.GetEnumerator(); + using var enum2 = second.GetEnumerator(); + using var enum3 = third.GetEnumerator(); + while (enum1.MoveNext() && enum2.MoveNext() && enum3.MoveNext()) { + yield return resultSelector( + enum1.Current, + enum2.Current, + enum3.Current); + } + } + } + public static bool SequenceEqual(this IEnumerable enumerable, + IEnumerable otherEnumerable, Func? equalityTester = null) { + if (equalityTester == null) equalityTester = EqualityComparer.Default.Equals; + bool r; + if (enumerable == null && otherEnumerable == null) { + r = true; + } else if (enumerable != null && otherEnumerable != null) { + if (r = enumerable.Count() == otherEnumerable.Count()) { + using var enum1 = enumerable.GetEnumerator(); + using var enum2 = otherEnumerable.GetEnumerator(); + while (r && enum1.MoveNext() && enum2.MoveNext()) { + r = equalityTester(enum1.Current, enum2.Current); + } + } + } else { + // one enumerable is null and the other isn't + r = false; + } + return r; + } + public static float CollectionAscent + (this IEnumerable> displays) where TFont : IFont => + displays.IsNonEmpty() ? displays.Max(display => display.Ascent + display.Position.Y) : 0; + public static float CollectionDescent + (this IEnumerable> displays) where TFont : IFont => + displays.IsNonEmpty() ? displays.Max(display => display.Descent - display.Position.Y) : 0; + public static float CollectionWidth + (this IEnumerable> displays) where TFont : IFont => + displays.IsNonEmpty() + ? displays.Max(d => d.Position.X + d.Width) - displays.Min(d => d.Position.X) + : 0; + public static PointF Plus(this PointF point1, PointF point2) => + new PointF(point1.X + point2.X, point1.Y + point2.Y); + public static bool Is(this ReadOnlySpan span, char c) => + span.Length == 1 && span[0] == c; + public static bool IsNot(this ReadOnlySpan span, char c) => + span.Length != 1 || span[0] != c; + public static bool Is(this ReadOnlySpan span, string s) => + span.SequenceEqual(s.AsSpan()); + public static bool IsNot(this ReadOnlySpan span, string s) => + span.SequenceEqual(s.AsSpan()); + public static bool StartsWithInvariant(this ReadOnlySpan str, string prefix) => + str.StartsWith(prefix.AsSpan(), StringComparison.OrdinalIgnoreCase); + public static ReadOnlySpan RemovePrefix(this ReadOnlySpan str, + string prefix, StringComparison compare = StringComparison.OrdinalIgnoreCase) => + str.StartsWith(prefix.AsSpan(), compare) ? str.Slice(prefix.Length) : str; + public static RectangleF Plus(this RectangleF rect, PointF vector) => + new RectangleF(rect.Location.Plus(vector), rect.Size); + public static RectangleF Union(this RectangleF rect1, RectangleF rect2) { + var x = Math.Min(rect1.X, rect2.X); + var y = Math.Min(rect1.Y, rect2.Y); + var maxX = Math.Max(rect1.Right, rect2.Right); + var maxY = Math.Max(rect1.Bottom, rect2.Bottom); + return new RectangleF(x, y, maxX - x, maxY - y); + } + /// Because we are NOT inverting our y axis, + /// the properties "Top" and "Bottom" have misleading names. + public static float YMin(this RectangleF rect) => rect.Top; + /// Because we are NOT inverting our y axis, + /// the properties "Top" and "Bottom" have misleading names. + public static float YMax(this RectangleF rect) => rect.Bottom; + public static void GetAscentDescentWidth(this RectangleF rect, + out float ascent, out float descent, out float width) { + ascent = rect.Bottom; + width = rect.Width; + descent = -rect.Y; + } + [return: System.Diagnostics.CodeAnalysis.MaybeNull] + public static T PeekOrDefault(this Stack stack) => stack.Count == 0 ? default : stack.Peek(); + public static StringBuilder Append(this StringBuilder sb, ReadOnlySpan value) { + sb.EnsureCapacity(sb.Length + value.Length); + for (int i = 0; i < value.Length; i++) sb.Append(value[i]); + return sb; + } + private enum NullHandling { + ///the string "null", without the quotes + LiteralNull, + /// Change the null to the empty string, then wrap. + EmptyContent, + /// Return the empty string. Do not wrap. + EmptyString, + } + private static StringBuilder AppendIn(this StringBuilder b, char l, string? s, char r, NullHandling h) => + h switch + { + NullHandling.EmptyContent => b.Append(l).Append(s).Append(r), + NullHandling.EmptyString => s != null ? b.Append(l).Append(s).Append(r) : b, + NullHandling.LiteralNull => b.Append(l).Append(s ?? "null").Append(r), + _ => + throw new System.ComponentModel.InvalidEnumArgumentException(nameof(h), (int)h, typeof(NullHandling)) + }; + internal static StringBuilder AppendInBracesOrLiteralNull(this StringBuilder builder, string? appendMe) => + builder.AppendIn('{', appendMe, '}', NullHandling.LiteralNull); + internal static StringBuilder AppendInBracesOrEmptyBraces(this StringBuilder builder, string? appendMe) => + builder.AppendIn('{', appendMe, '}', NullHandling.EmptyContent); + internal static StringBuilder AppendInBracketsOrNothing(this StringBuilder builder, string? appendMe) => + builder.AppendIn('[', appendMe, ']', NullHandling.EmptyString); + internal static StringBuilder AppendDebugStringOfScripts(this StringBuilder builder, Atom.MathAtom target) { + if (target.Subscript.IsNonEmpty()) { + builder.Append('_').AppendInBracesOrLiteralNull(target.Subscript.DebugString); + } + if (target.Superscript.IsNonEmpty()) { + builder.Append('^').AppendInBracesOrLiteralNull(target.Superscript.DebugString); + } + return builder; + } + } +} diff --git a/CSharpMath/Extensions/IConvertible.cs b/CSharpMath/Extensions/IConvertible.cs deleted file mode 100644 index 74cd727f..00000000 --- a/CSharpMath/Extensions/IConvertible.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using System.Globalization; - -namespace CSharpMath { - public static partial class Extensions { - public static string ToStringInvariant(this T value) where T : IConvertible => - value.ToString(CultureInfo.InvariantCulture); - } -} \ No newline at end of file diff --git a/CSharpMath/Extensions/IEnumerable.cs b/CSharpMath/Extensions/IEnumerable.cs deleted file mode 100644 index 8fb1126a..00000000 --- a/CSharpMath/Extensions/IEnumerable.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace CSharpMath { - public static partial class Extensions { - public static bool IsEmpty(this IEnumerable enumerable) { - if (enumerable == null) return true; - foreach (var _ in enumerable) return false; - return true; - } - public static bool IsNonEmpty(this IEnumerable enumerable) => !enumerable.IsEmpty(); - public static IEnumerable Zip( - this IEnumerable first, - IEnumerable second, - IEnumerable third, - Func resultSelector) { - if (first is null) throw new ArgumentNullException(nameof(first)); - if (second is null) throw new ArgumentNullException(nameof(second)); - if (third is null) throw new ArgumentNullException(nameof(third)); - return ZipIterator(); - IEnumerable ZipIterator() { - using var enum1 = first.GetEnumerator(); - using var enum2 = second.GetEnumerator(); - using var enum3 = third.GetEnumerator(); - while (enum1.MoveNext() && enum2.MoveNext() && enum3.MoveNext()) { - yield return resultSelector( - enum1.Current, - enum2.Current, - enum3.Current); - } - } - } - public static bool SequenceEqual(this IEnumerable enumerable, - IEnumerable otherEnumerable, Func? equalityTester = null) { - if (equalityTester == null) equalityTester = EqualityComparer.Default.Equals; - bool r; - if (enumerable == null && otherEnumerable == null) { - r = true; - } else if (enumerable != null && otherEnumerable != null) { - if (r = enumerable.Count() == otherEnumerable.Count()) { - using var enum1 = enumerable.GetEnumerator(); - using var enum2 = otherEnumerable.GetEnumerator(); - while (r && enum1.MoveNext() && enum2.MoveNext()) { - r = equalityTester(enum1.Current, enum2.Current); - } - } - } else { - // one enumerable is null and the other isn't - r = false; - } - return r; - } - } -} \ No newline at end of file diff --git a/CSharpMath/Extensions/IEnumerable[IDisplay].cs b/CSharpMath/Extensions/IEnumerable[IDisplay].cs deleted file mode 100644 index a21dcf0c..00000000 --- a/CSharpMath/Extensions/IEnumerable[IDisplay].cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace CSharpMath { - using Display; - using Display.FrontEnd; - public static partial class Extensions { - public static float CollectionAscent - (this IEnumerable> displays) where TFont : IFont => - displays.IsNonEmpty() ? displays.Max(display => display.Ascent + display.Position.Y) : 0; - public static float CollectionDescent - (this IEnumerable> displays) where TFont : IFont => - displays.IsNonEmpty() ? displays.Max(display => display.Descent - display.Position.Y) : 0; - public static float CollectionWidth - (this IEnumerable> displays) where TFont : IFont => - displays.IsNonEmpty() - ? displays.Max(d => d.Position.X + d.Width) - displays.Min(d => d.Position.X) - : 0; - } -} diff --git a/CSharpMath/Extensions/IFormattable.cs b/CSharpMath/Extensions/IFormattable.cs deleted file mode 100644 index e21edc81..00000000 --- a/CSharpMath/Extensions/IFormattable.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using System.Globalization; - -namespace CSharpMath { - public static partial class Extensions { - public static string ToStringInvariant(this T value, string? format = null) - where T : IFormattable => value.ToString(format, CultureInfo.InvariantCulture); - } -} \ No newline at end of file diff --git a/CSharpMath/Extensions/PointF.cs b/CSharpMath/Extensions/PointF.cs deleted file mode 100644 index b6b0fb1a..00000000 --- a/CSharpMath/Extensions/PointF.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Drawing; - -namespace CSharpMath { - public static partial class Extensions { - public static PointF Plus(this PointF point1, PointF point2) => - new PointF(point1.X + point2.X, point1.Y + point2.Y); - } -} \ No newline at end of file diff --git a/CSharpMath/Extensions/ReadOnlySpan.cs b/CSharpMath/Extensions/ReadOnlySpan.cs deleted file mode 100644 index 61b3137e..00000000 --- a/CSharpMath/Extensions/ReadOnlySpan.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Linq; - -namespace CSharpMath { - public static partial class Extensions { - public static bool Is(this ReadOnlySpan span, char c) => - span.Length == 1 && span[0] == c; - public static bool IsNot(this ReadOnlySpan span, char c) => - span.Length != 1 || span[0] != c; - public static bool Is(this ReadOnlySpan span, string s) => - span.SequenceEqual(s.AsSpan()); - public static bool IsNot(this ReadOnlySpan span, string s) => - span.SequenceEqual(s.AsSpan()); - public static bool StartsWithInvariant(this ReadOnlySpan str, string prefix) => - str.StartsWith(prefix.AsSpan(), StringComparison.OrdinalIgnoreCase); - public static ReadOnlySpan RemovePrefix(this ReadOnlySpan str, - string prefix, StringComparison compare = StringComparison.OrdinalIgnoreCase) => - str.StartsWith(prefix.AsSpan(), compare) ? str.Slice(prefix.Length) : str; - } -} diff --git a/CSharpMath/Extensions/RectangleF.cs b/CSharpMath/Extensions/RectangleF.cs deleted file mode 100644 index 0b3a3081..00000000 --- a/CSharpMath/Extensions/RectangleF.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Drawing; - -namespace CSharpMath { - public static partial class Extensions { - public static RectangleF Plus(this RectangleF rect, PointF vector) => - new RectangleF(rect.Location.Plus(vector), rect.Size); - public static RectangleF Union(this RectangleF rect1, RectangleF rect2) { - var x = Math.Min(rect1.X, rect2.X); - var y = Math.Min(rect1.Y, rect2.Y); - var maxX = Math.Max(rect1.Right, rect2.Right); - var maxY = Math.Max(rect1.Bottom, rect2.Bottom); - return new RectangleF(x, y, maxX - x, maxY - y); - } - /// Because we are NOT inverting our y axis, - /// the properties "Top" and "Bottom" have misleading names. - public static float YMin(this RectangleF rect) => rect.Top; - /// Because we are NOT inverting our y axis, - /// the properties "Top" and "Bottom" have misleading names. - public static float YMax(this RectangleF rect) => rect.Bottom; - public static void GetAscentDescentWidth(this RectangleF rect, - out float ascent, out float descent, out float width) { - ascent = rect.Bottom; - width = rect.Width; - descent = -rect.Y; - } - } -} diff --git a/CSharpMath/Extensions/Stack.cs b/CSharpMath/Extensions/Stack.cs deleted file mode 100644 index 544f3428..00000000 --- a/CSharpMath/Extensions/Stack.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CSharpMath { - partial class Extensions { - [return: System.Diagnostics.CodeAnalysis.MaybeNull] - public static T PeekOrDefault(this Stack stack) => stack.Count == 0 ? default : stack.Peek(); - } -} diff --git a/CSharpMath/Extensions/StringBuilder.cs b/CSharpMath/Extensions/StringBuilder.cs deleted file mode 100644 index 8d107e3f..00000000 --- a/CSharpMath/Extensions/StringBuilder.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Text; - -namespace CSharpMath { - public static partial class Extensions { - public static StringBuilder Append(this StringBuilder sb, ReadOnlySpan value) { - sb.EnsureCapacity(sb.Length + value.Length); - for (int i = 0; i < value.Length; i++) sb.Append(value[i]); - return sb; - } - private enum NullHandling { - ///the string "null", without the quotes - LiteralNull, - /// Change the null to the empty string, then wrap. - EmptyContent, - /// Return the empty string. Do not wrap. - EmptyString, - } - private static StringBuilder AppendIn(this StringBuilder b, char l, string? s, char r, NullHandling h) => - h switch - { - NullHandling.EmptyContent => b.Append(l).Append(s).Append(r), - NullHandling.EmptyString => s != null ? b.Append(l).Append(s).Append(r) : b, - NullHandling.LiteralNull => b.Append(l).Append(s ?? "null").Append(r), - _ => - throw new System.ComponentModel.InvalidEnumArgumentException(nameof(h), (int)h, typeof(NullHandling)) - }; - internal static StringBuilder AppendInBracesOrLiteralNull(this StringBuilder builder, string? appendMe) => - builder.AppendIn('{', appendMe, '}', NullHandling.LiteralNull); - internal static StringBuilder AppendInBracesOrEmptyBraces(this StringBuilder builder, string? appendMe) => - builder.AppendIn('{', appendMe, '}', NullHandling.EmptyContent); - internal static StringBuilder AppendInBracketsOrNothing(this StringBuilder builder, string? appendMe) => - builder.AppendIn('[', appendMe, ']', NullHandling.EmptyString); - internal static StringBuilder AppendDebugStringOfScripts(this StringBuilder builder, Atom.MathAtom target) { - if (target.Subscript.IsNonEmpty()) { - builder.Append('_').AppendInBracesOrLiteralNull(target.Subscript.DebugString); - } - if (target.Superscript.IsNonEmpty()) { - builder.Append('^').AppendInBracesOrLiteralNull(target.Superscript.DebugString); - } - return builder; - } - } -} \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 165216a0..0d70ead4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -38,19 +38,27 @@ snupkg + false $(Configuration.Equals('Release')) + And !$(MSBuildProjectName.Contains('Playground')) + And $(Configuration.Equals('Release'))">true + + + + $(GeneratePackageOnBuild) + $(MSBuildThisFileDirectory).nupkgs en $(MSBuildProjectName) $(MSBuildProjectName) - + + @@ -61,7 +69,7 @@ - + diff --git a/ReadMe.md b/ReadMe.md index aa77bb1b..92a37fe9 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -291,7 +291,7 @@ For those who wish to be even more updated than prereleases, you can opt in to t ``` -7. Replace `PACKAGE` in the above file by the package name in the webpage, e.g. `CSharpMath.SkiaSharp`, and `VERSION` by the version in the webpage, e.g. `0.4.2-ci-9db8a6dec29202804764fab9d6f7f19e43c3c083`. The 40-digit hexadecimal number at the end of the version is the Git commit that was the package was built on. CI versions for a version are newer than that version, aka chronologically `0.4.1-ci-xxx` → `0.4.2` → `0.4.2-ci-xxx`. +7. Replace `PACKAGE` in the above file by the package name in the webpage, e.g. `CSharpMath.SkiaSharp`, and `VERSION` by the version in the webpage, e.g. `0.4.2-ci-9db8a6dec29202804764fab9d6f7f19e43c3c083`. The 40-digit hexadecimal number at the end of the version is the Git commit that was the package was built on. CI versions for a version are older than that version, aka chronologically `0.4.2-ci-xxx` → `0.4.2` → `0.4.3-ci-xxx` → `0.5.0` → `0.5.1-ci-xxx`. ### SourceLink for CI packages Unfortunately, non-NuGet.org feeds do not support `.snupkg`s, so you will have to download all the packages yourself.