Skip to content

Commit a16b094

Browse files
committed
More README; don't expose named variants of built-in operators
1 parent 8921773 commit a16b094

File tree

15 files changed

+187
-112
lines changed

15 files changed

+187
-112
lines changed

README.md

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,58 @@ The following properties are available in expressions:
107107
* `@x` - the exception associated with the event, if any, as an `Exception`
108108
* `@p` - a dictionary containing all first-class properties; this supports properties with non-identifier names, for example `@p['snake-case-name']`
109109

110-
### Data types
110+
### Literals
111+
112+
| Data type | Description | Examples |
113+
| :--- | :--- | :--- |
114+
| Null | Corresponds to .NET's `null` value | `null` |
115+
| Number | A number in decimal or hexadecimal notation, represented by .NET `decimal` | `0`, `100`, `-12.34`, `0xC0FFEE` |
116+
| String | A single-quoted Unicode string literal; to escape `'`, double it | `'pie'`, `'isn''t'`, `'😋'` |
117+
| Boolean | A Boolean value | `true`, `false` |
118+
| Array | An array of values, in square brackets | `[1, 'two', null]` |
119+
| Object | A mapping of string keys to values; keys that are valid identifiers do not need to be quoted | `{a: 1, 'b c': 2}` |
120+
121+
### Operators and conditionals
122+
123+
A typical set of operators is supported:
124+
125+
* Equality `=` and inequality `<>`, including for arrays and objects
126+
* Boolean `and`, `or`, `not`
127+
* Arithmetic `+`, `-`, `*`, `/`, `^`, `%`
128+
* Numeric comparison `<`, `<=`, `>`, `>=`
129+
* Existence `is null` and `is not null`
130+
* SQL-style `like` and `not like`, with `%` and `_` wildcards (double wildcards to escape them)
131+
* Array membership with `in` and `not in`
132+
* Indexers `a[b]` and accessors `a.b`
133+
* Wildcard indexing - `a[?]` any, and `a[*]` all
134+
* Conditional `if a then b else c` (all branches required)
111135

112136
### Functions
113137

114-
### String manipulation
138+
Functions are called using typical `Identifier(args)` syntax.
139+
140+
Except for the `IsDefined()` function, the result of
141+
calling a function will be undefined if:
142+
143+
* any argument is undefined, or
144+
* any argument is of an incompatible type.
145+
146+
| Function | Description |
147+
| :--- | :--- |
148+
| `Coalesce(p0, p1, ..pN)` | Returns the first defined, non-null argument. |
149+
| `Contains(s, t)` | Tests whether the string `s` contains the substring `t`. |
150+
| `EndsWith(s, t)` | Tests whether the string `s` ends with substring `t`. |
151+
| `IndexOf(s, t)` | Returns the first index of substring `t` in string `s`, or -1 if the substring does not appear. |
152+
| `IndexOfMatch(s, p)` | Returns the index of the first match of regular expression `p` in string `s`, or -1 if the regular expression does not match. |
153+
| `IsMatch(s, p)` | Tests whether the regular expression `p` matches within the string `s`. |
154+
| `IsDefined(x)` | Returns `true` if the expression `x` has a value, including `null`, or `false` if `x` is undefined. |
155+
| `LastIndexOf(s, t)` | Returns the last index of substring `t` in string `s`, or -1 if the substring does not appear. |
156+
| `Length(x)` | Returns the length of a string or array. |
157+
| `Round(n, m)` | Round the number `n` to `m` decimal places. |
158+
| `StartsWith(s, t)` | Tests whether the string `s` starts with substring `t`. |
159+
| `Substring(s, start, [length])` | Return the substring of string `s` from `start` to the end of the string, or of `length` characters, if this argument is supplied. |
160+
| `TagOf(o)` | Returns the `TypeTag` field of a captured object (i.e. where `TypeOf(x)` is `'object'`). |
161+
| `TypeOf(x)` | Returns a string describing the type of expression `x`: a .NET type name if `x` is scalar and non-null, or, `'array'`, `'object'`, `'dictionary'`, `'null'`, or `'undefined'`. |
115162

116163
## Working with the raw API
117164

example/Sample/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public static void Main()
2626
log.ForContext<Program>().Information("Cart contains {@Items}", new[] { "Tea", "Coffee" });
2727
log.Warning("Cart contains {@Items}", new[] { "Tea", "Coffee" });
2828
log.Information("Cart contains {@Items}", new[] { "Apricots" });
29-
log.Information("Cart contains {@Items}", new[] { "Peanuts", "Chocolate" });
29+
log.Information("Cart for {Name} contains {@Items}", Environment.UserName, new[] { "Peanuts", "Chocolate" });
3030
}
3131
}
3232
}

