From 9cdb71c8a010b2d7409c2d8232ff5f0dad8bb57d Mon Sep 17 00:00:00 2001 From: Matthew Whelan Date: Thu, 10 Oct 2024 12:41:24 -0400 Subject: [PATCH] Support "Old" string concat behavior Prior to v5.1, NCalc would treat + as string concatenation if the first argument was a string. Later versions changed this logic and while they did add options to control it, no combination of those options restored the old behavior. The Tech Software fork exists to allow callers to opt-in to the old behavior. --- .github/workflows/publish-nuget.yml | 17 +++++----- Directory.Build.props | 2 +- src/NCalc.Core/ExpressionOptions.cs | 11 ++++++- src/NCalc.Core/Helpers/EvaluationHelper.cs | 8 +++++ src/NCalc.Core/NCalc.Core.csproj | 2 +- src/NCalc.Sync/NCalc.Sync.csproj | 2 +- .../UseOldStringConcatBehaviorTests.cs | 31 +++++++++++++++++++ 7 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 test/NCalc.Tests/UseOldStringConcatBehaviorTests.cs diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index 4f85d55..305965b 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -21,6 +21,8 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0.x' + - name: Configure NuGet + run: dotnet nuget add source https://nuget.pkg.github.com/tech-software/index.json --name github --username tech-software --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text - name: Restore dependencies run: dotnet restore - name: Build @@ -29,15 +31,12 @@ jobs: run: dotnet test -c Release --no-build --verbosity normal - name: Pack unsigned run: dotnet pack -c Release --no-build -p:PublicRelease=true - - name: Pack signed - run: dotnet pack -c SignedRelease -p:PublicRelease=true - name: Publish packages run: | - dotnet nuget push src/NCalc.Core/bin/Release/NCalc.Core*.nupkg --api-key "${{ secrets.NUGET_NCALC_PLUGINS_API_TOKEN }}" --source https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push src/NCalc.Sync/bin/Release/NCalcSync*.nupkg --api-key "${{ secrets.NUGET_NCALC_SYNC_API_TOKEN }}" --source https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push src/NCalc.Sync/bin/SignedRelease/NCalcSync*.nupkg --api-key "${{ secrets.NUGET_NCALC_SYNC_API_TOKEN }}" --source https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push src/NCalc.Async/bin/Release/NCalcAsync*.nupkg --api-key "${{ secrets.NUGET_NCALC_PLUGINS_API_TOKEN }}" --source https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push src/NCalc.DependencyInjection/bin/Release/NCalc.DependencyInjection*.nupkg --api-key "${{ secrets.NUGET_NCALC_PLUGINS_API_TOKEN }}" --source https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push src/Plugins/NCalc.MemoryCache/bin/Release/NCalc.MemoryCache*.nupkg --api-key "${{ secrets.NUGET_NCALC_PLUGINS_API_TOKEN }}" --source https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push src/Plugins/NCalc.Antlr/bin/Release/NCalc.Antlr*.nupkg --api-key "${{ secrets.NUGET_NCALC_PLUGINS_API_TOKEN }}" --source https://api.nuget.org/v3/index.json --skip-duplicate + dotnet nuget push src/NCalc.Core/bin/Release/TSNCalc.Core*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate + dotnet nuget push src/NCalc.Sync/bin/Release/TSNCalcSync*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate + # dotnet nuget push src/NCalc.Async/bin/Release/NCalcAsync*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate + # dotnet nuget push src/NCalc.DependencyInjection/bin/Release/NCalc.DependencyInjection*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate + # dotnet nuget push src/Plugins/NCalc.MemoryCache/bin/Release/NCalc.MemoryCache*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate + # dotnet nuget push src/Plugins/NCalc.Antlr/bin/Release/NCalc.Antlr*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate diff --git a/Directory.Build.props b/Directory.Build.props index d2a4635..d571e3b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ NCalc.png Sebastien Ros and contributors Sebastien Ros and contributors - https://github.com/ncalc + https://github.com/tech-software MIT true true diff --git a/src/NCalc.Core/ExpressionOptions.cs b/src/NCalc.Core/ExpressionOptions.cs index 5d32705..7c3d291 100644 --- a/src/NCalc.Core/ExpressionOptions.cs +++ b/src/NCalc.Core/ExpressionOptions.cs @@ -79,5 +79,14 @@ public enum ExpressionOptions /// /// Return the value instead of throwing an exception when the expression is null or empty. /// - AllowNullOrEmptyExpressions = 1 << 14 + AllowNullOrEmptyExpressions = 1 << 14, + + /// + /// Use the pre-NCalc 5.1 behavior for string concatenation. + /// + /// + ///

