Skip to content

Commit 5bf94d1

Browse files
committed
Basics of #if/else if/else/end conditionals
1 parent f208bf8 commit 5bf94d1

File tree

9 files changed

+124
-9
lines changed

9 files changed

+124
-9
lines changed

src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ enum ExpressionToken
129129
[Token(Category = "keyword", Example = "ci")]
130130
CI,
131131

132-
// Template syntax only
132+
// Template syntax
133133

134134
[Token(Description = "text")]
135135
Text,
@@ -140,7 +140,13 @@ enum ExpressionToken
140140
[Token(Example = "}}")]
141141
DoubleRBrace,
142142

143+
[Token(Example = "{#")]
144+
LBraceHash,
145+
143146
[Token(Description = "format specifier")]
144147
Format,
148+
149+
[Token(Category = "keyword", Example = "end")]
150+
End,
145151
}
146152
}

src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class ExpressionTokenizer : Tokenizer<ExpressionToken>
2323
new ExpressionKeyword("if", ExpressionToken.If),
2424
new ExpressionKeyword("then", ExpressionToken.Then),
2525
new ExpressionKeyword("else", ExpressionToken.Else),
26+
new ExpressionKeyword("end", ExpressionToken.End),
2627
new ExpressionKeyword("ci", ExpressionToken.CI)
2728
};
2829

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using Serilog.Expressions.Ast;
3+
4+
namespace Serilog.Templates.Ast
5+
{
6+
class Conditional : Template
7+
{
8+
public Expression Condition { get; }
9+
public Template Consequent { get; }
10+
public Template? Alternative { get; }
11+
12+
public Conditional(Expression condition, Template consequent, Template? alternative)
13+
{
14+
Condition = condition ?? throw new ArgumentNullException(nameof(condition));
15+
Consequent = consequent ?? throw new ArgumentNullException(nameof(consequent));
16+
Alternative = alternative;
17+
}
18+
}
19+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.IO;
3+
using Serilog.Events;
4+
using Serilog.Expressions;
5+
6+
namespace Serilog.Templates.Compilation
7+
{
8+
class CompiledConditional : CompiledTemplate
9+
{
10+
readonly CompiledExpression _condition;
11+
readonly CompiledTemplate _consequent;
12+
readonly CompiledTemplate? _alternative;
13+
14+
public CompiledConditional(CompiledExpression condition, CompiledTemplate consequent, CompiledTemplate? alternative)
15+
{
16+
_condition = condition ?? throw new ArgumentNullException(nameof(condition));
17+
_consequent = consequent ?? throw new ArgumentNullException(nameof(consequent));
18+
_alternative = alternative;
19+
}
20+
21+
public override void Evaluate(LogEvent logEvent, TextWriter output, IFormatProvider? formatProvider)
22+
{
23+
if (ExpressionResult.IsTrue(_condition.Invoke(logEvent)))
24+
_consequent.Evaluate(logEvent, output, formatProvider);
25+
else
26+
_alternative?.Evaluate(logEvent, output, formatProvider);
27+
}
28+
}
29+
}

src/Serilog.Expressions/Templates/Compilation/TemplateCompiler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ public static CompiledTemplate Compile(Template template, NameResolver nameResol
1717
FormattedExpression expression => new CompiledFormattedExpression(
1818
ExpressionCompiler.Compile(expression.Expression, nameResolver), expression.Format, expression.Alignment),
1919
TemplateBlock block => new CompiledTemplateBlock(block.Elements.Select(e => Compile(e, nameResolver)).ToArray()),
20+
Conditional conditional => new CompiledConditional(
21+
ExpressionCompiler.Compile(conditional.Condition, nameResolver),
22+
Compile(conditional.Consequent, nameResolver),
23+
conditional.Alternative == null ? null : Compile(conditional.Consequent, nameResolver)),
2024
_ => throw new NotSupportedException()
2125
};
2226
}

src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
using Serilog.Expressions.Parsing;
1+
using Serilog.Expressions.Ast;
2+
using Serilog.Expressions.Parsing;
23
using Serilog.Parsing;
34
using Serilog.Templates.Ast;
45
using Superpower;
56
using Superpower.Model;
67
using Superpower.Parsers;
78
using static Serilog.Expressions.Parsing.ExpressionToken;
89