src/Serilog.Expressions/Expressions/Ast/CallExpression.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,7 @@ public CallExpression(string operatorName, params Expression[] operands)
1717

1818
public override string ToString()
1919
{
20-
if (OperatorName == Operators.OpElementAt && Operands.Length == 2)
21-
{
22-
return Operands[0] + "[" + Operands[1] + "]";
23-
}
24-
2520
return OperatorName + "(" + string.Join(", ", Operands.Select(o => o.ToString())) + ")";
2621
}
2722
}
28-
}
23+
}

src/Serilog.Expressions/Expressions/Compilation/Linq/Intrinsics.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
using System.Collections.Generic;
2+
using System.IO;
23
using System.Linq;
34
using System.Text.RegularExpressions;
45
using Serilog.Events;
56
using Serilog.Expressions.Runtime;
7+
using Serilog.Formatting.Display;
68

79
namespace Serilog.Expressions.Compilation.Linq
810
{
911
static class Intrinsics
1012
{
1113
static readonly LogEventPropertyValue NegativeOne = new ScalarValue(-1);
12-
14+
static readonly MessageTemplateTextFormatter MessageFormatter = new MessageTemplateTextFormatter("{Message:lj}");
15+
1316
public static LogEventPropertyValue? ConstructSequenceValue(LogEventPropertyValue?[] elements)
1417
{
1518
// Avoid upsetting Serilog's (currently) fragile `SequenceValue.Render()`.
@@ -77,5 +80,13 @@ public static bool CoerceToScalarBoolean(LogEventPropertyValue value)
7780

7881
return null;
7982
}
83+
84+
public static string RenderMessage(LogEvent logEvent)
85+
{
86+
// Use the same `:lj`-style formatting default as Serilog.Sinks.Console.
87+
var sw = new StringWriter();
88+
MessageFormatter.Format(logEvent, sw);
89+
return sw.ToString();
90+
}
8091
}
8192
}

src/Serilog.Expressions/Expressions/Compilation/Linq/LinqExpressionCompiler.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ protected override ExpressionBody Transform(CallExpression lx)
6363
var operands = lx.Operands.Select(Transform).ToArray();
6464

6565
// `and` and `or` short-circuit to save execution time; unlike the earlier Serilog.Filters.Expressions, nothing else does.
66-
if (Operators.SameOperator(lx.OperatorName, Operators.OpAnd))
66+
if (Operators.SameOperator(lx.OperatorName, Operators.RuntimeOpAnd))
6767
return CompileLogical(LX.AndAlso, operands[0], operands[1]);
6868

69-
if (Operators.SameOperator(lx.OperatorName, Operators.OpOr))
69+
if (Operators.SameOperator(lx.OperatorName, Operators.RuntimeOpOr))
7070
return CompileLogical(LX.OrElse, operands[0], operands[1]);
7171

7272
return LX.Call(m, operands);
@@ -102,7 +102,7 @@ protected override ExpressionBody Transform(AmbientPropertyExpression px)
102102
return Splice(context => new ScalarValue(context.Level));
103103

104104
if (px.PropertyName == BuiltInProperty.Message)
105-
return Splice(context => new ScalarValue(context.RenderMessage(null)));
105+
return Splice(context => new ScalarValue(Intrinsics.RenderMessage(context)));
106106

107107
if (px.PropertyName == BuiltInProperty.Exception)
108108
return Splice(context => context.Exception == null ? null : new ScalarValue(context.Exception));
@@ -189,7 +189,7 @@ protected override ExpressionBody Transform(ObjectExpression ox)
189189

190190
protected override ExpressionBody Transform(IndexerExpression ix)
191191
{
192-
return Transform(new CallExpression(Operators.OpElementAt, ix.Receiver, ix.Index));
192+
return Transform(new CallExpression(Operators.RuntimeOpElementAt, ix.Receiver, ix.Index));
193193
}
194194

195195
protected override ExpressionBody Transform(IndexOfMatchExpression mx)

