diff --git a/README.md b/README.md index 3bb1874..5acac2a 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,8 @@ calling a function will be undefined if: | Function | Description | | :--- | :--- | -| `Coalesce(p0, p1, ..pN)` | Returns the first defined, non-null argument. | +| `Coalesce(p0, p1, [..pN])` | Returns the first defined, non-null argument. | +| `Concat(s0, s1, [..sN])` | Concatenate two or more strings. | | `Contains(s, t)` | Tests whether the string `s` contains the substring `t`. | | `ElementAt(x, i)` | Retrieves a property of `x` by name `i`, or array element of `x` by numeric index `i`. | | `EndsWith(s, t)` | Tests whether the string `s` ends with substring `t`. | diff --git a/src/Serilog.Expressions/Expressions/Compilation/Variadics/VariadicCallRewriter.cs b/src/Serilog.Expressions/Expressions/Compilation/Variadics/VariadicCallRewriter.cs index 5de2c7c..560a8df 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Variadics/VariadicCallRewriter.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Variadics/VariadicCallRewriter.cs @@ -18,7 +18,7 @@ namespace Serilog.Expressions.Compilation.Variadics { - // Now a bit of a misnomer - handles variadic `coalesce()`, as well as optional arguments for other functions. + // Handles variadic `coalesce()` and `concat()`, as well as optional arguments for other functions. class VariadicCallRewriter : IdentityTransformer { static readonly VariadicCallRewriter Instance = new VariadicCallRewriter(); @@ -39,7 +39,8 @@ protected override Expression Transform(CallExpression call) return new CallExpression(call.IgnoreCase, call.OperatorName, operands); } - if (Operators.SameOperator(call.OperatorName, Operators.OpCoalesce)) + if (Operators.SameOperator(call.OperatorName, Operators.OpCoalesce) || + Operators.SameOperator(call.OperatorName, Operators.OpConcat)) { if (call.Operands.Length == 0) return CallUndefined(); @@ -64,7 +65,7 @@ protected override Expression Transform(CallExpression call) static CallExpression CallUndefined() { - return new CallExpression(false, Operators.OpUndefined); + return new(false, Operators.OpUndefined); } } } diff --git a/src/Serilog.Expressions/Expressions/Operators.cs b/src/Serilog.Expressions/Expressions/Operators.cs index 6a4b72d..c9e0c93 100644 --- a/src/Serilog.Expressions/Expressions/Operators.cs +++ b/src/Serilog.Expressions/Expressions/Operators.cs @@ -29,6 +29,7 @@ static class Operators // RuntimeOp* means runtime only. public const string OpCoalesce = "Coalesce"; + public const string OpConcat = "Concat"; public const string OpContains = "Contains"; public const string OpElementAt = "ElementAt"; public const string OpEndsWith = "EndsWith"; diff --git a/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs b/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs index 18e10df..2f1d717 100644 --- a/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs +++ b/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs @@ -461,6 +461,16 @@ public static LogEventPropertyValue _Internal_IsNotNull(LogEventPropertyValue? v return new ScalarValue(str.Substring((int)si, (int)len)); } + public static LogEventPropertyValue? Concat(LogEventPropertyValue? first, LogEventPropertyValue? second) + { + if (Coerce.String(first, out var f) && Coerce.String(second, out var s)) + { + return new ScalarValue(f + s); + } + + return null; + } + // ReSharper disable once ReturnTypeCanBeNotNullable public static LogEventPropertyValue? IndexOfMatch(StringComparison sc, LogEventPropertyValue? corpus, LogEventPropertyValue? regex) { diff --git a/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv b/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv index 010f299..9e0d832 100644 --- a/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv +++ b/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv @@ -208,6 +208,10 @@ ismatch('foo', '^o') ⇶ false indexofmatch('foo', 'x') ⇶ -1 substring('abcd', 1, 2) ⇶ 'bc' substring('abcd', 1) ⇶ 'bcd' +concat('a', 'b', 'c') ⇶ 'abc' +concat('a', 42, 'c') ⇶ undefined() +concat('a', undefined()) ⇶ undefined() +concat(undefined(), 'b') ⇶ undefined() // Conditional if true then 1 else 2 ⇶ 1