Skip to content

Commit 9cdb71c

Browse files
committed
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.
1 parent 67d11ea commit 9cdb71c

File tree

7 files changed

+60
-13
lines changed

7 files changed

+60
-13
lines changed

.github/workflows/publish-nuget.yml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ jobs:
2121
uses: actions/setup-dotnet@v4
2222
with:
2323
dotnet-version: '8.0.x'
24+
- name: Configure NuGet
25+
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
2426
- name: Restore dependencies
2527
run: dotnet restore
2628
- name: Build
@@ -29,15 +31,12 @@ jobs:
2931
run: dotnet test -c Release --no-build --verbosity normal
3032
- name: Pack unsigned
3133
run: dotnet pack -c Release --no-build -p:PublicRelease=true
32-
- name: Pack signed
33-
run: dotnet pack -c SignedRelease -p:PublicRelease=true
3434
- name: Publish packages
3535
run: |
36-
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
37-
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
38-
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
39-
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
40-
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
41-
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
42-
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
36+
dotnet nuget push src/NCalc.Core/bin/Release/TSNCalc.Core*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate
37+
dotnet nuget push src/NCalc.Sync/bin/Release/TSNCalcSync*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate
38+
# dotnet nuget push src/NCalc.Async/bin/Release/NCalcAsync*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate
39+
# dotnet nuget push src/NCalc.DependencyInjection/bin/Release/NCalc.DependencyInjection*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate
40+
# dotnet nuget push src/Plugins/NCalc.MemoryCache/bin/Release/NCalc.MemoryCache*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate
41+
# dotnet nuget push src/Plugins/NCalc.Antlr/bin/Release/NCalc.Antlr*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source github --skip-duplicate
4342

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PackageIcon>NCalc.png</PackageIcon>
44
<Authors>Sebastien Ros and contributors</Authors>
55
<Copyright>Sebastien Ros and contributors</Copyright>
6-
<Company>https://github.com/ncalc</Company>
6+
<Company>https://github.com/tech-software</Company>
77
<PackageLicenseExpression>MIT</PackageLicenseExpression>
88
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
99
<EnableNETAnalyzers>true</EnableNETAnalyzers>

src/NCalc.Core/ExpressionOptions.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,14 @@ public enum ExpressionOptions
7979
/// <summary>
8080
/// Return the value instead of throwing an exception when the expression is null or empty.
8181
/// </summary>
82-
AllowNullOrEmptyExpressions = 1 << 14
82+
AllowNullOrEmptyExpressions = 1 << 14,
83+
84+
/// <summary>
85+
/// Use the pre-NCalc 5.1 behavior for string concatenation.
86+
/// </summary>
87+
/// <remarks>
88+
/// <p>If the left value is a string, the result of + will be string concatenation.
89+
/// Otherwise both parameters will be converted to numeric if needed and possible.</p>
90+
/// </remarks>
91+
UseOldStringConcatBehavior = 1 << 31,
8392
}

src/NCalc.Core/Helpers/EvaluationHelper.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,15 @@ public static class EvaluationHelper
1919
public static object? Plus(object? leftValue, object? rightValue, ExpressionContextBase context)
2020
{
2121
if (context.Options.HasFlag(ExpressionOptions.StringConcat))
22+
{
23+
return string.Concat(leftValue, rightValue);
24+
}
25+
26+
if (context.Options.HasFlag(ExpressionOptions.UseOldStringConcatBehavior) &&
27+
leftValue is string)
28+
{
2229
return string.Concat(leftValue, rightValue);
30+
}
2331

2432
if (context.Options.HasFlag(ExpressionOptions.NoStringTypeCoercion) &&
2533
(leftValue is string || rightValue is string))

src/NCalc.Core/NCalc.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<TargetFrameworks>net462;netstandard2.0;net6.0;net8.0</TargetFrameworks>
55
<LangVersion>12</LangVersion>
66
<Nullable>enable</Nullable>
7-
<PackageId>NCalc.Core</PackageId>
7+
<PackageId>TSNCalc.Core</PackageId>
88
<AssemblyName>NCalc.Core</AssemblyName>
99
<Description>Assembly with the core logic of NCalc.</Description>
1010
<SignAssembly>true</SignAssembly>

src/NCalc.Sync/NCalc.Sync.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
<!-- Unsigned Assembly Configuration -->
3131
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' Or '$(Configuration)' == 'Release' ">
3232
<AssemblyName>NCalc.Sync</AssemblyName>
33-
<PackageId>NCalcSync</PackageId>
33+
<PackageId>TSNCalcSync</PackageId>
3434
<Description>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.</Description>
3535
</PropertyGroup>
3636

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace NCalc.Tests;
2+
3+
[Trait("Category", "UseOldStringConcatBehavior")]
4+
public class UseOldStringConcatBehaviorTests
5+
{
6+
[Theory]
7+
[InlineData("'to' + 'to'", "toto")]
8+
[InlineData("'one' + 2", "one2")]
9+
[InlineData("'one' + 2.1", "one2.1")]
10+
[InlineData("'1' + '2'", "12")]
11+
[InlineData("'1.1' + '2'", "1.12")]
12+
[InlineData("'1' + '2.2'", "12.2")]
13+
[InlineData("1 + 2", 3)]
14+
[InlineData("1.5 + 2.5", 4.0)]
15+
[InlineData("1 + '2'", 3.0)]
16+
[InlineData("1.5 + '2.5'", 4.0)]
17+
[InlineData("'10' + 'mA - ' + (10 + 20) + 'mA'", "10mA - 30mA")]
18+
public void ShouldUseStringConcatenationIfFirstValueIsAString(string expression, object expected)
19+
{
20+
Assert.Equal(expected, new Expression(expression, ExpressionOptions.UseOldStringConcatBehavior).Evaluate());
21+
}
22+
23+
[Theory]
24+
[InlineData("2 + 'one'")]
25+
[InlineData("2.1 + 'one'")]
26+
public void ShouldThrowIfFirstValueIsNumericAndSecondIsNot(string expression)
27+
{
28+
Assert.Throws<FormatException>(()
29+
=> new Expression(expression, ExpressionOptions.UseOldStringConcatBehavior).Evaluate());
30+
}
31+
}

0 commit comments

Comments
 (0)