src/Serilog.Expressions/Expressions/Compilation/Text/LikeSyntaxTransformer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ cx.Constant is ScalarValue scalar &&
4141
var regex = LikeToRegex(s);
4242
var compiled = new Regex(regex, RegexOptions.Compiled | RegexOptions.ExplicitCapture, TimeSpan.FromMilliseconds(100));
4343
var indexof = new IndexOfMatchExpression(Transform(corpus), compiled);
44-
return new CallExpression(Operators.OpNotEqual, indexof, new ConstantExpression(new ScalarValue(-1)));
44+
return new CallExpression(Operators.RuntimeOpNotEqual, indexof, new ConstantExpression(new ScalarValue(-1)));
4545
}
4646

4747
SelfLog.WriteLine($"Serilog.Expressions: `like` requires a constant string argument; found ${like}.");

src/Serilog.Expressions/Expressions/Compilation/Text/TextMatchingTransformer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ protected override Expression Transform(CallExpression lx)
2626

2727
if (Operators.SameOperator(lx.OperatorName, Operators.OpIsMatch))
2828
return new CallExpression(
29-
Operators.OpNotEqual,
29+
Operators.RuntimeOpNotEqual,
3030
TryCompileIndexOfMatch(lx.Operands[0], lx.Operands[1]),
3131
new ConstantExpression(new ScalarValue(-1)));
3232

src/Serilog.Expressions/Expressions/Operators.cs

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Collections.Generic;
33
using Serilog.Expressions.Ast;
44

5+
// ReSharper disable MemberCanBePrivate.Global
6+
57
namespace Serilog.Expressions
68
{
79
static class Operators
@@ -12,66 +14,69 @@ static class Operators
1214
// Op* means usable in expressions _and_ runtime executable.
1315
// RuntimeOp* means runtime only.
1416

15-
public const string OpAdd = "Add";
16-
public const string OpSubtract = "Subtract";
17-
public const string OpMultiply = "Multiply";
18-
public const string OpDivide = "Divide";
19-
public const string OpModulo = "Modulo";
20-
public const string OpPower = "Power";
21-
public const string OpRound = "Round";
22-
public const string OpAnd = "And";
23-
public const string OpOr = "Or";
24-
public const string OpLessThanOrEqual = "LessThanOrEqual";
25-
public const string OpLessThan = "LessThan";
26-
public const string OpGreaterThan = "GreaterThan";
27-
public const string OpGreaterThanOrEqual = "GreaterThanOrEqual";
28-
public const string OpEqual = "Equal";
29-
public const string OpNotEqual = "NotEqual";
30-
public const string OpNegate = "Negate";
31-
public const string OpNot = "Not";
17+
public const string OpCoalesce = "Coalesce";
3218
public const string OpContains = "Contains";
19+
public const string OpEndsWith = "EndsWith";
3320
public const string OpIndexOf = "IndexOf";
34-
public const string OpLastIndexOf = "IndexOf";
21+
public const string OpIndexOfMatch = "IndexOfMatch";
22+
public const string OpIsMatch = "IsMatch";
23+
public const string OpIsDefined = "IsDefined";
24+
public const string OpLastIndexOf = "LastIndexOf";
3525
public const string OpLength = "Length";
26+
public const string OpRound = "Round";
3627
public const string OpStartsWith = "StartsWith";
37-
public const string OpEndsWith = "EndsWith";
38-
public const string OpHas = "Has";
39-
public const string OpDateTime = "DateTime";
40-
public const string OpTimeSpan = "TimeSpan";
41-
public const string OpTimeOfDay = "TimeOfDay";
42-
public const string OpElementAt = "ElementAt";
28+
public const string OpSubstring = "Substring";
29+
public const string OpTagOf = "TagOf";
30+
public const string OpTypeOf = "TypeOf";
31+
32+
public const string IntermediateOpLike = "_Internal_Like";
33+
public const string IntermediateOpNotLike = "_Internal_NotLike";
34+
35+
public const string RuntimeOpAdd = "_Internal_Add";
36+
public const string RuntimeOpSubtract = "_Internal_Subtract";
37+
public const string RuntimeOpMultiply = "_Internal_Multiply";
38+
public const string RuntimeOpDivide = "_Internal_Divide";
39+
public const string RuntimeOpModulo = "_Internal_Modulo";
40+
public const string RuntimeOpPower = "_Internal_Power";
41+
public const string RuntimeOpAnd = "_Internal_And";
42+
public const string RuntimeOpOr = "_Internal_Or";
43+
public const string RuntimeOpLessThanOrEqual = "_Internal_LessThanOrEqual";
44+
public const string RuntimeOpLessThan = "_Internal_LessThan";
45+
public const string RuntimeOpGreaterThan = "_Internal_GreaterThan";
46+
public const string RuntimeOpGreaterThanOrEqual = "_Internal_GreaterThanOrEqual";
47+
public const string RuntimeOpEqual = "_Internal_Equal";
48+
public const string RuntimeOpNotEqual = "_Internal_NotEqual";
49+
public const string RuntimeOpNegate = "_Internal_Negate";
50+
public const string RuntimeOpNot = "_Internal_Not";
51+
public const string RuntimeOpElementAt = "_Internal_ElementAt";
4352
public const string RuntimeOpAny = "_Internal_Any";
4453
public const string RuntimeOpAll = "_Internal_All";
45-
public const string OpTypeOf = "TypeOf";
46-
public const string OpTagOf = "TagOf";
47-
public const string OpTotalMilliseconds = "TotalMilliseconds";
4854
public const string RuntimeOpIsNull = "_Internal_IsNull";
4955
public const string RuntimeOpIsNotNull = "_Internal_IsNotNull";
50-
public const string OpCoalesce = "Coalesce";
51-
public const string IntermediateOpLike = "_Internal_Like";
52-
public const string IntermediateOpNotLike = "_Internal_NotLike";
5356
public const string RuntimeOpIn = "_Internal_In";
5457
public const string RuntimeOpNotIn = "_Internal_NotIn";
5558
public const string RuntimeOpStrictNot = "_Internal_StrictNot";
56-
public const string OpSubstring = "Substring";
57-
public const string OpIndexOfMatch = "IndexOfMatch";
58-
public const string OpIsMatch = "IsMatch";
5959
public const string RuntimeOpIfThenElse = "_Internal_IfThenElse";
6060

6161
public static readonly HashSet<string> WildcardComparators = new HashSet<string>(OperatorComparer)
6262
{
6363
OpContains,
6464
OpStartsWith,
6565
OpEndsWith,
66-
OpNotEqual,
67-
OpEqual,
68-
OpLessThan,
69-
OpLessThanOrEqual,
70-
OpGreaterThan,
71-
OpGreaterThanOrEqual,
66+
RuntimeOpNotEqual,
67+
RuntimeOpEqual,
68+
RuntimeOpLessThan,
69+
RuntimeOpLessThanOrEqual,
70+
RuntimeOpGreaterThan,
71+
RuntimeOpGreaterThanOrEqual,
7272
IntermediateOpLike,
7373
IntermediateOpNotLike,
7474
RuntimeOpIn,
75+
RuntimeOpNotIn,
76+
OpIsMatch,
77+
OpIsDefined,
78+
RuntimeOpIsNull,
79+
RuntimeOpIsNotNull
7580
};
7681

7782
public static bool SameOperator(string op1, string op2)
@@ -84,7 +89,12 @@ public static bool SameOperator(string op1, string op2)
8489

8590
public static string ToRuntimeWildcardOperator(IndexerWildcard wildcard)
8691
{
87-
return "_Internal_" + wildcard; // "Any"/"All"
92+
return wildcard switch
93+
{
94+
IndexerWildcard.All => RuntimeOpAll,
95+
IndexerWildcard.Any => RuntimeOpAny,
96+
_ => throw new ArgumentException("Unsupported wildcard.")
97+
};
8898
}
8999
}
90100
}