10+
// ReSharper disable SuggestBaseTypeForParameter, ConvertIfStatementToSwitchStatement, AccessToModifiedClosure
11+
912
namespace Serilog.Templates.Parsing
1013
{
1114
class TemplateTokenParsers
@@ -14,33 +17,76 @@ class TemplateTokenParsers
1417

1518
public TemplateTokenParsers()
1619
{
20+
TokenListParser<ExpressionToken, Template>? block = null;
21+
1722
var alignment =
1823
Token.EqualTo(Comma).IgnoreThen(
1924
(from direction in Token.EqualTo(Minus).Value(AlignmentDirection.Left)
2025
.OptionalOrDefault(AlignmentDirection.Right)
2126
from width in Token.EqualTo(Number).Apply(Numerics.NaturalUInt32)
22-
select new Alignment(direction, (int)width)).Named("alignment and width"));
23-
27+
select new Alignment(direction, (int) width)).Named("alignment and width"));
28+
2429
var format = Token.EqualTo(Colon)
2530
.IgnoreThen(Token.EqualTo(Format))
26-
.Select(fmt => (string?)fmt.ToStringValue());
27-
31+
.Select(fmt => (string?) fmt.ToStringValue());
32+
2833
var hole =
2934
from _ in Token.EqualTo(LBrace)
3035
from expr in ExpressionTokenParsers.Expr
3136
from align in alignment.OptionalOrDefault()
3237
from fmt in format.OptionalOrDefault()
3338
from __ in Token.EqualTo(RBrace)
3439
select (Template) new FormattedExpression(expr, fmt, align);
40+
41+
static TokenListParser<ExpressionToken, Expression?> Directive(
42+
bool hasArgument,
43+
params ExpressionToken[] signifiers)
44+
{
45+
var open = Token.EqualTo(LBraceHash)
46+
.IgnoreThen(Token.Sequence(signifiers)).Try();
47+
48+
if (hasArgument)
49+
return open
50+
.IgnoreThen(ExpressionTokenParsers.Expr.Cast<ExpressionToken, Expression, Expression?>())
51+
.Then(v => Token.EqualTo(RBrace).Value(v));
52+
53+
return open.IgnoreThen(Token.EqualTo(RBrace)).Value((Expression?)null);
54+
}
55+
56+
static Template? LeftReduceConditional((Expression?, Template)[] first, Template? last)
57+
{
58+
for (var i = first.Length -1; i >= 0; i--)
59+
{
60+
last = new Conditional(first[i].Item1!, first[i].Item2, last);
61+
}
62+
63+
return last;
64+
}
65+
66+
var conditional =
67+
from iff in Directive(true, If)
68+
from consequent in Parse.Ref(() => block)
69+
from alternatives in Directive(true, Else, If)
70+
.Then(elsif => Parse.Ref(() => block).Select(b => (elsif, b)))
71+
.Many()
72+
from final in Directive(false, Else)
73+
.IgnoreThen(Parse.Ref(() => block).Select(b => ((Expression?) null, b)))
74+
.OptionalOrDefault()
75+
from _ in Directive(false, End)
76+
let firstAlt = LeftReduceConditional(alternatives, final.b)
77+
select (Template)new Conditional(iff!, consequent, firstAlt);
3578

3679
var element = Token.EqualTo(Text).Select(t => (Template)new LiteralText(t.ToStringValue()))
3780
.Or(Token.EqualTo(DoubleLBrace)
3881
.Value((Template) new LiteralText("{")))
3982
.Or(Token.EqualTo(DoubleRBrace)
4083
.Value((Template) new LiteralText("}")))
84+
.Or(conditional)
4185
.Or(hole);
4286

43-
var block = element.Many().Select(elements => (Template) new TemplateBlock(elements));
87+
block = element.Many().Select(elements => elements.Length == 1 ?
88+
elements[0] :
89+
new TemplateBlock(elements));
4490

4591
_template = block.AtEnd();
4692
}

src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,16 @@ protected override IEnumerable<Result<ExpressionToken>> Tokenize(TextSpan span)
3737
}
3838
else
3939
{
40-
yield return Result.Value(ExpressionToken.LBrace, next.Location, next.Remainder);
41-
start = rem = next.Remainder;
40+
if (peek.HasValue && peek.Value == '#')
41+
{
42+
yield return Result.Value(ExpressionToken.LBraceHash, next.Location, peek.Remainder);
43+
start = rem = peek.Remainder;
44+
}
45+
else
46+
{
47+
yield return Result.Value(ExpressionToken.LBrace, next.Location, next.Remainder);
48+
start = rem = next.Remainder;
49+
}
4250

4351
foreach (var token in TokenizeHole(rem))
4452
{

test/Serilog.Expressions.Tests/Cases/template-evaluation-cases.asv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ Aligned {42,4}! ⇶ Aligned 42!
1313
Left {42,-4}! ⇶ Left 42 !
1414
Under width {42,0}! ⇶ Under width 42!
1515
{@m} ⇶ Hello, nblumhardt!
16+
Hello, {#if 1 = 1}world{#end}! ⇶ Hello, world!
17+
Hello, {#if true}world{#else}there{#end}! ⇶ Hello, world!

0 commit comments

Comments
 (0)