Skip to content

Commit 94baa8b

Browse files
committed
More repetition WIP
1 parent c42f6a4 commit 94baa8b

File tree

11 files changed

+126
-34
lines changed

11 files changed

+126
-34
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ namespace Serilog.Expressions.Parsing
55
enum ExpressionToken
66
{
77
None,
8-
9-
[Token(Description = "literal text")]
10-
TemplateLiteral,
118

129
Identifier,
1310

@@ -148,5 +145,11 @@ enum ExpressionToken
148145

149146
[Token(Category = "keyword", Example = "end")]
150147
End,
148+
149+
[Token(Category = "keyword", Example = "each")]
150+
Each,
151+
152+
[Token(Category = "keyword", Example = "delimit")]
153+
Delimit,
151154
}
152155
}

src/Serilog.Expressions/Templates/Ast/Repetition.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,22 @@ namespace Serilog.Templates.Ast
66
class Repetition: Template
77
{
88
public Expression Enumerable { get; }
9-
public string? KeyOrElementName { get; }
10-
public string? ValueName { get; }
9+
public string[] BindingNames { get; }
1110
public Template Body { get; }
12-
public Template? Separator { get; }
11+
public Template? Delimiter { get; }
1312
public Template? Alternative { get; }
1413

1514
public Repetition(
1615
Expression enumerable,
17-
string? keyOrElementName,
18-
string? valueName,
16+
string[] bindingNames,
1917
Template body,
20-
Template? separator,
18+
Template? delimiter,
2119
Template? alternative)
2220
{
2321
Enumerable = enumerable ?? throw new ArgumentNullException(nameof(enumerable));
24-
KeyOrElementName = keyOrElementName;
25-
ValueName = valueName;
22+
BindingNames = bindingNames;
2623
Body = body ?? throw new ArgumentNullException(nameof(body));
27-
Separator = separator;
24+
Delimiter = delimiter;
2825
Alternative = alternative;
2926
}
3027
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,22 @@ class CompiledRepetition : CompiledTemplate
1212
readonly string? _keyOrElementName;
1313
readonly string? _valueName;
1414
readonly CompiledTemplate _body;
15-
readonly CompiledTemplate? _separator;
15+
readonly CompiledTemplate? _delimiter;
1616
readonly CompiledTemplate? _alternative;
1717

1818
public CompiledRepetition(
1919
Evaluatable enumerable,
2020
string? keyOrElementName,
2121
string? valueName,
2222
CompiledTemplate body,
23-
CompiledTemplate? separator,
23+
CompiledTemplate? delimiter,
2424
CompiledTemplate? alternative)
2525
{
2626
_enumerable = enumerable;
2727
_keyOrElementName = keyOrElementName;
2828
_valueName = valueName;
2929
_body = body;
30-
_separator = separator;
30+
_delimiter = delimiter;
3131
_alternative = alternative;
3232
}
3333

@@ -58,7 +58,7 @@ public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatP
5858
if (first)
5959
{
6060
first = false;
61-
_separator?.Evaluate(ctx, output, formatProvider);
61+
_delimiter?.Evaluate(ctx, output, formatProvider);
6262
}
6363

6464
var local = _keyOrElementName != null
@@ -85,7 +85,7 @@ public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatP
8585
if (first)
8686
{
8787
first = false;
88-
_separator?.Evaluate(ctx, output, formatProvider);
88+
_delimiter?.Evaluate(ctx, output, formatProvider);
8989
}
9090

9191
var local = _keyOrElementName != null
@@ -114,7 +114,7 @@ public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatP
114114
if (first)
115115
{
116116
first = false;
117-
_separator?.Evaluate(ctx, output, formatProvider);
117+
_delimiter?.Evaluate(ctx, output, formatProvider);
118118
}
119119

120120
var local = _keyOrElementName != null

src/Serilog.Expressions/Templates/Compilation/NameResolution/TemplateLocalNameResolver.cs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,23 +56,19 @@ Template Transform(Conditional cond, Stack<string> locals)
5656

5757
Template Transform(Repetition rep, Stack<string> locals)
5858
{
59-
var orig = locals.Count;
60-
if (rep.KeyOrElementName != null)
61-
locals.Push(rep.KeyOrElementName);
62-
if (rep.ValueName != null)
63-
locals.Push(rep.ValueName);
59+
foreach (var name in rep.BindingNames)
60+
locals.Push(name);
6461

6562
var body = Transform(rep.Body, locals);
6663

67-
while (locals.Count != orig)
64+
foreach (var _ in rep.BindingNames)
6865
locals.Pop();
6966

7067
return new Repetition(
7168
rep.Enumerable,
72-
rep.KeyOrElementName,
73-
rep.ValueName,
69+
rep.BindingNames,
7470
body,
75-
rep.Separator != null ? Transform(rep.Separator, locals) : null,
71+
rep.Delimiter != null ? Transform(rep.Delimiter, locals) : null,
7672
rep.Alternative != null ? Transform(rep.Alternative, locals) : null);
7773
}
7874
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ public static CompiledTemplate Compile(Template template, NameResolver nameResol
2121
ExpressionCompiler.Compile(conditional.Condition, nameResolver),
2222
Compile(conditional.Consequent, nameResolver),
2323
conditional.Alternative == null ? null : Compile(conditional.Alternative, nameResolver)),
24+
Repetition repetition => new CompiledRepetition(
25+
ExpressionCompiler.Compile(repetition.Enumerable, nameResolver),
26+
repetition.BindingNames.Length > 0 ? repetition.BindingNames[0] : null,
27+
repetition.BindingNames.Length > 1 ? repetition.BindingNames[1] : null,
28+
Compile(repetition.Body, nameResolver),
29+
repetition.Delimiter == null ? null : Compile(repetition.Delimiter, nameResolver),
30+
repetition.Alternative == null ? null : Compile(repetition.Alternative, nameResolver)),
2431
_ => throw new NotSupportedException()
2532
};
2633
}

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

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ from __ in Token.EqualTo(RBrace)
4949
return open
5050
.IgnoreThen(ExpressionTokenParsers.Expr.Cast<ExpressionToken, Expression, Expression?>())
5151
.Then(v => Token.EqualTo(RBrace).Value(v));
52-
53-
return open.IgnoreThen(Token.EqualTo(RBrace)).Value((Expression?)null);
52+
53+
return open.IgnoreThen(Token.EqualTo(RBrace)).Value((Expression?) null);
5454
}
5555

