Skip to content

Commit f208bf8

Browse files
committed
New template parser
1 parent b78aba4 commit f208bf8

13 files changed

+143
-203
lines changed

src/Serilog.Expressions/Expressions/Parsing/ExpressionParser.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@
44

55
namespace Serilog.Expressions.Parsing
66
{
7-
static class ExpressionParser
7+
class ExpressionParser
88
{
9-
static ExpressionTokenizer Tokenizer { get; } = new ExpressionTokenizer();
9+
readonly ExpressionTokenizer _tokenizer = new ExpressionTokenizer();
1010

11-
public static Expression Parse(string expression)
11+
public Expression Parse(string expression)
1212
{
1313
if (!TryParse(expression, out var root, out var error))
1414
throw new ArgumentException(error);
1515

1616
return root;
1717
}
1818

19-
public static bool TryParse(string filterExpression,
19+
public bool TryParse(string filterExpression,
2020
[MaybeNullWhen(false)] out Expression root, [MaybeNullWhen(true)] out string error)
2121
{
2222
if (filterExpression == null) throw new ArgumentNullException(nameof(filterExpression));
2323

24-
var tokenList = Tokenizer.TryTokenize(filterExpression);
24+
var tokenList = _tokenizer.TryTokenize(filterExpression);
2525
if (!tokenList.HasValue)
2626
{
2727
error = tokenList.ToString();

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

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

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

134134
[Token(Description = "text")]
135135
Text,
136136

137-
138137
[Token(Example = "{{")]
139-
LBraceEscape,
138+
DoubleLBrace,
140139

141140
[Token(Example = "}}")]
142-
RBraceEscape,
141+
DoubleRBrace,
142+
143+
[Token(Description = "format specifier")]
144+
Format,
143145
}
144146
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ select MakeUnary(op, path))
215215

216216
static readonly TokenListParser<ExpressionToken, Expression> Disjunction = Parse.Chain(Or, Conjunction, MakeBinary);
217217

218-
static readonly TokenListParser<ExpressionToken, Expression> Expr = Disjunction;
218+
public static readonly TokenListParser<ExpressionToken, Expression> Expr = Disjunction;
219219

220220
static Expression MakeBinary(string operatorName, Expression leftOperand, Expression rightOperand)
221221
{

src/Serilog.Expressions/Expressions/SerilogExpression.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ static bool TryCompileImpl(
9191
[MaybeNullWhen(false)] out CompiledExpression result,
9292
[MaybeNullWhen(true)] out string error)
9393
{
94-
if (!ExpressionParser.TryParse(expression, out var root, out error))
94+
var expressionParser = new ExpressionParser();
95+
if (!expressionParser.TryParse(expression, out var root, out error))
9596
{
9697
result = null;
9798
return false;

src/Serilog.Expressions/Templates/ExpressionTemplate.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Diagnostics.CodeAnalysis;
43
using System.IO;
54
using Serilog.Events;
@@ -54,8 +53,9 @@ public static bool TryParse(
5453
[MaybeNullWhen(true)] out string error)
5554
{
5655
if (template == null) throw new ArgumentNullException(nameof(template));
57-
58-
if (!TemplateParser.TryParse(template, out var parsed, out error))
56+
57+
var templateParser = new TemplateParser();
58+
if (!templateParser.TryParse(template, out var parsed, out error))
5959
{
6060
result = null;
6161
return false;
@@ -86,7 +86,8 @@ public ExpressionTemplate(
8686
{
8787
if (template == null) throw new ArgumentNullException(nameof(template));
8888

89-
if (!TemplateParser.TryParse(template, out var parsed, out var error))
89+
var templateParser = new TemplateParser();
90+
if (!templateParser.TryParse(template, out var parsed, out var error))
9091
throw new ArgumentException(error);
9192

9293
_compiled = TemplateCompiler.Compile(parsed, DefaultFunctionNameResolver.Build(nameResolver));

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

Lines changed: 20 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,175 +1,38 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using System;
32
using System.Diagnostics.CodeAnalysis;
4-
using System.Globalization;
5-
using System.Linq;
6-
using System.Text;
7-
using Serilog.Expressions.Parsing;
83
using Serilog.Templates.Ast;
9-
using Serilog.Parsing;
10-
using Superpower.Model;
114

125
namespace Serilog.Templates.Parsing
136
{
14-
static class TemplateParser
7+
class TemplateParser
158
{
16-
public static bool TryParse(
9+
readonly TemplateTokenizer _tokenizer = new TemplateTokenizer();
10+
readonly TemplateTokenParsers _templateTokenParsers = new TemplateTokenParsers();
11+
12+
public bool TryParse(
1713
string template,
18-
[MaybeNullWhen(false)] out Template parsed,
14+
[MaybeNullWhen(false)] out Template parsed,
1915
[MaybeNullWhen(true)] out string error)
2016
{
2117
if (template == null) throw new ArgumentNullException(nameof(template));
2218

23-
var tokenizer = new ExpressionTokenizer();
24-
25-
parsed = null;
26-
var elements = new List<Template>();
27-
28-
var i = 0;
29-
while (i < template.Length)
19+
var tokenList = _tokenizer.TryTokenize(template);
20+
if (!tokenList.HasValue)
3021
{
31-
var ch = template[i];
32-
33-
if (ch == '{')
34-
{
35-
i++;
36-
if (i == template.Length)
37-
{
38-
error = "Character `{` must be escaped by doubling in literal text.";
39-
return false;
40-
}
41-
42-
if (template[i] == '{')
43-
{
44-
elements.Add(new LiteralText("{"));
45-
i++;
46-
}
47-
else
48-
{
49-
// No line/column tracking
50-
var tokens = tokenizer.GreedyTokenize(new TextSpan(template, new Position(i, 0, 0), template.Length - i));
51-
var expr = ExpressionTokenParsers.TryPartialParse(tokens);
52-
if (!expr.HasValue)
53-
{
54-
// Error message accuracy is not great here
55-
error = $"Invalid expression, {expr.FormatErrorMessageFragment()}.";
56-
return false;
57-
}
58-
59-
if (expr.Remainder.Position == tokens.Count())
60-
i = tokens.Last().Position.Absolute + tokens.Last().Span.Length;
61-
else
62-
i = tokens.ElementAt(expr.Remainder.Position).Position.Absolute;
63-
64-
if (i == template.Length)
65-
{
66-
error = "Un-closed hole, `}` expected.";
67-
return false;
68-
}
69-
70-
Alignment? alignment = null;
71-
if (template[i] == ',')
72-
{
73-
i++;
74-
75-
if (i >= template.Length || template[i] == '}')
76-
{
77-
error = "Incomplete alignment specifier, expected width.";
78-
return false;
79-
}
80-
81-
AlignmentDirection direction;
82-
if (template[i] == '-')
83-
{
84-
direction = AlignmentDirection.Left;
85-
i++;
86-
87-
if (i >= template.Length || template[i] == '}')
88-
{
89-
error = "Incomplete alignment specifier, expected digits.";
90-
return false;
91-
}
92-
}
93-
else
94-
direction = AlignmentDirection.Right;
95-
96-
if (!char.IsDigit(template[i]))
97-
{
98-
error = "Invalid alignment specifier, expected digits.";
99-
return false;
100-
}
101-
102-
var width = 0;
103-
while (i < template.Length && char.IsDigit(template[i]))
104-
{
105-
width = 10 * width + CharUnicodeInfo.GetDecimalDigitValue(template[i]);
106-
i++;
107-
}
108-
109-
alignment = new Alignment(direction, width);
110-
}
111-
112-
string? format = null;
113-
if (template[i] == ':')
114-
{
115-
i++;
116-
117-
var formatBuilder = new StringBuilder();
118-
while (i < template.Length && template[i] != '}')
119-
{
120-
formatBuilder.Append(template[i]);
121-
i++;
122-
}
123-
124-
format = formatBuilder.ToString();
125-
}
126-
127-
if (i == template.Length)
128-
{
129-
error = "Un-closed hole, `}` expected.";
130-
return false;
131-
}
132-
133-
if (template[i] != '}')
134-
{
135-
error = $"Invalid expression, unexpected `{template[i]}`.";
136-
return false;
137-
}
138-
139-
i++;
140-
141-
elements.Add(new FormattedExpression(expr.Value, format, alignment));
142-
}
143-
}
144-
else if (ch == '}')
145-
{
146-
i++;
147-
if (i == template.Length || template[i] != '}')
148-
{
149-
error = "Character `}` must be escaped by doubling in literal text.";
150-
return false;
151-
}
152-
153-
elements.Add(new LiteralText("}"));
154-
i++;
155-
}
156-
else
157-
{
158-
var literal = new StringBuilder();
159-
do
160-
{
161-
literal.Append(template[i]);
162-
i++;
163-
} while (i < template.Length && template[i] != '{' && template[i] != '}');
164-
elements.Add(new LiteralText(literal.ToString()));
165-
}
22+
error = tokenList.ToString();
23+
parsed = null;
24+
return false;
16625
}
16726

168-
if (elements.Count == 1)
169-
parsed = elements.Single();
170-
else
171-
parsed = new TemplateBlock(elements.ToArray());
27+
var result = _templateTokenParsers.TryParse(tokenList.Value);
28+
if (!result.HasValue)
29+
{
30+
error = result.ToString();
31+
parsed = null;
32+
return false;
33+
}
17234

35+
parsed = result.Value;
17336
error = null;
17437
return true;
17538
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using Serilog.Expressions.Parsing;
2+
using Serilog.Parsing;
3+
using Serilog.Templates.Ast;
4+
using Superpower;
5+
using Superpower.Model;
6+
using Superpower.Parsers;
7+
using static Serilog.Expressions.Parsing.ExpressionToken;
8+
9+
namespace Serilog.Templates.Parsing
10+
{
11+
class TemplateTokenParsers
12+
{
13+
readonly TokenListParser<ExpressionToken, Template> _template;
14+
15+
public TemplateTokenParsers()
16+
{
17+
var alignment =
18+
Token.EqualTo(Comma).IgnoreThen(
19+
(from direction in Token.EqualTo(Minus).Value(AlignmentDirection.Left)
20+
.OptionalOrDefault(AlignmentDirection.Right)
21+
from width in Token.EqualTo(Number).Apply(Numerics.NaturalUInt32)
22+
select new Alignment(direction, (int)width)).Named("alignment and width"));
23+
24+
var format = Token.EqualTo(Colon)
25+
.IgnoreThen(Token.EqualTo(Format))
26+
.Select(fmt => (string?)fmt.ToStringValue());
27+
28+
var hole =
29+
from _ in Token.EqualTo(LBrace)
30+
from expr in ExpressionTokenParsers.Expr
31+
from align in alignment.OptionalOrDefault()
32+
from fmt in format.OptionalOrDefault()
33+
from __ in Token.EqualTo(RBrace)
34+
select (Template) new FormattedExpression(expr, fmt, align);
35+
36+
var element = Token.EqualTo(Text).Select(t => (Template)new LiteralText(t.ToStringValue()))
37+
.Or(Token.EqualTo(DoubleLBrace)
38+
.Value((Template) new LiteralText("{")))
39+
.Or(Token.EqualTo(DoubleRBrace)
40+
.Value((Template) new LiteralText("}")))
41+
.Or(hole);
42+
43+
var block = element.Many().Select(elements => (Template) new TemplateBlock(elements));
44+
45+
_template = block.AtEnd();
46+
}
47+
48+
public TokenListParserResult<ExpressionToken, Template> TryParse(
49+
TokenList<ExpressionToken> input)
50+
{
51+
return _template.TryParse(input);
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)