If the left value is a string, the result of + will be string concatenation. + /// Otherwise both parameters will be converted to numeric if needed and possible.

+ ///
+ UseOldStringConcatBehavior = 1 << 31, } \ No newline at end of file diff --git a/src/NCalc.Core/Helpers/EvaluationHelper.cs b/src/NCalc.Core/Helpers/EvaluationHelper.cs index 01bfc19..052718b 100644 --- a/src/NCalc.Core/Helpers/EvaluationHelper.cs +++ b/src/NCalc.Core/Helpers/EvaluationHelper.cs @@ -19,7 +19,15 @@ public static class EvaluationHelper public static object? Plus(object? leftValue, object? rightValue, ExpressionContextBase context) { if (context.Options.HasFlag(ExpressionOptions.StringConcat)) + { + return string.Concat(leftValue, rightValue); + } + + if (context.Options.HasFlag(ExpressionOptions.UseOldStringConcatBehavior) && + leftValue is string) + { return string.Concat(leftValue, rightValue); + } if (context.Options.HasFlag(ExpressionOptions.NoStringTypeCoercion) && (leftValue is string || rightValue is string)) diff --git a/src/NCalc.Core/NCalc.Core.csproj b/src/NCalc.Core/NCalc.Core.csproj index 4c86269..3f4fb93 100644 --- a/src/NCalc.Core/NCalc.Core.csproj +++ b/src/NCalc.Core/NCalc.Core.csproj @@ -4,7 +4,7 @@ net462;netstandard2.0;net6.0;net8.0 12 enable - NCalc.Core + TSNCalc.Core NCalc.Core Assembly with the core logic of NCalc. true diff --git a/src/NCalc.Sync/NCalc.Sync.csproj b/src/NCalc.Sync/NCalc.Sync.csproj index 99a872b..9ce2ce9 100644 --- a/src/NCalc.Sync/NCalc.Sync.csproj +++ b/src/NCalc.Sync/NCalc.Sync.csproj @@ -30,7 +30,7 @@ NCalc.Sync - NCalcSync + TSNCalcSync NCalc is a fast and lightweight expression evaluator library for .NET, designed for flexibility and high performance. It supports a wide range of mathematical and logical operations. NCalc can parse any expression and evaluate the result, including static or dynamic parameters and custom functions. This package contains the unsigned assembly 'NCalc'. For the stronly signed assembly version of 'NCalc', see the NCalcSync.signed package. diff --git a/test/NCalc.Tests/UseOldStringConcatBehaviorTests.cs b/test/NCalc.Tests/UseOldStringConcatBehaviorTests.cs new file mode 100644 index 0000000..d2ea448 --- /dev/null +++ b/test/NCalc.Tests/UseOldStringConcatBehaviorTests.cs @@ -0,0 +1,31 @@ +namespace NCalc.Tests; + +[Trait("Category", "UseOldStringConcatBehavior")] +public class UseOldStringConcatBehaviorTests +{ + [Theory] + [InlineData("'to' + 'to'", "toto")] + [InlineData("'one' + 2", "one2")] + [InlineData("'one' + 2.1", "one2.1")] + [InlineData("'1' + '2'", "12")] + [InlineData("'1.1' + '2'", "1.12")] + [InlineData("'1' + '2.2'", "12.2")] + [InlineData("1 + 2", 3)] + [InlineData("1.5 + 2.5", 4.0)] + [InlineData("1 + '2'", 3.0)] + [InlineData("1.5 + '2.5'", 4.0)] + [InlineData("'10' + 'mA - ' + (10 + 20) + 'mA'", "10mA - 30mA")] + public void ShouldUseStringConcatenationIfFirstValueIsAString(string expression, object expected) + { + Assert.Equal(expected, new Expression(expression, ExpressionOptions.UseOldStringConcatBehavior).Evaluate()); + } + + [Theory] + [InlineData("2 + 'one'")] + [InlineData("2.1 + 'one'")] + public void ShouldThrowIfFirstValueIsNumericAndSecondIsNot(string expression) + { + Assert.Throws(() + => new Expression(expression, ExpressionOptions.UseOldStringConcatBehavior).Evaluate()); + } +} \ No newline at end of file