src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,22 @@ public static TokenListParserResult<ExpressionToken, Expression> TryPartialParse
2424
return Expr.TryParse(input);
2525
}
2626

27-
static readonly TokenListParser<ExpressionToken, string> Add = Token.EqualTo(ExpressionToken.Plus).Value(Operators.OpAdd);
28-
static readonly TokenListParser<ExpressionToken, string> Subtract = Token.EqualTo(ExpressionToken.Minus).Value(Operators.OpSubtract);
29-
static readonly TokenListParser<ExpressionToken, string> Multiply = Token.EqualTo(ExpressionToken.Asterisk).Value(Operators.OpMultiply);
30-
static readonly TokenListParser<ExpressionToken, string> Divide = Token.EqualTo(ExpressionToken.ForwardSlash).Value(Operators.OpDivide);
31-
static readonly TokenListParser<ExpressionToken, string> Modulo = Token.EqualTo(ExpressionToken.Percent).Value(Operators.OpModulo);
32-
static readonly TokenListParser<ExpressionToken, string> Power = Token.EqualTo(ExpressionToken.Caret).Value(Operators.OpPower);
33-
static readonly TokenListParser<ExpressionToken, string> And = Token.EqualTo(ExpressionToken.And).Value(Operators.OpAnd);
34-
static readonly TokenListParser<ExpressionToken, string> Or = Token.EqualTo(ExpressionToken.Or).Value(Operators.OpOr);
35-
static readonly TokenListParser<ExpressionToken, string> Lte = Token.EqualTo(ExpressionToken.LessThanOrEqual).Value(Operators.OpLessThanOrEqual);
36-
static readonly TokenListParser<ExpressionToken, string> Lt = Token.EqualTo(ExpressionToken.LessThan).Value(Operators.OpLessThan);
37-
static readonly TokenListParser<ExpressionToken, string> Gt = Token.EqualTo(ExpressionToken.GreaterThan).Value(Operators.OpGreaterThan);
38-
static readonly TokenListParser<ExpressionToken, string> Gte = Token.EqualTo(ExpressionToken.GreaterThanOrEqual).Value(Operators.OpGreaterThanOrEqual);
39-
static readonly TokenListParser<ExpressionToken, string> Eq = Token.EqualTo(ExpressionToken.Equal).Value(Operators.OpEqual);
40-
static readonly TokenListParser<ExpressionToken, string> Neq = Token.EqualTo(ExpressionToken.NotEqual).Value(Operators.OpNotEqual);
41-
static readonly TokenListParser<ExpressionToken, string> Negate = Token.EqualTo(ExpressionToken.Minus).Value(Operators.OpNegate);
42-
static readonly TokenListParser<ExpressionToken, string> Not = Token.EqualTo(ExpressionToken.Not).Value(Operators.OpNot);
27+
static readonly TokenListParser<ExpressionToken, string> Add = Token.EqualTo(ExpressionToken.Plus).Value(Operators.RuntimeOpAdd);
28+
static readonly TokenListParser<ExpressionToken, string> Subtract = Token.EqualTo(ExpressionToken.Minus).Value(Operators.RuntimeOpSubtract);
29+
static readonly TokenListParser<ExpressionToken, string> Multiply = Token.EqualTo(ExpressionToken.Asterisk).Value(Operators.RuntimeOpMultiply);
30+
static readonly TokenListParser<ExpressionToken, string> Divide = Token.EqualTo(ExpressionToken.ForwardSlash).Value(Operators.RuntimeOpDivide);
31+
static readonly TokenListParser<ExpressionToken, string> Modulo = Token.EqualTo(ExpressionToken.Percent).Value(Operators.RuntimeOpModulo);
32+
static readonly TokenListParser<ExpressionToken, string> Power = Token.EqualTo(ExpressionToken.Caret).Value(Operators.RuntimeOpPower);
33+
static readonly TokenListParser<ExpressionToken, string> And = Token.EqualTo(ExpressionToken.And).Value(Operators.RuntimeOpAnd);
34+
static readonly TokenListParser<ExpressionToken, string> Or = Token.EqualTo(ExpressionToken.Or).Value(Operators.RuntimeOpOr);
35+
static readonly TokenListParser<ExpressionToken, string> Lte = Token.EqualTo(ExpressionToken.LessThanOrEqual).Value(Operators.RuntimeOpLessThanOrEqual);
36+
static readonly TokenListParser<ExpressionToken, string> Lt = Token.EqualTo(ExpressionToken.LessThan).Value(Operators.RuntimeOpLessThan);
37+
static readonly TokenListParser<ExpressionToken, string> Gt = Token.EqualTo(ExpressionToken.GreaterThan).Value(Operators.RuntimeOpGreaterThan);
38+
static readonly TokenListParser<ExpressionToken, string> Gte = Token.EqualTo(ExpressionToken.GreaterThanOrEqual).Value(Operators.RuntimeOpGreaterThanOrEqual);
39+
static readonly TokenListParser<ExpressionToken, string> Eq = Token.EqualTo(ExpressionToken.Equal).Value(Operators.RuntimeOpEqual);
40+
static readonly TokenListParser<ExpressionToken, string> Neq = Token.EqualTo(ExpressionToken.NotEqual).Value(Operators.RuntimeOpNotEqual);
41+
static readonly TokenListParser<ExpressionToken, string> Negate = Token.EqualTo(ExpressionToken.Minus).Value(Operators.RuntimeOpNegate);
42+
static readonly TokenListParser<ExpressionToken, string> Not = Token.EqualTo(ExpressionToken.Not).Value(Operators.RuntimeOpNot);
4343

4444
static readonly TokenListParser<ExpressionToken, string> Like = Token.EqualTo(ExpressionToken.Like).Value(Operators.IntermediateOpLike);
4545

0 commit comments

Comments
 (0)