5656
static Template? LeftReduceConditional((Expression?, Template)[] first, Template? last)
5757
{
58-
for (var i = first.Length -1; i >= 0; i--)
58+
for (var i = first.Length - 1; i >= 0; i--)
5959
{
6060
last = new Conditional(first[i].Item1!, first[i].Item2, last);
6161
}
@@ -72,16 +72,47 @@ from alternatives in Directive(true, Else, If)
7272
from final in Directive(false, Else)
7373
.IgnoreThen(Parse.Ref(() => block).Select(b => ((Expression?) null, b)))
7474
.OptionalOrDefault()
75-
from _ in Directive(false, End)
75+
from end in Directive(false, End)
7676
let firstAlt = LeftReduceConditional(alternatives, final.b)
77-
select (Template)new Conditional(iff!, consequent, firstAlt);
78-
77+
select (Template) new Conditional(iff!, consequent, firstAlt);
78+
79+
var eachDirective =
80+
Token.EqualTo(LBraceHash)
81+
.IgnoreThen(Token.EqualTo(Each)).Try()
82+
.IgnoreThen(Token.EqualTo(ExpressionToken.Identifier)
83+
.Select(i => i.ToStringValue())
84+
.AtLeastOnceDelimitedBy(Token.EqualTo(Comma)))
85+
.Then(bindings => Token.EqualTo(In).Value(bindings))
86+
.Then(bindings => ExpressionTokenParsers.Expr.Cast<ExpressionToken, Expression, Expression?>()
87+
.Select(enumerable => new {enumerable, bindings}))
88+
.Then(v => Token.EqualTo(RBrace).Value(v));
89+
90+
var repetition =
91+
from each in eachDirective
92+
from body in Parse.Ref(() => block)
93+
from delimiter in Directive(false, Delimit)
94+
.IgnoreThen(Parse.Ref(() => block))
95+
.Cast<ExpressionToken, Template, Template?>()
96+
.OptionalOrDefault()
97+
from alternative in Directive(false, Else)
98+
.IgnoreThen(Parse.Ref(() => block))
99+
.Cast<ExpressionToken, Template, Template?>()
100+
.OptionalOrDefault()
101+
from end in Directive(false, End)
102+
select (Template) new Repetition(
103+
each.enumerable,
104+
each.bindings,
105+
body,
106+
delimiter,
107+
alternative);
108+
79109
var element = Token.EqualTo(Text).Select(t => (Template)new LiteralText(t.ToStringValue()))
80110
.Or(Token.EqualTo(DoubleLBrace)
81111
.Value((Template) new LiteralText("{")))
82112
.Or(Token.EqualTo(DoubleRBrace)
83113
.Value((Template) new LiteralText("}")))
84114
.Or(conditional)
115+
.Or(repetition)
85116
.Or(hole);
86117

87118
block = element.Many().Select(elements => elements.Length == 1 ?

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ Hello, {#if undefined()}world{#else}there{#end}! ⇶ Hello, there!
2121
A{#if false}B{#else if false}C{#else if true}D{#else}E{#end} ⇶ AD
2222
A{#if false}B{#else if true}C{#end} ⇶ AC
2323
{#if true}A{#if false}B{#else}C{#end}D{#end} ⇶ ACD
24+
{#each a in [1,2,3]}<{a}>{#delimit},{#end} ⇶ <1>,<2>,<3>
25+
{#each a in {x: 1, y: 2}}{a}{#end} ⇶ xy
26+
{#each a, b in {x: 1, y: 2}}{a}.{b}{#end} ⇶ x.1y.2

test/Serilog.Expressions.Tests/NameResolverTests.cs renamed to test/Serilog.Expressions.Tests/Expressions/NameResolverTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using Serilog.Expressions.Tests.Support;
44
using Xunit;
55

6-
namespace Serilog.Expressions.Tests
6+
namespace Serilog.Expressions.Tests.Expressions
77
{
88
public class NameResolverTests
99
{
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Serilog.Expressions.Runtime;
2+
using Serilog.Expressions.Tests.Support;
3+
using Xunit;
4+
5+
namespace Serilog.Expressions.Tests.Expressions.Runtime
6+
{
7+
public class LocalsTests
8+
{
9+
[Fact]
10+
public void NoValueIsDefinedInNoLocals()
11+
{
12+
Assert.False(Locals.TryGetValue(null, "A", out _));
13+
}
14+
15+
[Fact]
16+
public void ASetValueIsRetrieved()
17+
{
18+
var expected = Some.LogEventPropertyValue();
19+
var locals = Locals.Set(null, "A", expected);
20+
Assert.True(Locals.TryGetValue(locals, "A", out var actual));
21+
Assert.Same(expected, actual);
22+
}
23+
24+
[Fact]
25+
public void ASetValueIsRetrievedFromMany()
26+
{
27+
var expected = Some.LogEventPropertyValue();
28+
var locals = Locals.Set(null, "A", expected);
29+
locals = Locals.Set(locals, "B", Some.LogEventPropertyValue());
30+
Assert.True(Locals.TryGetValue(locals, "A", out var actual));
31+
Assert.Same(expected, actual);
32+
}
33+
34+
[Fact]
35+
public void TheTopmostValueIsRetrievedForAName()
36+
{
37+
var expected = Some.LogEventPropertyValue();
38+
var locals = Locals.Set(null, "A", Some.LogEventPropertyValue());
39+
locals = Locals.Set(locals, "B", Some.LogEventPropertyValue());
40+
locals = Locals.Set(locals, "A", expected);
41+
Assert.True(Locals.TryGetValue(locals, "A", out var actual));
42+
Assert.Same(expected, actual);
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)