From 8d57015d65db747c4c3bc76a9edf208bdba7e409 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Sat, 27 Mar 2021 17:37:54 +1000 Subject: [PATCH 1/7] Dev version bump [skip ci] --- src/Serilog.Expressions/Serilog.Expressions.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Expressions/Serilog.Expressions.csproj b/src/Serilog.Expressions/Serilog.Expressions.csproj index 15584a8..c55bc1b 100644 --- a/src/Serilog.Expressions/Serilog.Expressions.csproj +++ b/src/Serilog.Expressions/Serilog.Expressions.csproj @@ -3,7 +3,7 @@ An embeddable mini-language for filtering, enriching, and formatting Serilog events, ideal for use with JSON or XML configuration. - 2.0.0 + 2.0.1 Serilog Contributors netstandard2.0;netstandard2.1 true From 9b079d2f0377fedadba4d3bc6e04b09605d762f0 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 24 May 2021 19:33:46 +1000 Subject: [PATCH 2/7] Intern the parts of Superpower used by this assembly --- README.md | 5 + serilog-expressions.sln.DotSettings | 1 + .../Expressions/Parsing/Combinators.cs | 4 +- .../Parsing/ExpressionTextParsers.cs | 8 +- .../Expressions/Parsing/ExpressionToken.cs | 2 +- .../Parsing/ExpressionTokenParsers.cs | 28 +- .../Parsing/ExpressionTokenizer.cs | 4 +- .../Expressions/Parsing/ParserExtensions.cs | 4 +- .../Serilog.Expressions.csproj | 1 - .../Superpower/Combinators.cs | 698 ++++++++++++++++++ .../Superpower/Display/Presentation.cs | 151 ++++ .../Superpower/Display/TokenAttribute.cs | 43 ++ .../Superpower/Model/Position.cs | 90 +++ .../Superpower/Model/Result.cs | 93 +++ .../Superpower/Model/Result`1.cs | 145 ++++ .../Superpower/Model/TextSpan.cs | 228 ++++++ .../Superpower/Model/TokenListParserResult.cs | 116 +++ .../Model/TokenListParserResult`2.cs | 184 +++++ .../Superpower/Model/TokenList`1.cs | 182 +++++ .../Superpower/Model/Token`1.cs | 69 ++ .../Superpower/Model/Unit.cs | 27 + src/Serilog.Expressions/Superpower/Parse.cs | 129 ++++ .../Superpower/ParseException.cs | 50 ++ .../Superpower/ParserExtensions.cs | 41 + .../Superpower/Parsers/Character.cs | 86 +++ .../Superpower/Parsers/Numerics.cs | 78 ++ .../Superpower/Parsers/Span.cs | 56 ++ .../Superpower/Parsers/Token.cs | 67 ++ src/Serilog.Expressions/Superpower/README.md | 218 ++++++ .../Superpower/TextParser`1.cs | 26 + .../Superpower/TokenListParser`2.cs | 27 + .../Superpower/Tokenizer`1.cs | 96 +++ .../Superpower/Util/ArrayEnumerable.cs | 43 ++ .../Superpower/Util/CharInfo.cs | 24 + .../Superpower/Util/Friendly.cs | 53 ++ .../Templates/Parsing/TemplateTokenParsers.cs | 18 +- .../Templates/Parsing/TemplateTokenizer.cs | 4 +- 37 files changed, 3062 insertions(+), 37 deletions(-) create mode 100644 src/Serilog.Expressions/Superpower/Combinators.cs create mode 100644 src/Serilog.Expressions/Superpower/Display/Presentation.cs create mode 100644 src/Serilog.Expressions/Superpower/Display/TokenAttribute.cs create mode 100644 src/Serilog.Expressions/Superpower/Model/Position.cs create mode 100644 src/Serilog.Expressions/Superpower/Model/Result.cs create mode 100644 src/Serilog.Expressions/Superpower/Model/Result`1.cs create mode 100644 src/Serilog.Expressions/Superpower/Model/TextSpan.cs create mode 100644 src/Serilog.Expressions/Superpower/Model/TokenListParserResult.cs create mode 100644 src/Serilog.Expressions/Superpower/Model/TokenListParserResult`2.cs create mode 100644 src/Serilog.Expressions/Superpower/Model/TokenList`1.cs create mode 100644 src/Serilog.Expressions/Superpower/Model/Token`1.cs create mode 100644 src/Serilog.Expressions/Superpower/Model/Unit.cs create mode 100644 src/Serilog.Expressions/Superpower/Parse.cs create mode 100644 src/Serilog.Expressions/Superpower/ParseException.cs create mode 100644 src/Serilog.Expressions/Superpower/ParserExtensions.cs create mode 100644 src/Serilog.Expressions/Superpower/Parsers/Character.cs create mode 100644 src/Serilog.Expressions/Superpower/Parsers/Numerics.cs create mode 100644 src/Serilog.Expressions/Superpower/Parsers/Span.cs create mode 100644 src/Serilog.Expressions/Superpower/Parsers/Token.cs create mode 100644 src/Serilog.Expressions/Superpower/README.md create mode 100644 src/Serilog.Expressions/Superpower/TextParser`1.cs create mode 100644 src/Serilog.Expressions/Superpower/TokenListParser`2.cs create mode 100644 src/Serilog.Expressions/Superpower/Tokenizer`1.cs create mode 100644 src/Serilog.Expressions/Superpower/Util/ArrayEnumerable.cs create mode 100644 src/Serilog.Expressions/Superpower/Util/CharInfo.cs create mode 100644 src/Serilog.Expressions/Superpower/Util/Friendly.cs diff --git a/README.md b/README.md index 7c8bc08..b7996d8 100644 --- a/README.md +++ b/README.md @@ -370,3 +370,8 @@ var myFunctions = new StaticMemberNameResolver(typeof(MyFunctions)); var expr = SerilogExpression.Compile("IsHello(User.Name)", new[] { myFunctions }); // Filter events based on whether `User.Name` is `'Hello'` :-) ``` + +## Acknowledgements + +Includes the parser combinator implementation from [Superpower](https://github.com/datalust/superpower), copyright Datalust, +Superpower Contributors, and Sprache Contributors; licensed under the Apache License, 2.0. diff --git a/serilog-expressions.sln.DotSettings b/serilog-expressions.sln.DotSettings index da16a45..21575ef 100644 --- a/serilog-expressions.sln.DotSettings +++ b/serilog-expressions.sln.DotSettings @@ -6,6 +6,7 @@ True True True + True True True True diff --git a/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs b/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs index 0b82ae4..b4ac698 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs @@ -1,6 +1,6 @@ using System; -using Superpower; -using Superpower.Model; +using Serilog.Superpower; +using Serilog.Superpower.Model; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs index 261ede9..31e5a44 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs @@ -1,6 +1,6 @@ -using Superpower; -using Superpower.Model; -using Superpower.Parsers; +using Serilog.Superpower; +using Serilog.Superpower.Model; +using Serilog.Superpower.Parsers; namespace Serilog.Expressions.Parsing { @@ -31,6 +31,6 @@ static class ExpressionTextParsers public static readonly TextParser Real = Numerics.Integer .Then(n => Character.EqualTo('.').IgnoreThen(Numerics.Integer).OptionalOrDefault() - .Select(f => f == TextSpan.None ? n : new TextSpan(n.Source, n.Position, n.Length + f.Length + 1))); + .Select(f => f == TextSpan.None ? n : new TextSpan(n.Source!, n.Position, n.Length + f.Length + 1))); } } diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs index 624d71a..5e44a30 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs @@ -1,4 +1,4 @@ -using Superpower.Display; +using Serilog.Superpower.Display; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs index a60b2f8..b4a793b 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs @@ -3,9 +3,9 @@ using System.Linq; using Serilog.Events; using Serilog.Expressions.Ast; -using Superpower; -using Superpower.Model; -using Superpower.Parsers; +using Serilog.Superpower; +using Serilog.Superpower.Model; +using Serilog.Superpower.Parsers; namespace Serilog.Expressions.Parsing { @@ -65,23 +65,23 @@ public static TokenListParserResult TryPartialParse static readonly TokenListParser> PropertyPathIndexerStep = from open in Token.EqualTo(ExpressionToken.LBracket) - from indexer in Wildcard.Or(Parse.Ref(() => Expr)) + from indexer in Wildcard.Or(Parse.Ref(() => Expr!)) from close in Token.EqualTo(ExpressionToken.RBracket) select new Func(r => new IndexerExpression(r, indexer)); static readonly TokenListParser Function = (from name in Token.EqualTo(ExpressionToken.Identifier) from lparen in Token.EqualTo(ExpressionToken.LParen) - from expr in Parse.Ref(() => Expr).ManyDelimitedBy(Token.EqualTo(ExpressionToken.Comma)) + from expr in Parse.Ref(() => Expr!).ManyDelimitedBy(Token.EqualTo(ExpressionToken.Comma)) from rparen in Token.EqualTo(ExpressionToken.RParen) from ci in Token.EqualTo(ExpressionToken.CI).Value(true).OptionalOrDefault() select (Expression)new CallExpression(ci, name.ToStringValue(), expr)).Named("function"); static readonly TokenListParser ArrayElement = Token.EqualTo(ExpressionToken.Spread) - .IgnoreThen(Parse.Ref(() => Expr)) + .IgnoreThen(Parse.Ref(() => Expr!)) .Select(content => (Element)new SpreadElement(content)) - .Or(Parse.Ref(() => Expr).Select(item => (Element) new ItemElement(item))); + .Or(Parse.Ref(() => Expr!).Select(item => (Element) new ItemElement(item))); static readonly TokenListParser ArrayLiteral = (from lbracket in Token.EqualTo(ExpressionToken.LBracket) @@ -92,7 +92,7 @@ from rbracket in Token.EqualTo(ExpressionToken.RBracket) static readonly TokenListParser IdentifierMember = from key in Token.EqualTo(ExpressionToken.Identifier).Or(Token.EqualTo(ExpressionToken.BuiltInIdentifier)) from value in Token.EqualTo(ExpressionToken.Colon) - .IgnoreThen(Parse.Ref(() => Expr)) + .IgnoreThen(Parse.Ref(() => Expr!)) .Cast() .OptionalOrDefault() select (Member) new PropertyMember( @@ -104,12 +104,12 @@ from value in Token.EqualTo(ExpressionToken.Colon) static readonly TokenListParser StringMember = from key in Token.EqualTo(ExpressionToken.String).Apply(ExpressionTextParsers.String) from colon in Token.EqualTo(ExpressionToken.Colon) - from value in Parse.Ref(() => Expr) + from value in Parse.Ref(() => Expr!) select (Member)new PropertyMember(key, value); static readonly TokenListParser SpreadMember = from spread in Token.EqualTo(ExpressionToken.Spread) - from content in Parse.Ref(() => Expr) + from content in Parse.Ref(() => Expr!) select (Member) new SpreadMember(content); static readonly TokenListParser ObjectMember = @@ -146,11 +146,11 @@ from rbrace in Token.EqualTo(ExpressionToken.RBrace) static readonly TokenListParser Conditional = from _ in Token.EqualTo(ExpressionToken.If) - from condition in Parse.Ref(() => Expr) + from condition in Parse.Ref(() => Expr!) from __ in Token.EqualTo(ExpressionToken.Then) - from consequent in Parse.Ref(() => Expr) + from consequent in Parse.Ref(() => Expr!) from ___ in Token.EqualTo(ExpressionToken.Else) - from alternative in Parse.Ref(() => Expr) + from alternative in Parse.Ref(() => Expr!) select (Expression)new CallExpression(false, Operators.RuntimeOpIfThenElse, condition, consequent, alternative); static readonly TokenListParser Literal = @@ -172,7 +172,7 @@ from alternative in Parse.Ref(() => Expr) static readonly TokenListParser Factor = (from lparen in Token.EqualTo(ExpressionToken.LParen) - from expr in Parse.Ref(() => Expr) + from expr in Parse.Ref(() => Expr!) from rparen in Token.EqualTo(ExpressionToken.RParen) select expr) .Or(Item); diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs index 0003edf..57aa8d8 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -using Superpower; -using Superpower.Model; +using Serilog.Superpower; +using Serilog.Superpower.Model; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs b/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs index 442570f..91f94f0 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs @@ -1,6 +1,6 @@ using System; -using Superpower; -using Superpower.Model; +using Serilog.Superpower; +using Serilog.Superpower.Model; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Serilog.Expressions.csproj b/src/Serilog.Expressions/Serilog.Expressions.csproj index c55bc1b..2412662 100644 --- a/src/Serilog.Expressions/Serilog.Expressions.csproj +++ b/src/Serilog.Expressions/Serilog.Expressions.csproj @@ -24,7 +24,6 @@ - diff --git a/src/Serilog.Expressions/Superpower/Combinators.cs b/src/Serilog.Expressions/Superpower/Combinators.cs new file mode 100644 index 0000000..d42fbee --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Combinators.cs @@ -0,0 +1,698 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using Serilog.Superpower.Display; +using Serilog.Superpower.Model; +using Serilog.Superpower.Util; + +// ReSharper disable MemberCanBePrivate.Global + +namespace Serilog.Superpower +{ + /// + /// Functions that construct more complex parsers by combining simpler ones. + /// + static class Combinators + { + /// + /// Apply the text parser to the span represented by the parsed token. + /// + /// The kind of the tokens being parsed. + /// The type of the resulting value. + /// The parser. + /// A text parser to apply. + /// A parser that returns the result of parsing the token value. + public static TokenListParser Apply(this TokenListParser> parser, TextParser valueParser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (valueParser == null) throw new ArgumentNullException(nameof(valueParser)); + + var valueParserAtEnd = valueParser.AtEnd(); + return input => + { + var rt = parser(input); + if (!rt.HasValue) + return TokenListParserResult.CastEmpty, U>(rt); + + var uResult = valueParserAtEnd(rt.Value.Span); + if (uResult.HasValue) + return TokenListParserResult.Value(uResult.Value, rt.Location, rt.Remainder); + + var problem = uResult.Remainder.IsAtEnd ? "incomplete" : "invalid"; + var textError = uResult.Remainder.IsAtEnd ? (uResult.Expectations != null ? $", expected {Friendly.List(uResult.Expectations)}" : "") : $", {uResult.FormatErrorMessageFragment()}"; + var message = $"{problem} {Presentation.FormatExpectation(rt.Value.Kind)}{textError}"; + return new TokenListParserResult(input, rt.Remainder, uResult.Remainder.Position, message, null, uResult.Backtrack); + }; + } + + /// + /// Construct a parser that succeeds only if the source is at the end of input. + /// + /// The type of value being parsed. + /// The parser. + /// The resulting parser. + public static TextParser AtEnd(this TextParser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return input => + { + var result = parser(input); + if (!result.HasValue) + return result; + + if (result.Remainder.IsAtEnd) + return result; + + return Result.Empty(result.Remainder); + }; + } + + /// + /// Construct a parser that succeeds only if the source is at the end of input. + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The parser. + /// The resulting parser. + public static TokenListParser AtEnd(this TokenListParser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return input => + { + var result = parser(input); + if (!result.HasValue) + return result; + + if (result.Remainder.IsAtEnd) + return result; + + return TokenListParserResult.Empty(result.Remainder); + }; + } + + /// + /// Construct a parser that matches one or more instances of applying . + /// + /// The type of value being parsed. + /// The parser. + /// The resulting parser. + public static TextParser AtLeastOnce(this TextParser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + return parser.Then(first => parser.Many().Select(rest => ArrayEnumerable.Cons(first, rest))); + } + + /// + /// Construct a parser that matches one or more instances of applying , delimited by . + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The type of the resulting value. + /// The parser. + /// The parser that matches the delimiters. + /// The resulting parser. + public static TokenListParser AtLeastOnceDelimitedBy(this TokenListParser parser, TokenListParser delimiter) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (delimiter == null) throw new ArgumentNullException(nameof(delimiter)); + + return parser.Then(first => delimiter.IgnoreThen(parser).Many().Select(rest => ArrayEnumerable.Cons(first, rest))); + } + + /// + /// Construct a parser that matches , discards the resulting value, then returns the result of . + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The type of the resulting value. + /// The first parser. + /// The second parser. + /// The resulting parser. + public static TokenListParser IgnoreThen(this TokenListParser first, TokenListParser second) + { + if (first == null) throw new ArgumentNullException(nameof(first)); + if (second == null) throw new ArgumentNullException(nameof(second)); + + return input => + { + var rt = first(input); + if (!rt.HasValue) + return TokenListParserResult.CastEmpty(rt); + + var ru = second(rt.Remainder); + if (!ru.HasValue) + return ru; + + return TokenListParserResult.Value(ru.Value, input, ru.Remainder); + }; + } + + /// + /// Construct a parser that matches , discards the resulting value, then returns the result of . + /// + /// The type of value being parsed. + /// The type of the resulting value. + /// The first parser. + /// The second parser. + /// The resulting parser. + public static TextParser IgnoreThen(this TextParser first, TextParser second) + { + if (first == null) throw new ArgumentNullException(nameof(first)); + if (second == null) throw new ArgumentNullException(nameof(second)); + + return input => + { + var rt = first(input); + if (!rt.HasValue) + return Result.CastEmpty(rt); + + var ru = second(rt.Remainder); + if (!ru.HasValue) + return ru; + + return Result.Value(ru.Value, input, ru.Remainder); + }; + } + + /// + /// Construct a parser that matches zero or more times. + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The parser. + /// The resulting parser. + /// Many will fail if any item partially matches this. To modify this behavior use . + public static TokenListParser Many(this TokenListParser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return input => + { + var result = new List(); + var from = input; + var r = parser(input); + while (r.HasValue) + { + if (from == r.Remainder) // Broken parser, not a failed parsing. + throw new ParseException($"Many() cannot be applied to zero-width parsers; value {r.Value} at position {r.Location.Position}.", r.ErrorPosition); + + result.Add(r.Value); + from = r.Remainder; + r = parser(r.Remainder); + } + + if (!r.Backtrack && r.IsPartial(from)) + return TokenListParserResult.CastEmpty(r); + + return TokenListParserResult.Value(result.ToArray(), input, from); + }; + } + + /// + /// Construct a parser that matches zero or more times. + /// + /// The type of value being parsed. + /// The parser. + /// The resulting parser. + /// Many will fail if any item partially matches this. To modify this behavior use . + public static TextParser Many(this TextParser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return input => + { + var result = new List(); + var from = input; + var r = parser(input); + while (r.HasValue) + { + if (from == r.Remainder) // Broken parser, not a failed parsing. + throw new ParseException($"Many() cannot be applied to zero-width parsers; value {r.Value} at position {r.Location.Position}.", r.Location.Position); + + result.Add(r.Value); + + from = r.Remainder; + r = parser(r.Remainder); + } + + if (!r.Backtrack && r.IsPartial(from)) + return Result.CastEmpty(r); + + return Result.Value(result.ToArray(), input, from); + }; + } + + /// + /// Construct a parser that matches zero or more times, delimited by . + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The type of the resulting value. + /// The parser. + /// The parser that matches the delimiters. + /// A parser to match a final trailing delimiter, if required. Specifying + /// this can improve error reporting for some lists. + /// The resulting parser. + public static TokenListParser ManyDelimitedBy( + this TokenListParser parser, + TokenListParser delimiter, + TokenListParser? end = null) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (delimiter == null) throw new ArgumentNullException(nameof(delimiter)); + + // ReSharper disable once ConvertClosureToMethodGroup + + if (end != null) + return parser + .AtLeastOnceDelimitedBy(delimiter) + .Then(p => end.Value(p)) + .Or(end.Value(new T[0])); + + return parser + .Then(first => delimiter.IgnoreThen(parser).Many().Select(rest => ArrayEnumerable.Cons(first, rest))) + .OptionalOrDefault(new T[0]); + } + + /// + /// Construct a parser that returns as its "expectation" if fails. + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The parser. + /// The name given to . + /// The resulting parser. + public static TokenListParser Named(this TokenListParser parser, string name) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (name == null) throw new ArgumentNullException(nameof(name)); + + return input => + { + var result = parser(input); + if (result.HasValue || result.IsPartial(input)) + return result; + + return TokenListParserResult.Empty(result.Remainder, new[] { name }); + }; + } + + /// + /// Construct a parser that returns as its "expectation" if fails. + /// + /// The type of value being parsed. + /// The parser. + /// The name given to . + /// The resulting parser. + public static TextParser Named(this TextParser parser, string name) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (name == null) throw new ArgumentNullException(nameof(name)); + + return input => + { + var result = parser(input); + if (result.HasValue || result.IsPartial(input)) + return result; + + return Result.Empty(result.Remainder, new[] { name }); + }; + } + + /// + /// Construct a parser that matches zero or one instance of , returning when + /// no match is possible. + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The parser. + /// The default value + /// The resulting parser. + public static TokenListParser OptionalOrDefault(this TokenListParser parser, T defaultValue = default!) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.Or(Parse.Return(defaultValue!)); + } + + /// + /// Construct a parser that matches zero or one instance of , returning when + /// no match is possible. + /// + /// The type of value being parsed. + /// The parser. + /// The default value. + /// The resulting parser. + public static TextParser OptionalOrDefault(this TextParser parser, T defaultValue = default!) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.Or(Parse.Return(defaultValue!)); + } + + /// + /// Construct a parser that tries first the parser, and if it fails, applies . + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The first parser to try. + /// The second parser to try. + /// The resulting parser. + /// Or will fail if the first item partially matches this. To modify this behavior use . + public static TokenListParser Or(this TokenListParser lhs, TokenListParser rhs) + { + if (lhs == null) throw new ArgumentNullException(nameof(lhs)); + if (rhs == null) throw new ArgumentNullException(nameof(rhs)); + + return input => + { + var first = lhs(input); + if (first.HasValue || !first.Backtrack && first.IsPartial(input)) + return first; + + var second = rhs(input); + if (second.HasValue) + return second; + + return TokenListParserResult.CombineEmpty(first, second); + }; + } + + /// + /// Construct a parser that tries first the parser, and if it fails, applies . + /// + /// The type of value being parsed. + /// The first parser to try. + /// The second parser to try. + /// The resulting parser. + /// Or will fail if the first item partially matches this. To modify this behavior use . + public static TextParser Or(this TextParser lhs, TextParser rhs) + { + if (lhs == null) throw new ArgumentNullException(nameof(lhs)); + if (rhs == null) throw new ArgumentNullException(nameof(rhs)); + + return input => + { + var first = lhs(input); + if (first.HasValue || !first.Backtrack && first.IsPartial(input)) + return first; + + var second = rhs(input); + if (second.HasValue) + return second; + + return Result.CombineEmpty(first, second); + }; + } + + /// + /// Construct a parser that takes the result of and converts it value using . + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The type of the resulting value. + /// The parser. + /// A mapping from the first result to the second. + /// The resulting parser. + public static TokenListParser Select(this TokenListParser parser, Func selector) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (selector == null) throw new ArgumentNullException(nameof(selector)); + + return input => + { + var rt = parser(input); + if (!rt.HasValue) + return TokenListParserResult.CastEmpty(rt); + + var u = selector(rt.Value); + + return TokenListParserResult.Value(u, input, rt.Remainder); + }; + } + + /// + /// Construct a parser that takes the result of and converts it value using . + /// + /// The type of value being parsed. + /// The type of the resulting value. + /// The parser. + /// A mapping from the first result to the second. + /// The resulting parser. + public static TextParser Select(this TextParser parser, Func selector) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (selector == null) throw new ArgumentNullException(nameof(selector)); + + return input => + { + var rt = parser(input); + if (!rt.HasValue) + return Result.CastEmpty(rt); + + var u = selector(rt.Value); + + return Result.Value(u, input, rt.Remainder); + }; + } + + /// + /// Construct a parser that takes the result of and casts it to . + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The type of the resulting value. + /// The parser. + /// The resulting parser. + public static TokenListParser Cast(this TokenListParser parser) + where T: U + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.Select(rt => (U)rt); + } + + /// + /// The LINQ query comprehension pattern. + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The type of the resulting value. + /// + /// The parser. + /// A mapping from the first result to the second parser. + /// Function mapping the results of the first two parsers onto the final result. + /// The resulting parser. + public static TokenListParser SelectMany( + this TokenListParser parser, + Func> selector, + Func projector) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (selector == null) throw new ArgumentNullException(nameof(selector)); + if (projector == null) throw new ArgumentNullException(nameof(projector)); + + return parser.Then(t => selector(t).Select(u => projector(t, u))); + } + + /// + /// Construct a parser that applies , provides the value to and returns the result. + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The type of the resulting value. + /// The first parser. + /// The second parser. + /// The resulting parser. + public static TokenListParser Then(this TokenListParser first, Func> second) + { + if (first == null) throw new ArgumentNullException(nameof(first)); + if (second == null) throw new ArgumentNullException(nameof(second)); + + return input => + { + var rt = first(input); + if (!rt.HasValue) + return TokenListParserResult.CastEmpty(rt); + + var ru = second(rt.Value)(rt.Remainder); + if (!ru.HasValue) + return ru; + + return TokenListParserResult.Value(ru.Value, input, ru.Remainder); + }; + } + + /// + /// Construct a parser that applies , provides the value to and returns the result. + /// + /// The type of value being parsed. + /// The type of the resulting value. + /// The first parser. + /// The second parser. + /// The resulting parser. + public static TextParser Then(this TextParser first, Func> second) + { + if (first == null) throw new ArgumentNullException(nameof(first)); + if (second == null) throw new ArgumentNullException(nameof(second)); + + return input => + { + var rt = first(input); + if (!rt.HasValue) + return Result.CastEmpty(rt); + + var ru = second(rt.Value)(rt.Remainder); + if (!ru.HasValue) + return ru; + + return Result.Value(ru.Value, input, ru.Remainder); + }; + } + + /// + /// Construct a parser that tries one parser, and backtracks if unsuccessful so that no input + /// appears to have been consumed by subsequent checks against the result. + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The parser. + /// The resulting parser. + public static TokenListParser Try(this TokenListParser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return input => + { + var rt = parser(input); + if (rt.HasValue) + return rt; + + rt.Backtrack = true; + return rt; + }; + } + + /// + /// Construct a parser that tries one parser, and backtracks if unsuccessful so that no input + /// appears to have been consumed by subsequent checks against the result. + /// + /// The type of value being parsed. + /// The parser. + /// The resulting parser. + public static TextParser Try(this TextParser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return input => + { + var rt = parser(input); + if (rt.HasValue) + return rt; + + rt.Backtrack = true; + return rt; + }; + } + + /// + /// Construct a parser that applies the first, and returns . + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The type of the resulting value. + /// The parser. + /// The value to return. + /// The resulting parser. + public static TokenListParser Value(this TokenListParser parser, U value) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.IgnoreThen(Parse.Return(value)); + } + + /// + /// Construct a parser that applies the first, and returns . + /// + /// The type of value being parsed. + /// The type of the resulting value. + /// The parser. + /// The value to return. + /// The resulting parser. + public static TextParser Value(this TextParser parser, U value) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.IgnoreThen(Parse.Return(value)); + } + + /// + /// Parse a sequence of operands connected by left-associative operators. + /// + /// The kind of the tokens being parsed. + /// The type of the leftmost operand and of the ultimate result. + /// The type of the operator. + /// The type of subsequent operands. + /// The parser for the leftmost operand. + /// A parser matching operators. + /// A parser matching operands. + /// A function combining the operator, left operand, and right operand, into the result. + /// The result of calling successively on pairs of operands. + public static TokenListParser Chain( + this TokenListParser parser, + TokenListParser @operator, + TokenListParser operand, + Func apply) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (@operator == null) throw new ArgumentNullException(nameof(@operator)); + if (operand == null) throw new ArgumentNullException(nameof(operand)); + if (apply == null) throw new ArgumentNullException(nameof(apply)); + + return input => + { + var parseResult = parser(input); + if (!parseResult.HasValue ) + return parseResult; + + var result = parseResult.Value; + var operandRemainder = parseResult.Remainder; + + var operatorResult = @operator(operandRemainder); + while (operatorResult.HasValue || operatorResult.IsPartial(operandRemainder)) + { + // If operator read any input, but failed to read complete input, we return error + if (!operatorResult.HasValue) + return TokenListParserResult.CastEmpty(operatorResult); + + var operandResult = operand(operatorResult.Remainder); + operandRemainder = operandResult.Remainder; + + if (!operandResult.HasValue) + return TokenListParserResult.CastEmpty(operandResult); + + result = apply(operatorResult.Value, result, operandResult.Value); + + operatorResult = @operator(operandRemainder); + } + + return TokenListParserResult.Value(result, input, operandRemainder); + }; + } + } +} diff --git a/src/Serilog.Expressions/Superpower/Display/Presentation.cs b/src/Serilog.Expressions/Superpower/Display/Presentation.cs new file mode 100644 index 0000000..1fc7f3d --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Display/Presentation.cs @@ -0,0 +1,151 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Reflection; +using Serilog.Superpower.Util; + +namespace Serilog.Superpower.Display +{ + static class Presentation + { + static string FormatKind(object kind) + { + return kind.ToString()!.ToLower(); + } + + static TokenAttribute? TryGetTokenAttribute(Type type) + { + return type.GetTypeInfo().GetCustomAttribute(); + } + + static TokenAttribute? TryGetTokenAttribute(TKind kind) + { + var kindTypeInfo = typeof(TKind).GetTypeInfo(); + if (kindTypeInfo.IsEnum) + { + var field = kindTypeInfo.GetDeclaredField(kind!.ToString()!); + if (field != null) + { + return field.GetCustomAttribute() ?? TryGetTokenAttribute(typeof(TKind)); + } + } + + return TryGetTokenAttribute(typeof(TKind)); + } + + public static string FormatExpectation(TKind kind) + { + var description = TryGetTokenAttribute(kind); + if (description != null) + { + if (description.Description != null) + return description.Description; + if (description.Example != null) + return FormatLiteral(description.Example); + } + + return FormatKind(kind!); + } + + public static string FormatAppearance(TKind kind, string value) + { + var clipped = FormatLiteral(Friendly.Clip(value, 12)); + + var description = TryGetTokenAttribute(kind); + if (description != null) + { + if (description.Category != null) + return $"{description.Category} {clipped}"; + + if (description.Example != null) + return clipped; + } + + return $"{FormatKind(kind!)} {clipped}"; + } + public static string FormatLiteral(char literal) + { + switch (literal) + { + //Unicode Category: Space Separators + case '\x00A0': return "U+00A0 no-break space"; + case '\x1680': return "U+1680 ogham space mark"; + case '\x2000': return "U+2000 en quad"; + case '\x2001': return "U+2001 em quad"; + case '\x2002': return "U+2002 en space"; + case '\x2003': return "U+2003 em space"; + case '\x2004': return "U+2004 three-per-em space"; + case '\x2005': return "U+2005 four-per-em space"; + case '\x2006': return "U+2006 six-per-em space"; + case '\x2007': return "U+2007 figure space"; + case '\x2008': return "U+2008 punctuation space"; + case '\x2009': return "U+2009 thin space"; + case '\x200A': return "U+200A hair space"; + case '\x202F': return "U+202F narrow no-break space"; + case '\x205F': return "U+205F medium mathematical space"; + case '\x3000': return "U+3000 ideographic space"; + + //Line Separator + case '\x2028': return "U+2028 line separator"; + + //Paragraph Separator + case '\x2029': return "U+2029 paragraph separator"; + + //Unicode C0 Control Codes (ASCII equivalent) + case '\x0000': return "NUL"; //\0 + case '\x0001': return "U+0001 start of heading"; + case '\x0002': return "U+0002 start of text"; + case '\x0003': return "U+0003 end of text"; + case '\x0004': return "U+0004 end of transmission"; + case '\x0005': return "U+0005 enquiry"; + case '\x0006': return "U+0006 acknowledge"; + case '\x0007': return "U+0007 bell"; + case '\x0008': return "U+0008 backspace"; + case '\x0009': return "tab"; //\t + case '\x000A': return "line feed"; //\n + case '\x000B': return "U+000B vertical tab"; + case '\x000C': return "U+000C form feed"; + case '\x000D': return "carriage return"; //\r + case '\x000E': return "U+000E shift in"; + case '\x000F': return "U+000F shift out"; + case '\x0010': return "U+0010 data link escape"; + case '\x0011': return "U+0011 device ctrl 1"; + case '\x0012': return "U+0012 device ctrl 2"; + case '\x0013': return "U+0013 device ctrl 3"; + case '\x0014': return "U+0014 device ctrl 4"; + case '\x0015': return "U+0015 not acknowledge"; + case '\x0016': return "U+0016 synchronous idle"; + case '\x0017': return "U+0017 end transmission block"; + case '\x0018': return "U+0018 cancel"; + case '\x0019': return "U+0019 end of medium"; + case '\x0020': return "space"; + case '\x001A': return "U+001A substitute"; + case '\x001B': return "U+001B escape"; + case '\x001C': return "U+001C file separator"; + case '\x001D': return "U+001D group separator"; + case '\x001E': return "U+001E record separator"; + case '\x001F': return "U+001F unit separator"; + case '\x007F': return "U+007F delete"; + + default: return "`" + literal + "`"; + } + } + + public static string FormatLiteral(string literal) + { + return "`" + literal + "`"; + } + } +} diff --git a/src/Serilog.Expressions/Superpower/Display/TokenAttribute.cs b/src/Serilog.Expressions/Superpower/Display/TokenAttribute.cs new file mode 100644 index 0000000..941add9 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Display/TokenAttribute.cs @@ -0,0 +1,43 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +// ReSharper disable UnusedAutoPropertyAccessor.Global, ClassNeverInstantiated.Global + +namespace Serilog.Superpower.Display +{ + /// + /// Applied to enum members representing tokens to control how they are rendered. + /// + [AttributeUsage(AttributeTargets.Field|AttributeTargets.Class)] + class TokenAttribute : Attribute + { + /// + /// The category of the token, e.g. "keyword" or "identifier". + /// + public string? Category { get; set; } + + /// + /// For tokens that correspond to exact text, e.g. punctuation, the canonical + /// example of how the token looks. + /// + public string? Example { get; set; } + + /// + /// A description of the token, for example "regular expression". + /// + public string? Description { get; set; } + } +} diff --git a/src/Serilog.Expressions/Superpower/Model/Position.cs b/src/Serilog.Expressions/Superpower/Model/Position.cs new file mode 100644 index 0000000..64e34d7 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Model/Position.cs @@ -0,0 +1,90 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Superpower.Model +{ + /// + /// A position within a stream of character input. + /// + readonly struct Position + { + /// + /// The zero-based absolute index of the position. + /// + public int Absolute { get; } + + /// + /// The one-based line number. + /// + public int Line { get; } + + /// + /// The one-based column number. + /// + public int Column { get; } + + /// + /// Construct a position. + /// + /// The absolute position. + /// The line number. + /// The column number. + Position(int absolute, int line, int column) + { +#if CHECKED + if (absolute < 0) throw new ArgumentOutOfRangeException(nameof(line), "Absolute positions start at 0."); + if (line < 1) throw new ArgumentOutOfRangeException(nameof(line), "Line numbering starts at 1."); + if (column < 1) throw new ArgumentOutOfRangeException(nameof(column), "Column numbering starts at 1."); +#endif + Absolute = absolute; + Line = line; + Column = column; + } + + /// + /// The position corresponding to the zero index. + /// + public static Position Zero { get; } = new Position(0, 1, 1); + + /// + /// A position with no value. + /// + public static Position Empty => default; + + /// + /// True if the position has a value. + /// + public bool HasValue => Line > 0; + + /// + /// Advance over , advancing line and column numbers + /// as appropriate. + /// + /// The character being advanced over. + /// The updated position. + public Position Advance(char overChar) + { + if (overChar == '\n') + return new Position(Absolute + 1, Line + 1, 1); + + return new Position(Absolute + 1, Line, Column + 1); + } + + /// + public override string ToString() + { + return $"{Absolute} (line {Line}, column {Column})"; + } + } +} \ No newline at end of file diff --git a/src/Serilog.Expressions/Superpower/Model/Result.cs b/src/Serilog.Expressions/Superpower/Model/Result.cs new file mode 100644 index 0000000..fe29a63 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Model/Result.cs @@ -0,0 +1,93 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Superpower.Util; + +namespace Serilog.Superpower.Model +{ + /// + /// Helper methods for working with . + /// + static class Result + { + /// + /// An empty result indicating no value could be parsed. + /// + /// The result type. + /// The start of un-parsed input. + /// A result. + public static Result Empty(TextSpan remainder) + { + return new Result(remainder, null, null, false); + } + + /// + /// An empty result indicating no value could be parsed. + /// + /// The result type. + /// The start of un-parsed input. + /// Literal descriptions of expectations not met. + /// A result. + public static Result Empty(TextSpan remainder, string[] expectations) + { + return new Result(remainder, null, expectations, false); + } + + /// + /// A result carrying a successfully-parsed value. + /// + /// The result type. + /// The value. + /// The location corresponding to the beginning of the parsed span. + /// The start of un-parsed input. + /// A result. + public static Result Value(T value, TextSpan location, TextSpan remainder) + { + return new Result(value, location, remainder, false); + } + + /// + /// Convert an empty result of one type into another. + /// + /// The source type. + /// The target type. + /// The value to convert. + /// A result of type carrying the same information as . + public static Result CastEmpty(Result result) + { + return new Result(result.Remainder, result.ErrorMessage, result.Expectations, result.Backtrack); + } + + /// + /// Combine two empty results. + /// + /// The source type. + /// The first value to combine. + /// The second value to combine. + /// A result of type carrying information from both results. + public static Result CombineEmpty(Result first, Result second) + { + if (first.Remainder != second.Remainder) + return second; + + var expectations = first.Expectations; + if (expectations == null) + expectations = second.Expectations; + else if (second.Expectations != null) + expectations = ArrayEnumerable.Concat(first.Expectations!, second.Expectations); + + return new Result(second.Remainder, second.ErrorMessage, expectations, second.Backtrack); + } + } +} diff --git a/src/Serilog.Expressions/Superpower/Model/Result`1.cs b/src/Serilog.Expressions/Superpower/Model/Result`1.cs new file mode 100644 index 0000000..0d16447 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Model/Result`1.cs @@ -0,0 +1,145 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Serilog.Superpower.Util; + +namespace Serilog.Superpower.Model +{ + /// + /// The result of parsing from a text span. + /// + /// The type of the value being parsed. + struct Result + { + readonly T _value; + + /// + /// If the result is a value, the location in the input corresponding to the + /// value. If the result is an error, it's the location of the error. + /// + public TextSpan Location { get; } + + /// + /// The first un-parsed location in the input. + /// + public TextSpan Remainder { get; } + + /// + /// True if the result carries a successfully-parsed value; otherwise, false. + /// + public bool HasValue { get; } + + /// + /// If the result is an error, the source-level position of the error; otherwise, . + /// + public Position ErrorPosition => HasValue ? Position.Empty : Location.Position; + + /// + /// A provided error message, or null. + /// + public string? ErrorMessage { get; } + + /// + /// A list of expectations that were unmet, or null. + /// + public string[]? Expectations { get; } + + internal bool IsPartial(TextSpan from) => from != Remainder; + + internal bool Backtrack { get; set; } + + /// + /// The parsed value. + /// + public T Value + { + get + { + if (!HasValue) + throw new InvalidOperationException($"{nameof(Result)} has no value."); + return _value; + } + } + + internal Result(T value, TextSpan location, TextSpan remainder, bool backtrack) + { + Location = location; + Remainder = remainder; + _value = value; + HasValue = true; + ErrorMessage = null; + Expectations = null; + Backtrack = backtrack; + } + + internal Result(TextSpan remainder, string? errorMessage, string[]? expectations, bool backtrack) + { + Location = Remainder = remainder; + _value = default!; // Default value is not observable. + HasValue = false; + Expectations = expectations; + ErrorMessage = errorMessage; + Backtrack = backtrack; + } + + /// + public override string ToString() + { + if (Remainder == TextSpan.None) + return "(Empty result.)"; + + if (HasValue) + return $"Successful parsing of {Value}."; + + var message = FormatErrorMessageFragment(); + var location = ""; + if (!Location.IsAtEnd) + { + location = $" (line {Location.Position.Line}, column {Location.Position.Column})"; + } + + return $"Syntax error{location}: {message}."; + } + + /// + /// If the result is empty, format the fragment of text describing the error. + /// + /// The error fragment. + public string FormatErrorMessageFragment() + { + if (ErrorMessage != null) + return ErrorMessage; + + string message; + if (Location.IsAtEnd) + { + message = "unexpected end of input"; + } + else + { + var next = Location.ConsumeChar().Value; + message = $"unexpected {Display.Presentation.FormatLiteral(next)}"; + } + + if (Expectations != null) + { + var expected = Friendly.List(Expectations); + message += $", expected {expected}"; + } + + return message; + } + } +} diff --git a/src/Serilog.Expressions/Superpower/Model/TextSpan.cs b/src/Serilog.Expressions/Superpower/Model/TextSpan.cs new file mode 100644 index 0000000..0295e6d --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Model/TextSpan.cs @@ -0,0 +1,228 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace Serilog.Superpower.Model +{ + /// + /// A span of text within a larger string. + /// + readonly struct TextSpan : IEquatable + { + /// + /// The source string containing the span. + /// + public string? Source { get; } + + /// + /// The position of the start of the span within the string. + /// + public Position Position { get; } + + /// + /// The length of the span. + /// + public int Length { get; } + + /// + /// Construct a span encompassing an entire string. + /// + /// The source string. + public TextSpan(string source) + : this(source, Position.Zero, source.Length) + { + } + + /// + /// Construct a string span for a substring of . + /// + /// The source string. + /// The start of the span. + /// The length of the span. + public TextSpan(string source, Position position, int length) + { +#if CHECKED + if (source == null) throw new ArgumentNullException(nameof(source)); + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length), "The length must be non-negative."); + if (source.Length < position.Absolute + length) + throw new ArgumentOutOfRangeException(nameof(length), "The token extends beyond the end of the input."); +#endif + + Source = source; + Position = position; + Length = length; + } + + /// + /// A span with no value. + /// + public static TextSpan None => default; + + /// + /// True if the span has no content. + /// + public bool IsAtEnd + { + get + { + EnsureHasValue(); + return Length == 0; + } + } + + void EnsureHasValue() + { + if (Source == null) + throw new InvalidOperationException("String span has no value."); + } + + /// + /// Consume a character from the start of the span. + /// + /// A result with the character and remainder. + public Result ConsumeChar() + { + EnsureHasValue(); + + if (IsAtEnd) + return Result.Empty(this); + + var ch = Source![Position.Absolute]; + return Result.Value(ch, this, new TextSpan(Source, Position.Advance(ch), Length - 1)); + } + + /// + public override bool Equals(object? obj) + { + if (!(obj is TextSpan other)) + return false; + + return Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((Source?.GetHashCode() ?? 0) * 397) ^ Position.Absolute; + } + } + + /// + /// Compare a string span with another using source identity + /// semantics - same source, same position, same length. + /// + /// The other span. + /// True if the spans are the same. + public bool Equals(TextSpan other) + { + return ReferenceEquals(Source, other.Source) && + Position.Absolute == other.Position.Absolute && + Length == other.Length; + } + + /// + /// Compare two spans using source identity semantics. + /// + /// One span. + /// Another span. + /// True if the spans are the same. + public static bool operator ==(TextSpan lhs, TextSpan rhs) + { + return lhs.Equals(rhs); + } + + /// + /// Compare two spans using source identity semantics. + /// + /// One span. + /// Another span. + /// True if the spans are the different. + public static bool operator !=(TextSpan lhs, TextSpan rhs) + { + return !(lhs == rhs); + } + + /// + /// Return a new span from the start of this span to the beginning of another. + /// + /// The next span. + /// A sub-span. + public TextSpan Until(TextSpan next) + { +#if CHECKED + next.EnsureHasValue(); + if (next.Source != Source) throw new ArgumentException("The spans are on different source strings.", nameof(next)); +#endif + var charCount = next.Position.Absolute - Position.Absolute; + return First(charCount); + } + + /// + /// Return a span comprising the first characters of this span. + /// + /// The number of characters to return. + /// The sub-span. + TextSpan First(int length) + { +#if CHECKED + if (length > Length) + throw new ArgumentOutOfRangeException(nameof(length), "Length exceeds the source span's length."); +#endif + + return new TextSpan(Source!, Position, length); + } + + /// + public override string ToString() + { + if (Source == null) + return "(empty source span)"; + + return ToStringValue(); + } + + /// + /// Compute the string value of this span. + /// + /// A string with the value of this span. + public string ToStringValue() + { + EnsureHasValue(); + return Source!.Substring(Position.Absolute, Length); + } + + /// + /// Compare the contents of this span with , ignoring invariant character case. + /// + /// The string value to compare. + /// True if the values are the same ignoring case. + public bool EqualsValueIgnoreCase(string otherValue) + { + if (otherValue == null) throw new ArgumentNullException(nameof(otherValue)); + EnsureHasValue(); + if (Length != otherValue.Length) + return false; + for (var i = 0; i < Length; ++i) + { + if (char.ToUpperInvariant(Source![Position.Absolute + i]) != char.ToUpperInvariant(otherValue[i])) + return false; + } + return true; + } + } +} \ No newline at end of file diff --git a/src/Serilog.Expressions/Superpower/Model/TokenListParserResult.cs b/src/Serilog.Expressions/Superpower/Model/TokenListParserResult.cs new file mode 100644 index 0000000..5e4420b --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Model/TokenListParserResult.cs @@ -0,0 +1,116 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Superpower.Model +{ + /// + /// Helper methods for working with . + /// + static class TokenListParserResult + { + /// + /// Create a token result with no value, indicating a failure to parse any value. + /// + /// The kind of token. + /// The result type. + /// The start of un-parsed input. + /// An empty result. + public static TokenListParserResult Empty(TokenList remainder) + { + return new TokenListParserResult(remainder, Position.Empty, null, null, false); + } + + /// + /// Create a token result with no value, indicating a failure to parse any value. + /// + /// The kind of token. + /// The result type. + /// The start of un-parsed input. + /// Expectations that could not be fulfilled. + /// An empty result. + public static TokenListParserResult Empty(TokenList remainder, string[] expectations) + { + return new TokenListParserResult(remainder, Position.Empty, null, expectations, false); + } + + /// + /// Create a token result with no value, indicating a failure to parse any value. + /// + /// The kind of token. + /// The result type. + /// The start of un-parsed input. + /// An error message describing why the tokens could not be parsed. + /// An empty result. + public static TokenListParserResult Empty(TokenList remainder, string errorMessage) + { + return new TokenListParserResult(remainder, Position.Empty, errorMessage, null, false); + } + + /// + /// Create a token result with the provided value. + /// + /// The kind of token. + /// The result type. + /// The value. + /// The location where parsing began. + /// The first un-parsed location. + /// + public static TokenListParserResult Value(T value, TokenList location, TokenList remainder) + { + return new TokenListParserResult(value, location, remainder, false); + } + + /// + /// Convert an empty result of one type into another. + /// + /// The kind of token. + /// The source type. + /// The destination type. + /// The result to convert. + /// The converted result. + public static TokenListParserResult CastEmpty(TokenListParserResult result) + { + return new TokenListParserResult(result.Remainder, result.SubTokenErrorPosition, result.ErrorMessage, result.Expectations, result.Backtrack); + } + + /// + /// Combine two empty results. + /// + /// The source type. + /// The kind of token. + /// The first value to combine. + /// The second value to combine. + /// A result of type carrying information from both results. + public static TokenListParserResult CombineEmpty(TokenListParserResult first, TokenListParserResult second) + { + if (first.Remainder != second.Remainder) + return second; + + var expectations = first.Expectations; + if (expectations == null) + expectations = second.Expectations; + else if (second.Expectations != null) + { + expectations = new string[first.Expectations!.Length + second.Expectations.Length]; + var i = 0; + for (; i < first.Expectations!.Length; ++i) + expectations[i] = first.Expectations![i]; + for (var j = 0; j < second.Expectations.Length; ++i, ++j) + expectations[i] = second.Expectations[j]; + } + + return new TokenListParserResult(second.Remainder, second.SubTokenErrorPosition, first.ErrorMessage, expectations, second.Backtrack); + } + } +} \ No newline at end of file diff --git a/src/Serilog.Expressions/Superpower/Model/TokenListParserResult`2.cs b/src/Serilog.Expressions/Superpower/Model/TokenListParserResult`2.cs new file mode 100644 index 0000000..14f81b1 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Model/TokenListParserResult`2.cs @@ -0,0 +1,184 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Serilog.Superpower.Display; +using Serilog.Superpower.Util; + +namespace Serilog.Superpower.Model +{ + /// + /// The result of parsing from a token list. + /// + /// The type of the value being parsed. + /// The kind of token being parsed. + struct TokenListParserResult + { + readonly T _value; + + /// + /// If the result has a value, this carries the location of the value in the token + /// list. If the result is an error, it's the location of the error. + /// + public TokenList Location { get; } + + /// + /// The first un-parsed location in the list. + /// + public TokenList Remainder { get; } + + /// + /// True if the result carries a successfully-parsed value; otherwise, false. + /// + public bool HasValue { get; } + + /// + /// If the result is an error, the source-level position of the error; otherwise, . + /// + public Position ErrorPosition + { + get + { + if (HasValue) + return Position.Empty; + + if (SubTokenErrorPosition.HasValue) + return SubTokenErrorPosition; + + if (!Remainder.IsAtEnd) + return Remainder.ConsumeToken().Value.Position; + + return Location.ComputeEndOfInputPosition(); + } + } + + /// + /// If the result is an error, the source-level position of the error; otherwise, . + /// + public Position SubTokenErrorPosition { get; } + + /// + /// A provided error message, or null. + /// + public string? ErrorMessage { get; } + + /// + /// A list of expectations that were unmet, or null. + /// + public string[]? Expectations { get; } + + /// + /// The parsed value. + /// + public T Value + { + get + { + if (!HasValue) + throw new InvalidOperationException($"{nameof(TokenListParserResult)} has no value."); + return _value; + } + } + + internal bool IsPartial(TokenList from) => SubTokenErrorPosition.HasValue || from != Remainder; + + internal bool Backtrack { get; set; } + + internal TokenListParserResult(T value, TokenList location, TokenList remainder, bool backtrack) + { + Location = location; + Remainder = remainder; + _value = value; + HasValue = true; + SubTokenErrorPosition = Position.Empty; + ErrorMessage = null; + Expectations = null; + Backtrack = backtrack; + } + + internal TokenListParserResult(TokenList location, TokenList remainder, Position errorPosition, string? errorMessage, string[]? expectations, bool backtrack) + { + Location = location; + Remainder = remainder; + _value = default!; // Default value is not observable. + HasValue = false; + SubTokenErrorPosition = errorPosition; + ErrorMessage = errorMessage; + Expectations = expectations; + Backtrack = backtrack; + } + + internal TokenListParserResult(TokenList remainder, Position errorPosition, string? errorMessage, string[]? expectations, bool backtrack) + { + Location = Remainder = remainder; + _value = default!; // Default value is not observable. + HasValue = false; + SubTokenErrorPosition = errorPosition; + ErrorMessage = errorMessage; + Expectations = expectations; + Backtrack = backtrack; + } + + /// + public override string ToString() + { + if (Remainder == TokenList.Empty) + return "(Empty result.)"; + + if (HasValue) + return $"Successful parsing of {Value}."; + + var message = FormatErrorMessageFragment(); + var location = ""; + if (!Remainder.IsAtEnd) + { + // Since the message notes `end of input`, don't report line/column here. + var sourcePosition = SubTokenErrorPosition.HasValue ? SubTokenErrorPosition : Remainder.ConsumeToken().Value.Position; + location = $" (line {sourcePosition.Line}, column {sourcePosition.Column})"; + } + + return $"Syntax error{location}: {message}."; + } + + /// + /// If the result is empty, format the fragment of text describing the error. + /// + /// The error fragment. + string FormatErrorMessageFragment() + { + if (ErrorMessage != null) + return ErrorMessage; + + string message; + if (Remainder.IsAtEnd) + { + message = "unexpected end of input"; + } + else + { + var next = Remainder.ConsumeToken().Value; + var appearance = Presentation.FormatAppearance(next.Kind, next.ToStringValue()); + message = $"unexpected {appearance}"; + } + + if (Expectations != null) + { + var expected = Friendly.List(Expectations); + message += $", expected {expected}"; + } + + return message; + } + } +} diff --git a/src/Serilog.Expressions/Superpower/Model/TokenList`1.cs b/src/Serilog.Expressions/Superpower/Model/TokenList`1.cs new file mode 100644 index 0000000..8069ff5 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Model/TokenList`1.cs @@ -0,0 +1,182 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Serilog.Superpower.Model +{ + /// + /// A list of + /// + /// The kind of tokens held in the list. + readonly struct TokenList : IEquatable>, IEnumerable> + { + readonly Token[]? _tokens; + + /// + /// The position of the token list in the token stream. + /// + public int Position { get; } + + /// + /// Construct a token list containing . + /// + /// The tokens in the list. + public TokenList(Token[] tokens) + : this(tokens, 0) + { + if (tokens == null) throw new ArgumentNullException(nameof(tokens)); + } + + TokenList(Token[] tokens, int position) + { +#if CHECKED // Called on every advance or backtrack + if (tokens == null) throw new ArgumentNullException(nameof(tokens)); + if (position > tokens.Length) throw new ArgumentOutOfRangeException(nameof(position), "Position is past end + 1."); +#endif + + Position = position; + _tokens = tokens; + } + + /// + /// A token list with no value. + /// + public static TokenList Empty { get; } = default; + + /// + /// True if the token list contains no tokens. + /// + public bool IsAtEnd + { + get + { + EnsureHasValue(); + return Position == _tokens!.Length; + } + } + + void EnsureHasValue() + { + if (_tokens == null) + throw new InvalidOperationException("Token list has no value."); + } + + /// + /// Consume a token from the start of the list, returning a result with the token and remainder. + /// + /// + public TokenListParserResult> ConsumeToken() + { + EnsureHasValue(); + + if (IsAtEnd) + return TokenListParserResult.Empty>(this); + + var token = _tokens![Position]; + return TokenListParserResult.Value(token, this, new TokenList(_tokens, Position + 1)); + } + + /// + public IEnumerator> GetEnumerator() + { + EnsureHasValue(); + + for (var position = Position; position < _tokens!.Length; ++position) + yield return _tokens[position]; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + public override bool Equals(object? obj) + { + if (!(obj is TokenList other)) + return false; + + return Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((_tokens?.GetHashCode() ?? 0) * 397) ^ Position; + } + } + + /// + /// Compare two token lists using identity semantics - same list, same position. + /// + /// The other token list. + /// True if the token lists are the same. + public bool Equals(TokenList other) + { + return Equals(_tokens, other._tokens) && Position == other.Position; + } + + /// + /// Compare two token lists using identity semantics. + /// + /// The first token list. + /// The second token list. + /// True if the token lists are the same. + public static bool operator ==(TokenList lhs, TokenList rhs) + { + return lhs.Equals(rhs); + } + + /// + /// Compare two token lists using identity semantics. + /// + /// The first token list. + /// The second token list. + /// True if the token lists are the different. + public static bool operator !=(TokenList lhs, TokenList rhs) + { + return !(lhs == rhs); + } + + /// + public override string ToString() + { + if (_tokens == null) + return "Token list (empty)"; + + return "Token list"; + } + + // A mildly expensive way to find the "end of input" position for error reporting. + internal Position ComputeEndOfInputPosition() + { + EnsureHasValue(); + + if (_tokens!.Length == 0) + return Model.Position.Zero; + + var lastSpan = _tokens[_tokens.Length - 1].Span; + var source = lastSpan.Source; + var position = lastSpan.Position; + for (var i = position.Absolute; i < source!.Length; ++i) + position = position.Advance(source[i]); + return position; + } + } +} \ No newline at end of file diff --git a/src/Serilog.Expressions/Superpower/Model/Token`1.cs b/src/Serilog.Expressions/Superpower/Model/Token`1.cs new file mode 100644 index 0000000..58d21c7 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Model/Token`1.cs @@ -0,0 +1,69 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Superpower.Model +{ + /// + /// A token. + /// + /// The type of the token's kind. + readonly struct Token + { + /// + /// The kind of the token. + /// + public TKind Kind { get; } + + /// + /// The string span containing the value of the token. + /// + public TextSpan Span { get; } + + /// + /// Get the string value of the token. + /// + /// The token as a string. + public string ToStringValue() => Span.ToStringValue(); + + /// + /// The position of the token within the source string. + /// + public Position Position => Span.Position; + + /// + /// True if the token has a value. + /// + bool HasValue => Span != TextSpan.None; + + /// + /// Construct a token. + /// + /// The kind of the token. + /// The span holding the token's value. + public Token(TKind kind, TextSpan span) + { + Kind = kind; + Span = span; + } + + /// + public override string ToString() + { + if (!HasValue) + return "(empty token)"; + + return $"{Kind}@{Position}: {Span}"; + } + } +} \ No newline at end of file diff --git a/src/Serilog.Expressions/Superpower/Model/Unit.cs b/src/Serilog.Expressions/Superpower/Model/Unit.cs new file mode 100644 index 0000000..f9c2218 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Model/Unit.cs @@ -0,0 +1,27 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Superpower.Model +{ + /// + /// A structure with no information. + /// + struct Unit + { + /// + /// The singleton value of the struct, with no value. + /// + public static Unit Value => default; + } +} diff --git a/src/Serilog.Expressions/Superpower/Parse.cs b/src/Serilog.Expressions/Superpower/Parse.cs new file mode 100644 index 0000000..96511dc --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Parse.cs @@ -0,0 +1,129 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Serilog.Superpower.Display; +using Serilog.Superpower.Model; +using Serilog.Superpower.Util; + +namespace Serilog.Superpower +{ + /// + /// General parsing helper methods. + /// + static class Parse + { + /// + /// Parse a sequence of similar operands connected by left-associative operators. + /// + /// The type being parsed. + /// The type of the operator. + /// The kind of token being parsed. + /// A parser matching operators. + /// A parser matching operands. + /// A function combining an operator and two operands into the result. + /// The result of calling successively on pairs of operands. + /// + public static TokenListParser Chain( + TokenListParser @operator, + TokenListParser operand, + Func apply) + { + return operand.Chain(@operator, operand, apply); + } + + /// + /// Constructs a parser that will fail if the given parser succeeds, + /// and will succeed if the given parser fails. In any case, it won't + /// consume any input. It's like a negative look-ahead in a regular expression. + /// + /// The result type of the given parser. + /// The kind of token being parsed. + /// The parser to wrap + /// A parser that is the negation of the given parser. + public static TokenListParser Not(TokenListParser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return input => + { + var result = parser(input); + + if (result.HasValue) + { + // This is usually a success case for Not(), so the allocations here are a bit of a pity. + + var current = input.ConsumeToken(); + var last = result.Remainder.ConsumeToken(); + if (current.HasValue) + { + var span = last.HasValue ? + current.Value.Span.Source!.Substring(current.Value.Position.Absolute, last.Value.Position.Absolute - current.Value.Position.Absolute) : + current.Value.Span.Source!.Substring(current.Value.Position.Absolute); + return TokenListParserResult.Empty(input, $"unexpected successful parsing of {Presentation.FormatLiteral(Friendly.Clip(span, 12))}"); + } + + return TokenListParserResult.Empty(input, "unexpected successful parsing"); + } + + return TokenListParserResult.Value(Unit.Value, input, input); + }; + } + + /// + /// Lazily construct a parser, so that circular dependencies are possible. + /// + /// A function creating the parser, when required. + /// The type of value being parsed. + /// The kind of token being parsed. + /// A parser that lazily evaluates . + /// is null. + public static TokenListParser Ref(Func> reference) + { + if (reference == null) throw new ArgumentNullException(nameof(reference)); + + TokenListParser? parser = null; + + return i => + { + parser ??= reference(); + + return parser(i); + }; + } + + /// + /// Construct a parser with a fixed value. + /// + /// The value returned by the parser. + /// The type of . + /// The parser. + public static TextParser Return(T value) + { + return input => Result.Value(value, input, input); + } + + /// + /// Construct a parser with a fixed value. + /// + /// The value returned by the parser. + /// The type of . + /// The kind of token being parsed. + /// The parser. + public static TokenListParser Return(T value) + { + return input => TokenListParserResult.Value(value, input, input); + } + } +} diff --git a/src/Serilog.Expressions/Superpower/ParseException.cs b/src/Serilog.Expressions/Superpower/ParseException.cs new file mode 100644 index 0000000..d5d15c4 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/ParseException.cs @@ -0,0 +1,50 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Serilog.Superpower.Model; + +// ReSharper disable IntroduceOptionalParameters.Global, MemberCanBePrivate.Global, UnusedAutoPropertyAccessor.Global + +namespace Serilog.Superpower +{ + /// + /// Represents an error that occurs during parsing. + /// + class ParseException : Exception + { + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + /// The position of the error in the input text. + public ParseException(string message, Position errorPosition) : this(message, errorPosition, null) { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + /// The position of the error in the input text. + /// The exception that is the cause of the current exception. + public ParseException(string message, Position errorPosition, Exception? innerException) : base(message, innerException) + { + ErrorPosition = errorPosition; + } + + /// + /// The position of the error in the input text, or if no position is specified. + /// + public Position ErrorPosition { get; } + } +} diff --git a/src/Serilog.Expressions/Superpower/ParserExtensions.cs b/src/Serilog.Expressions/Superpower/ParserExtensions.cs new file mode 100644 index 0000000..8b95492 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/ParserExtensions.cs @@ -0,0 +1,41 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Serilog.Superpower.Model; + +namespace Serilog.Superpower +{ + /// + /// Helper methods for working with parsers. + /// + static class ParserExtensions + { + /// + /// Tries to parse the input without throwing an exception upon failure. + /// + /// The type of tokens consumed by the parser. + /// The type of the result. + /// The parser. + /// The input. + /// The result of the parser + /// The parser or input is null. + public static TokenListParserResult TryParse(this TokenListParser parser, TokenList input) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser(input); + } + } +} diff --git a/src/Serilog.Expressions/Superpower/Parsers/Character.cs b/src/Serilog.Expressions/Superpower/Parsers/Character.cs new file mode 100644 index 0000000..90094df --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Parsers/Character.cs @@ -0,0 +1,86 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Serilog.Superpower.Display; +using Serilog.Superpower.Model; + +namespace Serilog.Superpower.Parsers +{ + /// + /// Parsers for matching individual characters. + /// + static class Character + { + static TextParser Matching(Func predicate, string[] expectations) + { + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); + if (expectations == null) throw new ArgumentNullException(nameof(expectations)); + + return input => + { + var next = input.ConsumeChar(); + if (!next.HasValue || !predicate(next.Value)) + return Result.Empty(input, expectations); + + return next; + }; + } + + /// + /// Parse a single character matching . + /// + public static TextParser Matching(Func predicate, string name) + { + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); + if (name == null) throw new ArgumentNullException(nameof(name)); + + return Matching(predicate, new[] { name }); + } + + /// + /// Parse a single character except those matching . + /// + /// Characters not to match. + /// Description of characters that don't match. + /// A parser for characters except those matching . + static TextParser Except(Func predicate, string description) + { + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); + if (description == null) throw new ArgumentNullException(nameof(description)); + + return Matching(c => !predicate(c), "any character except " + description); + } + + /// + /// Parse a single specified character. + /// + public static TextParser EqualTo(char ch) + { + return Matching(parsed => parsed == ch, Presentation.FormatLiteral(ch)); + } + + /// + /// Parse a single character except . + /// + public static TextParser Except(char ch) + { + return Except(parsed => parsed == ch, Presentation.FormatLiteral(ch)); + } + /// + /// Parse a digit. + /// + public static TextParser Digit { get; } = Matching(char.IsDigit, "digit"); + } +} diff --git a/src/Serilog.Expressions/Superpower/Parsers/Numerics.cs b/src/Serilog.Expressions/Superpower/Parsers/Numerics.cs new file mode 100644 index 0000000..e3a0af3 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Parsers/Numerics.cs @@ -0,0 +1,78 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Superpower.Model; +using Serilog.Superpower.Util; + +namespace Serilog.Superpower.Parsers +{ + /// + /// Parsers for numeric patterns. + /// + //* Fairly large amount of duplication/repetition here, due to the lack + //* of generics over numbers in C#. + static class Numerics + { + static readonly string[] ExpectedDigit = { "digit" }; + static readonly string[] ExpectedSignOrDigit = { "sign", "digit" }; + + /// + /// A string of digits, converted into a . + /// + public static TextParser NaturalUInt32 { get; } = input => + { + var next = input.ConsumeChar(); + + if (!next.HasValue || !CharInfo.IsLatinDigit(next.Value)) + return Result.Empty(input, ExpectedDigit); + + TextSpan remainder; + var val = 0u; + do + { + val = 10 * val + (uint)(next.Value - '0'); + remainder = next.Remainder; + next = remainder.ConsumeChar(); + } while (next.HasValue && CharInfo.IsLatinDigit(next.Value)); + + return Result.Value(val, input, remainder); + }; + + /// + /// A string of digits with an optional +/- sign. + /// + public static TextParser Integer { get; } = input => + { + var next = input.ConsumeChar(); + + if (!next.HasValue) + return Result.Empty(input, ExpectedSignOrDigit); + + if (next.Value == '-' || next.Value == '+') + next = next.Remainder.ConsumeChar(); + + if (!next.HasValue || !CharInfo.IsLatinDigit(next.Value)) + return Result.Empty(input, ExpectedDigit); + + TextSpan remainder; + do + { + remainder = next.Remainder; + next = remainder.ConsumeChar(); + } while (next.HasValue && CharInfo.IsLatinDigit(next.Value)); + + return Result.Value(input.Until(remainder), input, remainder); + }; + } +} diff --git a/src/Serilog.Expressions/Superpower/Parsers/Span.cs b/src/Serilog.Expressions/Superpower/Parsers/Span.cs new file mode 100644 index 0000000..16341c6 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Parsers/Span.cs @@ -0,0 +1,56 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Serilog.Superpower.Display; +using Serilog.Superpower.Model; + +namespace Serilog.Superpower.Parsers +{ + /// + /// Parsers for spans of characters. + /// + static class Span + { + /// + /// Match a span equal to . + /// + /// The text to match. + /// The matched text. + public static TextParser EqualTo(string text) + { + if (text == null) throw new ArgumentNullException(nameof(text)); + + var expectations = new[] { Presentation.FormatLiteral(text) }; + return input => + { + var remainder = input; + // ReSharper disable once ForCanBeConvertedToForeach + for (var i = 0; i < text.Length; ++i) + { + var ch = remainder.ConsumeChar(); + if (!ch.HasValue || ch.Value != text[i]) + { + if (ch.Location == input) + return Result.Empty(ch.Location, expectations); + + return Result.Empty(ch.Location, new[] { Presentation.FormatLiteral(text[i]) }); + } + remainder = ch.Remainder; + } + return Result.Value(input.Until(remainder), input, remainder); + }; + } + } +} diff --git a/src/Serilog.Expressions/Superpower/Parsers/Token.cs b/src/Serilog.Expressions/Superpower/Parsers/Token.cs new file mode 100644 index 0000000..6807407 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Parsers/Token.cs @@ -0,0 +1,67 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Serilog.Superpower.Display; +using Serilog.Superpower.Model; + +namespace Serilog.Superpower.Parsers +{ + /// + /// Parsers for matching individual tokens. + /// + static class Token + { + /// + /// Parse a token of the kind . + /// + /// The type of the token being matched. + /// The kind of token to match. + /// The matched token. + // ReSharper disable once MemberCanBePrivate.Global + public static TokenListParser> EqualTo(TKind kind) + { + var expectations = new[] { Presentation.FormatExpectation(kind) }; + + return input => + { + var next = input.ConsumeToken(); + if (!next.HasValue || !next.Value.Kind!.Equals(kind)) + return TokenListParserResult.Empty>(input, expectations); + + return next; + }; + } + + /// + /// Parse a sequence of tokens of the kind . + /// + /// The type of the tokens being matched. + /// The kinds of token to match, once each in order. + /// The matched tokens. + public static TokenListParser[]> Sequence(params TKind[] kinds) + { + if (kinds == null) throw new ArgumentNullException(nameof(kinds)); + + TokenListParser[]> result = input => TokenListParserResult.Value(new Token[kinds.Length], input, input); + for (var i = 0; i < kinds.Length; ++i) + { + var token = EqualTo(kinds[i]); + var index = i; + result = result.Then(arr => token.Select(t => { arr[index] = t; return arr; })); + } + return result; + } + } +} diff --git a/src/Serilog.Expressions/Superpower/README.md b/src/Serilog.Expressions/Superpower/README.md new file mode 100644 index 0000000..8894ca4 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/README.md @@ -0,0 +1,218 @@ +# Superpower + +The classes in this namespace are from [Superpower](https://github.com/datalust/superpower). + +They are included here because _Serilog.Expressions_ aims to eventually be integrated at a lower +level in the Serilog ecosystem, where being dependency-free is advantageous. + +_Serilog.Expressions_ only uses a small slice of Superpower; if something you need appears to +be missing, it's probably been shaken out and can be retrieved again from the original codebase +for inclusion. + +The Superpower tests have not been brought across; as only code used in _Serilog.Expressions_ +remains here, we should be able to achieve good test coverage by testing the expression parser +implementation. + +## License + +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright {yyyy} {name of copyright owner} + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/Serilog.Expressions/Superpower/TextParser`1.cs b/src/Serilog.Expressions/Superpower/TextParser`1.cs new file mode 100644 index 0000000..771e360 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/TextParser`1.cs @@ -0,0 +1,26 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Superpower.Model; + +namespace Serilog.Superpower +{ + /// + /// A parser that consumes text from a string span. + /// + /// The type of values produced by the parser. + /// The span of text to parse. + /// A result with a parsed value, or an empty result indicating error. + delegate Result TextParser(TextSpan input); +} \ No newline at end of file diff --git a/src/Serilog.Expressions/Superpower/TokenListParser`2.cs b/src/Serilog.Expressions/Superpower/TokenListParser`2.cs new file mode 100644 index 0000000..fd11692 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/TokenListParser`2.cs @@ -0,0 +1,27 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Superpower.Model; + +namespace Serilog.Superpower +{ + /// + /// A parser that consumes elements from a list of tokens. + /// + /// The type of values produced by the parser. + /// The type of tokens being parsed. + /// The list of tokens to parse. + /// A result with a parsed value, or an empty result indicating error. + delegate TokenListParserResult TokenListParser(TokenList input); +} \ No newline at end of file diff --git a/src/Serilog.Expressions/Superpower/Tokenizer`1.cs b/src/Serilog.Expressions/Superpower/Tokenizer`1.cs new file mode 100644 index 0000000..b1d788d --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Tokenizer`1.cs @@ -0,0 +1,96 @@ +// Copyright 2016-2018 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using Serilog.Superpower.Display; +using Serilog.Superpower.Model; + +namespace Serilog.Superpower +{ + /// + /// Base class for tokenizers, types whose instances convert strings into lists of tokens. + /// + /// The kind of tokens produced. + abstract class Tokenizer + { + /// + /// Tokenize . + /// + /// The source to tokenize. + /// The list of tokens or an error. + /// Tokenization failed. + public TokenList Tokenize(string source) + { + var result = TryTokenize(source); + if (result.HasValue) + return result.Value; + + throw new ParseException(result.ToString(), result.ErrorPosition); + } + + /// + /// Tokenize . + /// + /// The source to tokenize. + /// A result with the list of tokens or an error. + /// is null. + /// The tokenizer could not correctly perform tokenization. + public Result> TryTokenize(string source) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + + var sourceSpan = new TextSpan(source); + var remainder = sourceSpan; + var results = new List>(); + foreach (var result in Tokenize(sourceSpan)) + { + if (!result.HasValue) + return Result.CastEmpty>(result); + + if (result.Remainder == remainder) // Broken parser, not a failed parsing. + throw new ParseException($"Zero-width tokens are not supported; token {Presentation.FormatExpectation(result.Value)} at position {result.Location.Position}.", result.Location.Position); + + remainder = result.Remainder; + var token = new Token(result.Value, result.Location.Until(result.Remainder)); + results.Add(token); + } + + var value = new TokenList(results.ToArray()); + return Result.Value(value, sourceSpan, remainder); + } + + /// + /// Subclasses should override to perform tokenization. + /// + /// The input span to tokenize. + /// A list of parsed tokens. + protected abstract IEnumerable> Tokenize(TextSpan span); + + /// + /// Advance until the first non-whitespace character is encountered. + /// + /// The span to advance from. + /// A result with the first non-whitespace character. + protected static Result SkipWhiteSpace(TextSpan span) + { + var next = span.ConsumeChar(); + while (next.HasValue && char.IsWhiteSpace(next.Value)) + { + next = next.Remainder.ConsumeChar(); + } + return next; + } + } +} \ No newline at end of file diff --git a/src/Serilog.Expressions/Superpower/Util/ArrayEnumerable.cs b/src/Serilog.Expressions/Superpower/Util/ArrayEnumerable.cs new file mode 100644 index 0000000..131b2de --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Util/ArrayEnumerable.cs @@ -0,0 +1,43 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Runtime.CompilerServices; + +namespace Serilog.Superpower.Util +{ + static class ArrayEnumerable + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T[] Cons(T first, T[] rest) + { + var all = new T[rest.Length + 1]; + all[0] = first; + for (var i = 0; i < rest.Length; ++i) + all[i + 1] = rest[i]; + return all; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T[] Concat(T[] first, T[] rest) + { + var all = new T[first.Length + rest.Length]; + var i = 0; + for (; i < first.Length; ++i) + all[i] = first[i]; + for (var j = 0; j < rest.Length; ++i, ++j) + all[i] = rest[j]; + return all; + } + } +} diff --git a/src/Serilog.Expressions/Superpower/Util/CharInfo.cs b/src/Serilog.Expressions/Superpower/Util/CharInfo.cs new file mode 100644 index 0000000..199d6f0 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Util/CharInfo.cs @@ -0,0 +1,24 @@ +// Copyright 2018 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Superpower.Util +{ + static class CharInfo + { + public static bool IsLatinDigit(char ch) + { + return ch >= '0' && ch <= '9'; + } + } +} \ No newline at end of file diff --git a/src/Serilog.Expressions/Superpower/Util/Friendly.cs b/src/Serilog.Expressions/Superpower/Util/Friendly.cs new file mode 100644 index 0000000..4e76525 --- /dev/null +++ b/src/Serilog.Expressions/Superpower/Util/Friendly.cs @@ -0,0 +1,53 @@ +// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Serilog.Superpower.Util +{ + static class Friendly + { + public static string List(IEnumerable items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + // Keep the order stable + var seen = new HashSet(); + var unique = new List(); + foreach (var item in items) + { + if (seen.Contains(item)) continue; + seen.Add(item); + unique.Add(item); + } + + if (unique.Count == 0) + throw new ArgumentException("Friendly list formatting requires at least one element.", nameof(items)); + + if (unique.Count == 1) + return unique.Single(); + + return $"{string.Join(", ", unique.Take(unique.Count - 1))} or {unique.Last()}"; + } + + public static string Clip(string value, int maxLength) + { + if (value.Length > maxLength) + return value.Substring(0, maxLength - 3) + "..."; + return value; + } + } +} \ No newline at end of file diff --git a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs index b947fba..a51f6be 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs @@ -1,10 +1,10 @@ using Serilog.Expressions.Ast; using Serilog.Expressions.Parsing; using Serilog.Parsing; +using Serilog.Superpower; +using Serilog.Superpower.Model; +using Serilog.Superpower.Parsers; using Serilog.Templates.Ast; -using Superpower; -using Superpower.Model; -using Superpower.Parsers; using static Serilog.Expressions.Parsing.ExpressionToken; // ReSharper disable SuggestBaseTypeForParameter, ConvertIfStatementToSwitchStatement, AccessToModifiedClosure @@ -65,12 +65,12 @@ from __ in Token.EqualTo(RBrace) var conditional = from iff in Directive(true, If) - from consequent in Parse.Ref(() => block) + from consequent in Parse.Ref(() => block!) from alternatives in Directive(true, Else, If) - .Then(elsif => Parse.Ref(() => block).Select(b => (elsif, b))) + .Then(elsif => Parse.Ref(() => block!).Select(b => (elsif, b))) .Many() from final in Directive(false, Else) - .IgnoreThen(Parse.Ref(() => block).Select(b => ((Expression?) null, b))) + .IgnoreThen(Parse.Ref(() => block!).Select(b => ((Expression?) null, b))) .OptionalOrDefault() from end in Directive(false, End) let firstAlt = LeftReduceConditional(alternatives, final.b) @@ -89,13 +89,13 @@ from end in Directive(false, End) var repetition = from each in eachDirective - from body in Parse.Ref(() => block) + from body in Parse.Ref(() => block!) from delimiter in Directive(false, Delimit) - .IgnoreThen(Parse.Ref(() => block)) + .IgnoreThen(Parse.Ref(() => block!)) .Cast() .OptionalOrDefault() from alternative in Directive(false, Else) - .IgnoreThen(Parse.Ref(() => block)) + .IgnoreThen(Parse.Ref(() => block!)) .Cast() .OptionalOrDefault() from end in Directive(false, End) diff --git a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs index cb97d19..b8d09b3 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Serilog.Expressions.Parsing; -using Superpower; -using Superpower.Model; +using Serilog.Superpower; +using Serilog.Superpower.Model; namespace Serilog.Templates.Parsing { From da683a0248351077f81679564dd494ea8cc80fb1 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 24 May 2021 19:56:40 +1000 Subject: [PATCH 3/7] Dropping a dependency is a breaking change; bump dev version to 3.0 --- src/Serilog.Expressions/Serilog.Expressions.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Expressions/Serilog.Expressions.csproj b/src/Serilog.Expressions/Serilog.Expressions.csproj index 2412662..be9f9b4 100644 --- a/src/Serilog.Expressions/Serilog.Expressions.csproj +++ b/src/Serilog.Expressions/Serilog.Expressions.csproj @@ -3,7 +3,7 @@ An embeddable mini-language for filtering, enriching, and formatting Serilog events, ideal for use with JSON or XML configuration. - 2.0.1 + 3.0.0 Serilog Contributors netstandard2.0;netstandard2.1 true From dea5189c4ef97d1176d157edfc215ab31b576941 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 25 May 2021 16:57:08 +1000 Subject: [PATCH 4/7] net5.0 target; fixes; use ParserConstruction namespace for Superpower types --- .../Expressions/Ast/ConstantExpression.cs | 2 +- .../Expressions/Parsing/Combinators.cs | 2 -- .../Expressions/Parsing/ExpressionTextParsers.cs | 6 +----- .../Expressions/Parsing/ExpressionToken.cs | 4 +--- .../Expressions/Parsing/ExpressionTokenParsers.cs | 3 --- .../Expressions/Parsing/ExpressionTokenizer.cs | 2 -- .../Expressions/Parsing/ParserExtensions.cs | 2 -- src/Serilog.Expressions/Expressions/Runtime/Coerce.cs | 2 +- .../Expressions/Runtime/RuntimeOperators.cs | 6 +++--- .../Expressions/StaticMemberNameResolver.cs | 5 +++-- .../{Superpower => ParserConstruction}/Combinators.cs | 8 ++++---- .../Display/Presentation.cs | 4 ++-- .../Display/TokenAttribute.cs | 2 +- .../{Superpower => ParserConstruction}/Model/Position.cs | 2 +- .../{Superpower => ParserConstruction}/Model/Result.cs | 4 ++-- .../{Superpower => ParserConstruction}/Model/Result`1.cs | 4 ++-- .../{Superpower => ParserConstruction}/Model/TextSpan.cs | 2 +- .../Model/TokenListParserResult.cs | 2 +- .../Model/TokenListParserResult`2.cs | 6 +++--- .../Model/TokenList`1.cs | 2 +- .../{Superpower => ParserConstruction}/Model/Token`1.cs | 2 +- .../{Superpower => ParserConstruction}/Model/Unit.cs | 2 +- .../{Superpower => ParserConstruction}/Parse.cs | 8 ++++---- .../{Superpower => ParserConstruction}/ParseException.cs | 4 ++-- .../ParserExtensions.cs | 4 ++-- .../Parsers/Character.cs | 6 +++--- .../Parsers/Numerics.cs | 6 +++--- .../{Superpower => ParserConstruction}/Parsers/Span.cs | 6 +++--- .../{Superpower => ParserConstruction}/Parsers/Token.cs | 6 +++--- .../{Superpower => ParserConstruction}/README.md | 0 .../{Superpower => ParserConstruction}/TextParser`1.cs | 4 ++-- .../TokenListParser`2.cs | 4 ++-- .../{Superpower => ParserConstruction}/Tokenizer`1.cs | 6 +++--- .../Util/ArrayEnumerable.cs | 2 +- .../{Superpower => ParserConstruction}/Util/CharInfo.cs | 2 +- .../{Superpower => ParserConstruction}/Util/Friendly.cs | 2 +- src/Serilog.Expressions/Serilog.Expressions.csproj | 4 ++-- .../Templates/Parsing/TemplateTokenParsers.cs | 3 --- .../Templates/Parsing/TemplateTokenizer.cs | 2 -- 39 files changed, 62 insertions(+), 81 deletions(-) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Combinators.cs (99%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Display/Presentation.cs (98%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Display/TokenAttribute.cs (97%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Model/Position.cs (98%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Model/Result.cs (97%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Model/Result`1.cs (98%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Model/TextSpan.cs (99%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Model/TokenListParserResult.cs (99%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Model/TokenListParserResult`2.cs (98%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Model/TokenList`1.cs (99%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Model/Token`1.cs (98%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Model/Unit.cs (95%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Parse.cs (97%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/ParseException.cs (96%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/ParserExtensions.cs (95%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Parsers/Character.cs (96%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Parsers/Numerics.cs (95%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Parsers/Span.cs (94%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Parsers/Token.cs (95%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/README.md (100%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/TextParser`1.cs (93%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/TokenListParser`2.cs (93%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Tokenizer`1.cs (97%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Util/ArrayEnumerable.cs (97%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Util/CharInfo.cs (94%) rename src/Serilog.Expressions/{Superpower => ParserConstruction}/Util/Friendly.cs (97%) diff --git a/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs b/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs index f12580b..5dca402 100644 --- a/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs @@ -28,7 +28,7 @@ public override string ToString() case IFormattable formattable: return formattable.ToString(null, CultureInfo.InvariantCulture); default: - return (sv.Value ?? "null").ToString(); + return (sv.Value ?? "null").ToString() ?? ""; } } diff --git a/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs b/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs index b4ac698..d32590a 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs @@ -1,6 +1,4 @@ using System; -using Serilog.Superpower; -using Serilog.Superpower.Model; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs index 31e5a44..bf56fd1 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs @@ -1,8 +1,4 @@ -using Serilog.Superpower; -using Serilog.Superpower.Model; -using Serilog.Superpower.Parsers; - -namespace Serilog.Expressions.Parsing +namespace Serilog.Expressions.Parsing { static class ExpressionTextParsers { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs index 5e44a30..05ca1ab 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs @@ -1,6 +1,4 @@ -using Serilog.Superpower.Display; - -namespace Serilog.Expressions.Parsing +namespace Serilog.Expressions.Parsing { enum ExpressionToken { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs index b4a793b..59cc9b0 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs @@ -3,9 +3,6 @@ using System.Linq; using Serilog.Events; using Serilog.Expressions.Ast; -using Serilog.Superpower; -using Serilog.Superpower.Model; -using Serilog.Superpower.Parsers; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs index 57aa8d8..1a1c66f 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; using System.Linq; -using Serilog.Superpower; -using Serilog.Superpower.Model; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs b/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs index 91f94f0..8a5debd 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs @@ -1,6 +1,4 @@ using System; -using Serilog.Superpower; -using Serilog.Superpower.Model; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs b/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs index f3d0778..2fee8fe 100644 --- a/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs +++ b/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs @@ -62,7 +62,7 @@ public static bool String(LogEventPropertyValue? value, [MaybeNullWhen(false)] o if (sv.Value?.GetType().IsEnum ?? false) { - str = sv.Value.ToString(); + str = sv.Value.ToString()!; return true; } } diff --git a/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs b/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs index a926379..b2e2f95 100644 --- a/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs +++ b/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs @@ -470,14 +470,14 @@ public static LogEventPropertyValue _Internal_IsNotNull(LogEventPropertyValue? v public static LogEventPropertyValue? ToString(LogEventPropertyValue? value, LogEventPropertyValue? format) { - if (!(value is ScalarValue sv) || + if (value is not ScalarValue sv || sv.Value == null || - !(Coerce.String(format, out var fmt) || format == null || format is ScalarValue { Value: null })) + !(Coerce.String(format, out var fmt) || format is null or ScalarValue { Value: null })) { return null; } - string toString; + string? toString; if (sv.Value is IFormattable formattable) { // TODO #19: formatting is culture-specific. diff --git a/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs b/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs index 88cb5e8..d28c01c 100644 --- a/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs +++ b/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -27,9 +28,9 @@ public StaticMemberNameResolver(Type type) } /// - public override bool TryResolveFunctionName(string name, out MethodInfo implementation) + public override bool TryResolveFunctionName(string name, [MaybeNullWhen(false)] out MethodInfo implementation) { return _methods.TryGetValue(name, out implementation); } } -} \ No newline at end of file +} diff --git a/src/Serilog.Expressions/Superpower/Combinators.cs b/src/Serilog.Expressions/ParserConstruction/Combinators.cs similarity index 99% rename from src/Serilog.Expressions/Superpower/Combinators.cs rename to src/Serilog.Expressions/ParserConstruction/Combinators.cs index d42fbee..fc0692a 100644 --- a/src/Serilog.Expressions/Superpower/Combinators.cs +++ b/src/Serilog.Expressions/ParserConstruction/Combinators.cs @@ -14,13 +14,13 @@ using System; using System.Collections.Generic; -using Serilog.Superpower.Display; -using Serilog.Superpower.Model; -using Serilog.Superpower.Util; +using Serilog.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; +using Serilog.ParserConstruction.Util; // ReSharper disable MemberCanBePrivate.Global -namespace Serilog.Superpower +namespace Serilog.ParserConstruction { /// /// Functions that construct more complex parsers by combining simpler ones. diff --git a/src/Serilog.Expressions/Superpower/Display/Presentation.cs b/src/Serilog.Expressions/ParserConstruction/Display/Presentation.cs similarity index 98% rename from src/Serilog.Expressions/Superpower/Display/Presentation.cs rename to src/Serilog.Expressions/ParserConstruction/Display/Presentation.cs index 1fc7f3d..faac226 100644 --- a/src/Serilog.Expressions/Superpower/Display/Presentation.cs +++ b/src/Serilog.Expressions/ParserConstruction/Display/Presentation.cs @@ -14,9 +14,9 @@ using System; using System.Reflection; -using Serilog.Superpower.Util; +using Serilog.ParserConstruction.Util; -namespace Serilog.Superpower.Display +namespace Serilog.ParserConstruction.Display { static class Presentation { diff --git a/src/Serilog.Expressions/Superpower/Display/TokenAttribute.cs b/src/Serilog.Expressions/ParserConstruction/Display/TokenAttribute.cs similarity index 97% rename from src/Serilog.Expressions/Superpower/Display/TokenAttribute.cs rename to src/Serilog.Expressions/ParserConstruction/Display/TokenAttribute.cs index 941add9..89dabdc 100644 --- a/src/Serilog.Expressions/Superpower/Display/TokenAttribute.cs +++ b/src/Serilog.Expressions/ParserConstruction/Display/TokenAttribute.cs @@ -16,7 +16,7 @@ // ReSharper disable UnusedAutoPropertyAccessor.Global, ClassNeverInstantiated.Global -namespace Serilog.Superpower.Display +namespace Serilog.ParserConstruction.Display { /// /// Applied to enum members representing tokens to control how they are rendered. diff --git a/src/Serilog.Expressions/Superpower/Model/Position.cs b/src/Serilog.Expressions/ParserConstruction/Model/Position.cs similarity index 98% rename from src/Serilog.Expressions/Superpower/Model/Position.cs rename to src/Serilog.Expressions/ParserConstruction/Model/Position.cs index 64e34d7..dcace0d 100644 --- a/src/Serilog.Expressions/Superpower/Model/Position.cs +++ b/src/Serilog.Expressions/ParserConstruction/Model/Position.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Serilog.Superpower.Model +namespace Serilog.ParserConstruction.Model { /// /// A position within a stream of character input. diff --git a/src/Serilog.Expressions/Superpower/Model/Result.cs b/src/Serilog.Expressions/ParserConstruction/Model/Result.cs similarity index 97% rename from src/Serilog.Expressions/Superpower/Model/Result.cs rename to src/Serilog.Expressions/ParserConstruction/Model/Result.cs index fe29a63..1883262 100644 --- a/src/Serilog.Expressions/Superpower/Model/Result.cs +++ b/src/Serilog.Expressions/ParserConstruction/Model/Result.cs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Serilog.Superpower.Util; +using Serilog.ParserConstruction.Util; -namespace Serilog.Superpower.Model +namespace Serilog.ParserConstruction.Model { /// /// Helper methods for working with . diff --git a/src/Serilog.Expressions/Superpower/Model/Result`1.cs b/src/Serilog.Expressions/ParserConstruction/Model/Result`1.cs similarity index 98% rename from src/Serilog.Expressions/Superpower/Model/Result`1.cs rename to src/Serilog.Expressions/ParserConstruction/Model/Result`1.cs index 0d16447..8e9fbde 100644 --- a/src/Serilog.Expressions/Superpower/Model/Result`1.cs +++ b/src/Serilog.Expressions/ParserConstruction/Model/Result`1.cs @@ -13,9 +13,9 @@ // limitations under the License. using System; -using Serilog.Superpower.Util; +using Serilog.ParserConstruction.Util; -namespace Serilog.Superpower.Model +namespace Serilog.ParserConstruction.Model { /// /// The result of parsing from a text span. diff --git a/src/Serilog.Expressions/Superpower/Model/TextSpan.cs b/src/Serilog.Expressions/ParserConstruction/Model/TextSpan.cs similarity index 99% rename from src/Serilog.Expressions/Superpower/Model/TextSpan.cs rename to src/Serilog.Expressions/ParserConstruction/Model/TextSpan.cs index 0295e6d..59f87c1 100644 --- a/src/Serilog.Expressions/Superpower/Model/TextSpan.cs +++ b/src/Serilog.Expressions/ParserConstruction/Model/TextSpan.cs @@ -14,7 +14,7 @@ using System; -namespace Serilog.Superpower.Model +namespace Serilog.ParserConstruction.Model { /// /// A span of text within a larger string. diff --git a/src/Serilog.Expressions/Superpower/Model/TokenListParserResult.cs b/src/Serilog.Expressions/ParserConstruction/Model/TokenListParserResult.cs similarity index 99% rename from src/Serilog.Expressions/Superpower/Model/TokenListParserResult.cs rename to src/Serilog.Expressions/ParserConstruction/Model/TokenListParserResult.cs index 5e4420b..fd82086 100644 --- a/src/Serilog.Expressions/Superpower/Model/TokenListParserResult.cs +++ b/src/Serilog.Expressions/ParserConstruction/Model/TokenListParserResult.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Serilog.Superpower.Model +namespace Serilog.ParserConstruction.Model { /// /// Helper methods for working with . diff --git a/src/Serilog.Expressions/Superpower/Model/TokenListParserResult`2.cs b/src/Serilog.Expressions/ParserConstruction/Model/TokenListParserResult`2.cs similarity index 98% rename from src/Serilog.Expressions/Superpower/Model/TokenListParserResult`2.cs rename to src/Serilog.Expressions/ParserConstruction/Model/TokenListParserResult`2.cs index 14f81b1..ac04621 100644 --- a/src/Serilog.Expressions/Superpower/Model/TokenListParserResult`2.cs +++ b/src/Serilog.Expressions/ParserConstruction/Model/TokenListParserResult`2.cs @@ -13,10 +13,10 @@ // limitations under the License. using System; -using Serilog.Superpower.Display; -using Serilog.Superpower.Util; +using Serilog.ParserConstruction.Display; +using Serilog.ParserConstruction.Util; -namespace Serilog.Superpower.Model +namespace Serilog.ParserConstruction.Model { /// /// The result of parsing from a token list. diff --git a/src/Serilog.Expressions/Superpower/Model/TokenList`1.cs b/src/Serilog.Expressions/ParserConstruction/Model/TokenList`1.cs similarity index 99% rename from src/Serilog.Expressions/Superpower/Model/TokenList`1.cs rename to src/Serilog.Expressions/ParserConstruction/Model/TokenList`1.cs index 8069ff5..83787cc 100644 --- a/src/Serilog.Expressions/Superpower/Model/TokenList`1.cs +++ b/src/Serilog.Expressions/ParserConstruction/Model/TokenList`1.cs @@ -16,7 +16,7 @@ using System.Collections; using System.Collections.Generic; -namespace Serilog.Superpower.Model +namespace Serilog.ParserConstruction.Model { /// /// A list of diff --git a/src/Serilog.Expressions/Superpower/Model/Token`1.cs b/src/Serilog.Expressions/ParserConstruction/Model/Token`1.cs similarity index 98% rename from src/Serilog.Expressions/Superpower/Model/Token`1.cs rename to src/Serilog.Expressions/ParserConstruction/Model/Token`1.cs index 58d21c7..91cbe2e 100644 --- a/src/Serilog.Expressions/Superpower/Model/Token`1.cs +++ b/src/Serilog.Expressions/ParserConstruction/Model/Token`1.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Serilog.Superpower.Model +namespace Serilog.ParserConstruction.Model { /// /// A token. diff --git a/src/Serilog.Expressions/Superpower/Model/Unit.cs b/src/Serilog.Expressions/ParserConstruction/Model/Unit.cs similarity index 95% rename from src/Serilog.Expressions/Superpower/Model/Unit.cs rename to src/Serilog.Expressions/ParserConstruction/Model/Unit.cs index f9c2218..e5212c7 100644 --- a/src/Serilog.Expressions/Superpower/Model/Unit.cs +++ b/src/Serilog.Expressions/ParserConstruction/Model/Unit.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Serilog.Superpower.Model +namespace Serilog.ParserConstruction.Model { /// /// A structure with no information. diff --git a/src/Serilog.Expressions/Superpower/Parse.cs b/src/Serilog.Expressions/ParserConstruction/Parse.cs similarity index 97% rename from src/Serilog.Expressions/Superpower/Parse.cs rename to src/Serilog.Expressions/ParserConstruction/Parse.cs index 96511dc..be1d458 100644 --- a/src/Serilog.Expressions/Superpower/Parse.cs +++ b/src/Serilog.Expressions/ParserConstruction/Parse.cs @@ -13,11 +13,11 @@ // limitations under the License. using System; -using Serilog.Superpower.Display; -using Serilog.Superpower.Model; -using Serilog.Superpower.Util; +using Serilog.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; +using Serilog.ParserConstruction.Util; -namespace Serilog.Superpower +namespace Serilog.ParserConstruction { /// /// General parsing helper methods. diff --git a/src/Serilog.Expressions/Superpower/ParseException.cs b/src/Serilog.Expressions/ParserConstruction/ParseException.cs similarity index 96% rename from src/Serilog.Expressions/Superpower/ParseException.cs rename to src/Serilog.Expressions/ParserConstruction/ParseException.cs index d5d15c4..e20334a 100644 --- a/src/Serilog.Expressions/Superpower/ParseException.cs +++ b/src/Serilog.Expressions/ParserConstruction/ParseException.cs @@ -13,11 +13,11 @@ // limitations under the License. using System; -using Serilog.Superpower.Model; +using Serilog.ParserConstruction.Model; // ReSharper disable IntroduceOptionalParameters.Global, MemberCanBePrivate.Global, UnusedAutoPropertyAccessor.Global -namespace Serilog.Superpower +namespace Serilog.ParserConstruction { /// /// Represents an error that occurs during parsing. diff --git a/src/Serilog.Expressions/Superpower/ParserExtensions.cs b/src/Serilog.Expressions/ParserConstruction/ParserExtensions.cs similarity index 95% rename from src/Serilog.Expressions/Superpower/ParserExtensions.cs rename to src/Serilog.Expressions/ParserConstruction/ParserExtensions.cs index 8b95492..3c67dc2 100644 --- a/src/Serilog.Expressions/Superpower/ParserExtensions.cs +++ b/src/Serilog.Expressions/ParserConstruction/ParserExtensions.cs @@ -13,9 +13,9 @@ // limitations under the License. using System; -using Serilog.Superpower.Model; +using Serilog.ParserConstruction.Model; -namespace Serilog.Superpower +namespace Serilog.ParserConstruction { /// /// Helper methods for working with parsers. diff --git a/src/Serilog.Expressions/Superpower/Parsers/Character.cs b/src/Serilog.Expressions/ParserConstruction/Parsers/Character.cs similarity index 96% rename from src/Serilog.Expressions/Superpower/Parsers/Character.cs rename to src/Serilog.Expressions/ParserConstruction/Parsers/Character.cs index 90094df..e7211e9 100644 --- a/src/Serilog.Expressions/Superpower/Parsers/Character.cs +++ b/src/Serilog.Expressions/ParserConstruction/Parsers/Character.cs @@ -13,10 +13,10 @@ // limitations under the License. using System; -using Serilog.Superpower.Display; -using Serilog.Superpower.Model; +using Serilog.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; -namespace Serilog.Superpower.Parsers +namespace Serilog.ParserConstruction.Parsers { /// /// Parsers for matching individual characters. diff --git a/src/Serilog.Expressions/Superpower/Parsers/Numerics.cs b/src/Serilog.Expressions/ParserConstruction/Parsers/Numerics.cs similarity index 95% rename from src/Serilog.Expressions/Superpower/Parsers/Numerics.cs rename to src/Serilog.Expressions/ParserConstruction/Parsers/Numerics.cs index e3a0af3..7494247 100644 --- a/src/Serilog.Expressions/Superpower/Parsers/Numerics.cs +++ b/src/Serilog.Expressions/ParserConstruction/Parsers/Numerics.cs @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Serilog.Superpower.Model; -using Serilog.Superpower.Util; +using Serilog.ParserConstruction.Model; +using Serilog.ParserConstruction.Util; -namespace Serilog.Superpower.Parsers +namespace Serilog.ParserConstruction.Parsers { /// /// Parsers for numeric patterns. diff --git a/src/Serilog.Expressions/Superpower/Parsers/Span.cs b/src/Serilog.Expressions/ParserConstruction/Parsers/Span.cs similarity index 94% rename from src/Serilog.Expressions/Superpower/Parsers/Span.cs rename to src/Serilog.Expressions/ParserConstruction/Parsers/Span.cs index 16341c6..f6cfdc6 100644 --- a/src/Serilog.Expressions/Superpower/Parsers/Span.cs +++ b/src/Serilog.Expressions/ParserConstruction/Parsers/Span.cs @@ -13,10 +13,10 @@ // limitations under the License. using System; -using Serilog.Superpower.Display; -using Serilog.Superpower.Model; +using Serilog.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; -namespace Serilog.Superpower.Parsers +namespace Serilog.ParserConstruction.Parsers { /// /// Parsers for spans of characters. diff --git a/src/Serilog.Expressions/Superpower/Parsers/Token.cs b/src/Serilog.Expressions/ParserConstruction/Parsers/Token.cs similarity index 95% rename from src/Serilog.Expressions/Superpower/Parsers/Token.cs rename to src/Serilog.Expressions/ParserConstruction/Parsers/Token.cs index 6807407..faed524 100644 --- a/src/Serilog.Expressions/Superpower/Parsers/Token.cs +++ b/src/Serilog.Expressions/ParserConstruction/Parsers/Token.cs @@ -13,10 +13,10 @@ // limitations under the License. using System; -using Serilog.Superpower.Display; -using Serilog.Superpower.Model; +using Serilog.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; -namespace Serilog.Superpower.Parsers +namespace Serilog.ParserConstruction.Parsers { /// /// Parsers for matching individual tokens. diff --git a/src/Serilog.Expressions/Superpower/README.md b/src/Serilog.Expressions/ParserConstruction/README.md similarity index 100% rename from src/Serilog.Expressions/Superpower/README.md rename to src/Serilog.Expressions/ParserConstruction/README.md diff --git a/src/Serilog.Expressions/Superpower/TextParser`1.cs b/src/Serilog.Expressions/ParserConstruction/TextParser`1.cs similarity index 93% rename from src/Serilog.Expressions/Superpower/TextParser`1.cs rename to src/Serilog.Expressions/ParserConstruction/TextParser`1.cs index 771e360..033fdb3 100644 --- a/src/Serilog.Expressions/Superpower/TextParser`1.cs +++ b/src/Serilog.Expressions/ParserConstruction/TextParser`1.cs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Serilog.Superpower.Model; +using Serilog.ParserConstruction.Model; -namespace Serilog.Superpower +namespace Serilog.ParserConstruction { /// /// A parser that consumes text from a string span. diff --git a/src/Serilog.Expressions/Superpower/TokenListParser`2.cs b/src/Serilog.Expressions/ParserConstruction/TokenListParser`2.cs similarity index 93% rename from src/Serilog.Expressions/Superpower/TokenListParser`2.cs rename to src/Serilog.Expressions/ParserConstruction/TokenListParser`2.cs index fd11692..1ddb61c 100644 --- a/src/Serilog.Expressions/Superpower/TokenListParser`2.cs +++ b/src/Serilog.Expressions/ParserConstruction/TokenListParser`2.cs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Serilog.Superpower.Model; +using Serilog.ParserConstruction.Model; -namespace Serilog.Superpower +namespace Serilog.ParserConstruction { /// /// A parser that consumes elements from a list of tokens. diff --git a/src/Serilog.Expressions/Superpower/Tokenizer`1.cs b/src/Serilog.Expressions/ParserConstruction/Tokenizer`1.cs similarity index 97% rename from src/Serilog.Expressions/Superpower/Tokenizer`1.cs rename to src/Serilog.Expressions/ParserConstruction/Tokenizer`1.cs index b1d788d..1454676 100644 --- a/src/Serilog.Expressions/Superpower/Tokenizer`1.cs +++ b/src/Serilog.Expressions/ParserConstruction/Tokenizer`1.cs @@ -14,10 +14,10 @@ using System; using System.Collections.Generic; -using Serilog.Superpower.Display; -using Serilog.Superpower.Model; +using Serilog.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; -namespace Serilog.Superpower +namespace Serilog.ParserConstruction { /// /// Base class for tokenizers, types whose instances convert strings into lists of tokens. diff --git a/src/Serilog.Expressions/Superpower/Util/ArrayEnumerable.cs b/src/Serilog.Expressions/ParserConstruction/Util/ArrayEnumerable.cs similarity index 97% rename from src/Serilog.Expressions/Superpower/Util/ArrayEnumerable.cs rename to src/Serilog.Expressions/ParserConstruction/Util/ArrayEnumerable.cs index 131b2de..6857806 100644 --- a/src/Serilog.Expressions/Superpower/Util/ArrayEnumerable.cs +++ b/src/Serilog.Expressions/ParserConstruction/Util/ArrayEnumerable.cs @@ -14,7 +14,7 @@ using System.Runtime.CompilerServices; -namespace Serilog.Superpower.Util +namespace Serilog.ParserConstruction.Util { static class ArrayEnumerable { diff --git a/src/Serilog.Expressions/Superpower/Util/CharInfo.cs b/src/Serilog.Expressions/ParserConstruction/Util/CharInfo.cs similarity index 94% rename from src/Serilog.Expressions/Superpower/Util/CharInfo.cs rename to src/Serilog.Expressions/ParserConstruction/Util/CharInfo.cs index 199d6f0..cad5df2 100644 --- a/src/Serilog.Expressions/Superpower/Util/CharInfo.cs +++ b/src/Serilog.Expressions/ParserConstruction/Util/CharInfo.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Serilog.Superpower.Util +namespace Serilog.ParserConstruction.Util { static class CharInfo { diff --git a/src/Serilog.Expressions/Superpower/Util/Friendly.cs b/src/Serilog.Expressions/ParserConstruction/Util/Friendly.cs similarity index 97% rename from src/Serilog.Expressions/Superpower/Util/Friendly.cs rename to src/Serilog.Expressions/ParserConstruction/Util/Friendly.cs index 4e76525..e0ff877 100644 --- a/src/Serilog.Expressions/Superpower/Util/Friendly.cs +++ b/src/Serilog.Expressions/ParserConstruction/Util/Friendly.cs @@ -16,7 +16,7 @@ using System.Collections.Generic; using System.Linq; -namespace Serilog.Superpower.Util +namespace Serilog.ParserConstruction.Util { static class Friendly { diff --git a/src/Serilog.Expressions/Serilog.Expressions.csproj b/src/Serilog.Expressions/Serilog.Expressions.csproj index be9f9b4..80b43fc 100644 --- a/src/Serilog.Expressions/Serilog.Expressions.csproj +++ b/src/Serilog.Expressions/Serilog.Expressions.csproj @@ -5,7 +5,7 @@ events, ideal for use with JSON or XML configuration. 3.0.0 Serilog Contributors - netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1;net5.0 true true Serilog @@ -18,7 +18,7 @@ Apache-2.0 https://github.com/serilog/serilog-expressions git - 8 + latest enable diff --git a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs index a51f6be..1f890c8 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs @@ -1,9 +1,6 @@ using Serilog.Expressions.Ast; using Serilog.Expressions.Parsing; using Serilog.Parsing; -using Serilog.Superpower; -using Serilog.Superpower.Model; -using Serilog.Superpower.Parsers; using Serilog.Templates.Ast; using static Serilog.Expressions.Parsing.ExpressionToken; diff --git a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs index b8d09b3..e41ea61 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; using Serilog.Expressions.Parsing; -using Serilog.Superpower; -using Serilog.Superpower.Model; namespace Serilog.Templates.Parsing { From 11b12a32093418934382cba77b2bd6cfbce5dd81 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 25 May 2021 17:18:46 +1000 Subject: [PATCH 5/7] Renames part 2 --- src/Serilog.Expressions/Expressions/Parsing/Combinators.cs | 2 ++ .../Expressions/Parsing/ExpressionTextParsers.cs | 6 +++++- .../Expressions/Parsing/ExpressionToken.cs | 4 +++- .../Expressions/Parsing/ExpressionTokenParsers.cs | 3 +++ .../Expressions/Parsing/ExpressionTokenizer.cs | 2 ++ .../Expressions/Parsing/ParserExtensions.cs | 2 ++ .../Templates/Parsing/TemplateTokenParsers.cs | 3 +++ .../Templates/Parsing/TemplateTokenizer.cs | 2 ++ 8 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs b/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs index d32590a..e1291e8 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs @@ -1,4 +1,6 @@ using System; +using Serilog.ParserConstruction; +using Serilog.ParserConstruction.Model; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs index bf56fd1..431eba7 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs @@ -1,4 +1,8 @@ -namespace Serilog.Expressions.Parsing +using Serilog.ParserConstruction; +using Serilog.ParserConstruction.Model; +using Serilog.ParserConstruction.Parsers; + +namespace Serilog.Expressions.Parsing { static class ExpressionTextParsers { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs index 05ca1ab..0830352 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs @@ -1,4 +1,6 @@ -namespace Serilog.Expressions.Parsing +using Serilog.ParserConstruction.Display; + +namespace Serilog.Expressions.Parsing { enum ExpressionToken { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs index 59cc9b0..3fbda4a 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs @@ -3,6 +3,9 @@ using System.Linq; using Serilog.Events; using Serilog.Expressions.Ast; +using Serilog.ParserConstruction; +using Serilog.ParserConstruction.Model; +using Serilog.ParserConstruction.Parsers; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs index 1a1c66f..faca88d 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using System.Linq; +using Serilog.ParserConstruction; +using Serilog.ParserConstruction.Model; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs b/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs index 8a5debd..8777811 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs @@ -1,4 +1,6 @@ using System; +using Serilog.ParserConstruction; +using Serilog.ParserConstruction.Model; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs index 1f890c8..f8dc970 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs @@ -1,6 +1,9 @@ using Serilog.Expressions.Ast; using Serilog.Expressions.Parsing; using Serilog.Parsing; +using Serilog.ParserConstruction; +using Serilog.ParserConstruction.Model; +using Serilog.ParserConstruction.Parsers; using Serilog.Templates.Ast; using static Serilog.Expressions.Parsing.ExpressionToken; diff --git a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs index e41ea61..4c639af 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using Serilog.Expressions.Parsing; +using Serilog.ParserConstruction; +using Serilog.ParserConstruction.Model; namespace Serilog.Templates.Parsing { From 81a525a66e1a5b499bfd2f6b66fd998ef792566f Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 1 Jun 2021 08:12:03 +1000 Subject: [PATCH 6/7] ANSI terminal themes; culture-specific formatting (#38) * Pass theme through at compile time * Exception rendering * Themed JSON * Nullability juggling * Themed message templates * Missing license headers * Construction of themes from existing themes * Finish off some TODOs; thread IFormatProvider through consistently; fixes #19 * Sample tidy-up * Tiny sample tweak --- example/Sample/Program.cs | 29 +- example/Sample/Sample.csproj | 2 +- serilog-expressions.sln.DotSettings | 1 + .../Expressions/Ast/AccessorExpression.cs | 16 +- .../Expressions/Ast/AmbientNameExpression.cs | 22 +- .../Expressions/Ast/ArrayExpression.cs | 16 +- .../Expressions/Ast/CallExpression.cs | 16 +- .../Expressions/Ast/ConstantExpression.cs | 16 +- .../Expressions/Ast/Element.cs | 16 +- .../Expressions/Ast/Expression.cs | 16 +- .../Expressions/Ast/IndexOfMatchExpression.cs | 14 + .../Expressions/Ast/IndexerExpression.cs | 14 + .../Expressions/Ast/IndexerWildcard.cs | 16 +- .../Ast/IndexerWildcardExpression.cs | 16 +- .../Expressions/Ast/ItemElement.cs | 16 +- .../Expressions/Ast/LambdaExpression.cs | 14 + .../Expressions/Ast/LocalNameExpression.cs | 16 +- .../Expressions/Ast/Member.cs | 16 +- .../Expressions/Ast/ObjectExpression.cs | 14 + .../Expressions/Ast/ParameterExpression.cs | 14 + .../Expressions/Ast/PropertyMember.cs | 16 +- .../Expressions/Ast/SpreadElement.cs | 16 +- .../Expressions/Ast/SpreadMember.cs | 16 +- .../Arrays/ConstantArrayEvaluator.cs | 16 +- .../DefaultFunctionNameResolver.cs | 16 +- .../Compilation/ExpressionCompiler.cs | 22 +- .../Linq/ExpressionConstantMapper.cs | 16 +- .../Compilation/Linq/Intrinsics.cs | 34 ++- .../Linq/LinqExpressionCompiler.cs | 34 ++- .../Linq/ParameterReplacementVisitor.cs | 16 +- .../Compilation/OrderedNameResolver.cs | 16 +- .../Expressions/Compilation/Pattern.cs | 14 + .../PropertiesObjectAccessorTransformer.cs | 16 +- .../Compilation/Text/LikeSyntaxTransformer.cs | 14 + .../Text/TextMatchingTransformer.cs | 14 + .../FilterExpressionTransformer`1.cs | 16 +- .../Transformations/IdentityTransformer.cs | 16 +- .../Transformations/NodeReplacer.cs | 16 +- .../Variadics/VariadicCallRewriter.cs | 14 + .../WildcardComprehensionTransformer.cs | 16 +- .../Compilation/Wildcards/WildcardSearch.cs | 16 +- .../Expressions/Evaluatable.cs | 16 +- .../Expressions/EvaluationContext.cs | 16 +- .../Expressions/NameResolver.cs | 19 +- .../Expressions/Operators.cs | 18 +- .../Expressions/Parsing/Combinators.cs | 16 +- .../Expressions/Parsing/ExpressionKeyword.cs | 16 +- .../Expressions/Parsing/ExpressionParser.cs | 16 +- .../Parsing/ExpressionTextParsers.cs | 16 +- .../Expressions/Parsing/ExpressionToken.cs | 16 +- .../Parsing/ExpressionTokenParsers.cs | 16 +- .../Parsing/ExpressionTokenizer.cs | 16 +- .../Expressions/Parsing/ParserExtensions.cs | 16 +- .../Expressions/Runtime/Coerce.cs | 14 + .../Expressions/Runtime/Locals.cs | 16 +- .../Expressions/Runtime/RuntimeOperators.cs | 27 +- .../Expressions/SerilogExpression.cs | 31 ++- .../Expressions/StaticMemberNameResolver.cs | 16 +- .../LoggerFilterConfigurationExtensions.cs | 16 +- .../Pipeline/ComputedPropertyEnricher.cs | 14 + .../Templates/Ast/Conditional.cs | 16 +- .../Templates/Ast/FormattedExpression.cs | 14 + .../Templates/Ast/LiteralText.cs | 14 + .../Templates/Ast/Repetition.cs | 16 +- .../Templates/Ast/Template.cs | 14 + .../Templates/Ast/TemplateBlock.cs | 14 + .../Compilation/CompiledConditional.cs | 23 +- .../Compilation/CompiledExceptionToken.cs | 51 ++++ .../CompiledFormattedExpression.cs | 47 +++- .../Compilation/CompiledLevelToken.cs | 70 +++++ .../Compilation/CompiledLiteralText.cs | 28 +- .../Compilation/CompiledMessageToken.cs | 184 +++++++++++++ .../Compilation/CompiledRepetition.cs | 37 ++- .../Templates/Compilation/CompiledTemplate.cs | 20 +- .../Compilation/CompiledTemplateBlock.cs | 21 +- .../ExpressionLocalNameBinder.cs | 16 +- .../TemplateLocalNameResolver.cs | 16 +- .../Templates/Compilation/TemplateCompiler.cs | 54 +++- .../Templates/ExpressionTemplate.cs | 43 ++- .../Templates/Parsing/TemplateParser.cs | 20 +- .../Templates/Parsing/TemplateTokenParsers.cs | 20 +- .../Templates/Parsing/TemplateTokenizer.cs | 16 +- .../Rendering/AlignmentExtensions.cs | 26 ++ .../Templates/Themes/Style.cs | 47 ++++ .../Templates/Themes/StyleReset.cs | 37 +++ .../Templates/Themes/TemplateTheme.cs | 79 ++++++ .../Templates/Themes/TemplateThemeStyle.cs | 104 +++++++ .../Templates/Themes/TemplateThemes.cs | 84 ++++++ .../Themes/ThemedJsonValueFormatter.cs | 254 ++++++++++++++++++ .../Cases/expression-evaluation-cases.asv | 3 + .../Cases/template-evaluation-cases.asv | 1 + .../ExpressionEvaluationTests.cs | 6 +- .../Expressions/NameResolverTests.cs | 2 +- .../TemplateEvaluationTests.cs | 4 +- .../TemplateParserTests.cs | 13 +- 95 files changed, 2236 insertions(+), 179 deletions(-) create mode 100644 src/Serilog.Expressions/Templates/Compilation/CompiledExceptionToken.cs create mode 100644 src/Serilog.Expressions/Templates/Compilation/CompiledLevelToken.cs create mode 100644 src/Serilog.Expressions/Templates/Compilation/CompiledMessageToken.cs create mode 100644 src/Serilog.Expressions/Templates/Rendering/AlignmentExtensions.cs create mode 100644 src/Serilog.Expressions/Templates/Themes/Style.cs create mode 100644 src/Serilog.Expressions/Templates/Themes/StyleReset.cs create mode 100644 src/Serilog.Expressions/Templates/Themes/TemplateTheme.cs create mode 100644 src/Serilog.Expressions/Templates/Themes/TemplateThemeStyle.cs create mode 100644 src/Serilog.Expressions/Templates/Themes/TemplateThemes.cs create mode 100644 src/Serilog.Expressions/Templates/Themes/ThemedJsonValueFormatter.cs diff --git a/example/Sample/Program.cs b/example/Sample/Program.cs index 1169c88..8a8443a 100644 --- a/example/Sample/Program.cs +++ b/example/Sample/Program.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using Serilog; using Serilog.Debugging; using Serilog.Templates; +using Serilog.Templates.Themes; namespace Sample { @@ -24,7 +26,8 @@ static void TextFormattingExample1() .WriteTo.Console(new ExpressionTemplate( "[{@t:HH:mm:ss} {@l:u3}" + "{#if SourceContext is not null} ({Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1)}){#end}] " + - "{@m} (first item is {coalesce(Items[0], '')})\n{@x}")) + "{@m} (first item is {coalesce(Items[0], '')})\n{@x}", + theme: TemplateTheme.Code)) .CreateLogger(); log.Information("Running {Example}", nameof(TextFormattingExample1)); @@ -41,7 +44,7 @@ static void JsonFormattingExample() using var log = new LoggerConfiguration() .Enrich.WithProperty("Application", "Example") .WriteTo.Console(new ExpressionTemplate( - "{ {@t, @mt, @l: if @l = 'Information' then undefined() else @l, @x, ..@p} }\n")) + "{ {@t: UtcDateTime(@t), @mt, @l: if @l = 'Information' then undefined() else @l, @x, ..@p} }\n")) .CreateLogger(); log.Information("Running {Example}", nameof(JsonFormattingExample)); @@ -75,6 +78,16 @@ static void PipelineComponentExample() static void TextFormattingExample2() { + // Emulates `Microsoft.Extensions.Logging`'s `ConsoleLogger`. + + var melon = new TemplateTheme(TemplateTheme.Literate, new Dictionary + { + // `Information` is dark green in MEL. + [TemplateThemeStyle.LevelInformation] = "\x1b[38;5;34m", + [TemplateThemeStyle.String] = "\x1b[38;5;159m", + [TemplateThemeStyle.Number] = "\x1b[38;5;159m" + }); + using var log = new LoggerConfiguration() .WriteTo.Console(new ExpressionTemplate( "{@l:w4}: {SourceContext}\n" + @@ -82,16 +95,18 @@ static void TextFormattingExample2() " {#each s in Scope}=> {s}{#delimit} {#end}\n" + "{#end}" + " {@m}\n" + - "{@x}")) + "{@x}", + theme: melon)) .CreateLogger(); var program = log.ForContext(); - program.Information("Starting up"); + program.Information("Host listening at {ListenUri}", "https://hello-world.local"); - // Emulate data produced by the Serilog.AspNetCore integration - var scoped = program.ForContext("Scope", new[] {"Main", "TextFormattingExample2()"}); + program + .ForContext("Scope", new[] {"Main", "TextFormattingExample2()"}) + .Information("HTTP {Method} {Path} responded {StatusCode} in {Elapsed:0.000} ms", "GET", "/api/hello", 200, 1.23); - scoped.Information("Hello, world!"); + program.Warning("We've reached the end of the line"); } } } diff --git a/example/Sample/Sample.csproj b/example/Sample/Sample.csproj index c4a34a0..78981c6 100644 --- a/example/Sample/Sample.csproj +++ b/example/Sample/Sample.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 Exe diff --git a/serilog-expressions.sln.DotSettings b/serilog-expressions.sln.DotSettings index 21575ef..b1832e8 100644 --- a/serilog-expressions.sln.DotSettings +++ b/serilog-expressions.sln.DotSettings @@ -1,6 +1,7 @@  True True + True True True True diff --git a/src/Serilog.Expressions/Expressions/Ast/AccessorExpression.cs b/src/Serilog.Expressions/Expressions/Ast/AccessorExpression.cs index 3031c56..5995308 100644 --- a/src/Serilog.Expressions/Expressions/Ast/AccessorExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/AccessorExpression.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; namespace Serilog.Expressions.Ast { diff --git a/src/Serilog.Expressions/Expressions/Ast/AmbientNameExpression.cs b/src/Serilog.Expressions/Expressions/Ast/AmbientNameExpression.cs index dce2562..1b2ea8a 100644 --- a/src/Serilog.Expressions/Expressions/Ast/AmbientNameExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/AmbientNameExpression.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; namespace Serilog.Expressions.Ast { @@ -6,11 +20,11 @@ class AmbientNameExpression : Expression { readonly bool _requiresEscape; - public AmbientNameExpression(string Name, bool isBuiltIn) + public AmbientNameExpression(string name, bool isBuiltIn) { - PropertyName = Name ?? throw new ArgumentNullException(nameof(Name)); + PropertyName = name ?? throw new ArgumentNullException(nameof(name)); IsBuiltIn = isBuiltIn; - _requiresEscape = !SerilogExpression.IsValidIdentifier(Name); + _requiresEscape = !SerilogExpression.IsValidIdentifier(name); } public string PropertyName { get; } diff --git a/src/Serilog.Expressions/Expressions/Ast/ArrayExpression.cs b/src/Serilog.Expressions/Expressions/Ast/ArrayExpression.cs index 18b98c1..7b49779 100644 --- a/src/Serilog.Expressions/Expressions/Ast/ArrayExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/ArrayExpression.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Linq; namespace Serilog.Expressions.Ast diff --git a/src/Serilog.Expressions/Expressions/Ast/CallExpression.cs b/src/Serilog.Expressions/Expressions/Ast/CallExpression.cs index f25eb4e..b1bd730 100644 --- a/src/Serilog.Expressions/Expressions/Ast/CallExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/CallExpression.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Linq; namespace Serilog.Expressions.Ast diff --git a/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs b/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs index 5dca402..38ad809 100644 --- a/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Globalization; using Serilog.Events; diff --git a/src/Serilog.Expressions/Expressions/Ast/Element.cs b/src/Serilog.Expressions/Expressions/Ast/Element.cs index 7a810f0..7a08406 100644 --- a/src/Serilog.Expressions/Expressions/Ast/Element.cs +++ b/src/Serilog.Expressions/Expressions/Ast/Element.cs @@ -1,4 +1,18 @@ -namespace Serilog.Expressions.Ast +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Expressions.Ast { abstract class Element { diff --git a/src/Serilog.Expressions/Expressions/Ast/Expression.cs b/src/Serilog.Expressions/Expressions/Ast/Expression.cs index 045d1dd..716ea7e 100644 --- a/src/Serilog.Expressions/Expressions/Ast/Expression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/Expression.cs @@ -1,4 +1,18 @@ -namespace Serilog.Expressions.Ast +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Expressions.Ast { abstract class Expression { diff --git a/src/Serilog.Expressions/Expressions/Ast/IndexOfMatchExpression.cs b/src/Serilog.Expressions/Expressions/Ast/IndexOfMatchExpression.cs index 574ae0a..24ecb41 100644 --- a/src/Serilog.Expressions/Expressions/Ast/IndexOfMatchExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/IndexOfMatchExpression.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; using System.Text.RegularExpressions; diff --git a/src/Serilog.Expressions/Expressions/Ast/IndexerExpression.cs b/src/Serilog.Expressions/Expressions/Ast/IndexerExpression.cs index 2029c70..282c1ea 100644 --- a/src/Serilog.Expressions/Expressions/Ast/IndexerExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/IndexerExpression.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + namespace Serilog.Expressions.Ast { class IndexerExpression : Expression diff --git a/src/Serilog.Expressions/Expressions/Ast/IndexerWildcard.cs b/src/Serilog.Expressions/Expressions/Ast/IndexerWildcard.cs index 0474676..aec0be0 100644 --- a/src/Serilog.Expressions/Expressions/Ast/IndexerWildcard.cs +++ b/src/Serilog.Expressions/Expressions/Ast/IndexerWildcard.cs @@ -1,4 +1,18 @@ -namespace Serilog.Expressions.Ast +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Expressions.Ast { enum IndexerWildcard { Undefined, Any, All } } diff --git a/src/Serilog.Expressions/Expressions/Ast/IndexerWildcardExpression.cs b/src/Serilog.Expressions/Expressions/Ast/IndexerWildcardExpression.cs index 280ca83..df431ce 100644 --- a/src/Serilog.Expressions/Expressions/Ast/IndexerWildcardExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/IndexerWildcardExpression.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; namespace Serilog.Expressions.Ast { diff --git a/src/Serilog.Expressions/Expressions/Ast/ItemElement.cs b/src/Serilog.Expressions/Expressions/Ast/ItemElement.cs index 7e9c366..f28dbd8 100644 --- a/src/Serilog.Expressions/Expressions/Ast/ItemElement.cs +++ b/src/Serilog.Expressions/Expressions/Ast/ItemElement.cs @@ -1,4 +1,18 @@ -namespace Serilog.Expressions.Ast +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Expressions.Ast { class ItemElement : Element { diff --git a/src/Serilog.Expressions/Expressions/Ast/LambdaExpression.cs b/src/Serilog.Expressions/Expressions/Ast/LambdaExpression.cs index 171a9b5..f26059b 100644 --- a/src/Serilog.Expressions/Expressions/Ast/LambdaExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/LambdaExpression.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System.Linq; namespace Serilog.Expressions.Ast diff --git a/src/Serilog.Expressions/Expressions/Ast/LocalNameExpression.cs b/src/Serilog.Expressions/Expressions/Ast/LocalNameExpression.cs index d786d68..b276784 100644 --- a/src/Serilog.Expressions/Expressions/Ast/LocalNameExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/LocalNameExpression.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; namespace Serilog.Expressions.Ast { diff --git a/src/Serilog.Expressions/Expressions/Ast/Member.cs b/src/Serilog.Expressions/Expressions/Ast/Member.cs index 9230f45..4769c36 100644 --- a/src/Serilog.Expressions/Expressions/Ast/Member.cs +++ b/src/Serilog.Expressions/Expressions/Ast/Member.cs @@ -1,4 +1,18 @@ -namespace Serilog.Expressions.Ast +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Expressions.Ast { abstract class Member { diff --git a/src/Serilog.Expressions/Expressions/Ast/ObjectExpression.cs b/src/Serilog.Expressions/Expressions/Ast/ObjectExpression.cs index cab763e..e747e67 100644 --- a/src/Serilog.Expressions/Expressions/Ast/ObjectExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/ObjectExpression.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System.Linq; namespace Serilog.Expressions.Ast diff --git a/src/Serilog.Expressions/Expressions/Ast/ParameterExpression.cs b/src/Serilog.Expressions/Expressions/Ast/ParameterExpression.cs index 23b158d..83870c1 100644 --- a/src/Serilog.Expressions/Expressions/Ast/ParameterExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/ParameterExpression.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + namespace Serilog.Expressions.Ast { class ParameterExpression : Expression diff --git a/src/Serilog.Expressions/Expressions/Ast/PropertyMember.cs b/src/Serilog.Expressions/Expressions/Ast/PropertyMember.cs index 862c1ff..d8e39dc 100644 --- a/src/Serilog.Expressions/Expressions/Ast/PropertyMember.cs +++ b/src/Serilog.Expressions/Expressions/Ast/PropertyMember.cs @@ -1,4 +1,18 @@ -using Serilog.Events; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Events; namespace Serilog.Expressions.Ast { diff --git a/src/Serilog.Expressions/Expressions/Ast/SpreadElement.cs b/src/Serilog.Expressions/Expressions/Ast/SpreadElement.cs index d6fa538..4da9b40 100644 --- a/src/Serilog.Expressions/Expressions/Ast/SpreadElement.cs +++ b/src/Serilog.Expressions/Expressions/Ast/SpreadElement.cs @@ -1,4 +1,18 @@ -namespace Serilog.Expressions.Ast +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Expressions.Ast { class SpreadElement : Element { diff --git a/src/Serilog.Expressions/Expressions/Ast/SpreadMember.cs b/src/Serilog.Expressions/Expressions/Ast/SpreadMember.cs index 7d13152..f039cba 100644 --- a/src/Serilog.Expressions/Expressions/Ast/SpreadMember.cs +++ b/src/Serilog.Expressions/Expressions/Ast/SpreadMember.cs @@ -1,4 +1,18 @@ -namespace Serilog.Expressions.Ast +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Expressions.Ast { class SpreadMember : Member { diff --git a/src/Serilog.Expressions/Expressions/Compilation/Arrays/ConstantArrayEvaluator.cs b/src/Serilog.Expressions/Expressions/Compilation/Arrays/ConstantArrayEvaluator.cs index 3ffc6ec..c25b6b1 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Arrays/ConstantArrayEvaluator.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Arrays/ConstantArrayEvaluator.cs @@ -1,4 +1,18 @@ -using System.Linq; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Linq; using Serilog.Events; using Serilog.Expressions.Ast; using Serilog.Expressions.Compilation.Transformations; diff --git a/src/Serilog.Expressions/Expressions/Compilation/DefaultFunctionNameResolver.cs b/src/Serilog.Expressions/Expressions/Compilation/DefaultFunctionNameResolver.cs index ba79f4f..ed149ea 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/DefaultFunctionNameResolver.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/DefaultFunctionNameResolver.cs @@ -1,4 +1,18 @@ -using Serilog.Expressions.Runtime; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Expressions.Runtime; namespace Serilog.Expressions.Compilation { diff --git a/src/Serilog.Expressions/Expressions/Compilation/ExpressionCompiler.cs b/src/Serilog.Expressions/Expressions/Compilation/ExpressionCompiler.cs index f504045..31d1fdd 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/ExpressionCompiler.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/ExpressionCompiler.cs @@ -1,4 +1,19 @@ -using Serilog.Expressions.Ast; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Serilog.Expressions.Ast; using Serilog.Expressions.Compilation.Arrays; using Serilog.Expressions.Compilation.Linq; using Serilog.Expressions.Compilation.Properties; @@ -22,10 +37,11 @@ public static Expression Translate(Expression expression) return actual; } - public static Evaluatable Compile(Expression expression, NameResolver nameResolver) + public static Evaluatable Compile(Expression expression, IFormatProvider? formatProvider, + NameResolver nameResolver) { var actual = Translate(expression); - return LinqExpressionCompiler.Compile(actual, nameResolver); + return LinqExpressionCompiler.Compile(actual, formatProvider, nameResolver); } } } diff --git a/src/Serilog.Expressions/Expressions/Compilation/Linq/ExpressionConstantMapper.cs b/src/Serilog.Expressions/Expressions/Compilation/Linq/ExpressionConstantMapper.cs index 085b7d4..b0eba87 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Linq/ExpressionConstantMapper.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Linq/ExpressionConstantMapper.cs @@ -1,4 +1,18 @@ -using System.Collections.Generic; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections.Generic; using System.Linq.Expressions; using Serilog.Events; diff --git a/src/Serilog.Expressions/Expressions/Compilation/Linq/Intrinsics.cs b/src/Serilog.Expressions/Expressions/Compilation/Linq/Intrinsics.cs index 74564b0..17ad067 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Linq/Intrinsics.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Linq/Intrinsics.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Collections.Generic; using System.Collections.Specialized; using System.IO; @@ -8,6 +22,7 @@ using Serilog.Expressions.Runtime; using Serilog.Formatting.Display; using Serilog.Parsing; +using Serilog.Templates.Compilation; // ReSharper disable ParameterTypeCanBeEnumerable.Global @@ -18,9 +33,6 @@ static class Intrinsics static readonly LogEventPropertyValue NegativeOne = new ScalarValue(-1); static readonly LogEventPropertyValue Tombstone = new ScalarValue("😬 (if you see this you have found a bug.)"); - // TODO #19: formatting is culture-specific. - static readonly MessageTemplateTextFormatter MessageFormatter = new MessageTemplateTextFormatter("{Message:lj}"); - public static List CollectSequenceElements(LogEventPropertyValue?[] elements) { return elements.ToList(); @@ -164,27 +176,27 @@ public static bool CoerceToScalarBoolean(LogEventPropertyValue value) return null; } - public static string RenderMessage(LogEvent logEvent) + // Use of `CompiledMessageToken` is a layering violation here, but we want to ensure the formatting implementations + // line up exactly. Some refactoring here might be worthwhile, though with an eye on indirection costs. + public static string RenderMessage(CompiledMessageToken formatter, EvaluationContext ctx) { - // Use the same `:lj`-style formatting default as Serilog.Sinks.Console. var sw = new StringWriter(); - MessageFormatter.Format(logEvent, sw); + formatter.Evaluate(ctx, sw); return sw.ToString(); } - public static LogEventPropertyValue? GetRenderings(LogEvent logEvent) + public static LogEventPropertyValue? GetRenderings(LogEvent logEvent, IFormatProvider? formatProvider) { List? elements = null; foreach (var token in logEvent.MessageTemplate.Tokens) { - if (token is PropertyToken pt && pt.Format != null) + if (token is PropertyToken {Format: { }} pt) { elements ??= new List(); var space = new StringWriter(); - // TODO #19: formatting is culture-specific. - pt.Render(logEvent.Properties, space); + pt.Render(logEvent.Properties, space, formatProvider); elements.Add(new ScalarValue(space.ToString())); } } diff --git a/src/Serilog.Expressions/Expressions/Compilation/Linq/LinqExpressionCompiler.cs b/src/Serilog.Expressions/Expressions/Compilation/Linq/LinqExpressionCompiler.cs index 1d5f4a8..d7bd435 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Linq/LinqExpressionCompiler.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Linq/LinqExpressionCompiler.cs @@ -20,6 +20,8 @@ using Serilog.Events; using Serilog.Expressions.Ast; using Serilog.Expressions.Compilation.Transformations; +using Serilog.Templates.Compilation; +using Serilog.Templates.Themes; using ConstantExpression = Serilog.Expressions.Ast.ConstantExpression; using Expression = Serilog.Expressions.Ast.Expression; using ParameterExpression = System.Linq.Expressions.ParameterExpression; @@ -31,6 +33,7 @@ namespace Serilog.Expressions.Compilation.Linq class LinqExpressionCompiler : SerilogExpressionTransformer { readonly NameResolver _nameResolver; + readonly IFormatProvider? _formatProvider; static readonly MethodInfo CollectSequenceElementsMethod = typeof(Intrinsics) .GetMethod(nameof(Intrinsics.CollectSequenceElements), BindingFlags.Static | BindingFlags.Public)!; @@ -70,15 +73,17 @@ class LinqExpressionCompiler : SerilogExpressionTransformer ParameterExpression Context { get; } = LX.Variable(typeof(EvaluationContext), "ctx"); - LinqExpressionCompiler(NameResolver nameResolver) + LinqExpressionCompiler(IFormatProvider? formatProvider, NameResolver nameResolver) { _nameResolver = nameResolver; + _formatProvider = formatProvider; } - public static Evaluatable Compile(Expression expression, NameResolver nameResolver) + public static Evaluatable Compile(Expression expression, IFormatProvider? formatProvider, + NameResolver nameResolver) { if (expression == null) throw new ArgumentNullException(nameof(expression)); - var compiler = new LinqExpressionCompiler(nameResolver); + var compiler = new LinqExpressionCompiler(formatProvider, nameResolver); var body = compiler.Transform(expression); return LX.Lambda(body, compiler.Context).Compile(); } @@ -93,7 +98,9 @@ protected override ExpressionBody Transform(CallExpression lx) if (!_nameResolver.TryResolveFunctionName(lx.OperatorName, out var m)) throw new ArgumentException($"The function name `{lx.OperatorName}` was not recognized."); - var parameterCount = m.GetParameters().Count(pi => pi.ParameterType == typeof(LogEventPropertyValue)); + var methodParameters = m.GetParameters(); + + var parameterCount = methodParameters.Count(pi => pi.ParameterType == typeof(LogEventPropertyValue)); if (parameterCount != lx.Operands.Length) throw new ArgumentException($"The function `{lx.OperatorName}` requires {parameterCount} arguments."); @@ -106,9 +113,15 @@ protected override ExpressionBody Transform(CallExpression lx) if (Operators.SameOperator(lx.OperatorName, Operators.RuntimeOpOr)) return CompileLogical(LX.OrElse, operands[0], operands[1]); - if (m.GetParameters().Any(pi => pi.ParameterType == typeof(StringComparison))) - operands.Insert(0, LX.Constant(lx.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)); - + for (var i = 0; i < methodParameters.Length; ++i) + { + var pi = methodParameters[i]; + if (pi.ParameterType == typeof(StringComparison)) + operands.Insert(i, LX.Constant(lx.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)); + else if (pi.ParameterType == typeof(IFormatProvider)) + operands.Insert(i, LX.Constant(_formatProvider, typeof(IFormatProvider))); + } + return LX.Call(m, operands); } @@ -138,10 +151,13 @@ protected override ExpressionBody Transform(AmbientNameExpression px) { if (px.IsBuiltIn) { + var formatter = new CompiledMessageToken(_formatProvider, null, TemplateTheme.None); + var formatProvider = _formatProvider; + return px.PropertyName switch { BuiltInProperty.Level => Splice(context => new ScalarValue(context.LogEvent.Level)), - BuiltInProperty.Message => Splice(context => new ScalarValue(Intrinsics.RenderMessage(context.LogEvent))), + BuiltInProperty.Message => Splice(context => new ScalarValue(Intrinsics.RenderMessage(formatter, context))), BuiltInProperty.Exception => Splice(context => context.LogEvent.Exception == null ? null : new ScalarValue(context.LogEvent.Exception)), BuiltInProperty.Timestamp => Splice(context => new ScalarValue(context.LogEvent.Timestamp)), @@ -149,7 +165,7 @@ protected override ExpressionBody Transform(AmbientNameExpression px) BuiltInProperty.Properties => Splice(context => new StructureValue(context.LogEvent.Properties.Select(kvp => new LogEventProperty(kvp.Key, kvp.Value)), null)), - BuiltInProperty.Renderings => Splice(context => Intrinsics.GetRenderings(context.LogEvent)), + BuiltInProperty.Renderings => Splice(context => Intrinsics.GetRenderings(context.LogEvent, formatProvider)), BuiltInProperty.EventId => Splice(context => new ScalarValue(EventIdHash.Compute(context.LogEvent.MessageTemplate.Text))), _ => LX.Constant(null, typeof(LogEventPropertyValue)) diff --git a/src/Serilog.Expressions/Expressions/Compilation/Linq/ParameterReplacementVisitor.cs b/src/Serilog.Expressions/Expressions/Compilation/Linq/ParameterReplacementVisitor.cs index 2438846..964e56d 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Linq/ParameterReplacementVisitor.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Linq/ParameterReplacementVisitor.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Linq; using System.Linq.Expressions; diff --git a/src/Serilog.Expressions/Expressions/Compilation/OrderedNameResolver.cs b/src/Serilog.Expressions/Expressions/Compilation/OrderedNameResolver.cs index 5983a3e..62b2b2b 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/OrderedNameResolver.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/OrderedNameResolver.cs @@ -1,4 +1,18 @@ -using System.Collections.Generic; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; diff --git a/src/Serilog.Expressions/Expressions/Compilation/Pattern.cs b/src/Serilog.Expressions/Expressions/Compilation/Pattern.cs index d0ff8fa..ca250b9 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Pattern.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Pattern.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System.Diagnostics.CodeAnalysis; using Serilog.Events; using Serilog.Expressions.Ast; diff --git a/src/Serilog.Expressions/Expressions/Compilation/Properties/PropertiesObjectAccessorTransformer.cs b/src/Serilog.Expressions/Expressions/Compilation/Properties/PropertiesObjectAccessorTransformer.cs index c20c2f7..c1180df 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Properties/PropertiesObjectAccessorTransformer.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Properties/PropertiesObjectAccessorTransformer.cs @@ -1,4 +1,18 @@ -using Serilog.Expressions.Ast; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Expressions.Ast; using Serilog.Expressions.Compilation.Transformations; namespace Serilog.Expressions.Compilation.Properties diff --git a/src/Serilog.Expressions/Expressions/Compilation/Text/LikeSyntaxTransformer.cs b/src/Serilog.Expressions/Expressions/Compilation/Text/LikeSyntaxTransformer.cs index e4a4463..2846138 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Text/LikeSyntaxTransformer.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Text/LikeSyntaxTransformer.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; using System.Text.RegularExpressions; using Serilog.Debugging; diff --git a/src/Serilog.Expressions/Expressions/Compilation/Text/TextMatchingTransformer.cs b/src/Serilog.Expressions/Expressions/Compilation/Text/TextMatchingTransformer.cs index 9b3ec97..281506a 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Text/TextMatchingTransformer.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Text/TextMatchingTransformer.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; using System.Text.RegularExpressions; using Serilog.Debugging; diff --git a/src/Serilog.Expressions/Expressions/Compilation/Transformations/FilterExpressionTransformer`1.cs b/src/Serilog.Expressions/Expressions/Compilation/Transformations/FilterExpressionTransformer`1.cs index 1c03a4c..5bfcbf7 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Transformations/FilterExpressionTransformer`1.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Transformations/FilterExpressionTransformer`1.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using Serilog.Expressions.Ast; namespace Serilog.Expressions.Compilation.Transformations diff --git a/src/Serilog.Expressions/Expressions/Compilation/Transformations/IdentityTransformer.cs b/src/Serilog.Expressions/Expressions/Compilation/Transformations/IdentityTransformer.cs index 82eb856..02e49c8 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Transformations/IdentityTransformer.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Transformations/IdentityTransformer.cs @@ -1,4 +1,18 @@ -using System.Collections.Generic; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections.Generic; using Serilog.Expressions.Ast; namespace Serilog.Expressions.Compilation.Transformations diff --git a/src/Serilog.Expressions/Expressions/Compilation/Transformations/NodeReplacer.cs b/src/Serilog.Expressions/Expressions/Compilation/Transformations/NodeReplacer.cs index ba570aa..b68a2ee 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Transformations/NodeReplacer.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Transformations/NodeReplacer.cs @@ -1,4 +1,18 @@ -using Serilog.Expressions.Ast; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Expressions.Ast; namespace Serilog.Expressions.Compilation.Transformations { diff --git a/src/Serilog.Expressions/Expressions/Compilation/Variadics/VariadicCallRewriter.cs b/src/Serilog.Expressions/Expressions/Compilation/Variadics/VariadicCallRewriter.cs index b199608..be7d224 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Variadics/VariadicCallRewriter.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Variadics/VariadicCallRewriter.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System.Linq; using Serilog.Expressions.Ast; using Serilog.Expressions.Compilation.Transformations; diff --git a/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardComprehensionTransformer.cs b/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardComprehensionTransformer.cs index 51a4ecd..0e79c25 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardComprehensionTransformer.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardComprehensionTransformer.cs @@ -1,4 +1,18 @@ -using System.Linq; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Linq; using Serilog.Expressions.Ast; using Serilog.Expressions.Compilation.Transformations; diff --git a/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardSearch.cs b/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardSearch.cs index 555e025..ade7c6c 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardSearch.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardSearch.cs @@ -1,4 +1,18 @@ -using System.Linq; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Linq; using Serilog.Expressions.Ast; using Serilog.Expressions.Compilation.Transformations; diff --git a/src/Serilog.Expressions/Expressions/Evaluatable.cs b/src/Serilog.Expressions/Expressions/Evaluatable.cs index 6c1e297..a133973 100644 --- a/src/Serilog.Expressions/Expressions/Evaluatable.cs +++ b/src/Serilog.Expressions/Expressions/Evaluatable.cs @@ -1,4 +1,18 @@ -using Serilog.Events; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Events; namespace Serilog.Expressions { diff --git a/src/Serilog.Expressions/Expressions/EvaluationContext.cs b/src/Serilog.Expressions/Expressions/EvaluationContext.cs index 2043d4f..01313c2 100644 --- a/src/Serilog.Expressions/Expressions/EvaluationContext.cs +++ b/src/Serilog.Expressions/Expressions/EvaluationContext.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using Serilog.Events; using Serilog.Expressions.Runtime; diff --git a/src/Serilog.Expressions/Expressions/NameResolver.cs b/src/Serilog.Expressions/Expressions/NameResolver.cs index 2c4cf5c..c874be0 100644 --- a/src/Serilog.Expressions/Expressions/NameResolver.cs +++ b/src/Serilog.Expressions/Expressions/NameResolver.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; using Serilog.Events; @@ -18,7 +32,8 @@ public abstract class NameResolver /// True if the name could be resolved; otherwise, false. /// The method implementing a function should be static, return , /// and accept parameters of type . If the ci modifier is supported, - /// a should be in the first argument position. + /// a should be included in the argument list. If the function is culture-specific, + /// an should be included in the argument list. public abstract bool TryResolveFunctionName(string name, [MaybeNullWhen(false)] out MethodInfo implementation); } } \ No newline at end of file diff --git a/src/Serilog.Expressions/Expressions/Operators.cs b/src/Serilog.Expressions/Expressions/Operators.cs index e90b495..6a4b72d 100644 --- a/src/Serilog.Expressions/Expressions/Operators.cs +++ b/src/Serilog.Expressions/Expressions/Operators.cs @@ -1,8 +1,22 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Collections.Generic; using Serilog.Expressions.Ast; -// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable InconsistentNaming, MemberCanBePrivate.Global namespace Serilog.Expressions { diff --git a/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs b/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs index e1291e8..da04606 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using Serilog.ParserConstruction; using Serilog.ParserConstruction.Model; diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionKeyword.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionKeyword.cs index 372c5d3..e02bdd3 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionKeyword.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionKeyword.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionParser.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionParser.cs index ede4508..df0398a 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionParser.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionParser.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Diagnostics.CodeAnalysis; using Serilog.Expressions.Ast; diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs index 431eba7..98d8b7d 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs @@ -1,4 +1,18 @@ -using Serilog.ParserConstruction; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.ParserConstruction; using Serilog.ParserConstruction.Model; using Serilog.ParserConstruction.Parsers; diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs index 0830352..93a7c34 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs @@ -1,4 +1,18 @@ -using Serilog.ParserConstruction.Display; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.ParserConstruction.Display; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs index 3fbda4a..049d0bc 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Globalization; using System.Linq; using Serilog.Events; diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs index faca88d..f78db88 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs @@ -1,4 +1,18 @@ -using System.Collections.Generic; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections.Generic; using System.Linq; using Serilog.ParserConstruction; using Serilog.ParserConstruction.Model; diff --git a/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs b/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs index 8777811..1f9f2c1 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using Serilog.ParserConstruction; using Serilog.ParserConstruction.Model; diff --git a/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs b/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs index 2fee8fe..0c0f181 100644 --- a/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs +++ b/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; using System.Diagnostics.CodeAnalysis; using System.Linq; diff --git a/src/Serilog.Expressions/Expressions/Runtime/Locals.cs b/src/Serilog.Expressions/Expressions/Runtime/Locals.cs index f2f3b30..3143208 100644 --- a/src/Serilog.Expressions/Expressions/Runtime/Locals.cs +++ b/src/Serilog.Expressions/Expressions/Runtime/Locals.cs @@ -1,4 +1,18 @@ -using System.Diagnostics.CodeAnalysis; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Diagnostics.CodeAnalysis; using Serilog.Events; namespace Serilog.Expressions.Runtime diff --git a/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs b/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs index b2e2f95..18e10df 100644 --- a/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs +++ b/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Collections.Generic; using System.Linq; using Serilog.Events; @@ -409,18 +423,18 @@ public static LogEventPropertyValue TypeOf(LogEventPropertyValue? value) public static LogEventPropertyValue _Internal_IsNull(LogEventPropertyValue? value) { - return ScalarBoolean(value is null || value is ScalarValue sv && sv.Value == null); + return ScalarBoolean(value is null or ScalarValue {Value: null}); } public static LogEventPropertyValue _Internal_IsNotNull(LogEventPropertyValue? value) { - return ScalarBoolean(!(value is null || value is ScalarValue sv && sv.Value == null)); + return ScalarBoolean(value is not (null or ScalarValue {Value: null})); } // Ideally this will be compiled as a short-circuiting intrinsic public static LogEventPropertyValue? Coalesce(LogEventPropertyValue? v1, LogEventPropertyValue? v2) { - if (v1 is null || v1 is ScalarValue sv && sv.Value == null) + if (v1 is null or ScalarValue {Value: null}) return v2; return v1; @@ -468,7 +482,7 @@ public static LogEventPropertyValue _Internal_IsNotNull(LogEventPropertyValue? v return Coerce.IsTrue(condition) ? consequent : alternative; } - public static LogEventPropertyValue? ToString(LogEventPropertyValue? value, LogEventPropertyValue? format) + public static LogEventPropertyValue? ToString(IFormatProvider? formatProvider, LogEventPropertyValue? value, LogEventPropertyValue? format) { if (value is not ScalarValue sv || sv.Value == null || @@ -480,8 +494,7 @@ public static LogEventPropertyValue _Internal_IsNotNull(LogEventPropertyValue? v string? toString; if (sv.Value is IFormattable formattable) { - // TODO #19: formatting is culture-specific. - toString = formattable.ToString(fmt, null); + toString = formattable.ToString(fmt, formatProvider); } else { diff --git a/src/Serilog.Expressions/Expressions/SerilogExpression.cs b/src/Serilog.Expressions/Expressions/SerilogExpression.cs index 8de20b8..37b1de2 100644 --- a/src/Serilog.Expressions/Expressions/SerilogExpression.cs +++ b/src/Serilog.Expressions/Expressions/SerilogExpression.cs @@ -14,6 +14,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Linq; using Serilog.Expressions.Compilation; using Serilog.Expressions.Parsing; @@ -31,15 +32,17 @@ public static class SerilogExpression /// Create an evaluation function based on the provided expression. /// /// An expression. + /// Optionally, a format provider that will be used for culture-specific formatting; + /// by default, is used. /// Optionally, a - /// with which to resolve function names that appear in the template. + /// with which to resolve function names that appear in the template. /// A function that evaluates the expression in the context of a log event. - public static CompiledExpression Compile( - string expression, + public static CompiledExpression Compile(string expression, + IFormatProvider? formatProvider = null, NameResolver? nameResolver = null) { if (expression == null) throw new ArgumentNullException(nameof(expression)); - if (!TryCompileImpl(expression, nameResolver, out var filter, out var error)) + if (!TryCompileImpl(expression, formatProvider, nameResolver, out var filter, out var error)) throw new ArgumentException(error); return filter; @@ -60,33 +63,35 @@ public static bool TryCompile( [MaybeNullWhen(true)] out string error) { if (expression == null) throw new ArgumentNullException(nameof(expression)); - return TryCompileImpl(expression, null, out result, out error); + return TryCompileImpl(expression, null, null, out result, out error); } /// /// Create an evaluation function based on the provided expression. /// /// An expression. + /// Optionally, a format provider that will be used for culture-specific formatting; + /// by default, is used. + /// A + /// with which to resolve function names that appear in the template. /// A function that evaluates the expression in the context of a log event. /// The reported error, if compilation was unsuccessful. - /// A - /// with which to resolve function names that appear in the template. /// True if the function could be created; otherwise, false. /// Regular expression syntax errors currently generate exceptions instead of producing friendly /// errors. - public static bool TryCompile( - string expression, + public static bool TryCompile(string expression, + IFormatProvider? formatProvider, NameResolver nameResolver, [MaybeNullWhen(false)] out CompiledExpression result, [MaybeNullWhen(true)] out string error) { if (expression == null) throw new ArgumentNullException(nameof(expression)); if (nameResolver == null) throw new ArgumentNullException(nameof(nameResolver)); - return TryCompileImpl(expression, nameResolver, out result, out error); + return TryCompileImpl(expression, formatProvider, nameResolver, out result, out error); } - static bool TryCompileImpl( - string expression, + static bool TryCompileImpl(string expression, + IFormatProvider? formatProvider, NameResolver? nameResolver, [MaybeNullWhen(false)] out CompiledExpression result, [MaybeNullWhen(true)] out string error) @@ -98,7 +103,7 @@ static bool TryCompileImpl( return false; } - var evaluate = ExpressionCompiler.Compile(root, DefaultFunctionNameResolver.Build(nameResolver)); + var evaluate = ExpressionCompiler.Compile(root, formatProvider, DefaultFunctionNameResolver.Build(nameResolver)); result = evt => evaluate(new EvaluationContext(evt)); error = null; return true; diff --git a/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs b/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs index d28c01c..0806ac9 100644 --- a/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs +++ b/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; diff --git a/src/Serilog.Expressions/LoggerFilterConfigurationExtensions.cs b/src/Serilog.Expressions/LoggerFilterConfigurationExtensions.cs index 3356132..87c0ae0 100644 --- a/src/Serilog.Expressions/LoggerFilterConfigurationExtensions.cs +++ b/src/Serilog.Expressions/LoggerFilterConfigurationExtensions.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using Serilog.Configuration; using Serilog.Expressions; using Serilog.Expressions.Runtime; diff --git a/src/Serilog.Expressions/Pipeline/ComputedPropertyEnricher.cs b/src/Serilog.Expressions/Pipeline/ComputedPropertyEnricher.cs index c85d676..810bff3 100644 --- a/src/Serilog.Expressions/Pipeline/ComputedPropertyEnricher.cs +++ b/src/Serilog.Expressions/Pipeline/ComputedPropertyEnricher.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; using Serilog.Core; using Serilog.Events; diff --git a/src/Serilog.Expressions/Templates/Ast/Conditional.cs b/src/Serilog.Expressions/Templates/Ast/Conditional.cs index 3bf74e4..a861922 100644 --- a/src/Serilog.Expressions/Templates/Ast/Conditional.cs +++ b/src/Serilog.Expressions/Templates/Ast/Conditional.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using Serilog.Expressions.Ast; namespace Serilog.Templates.Ast diff --git a/src/Serilog.Expressions/Templates/Ast/FormattedExpression.cs b/src/Serilog.Expressions/Templates/Ast/FormattedExpression.cs index ba43b80..caad317 100644 --- a/src/Serilog.Expressions/Templates/Ast/FormattedExpression.cs +++ b/src/Serilog.Expressions/Templates/Ast/FormattedExpression.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; using Serilog.Expressions.Ast; using Serilog.Parsing; diff --git a/src/Serilog.Expressions/Templates/Ast/LiteralText.cs b/src/Serilog.Expressions/Templates/Ast/LiteralText.cs index 97ad716..17e41f5 100644 --- a/src/Serilog.Expressions/Templates/Ast/LiteralText.cs +++ b/src/Serilog.Expressions/Templates/Ast/LiteralText.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; namespace Serilog.Templates.Ast diff --git a/src/Serilog.Expressions/Templates/Ast/Repetition.cs b/src/Serilog.Expressions/Templates/Ast/Repetition.cs index 2c29cd7..e5ee594 100644 --- a/src/Serilog.Expressions/Templates/Ast/Repetition.cs +++ b/src/Serilog.Expressions/Templates/Ast/Repetition.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using Serilog.Expressions.Ast; namespace Serilog.Templates.Ast diff --git a/src/Serilog.Expressions/Templates/Ast/Template.cs b/src/Serilog.Expressions/Templates/Ast/Template.cs index c81f37d..8fc76e6 100644 --- a/src/Serilog.Expressions/Templates/Ast/Template.cs +++ b/src/Serilog.Expressions/Templates/Ast/Template.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + namespace Serilog.Templates.Ast { abstract class Template diff --git a/src/Serilog.Expressions/Templates/Ast/TemplateBlock.cs b/src/Serilog.Expressions/Templates/Ast/TemplateBlock.cs index 867ae7b..d90e597 100644 --- a/src/Serilog.Expressions/Templates/Ast/TemplateBlock.cs +++ b/src/Serilog.Expressions/Templates/Ast/TemplateBlock.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; namespace Serilog.Templates.Ast diff --git a/src/Serilog.Expressions/Templates/Compilation/CompiledConditional.cs b/src/Serilog.Expressions/Templates/Compilation/CompiledConditional.cs index 6703408..795ef29 100644 --- a/src/Serilog.Expressions/Templates/Compilation/CompiledConditional.cs +++ b/src/Serilog.Expressions/Templates/Compilation/CompiledConditional.cs @@ -1,6 +1,19 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.IO; -using Serilog.Events; using Serilog.Expressions; namespace Serilog.Templates.Compilation @@ -18,12 +31,12 @@ public CompiledConditional(Evaluatable condition, CompiledTemplate consequent, C _alternative = alternative; } - public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatProvider? formatProvider) + public override void Evaluate(EvaluationContext ctx, TextWriter output) { if (ExpressionResult.IsTrue(_condition.Invoke(ctx))) - _consequent.Evaluate(ctx, output, formatProvider); + _consequent.Evaluate(ctx, output); else - _alternative?.Evaluate(ctx, output, formatProvider); + _alternative?.Evaluate(ctx, output); } } } diff --git a/src/Serilog.Expressions/Templates/Compilation/CompiledExceptionToken.cs b/src/Serilog.Expressions/Templates/Compilation/CompiledExceptionToken.cs new file mode 100644 index 0000000..8e932d3 --- /dev/null +++ b/src/Serilog.Expressions/Templates/Compilation/CompiledExceptionToken.cs @@ -0,0 +1,51 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.IO; +using Serilog.Expressions; +using Serilog.Templates.Themes; + +namespace Serilog.Templates.Compilation +{ + class CompiledExceptionToken : CompiledTemplate + { + const string StackFrameLinePrefix = " "; + + readonly Style _text, _secondaryText; + + public CompiledExceptionToken(TemplateTheme theme) + { + _text = theme.GetStyle(TemplateThemeStyle.Text); + _secondaryText = theme.GetStyle(TemplateThemeStyle.SecondaryText); + } + + public override void Evaluate(EvaluationContext ctx, TextWriter output) + { + // Padding and alignment are not applied by this renderer. + + if (ctx.LogEvent.Exception is null) + return; + + var lines = new StringReader(ctx.LogEvent.Exception.ToString()); + string? nextLine; + while ((nextLine = lines.ReadLine()) != null) + { + var style = nextLine.StartsWith(StackFrameLinePrefix) ? _secondaryText : _text; + var _ = 0; + using (style.Set(output, ref _)) + output.WriteLine(nextLine); + } + } + } +} diff --git a/src/Serilog.Expressions/Templates/Compilation/CompiledFormattedExpression.cs b/src/Serilog.Expressions/Templates/Compilation/CompiledFormattedExpression.cs index 53c9c57..02dcfc2 100644 --- a/src/Serilog.Expressions/Templates/Compilation/CompiledFormattedExpression.cs +++ b/src/Serilog.Expressions/Templates/Compilation/CompiledFormattedExpression.cs @@ -1,43 +1,63 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; using System.IO; using Serilog.Events; using Serilog.Expressions; -using Serilog.Formatting.Json; using Serilog.Parsing; using Serilog.Templates.Rendering; +using Serilog.Templates.Themes; namespace Serilog.Templates.Compilation { class CompiledFormattedExpression : CompiledTemplate { - static readonly JsonValueFormatter JsonFormatter = new JsonValueFormatter("$type"); - + readonly ThemedJsonValueFormatter _jsonFormatter; readonly Evaluatable _expression; readonly string? _format; readonly Alignment? _alignment; + readonly IFormatProvider? _formatProvider; + readonly Style _secondaryText; - public CompiledFormattedExpression(Evaluatable expression, string? format, Alignment? alignment) + public CompiledFormattedExpression(Evaluatable expression, string? format, Alignment? alignment, IFormatProvider? formatProvider, TemplateTheme theme) { _expression = expression ?? throw new ArgumentNullException(nameof(expression)); _format = format; _alignment = alignment; + _formatProvider = formatProvider; + _secondaryText = theme.GetStyle(TemplateThemeStyle.SecondaryText); + _jsonFormatter = new ThemedJsonValueFormatter(theme); } - public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatProvider? formatProvider) + public override void Evaluate(EvaluationContext ctx, TextWriter output) { + var invisibleCharacterCount = 0; + if (_alignment == null) { - EvaluateUnaligned(ctx, output, formatProvider); + EvaluateUnaligned(ctx, output, _formatProvider, ref invisibleCharacterCount); } else { var writer = new StringWriter(); - EvaluateUnaligned(ctx, writer, formatProvider); - Padding.Apply(output, writer.ToString(), _alignment.Value); + EvaluateUnaligned(ctx, writer, _formatProvider, ref invisibleCharacterCount); + Padding.Apply(output, writer.ToString(), _alignment.Value.Widen(invisibleCharacterCount)); } } - void EvaluateUnaligned(EvaluationContext ctx, TextWriter output, IFormatProvider? formatProvider) + void EvaluateUnaligned(EvaluationContext ctx, TextWriter output, IFormatProvider? formatProvider, ref int invisibleCharacterCount) { var value = _expression(ctx); if (value == null) @@ -48,17 +68,16 @@ void EvaluateUnaligned(EvaluationContext ctx, TextWriter output, IFormatProvider if (scalar.Value is null) return; // Null is empty - if (scalar.Value is LogEventLevel level) - // This would be better implemented using CompiledLevelToken : CompiledTemplate. - output.Write(LevelRenderer.GetLevelMoniker(level, _format)); - else if (scalar.Value is IFormattable fmt) + using var style = _secondaryText.Set(output, ref invisibleCharacterCount); + + if (scalar.Value is IFormattable fmt) output.Write(fmt.ToString(_format, formatProvider)); else output.Write(scalar.Value.ToString()); } else { - JsonFormatter.Format(value, output); + invisibleCharacterCount += _jsonFormatter.Format(value, output); } } } diff --git a/src/Serilog.Expressions/Templates/Compilation/CompiledLevelToken.cs b/src/Serilog.Expressions/Templates/Compilation/CompiledLevelToken.cs new file mode 100644 index 0000000..0fb1059 --- /dev/null +++ b/src/Serilog.Expressions/Templates/Compilation/CompiledLevelToken.cs @@ -0,0 +1,70 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.IO; +using Serilog.Expressions; +using Serilog.Parsing; +using Serilog.Templates.Rendering; +using Serilog.Templates.Themes; + +namespace Serilog.Templates.Compilation +{ + class CompiledLevelToken : CompiledTemplate + { + readonly string? _format; + readonly Alignment? _alignment; + readonly Style[] _levelStyles; + + public CompiledLevelToken(string? format, Alignment? alignment, TemplateTheme theme) + { + _format = format; + _alignment = alignment; + _levelStyles = new[] + { + theme.GetStyle(TemplateThemeStyle.LevelVerbose), + theme.GetStyle(TemplateThemeStyle.LevelDebug), + theme.GetStyle(TemplateThemeStyle.LevelInformation), + theme.GetStyle(TemplateThemeStyle.LevelWarning), + theme.GetStyle(TemplateThemeStyle.LevelError), + theme.GetStyle(TemplateThemeStyle.LevelFatal), + }; + } + + public override void Evaluate(EvaluationContext ctx, TextWriter output) + { + var invisibleCharacterCount = 0; + + if (_alignment == null) + { + EvaluateUnaligned(ctx, output, ref invisibleCharacterCount); + } + else + { + var writer = new StringWriter(); + EvaluateUnaligned(ctx, writer, ref invisibleCharacterCount); + Padding.Apply(output, writer.ToString(), _alignment.Value.Widen(invisibleCharacterCount)); + } + } + + void EvaluateUnaligned(EvaluationContext ctx, TextWriter output, ref int invisibleCharacterCount) + { + var levelIndex = (int) ctx.LogEvent.Level; + if (levelIndex < 0 || levelIndex >= _levelStyles.Length) + return; + + using var _ = _levelStyles[levelIndex].Set(output, ref invisibleCharacterCount); + output.Write(LevelRenderer.GetLevelMoniker(ctx.LogEvent.Level, _format)); + } + } +} diff --git a/src/Serilog.Expressions/Templates/Compilation/CompiledLiteralText.cs b/src/Serilog.Expressions/Templates/Compilation/CompiledLiteralText.cs index 0bcb0ba..582b715 100644 --- a/src/Serilog.Expressions/Templates/Compilation/CompiledLiteralText.cs +++ b/src/Serilog.Expressions/Templates/Compilation/CompiledLiteralText.cs @@ -1,22 +1,40 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; using System.IO; -using Serilog.Events; using Serilog.Expressions; +using Serilog.Templates.Themes; namespace Serilog.Templates.Compilation { class CompiledLiteralText : CompiledTemplate { readonly string _text; + readonly Style _style; - public CompiledLiteralText(string text) + public CompiledLiteralText(string text, TemplateTheme theme) { _text = text ?? throw new ArgumentNullException(nameof(text)); + _style = theme.GetStyle(TemplateThemeStyle.TertiaryText); } - public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatProvider? formatProvider) + public override void Evaluate(EvaluationContext ctx, TextWriter output) { - output.Write(_text); + var _ = 0; + using (_style.Set(output, ref _)) + output.Write(_text); } } -} \ No newline at end of file +} diff --git a/src/Serilog.Expressions/Templates/Compilation/CompiledMessageToken.cs b/src/Serilog.Expressions/Templates/Compilation/CompiledMessageToken.cs new file mode 100644 index 0000000..836d7dd --- /dev/null +++ b/src/Serilog.Expressions/Templates/Compilation/CompiledMessageToken.cs @@ -0,0 +1,184 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.IO; +using Serilog.Events; +using Serilog.Expressions; +using Serilog.Parsing; +using Serilog.Templates.Rendering; +using Serilog.Templates.Themes; + +namespace Serilog.Templates.Compilation +{ + class CompiledMessageToken : CompiledTemplate + { + readonly IFormatProvider? _formatProvider; + readonly Alignment? _alignment; + readonly Style _text, _invalid, _null, _bool, _string, _num, _scalar; + readonly ThemedJsonValueFormatter _jsonFormatter; + + public CompiledMessageToken(IFormatProvider? formatProvider, Alignment? alignment, TemplateTheme theme) + { + _formatProvider = formatProvider; + _alignment = alignment; + _text = theme.GetStyle(TemplateThemeStyle.Text); + _null = theme.GetStyle(TemplateThemeStyle.Null); + _bool = theme.GetStyle(TemplateThemeStyle.Boolean); + _num = theme.GetStyle(TemplateThemeStyle.Number); + _string = theme.GetStyle(TemplateThemeStyle.String); + _scalar = theme.GetStyle(TemplateThemeStyle.Scalar); + _invalid = theme.GetStyle(TemplateThemeStyle.Invalid); + _jsonFormatter = new ThemedJsonValueFormatter(theme); + } + + public override void Evaluate(EvaluationContext ctx, TextWriter output) + { + var invisibleCharacterCount = 0; + + if (_alignment == null) + { + EvaluateUnaligned(ctx, output, ref invisibleCharacterCount); + } + else + { + var writer = new StringWriter(); + EvaluateUnaligned(ctx, writer, ref invisibleCharacterCount); + Padding.Apply(output, writer.ToString(), _alignment.Value.Widen(invisibleCharacterCount)); + } + } + + void EvaluateUnaligned(EvaluationContext ctx, TextWriter output, ref int invisibleCharacterCount) + { + foreach (var token in ctx.LogEvent.MessageTemplate.Tokens) + { + switch (token) + { + case TextToken tt: + { + using var _ = _text.Set(output, ref invisibleCharacterCount); + output.Write(tt.Text); + break; + } + case PropertyToken pt: + { + EvaluateProperty(ctx.LogEvent.Properties, pt, output, ref invisibleCharacterCount); + break; + } + default: + { + output.Write(token); + break; + } + } + } + } + + void EvaluateProperty(IReadOnlyDictionary properties, PropertyToken pt, TextWriter output, ref int invisibleCharacterCount) + { + if (!properties.TryGetValue(pt.PropertyName, out var value)) + { + using var _ = _invalid.Set(output, ref invisibleCharacterCount); + output.Write(pt.ToString()); + return; + } + + if (pt.Alignment is null) + { + EvaluatePropertyUnaligned(value, output, pt.Format, ref invisibleCharacterCount); + return; + } + + var buffer = new StringWriter(); + var resultInvisibleCharacters = 0; + + EvaluatePropertyUnaligned(value, buffer, pt.Format, ref resultInvisibleCharacters); + + var result = buffer.ToString(); + invisibleCharacterCount += resultInvisibleCharacters; + + if (result.Length - resultInvisibleCharacters >= pt.Alignment.Value.Width) + output.Write(result); + else + Padding.Apply(output, result, pt.Alignment.Value.Widen(resultInvisibleCharacters)); + } + + void EvaluatePropertyUnaligned(LogEventPropertyValue propertyValue, TextWriter output, string? format, ref int invisibleCharacterCount) + { + if (propertyValue is not ScalarValue scalar) + { + invisibleCharacterCount += _jsonFormatter.Format(propertyValue, output); + return; + } + + var value = scalar.Value; + + if (value == null) + { + using (_null.Set(output, ref invisibleCharacterCount)) + output.Write("null"); + return; + } + + if (value is string str) + { + using (_string.Set(output, ref invisibleCharacterCount)) + output.Write(str); + return; + } + + if (value is ValueType) + { + if (value is int or uint or long or ulong or decimal or byte or sbyte or short or ushort) + { + using (_num.Set(output, ref invisibleCharacterCount)) + output.Write(((IFormattable)value).ToString(format, _formatProvider)); + return; + } + + if (value is double d) + { + using (_num.Set(output, ref invisibleCharacterCount)) + output.Write(d.ToString(format, _formatProvider)); + return; + } + + if (value is float f) + { + using (_num.Set(output, ref invisibleCharacterCount)) + output.Write(f.ToString(format, _formatProvider)); + return; + } + + if (value is bool b) + { + using (_bool.Set(output, ref invisibleCharacterCount)) + output.Write(b); + return; + } + } + + if (value is IFormattable formattable) + { + using (_scalar.Set(output, ref invisibleCharacterCount)) + output.Write(formattable.ToString(format, _formatProvider)); + return; + } + + using (_scalar.Set(output, ref invisibleCharacterCount)) + output.Write(value); + } + } +} diff --git a/src/Serilog.Expressions/Templates/Compilation/CompiledRepetition.cs b/src/Serilog.Expressions/Templates/Compilation/CompiledRepetition.cs index b3e4aad..4ca1e40 100644 --- a/src/Serilog.Expressions/Templates/Compilation/CompiledRepetition.cs +++ b/src/Serilog.Expressions/Templates/Compilation/CompiledRepetition.cs @@ -1,4 +1,17 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System.IO; using Serilog.Events; using Serilog.Expressions; @@ -31,13 +44,13 @@ public CompiledRepetition( _alternative = alternative; } - public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatProvider? formatProvider) + public override void Evaluate(EvaluationContext ctx, TextWriter output) { var enumerable = _enumerable(ctx); if (enumerable == null || enumerable is ScalarValue) { - _alternative?.Evaluate(ctx, output, formatProvider); + _alternative?.Evaluate(ctx, output); return; } @@ -45,7 +58,7 @@ public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatP { if (sv.Elements.Count == 0) { - _alternative?.Evaluate(ctx, output, formatProvider); + _alternative?.Evaluate(ctx, output); return; } @@ -58,13 +71,13 @@ public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatP if (first) first = false; else - _delimiter?.Evaluate(ctx, output, formatProvider); + _delimiter?.Evaluate(ctx, output); var local = _keyOrElementName != null ? new EvaluationContext(ctx.LogEvent, Locals.Set(ctx.Locals, _keyOrElementName, element)) : ctx; - _body.Evaluate(local, output, formatProvider); + _body.Evaluate(local, output); } return; @@ -74,7 +87,7 @@ public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatP { if (structure.Properties.Count == 0) { - _alternative?.Evaluate(ctx, output, formatProvider); + _alternative?.Evaluate(ctx, output); return; } @@ -84,7 +97,7 @@ public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatP if (first) first = false; else - _delimiter?.Evaluate(ctx, output, formatProvider); + _delimiter?.Evaluate(ctx, output); var local = _keyOrElementName != null ? new EvaluationContext(ctx.LogEvent, Locals.Set(ctx.Locals, _keyOrElementName, new ScalarValue(member.Name))) @@ -94,7 +107,7 @@ public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatP ? new EvaluationContext(local.LogEvent, Locals.Set(local.Locals, _valueName, member.Value)) : local; - _body.Evaluate(local, output, formatProvider); + _body.Evaluate(local, output); } } @@ -102,7 +115,7 @@ public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatP { if (dict.Elements.Count == 0) { - _alternative?.Evaluate(ctx, output, formatProvider); + _alternative?.Evaluate(ctx, output); return; } @@ -112,7 +125,7 @@ public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatP if (first) first = false; else - _delimiter?.Evaluate(ctx, output, formatProvider); + _delimiter?.Evaluate(ctx, output); var local = _keyOrElementName != null ? new EvaluationContext(ctx.LogEvent, Locals.Set(ctx.Locals, _keyOrElementName, element.Key)) @@ -122,7 +135,7 @@ public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatP ? new EvaluationContext(local.LogEvent, Locals.Set(local.Locals, _valueName, element.Value)) : local; - _body.Evaluate(local, output, formatProvider); + _body.Evaluate(local, output); } } diff --git a/src/Serilog.Expressions/Templates/Compilation/CompiledTemplate.cs b/src/Serilog.Expressions/Templates/Compilation/CompiledTemplate.cs index 9ff6222..a44889f 100644 --- a/src/Serilog.Expressions/Templates/Compilation/CompiledTemplate.cs +++ b/src/Serilog.Expressions/Templates/Compilation/CompiledTemplate.cs @@ -1,12 +1,24 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System.IO; -using Serilog.Events; using Serilog.Expressions; namespace Serilog.Templates.Compilation { abstract class CompiledTemplate { - public abstract void Evaluate(EvaluationContext ctx, TextWriter output, IFormatProvider? formatProvider); + public abstract void Evaluate(EvaluationContext ctx, TextWriter output); } -} \ No newline at end of file +} diff --git a/src/Serilog.Expressions/Templates/Compilation/CompiledTemplateBlock.cs b/src/Serilog.Expressions/Templates/Compilation/CompiledTemplateBlock.cs index 3b9832b..b335ded 100644 --- a/src/Serilog.Expressions/Templates/Compilation/CompiledTemplateBlock.cs +++ b/src/Serilog.Expressions/Templates/Compilation/CompiledTemplateBlock.cs @@ -1,6 +1,19 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; using System.IO; -using Serilog.Events; using Serilog.Expressions; namespace Serilog.Templates.Compilation @@ -14,10 +27,10 @@ public CompiledTemplateBlock(CompiledTemplate[] elements) _elements = elements ?? throw new ArgumentNullException(nameof(elements)); } - public override void Evaluate(EvaluationContext ctx, TextWriter output, IFormatProvider? formatProvider) + public override void Evaluate(EvaluationContext ctx, TextWriter output) { foreach (var element in _elements) - element.Evaluate(ctx, output, formatProvider); + element.Evaluate(ctx, output); } } -} \ No newline at end of file +} diff --git a/src/Serilog.Expressions/Templates/Compilation/NameResolution/ExpressionLocalNameBinder.cs b/src/Serilog.Expressions/Templates/Compilation/NameResolution/ExpressionLocalNameBinder.cs index 3894076..8d407f2 100644 --- a/src/Serilog.Expressions/Templates/Compilation/NameResolution/ExpressionLocalNameBinder.cs +++ b/src/Serilog.Expressions/Templates/Compilation/NameResolution/ExpressionLocalNameBinder.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Collections.Generic; using System.Linq; using Serilog.Expressions.Ast; diff --git a/src/Serilog.Expressions/Templates/Compilation/NameResolution/TemplateLocalNameResolver.cs b/src/Serilog.Expressions/Templates/Compilation/NameResolution/TemplateLocalNameResolver.cs index 62222a9..3b947e8 100644 --- a/src/Serilog.Expressions/Templates/Compilation/NameResolution/TemplateLocalNameResolver.cs +++ b/src/Serilog.Expressions/Templates/Compilation/NameResolution/TemplateLocalNameResolver.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Collections.Generic; using System.Linq; using Serilog.Templates.Ast; diff --git a/src/Serilog.Expressions/Templates/Compilation/TemplateCompiler.cs b/src/Serilog.Expressions/Templates/Compilation/TemplateCompiler.cs index 87980a7..70a93e3 100644 --- a/src/Serilog.Expressions/Templates/Compilation/TemplateCompiler.cs +++ b/src/Serilog.Expressions/Templates/Compilation/TemplateCompiler.cs @@ -1,33 +1,63 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; using System.Linq; using Serilog.Expressions; +using Serilog.Expressions.Ast; using Serilog.Expressions.Compilation; using Serilog.Templates.Ast; +using Serilog.Templates.Themes; namespace Serilog.Templates.Compilation { static class TemplateCompiler { - public static CompiledTemplate Compile(Template template, NameResolver nameResolver) + public static CompiledTemplate Compile(Template template, + IFormatProvider? formatProvider, NameResolver nameResolver, + TemplateTheme theme) { - if (template == null) throw new ArgumentNullException(nameof(template)); return template switch { - LiteralText text => new CompiledLiteralText(text.Text), + LiteralText text => new CompiledLiteralText(text.Text, theme), + FormattedExpression { Expression: AmbientNameExpression { IsBuiltIn: true, PropertyName: BuiltInProperty.Level} } level => new CompiledLevelToken( + level.Format, level.Alignment, theme), + FormattedExpression + { + Expression: AmbientNameExpression { IsBuiltIn: true, PropertyName: BuiltInProperty.Exception }, + Alignment: null, + Format: null + } => new CompiledExceptionToken(theme), + FormattedExpression + { + Expression: AmbientNameExpression { IsBuiltIn: true, PropertyName: BuiltInProperty.Message }, + Format: null + } message => new CompiledMessageToken(formatProvider, message.Alignment, theme), FormattedExpression expression => new CompiledFormattedExpression( - ExpressionCompiler.Compile(expression.Expression, nameResolver), expression.Format, expression.Alignment), - TemplateBlock block => new CompiledTemplateBlock(block.Elements.Select(e => Compile(e, nameResolver)).ToArray()), + ExpressionCompiler.Compile(expression.Expression, formatProvider, nameResolver), expression.Format, expression.Alignment, formatProvider, theme), + TemplateBlock block => new CompiledTemplateBlock(block.Elements.Select(e => Compile(e, formatProvider, nameResolver, theme)).ToArray()), Conditional conditional => new CompiledConditional( - ExpressionCompiler.Compile(conditional.Condition, nameResolver), - Compile(conditional.Consequent, nameResolver), - conditional.Alternative == null ? null : Compile(conditional.Alternative, nameResolver)), + ExpressionCompiler.Compile(conditional.Condition, formatProvider, nameResolver), + Compile(conditional.Consequent, formatProvider, nameResolver, theme), + conditional.Alternative == null ? null : Compile(conditional.Alternative, formatProvider, nameResolver, theme)), Repetition repetition => new CompiledRepetition( - ExpressionCompiler.Compile(repetition.Enumerable, nameResolver), + ExpressionCompiler.Compile(repetition.Enumerable, formatProvider, nameResolver), repetition.BindingNames.Length > 0 ? repetition.BindingNames[0] : null, repetition.BindingNames.Length > 1 ? repetition.BindingNames[1] : null, - Compile(repetition.Body, nameResolver), - repetition.Delimiter == null ? null : Compile(repetition.Delimiter, nameResolver), - repetition.Alternative == null ? null : Compile(repetition.Alternative, nameResolver)), + Compile(repetition.Body, formatProvider, nameResolver, theme), + repetition.Delimiter == null ? null : Compile(repetition.Delimiter, formatProvider, nameResolver, theme), + repetition.Alternative == null ? null : Compile(repetition.Alternative, formatProvider, nameResolver, theme)), _ => throw new NotSupportedException() }; } diff --git a/src/Serilog.Expressions/Templates/ExpressionTemplate.cs b/src/Serilog.Expressions/Templates/ExpressionTemplate.cs index 60f0305..28afab9 100644 --- a/src/Serilog.Expressions/Templates/ExpressionTemplate.cs +++ b/src/Serilog.Expressions/Templates/ExpressionTemplate.cs @@ -1,3 +1,17 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using System; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -8,6 +22,7 @@ using Serilog.Templates.Compilation; using Serilog.Templates.Compilation.NameResolution; using Serilog.Templates.Parsing; +using Serilog.Templates.Themes; namespace Serilog.Templates { @@ -16,9 +31,8 @@ namespace Serilog.Templates /// public class ExpressionTemplate : ITextFormatter { - readonly IFormatProvider? _formatProvider; readonly CompiledTemplate _compiled; - + /// /// Construct an . /// @@ -32,7 +46,7 @@ public static bool TryParse( [MaybeNullWhen(true)] out string error) { if (template == null) throw new ArgumentNullException(nameof(template)); - return TryParse(template, null, null, out result, out error); + return TryParse(template, null, null, null, out result, out error); } /// @@ -41,6 +55,7 @@ public static bool TryParse( /// The template text. /// Optionally, an to use when formatting /// embedded values. + /// Optionally, an ANSI theme to apply to the template output. /// The parsed template, if successful. /// A description of the error, if unsuccessful. /// Optionally, a @@ -50,6 +65,7 @@ public static bool TryParse( string template, IFormatProvider? formatProvider, NameResolver? nameResolver, + TemplateTheme? theme, [MaybeNullWhen(false)] out ExpressionTemplate result, [MaybeNullWhen(true)] out string error) { @@ -64,14 +80,18 @@ public static bool TryParse( var planned = TemplateLocalNameBinder.BindLocalValueNames(parsed); - result = new ExpressionTemplate(TemplateCompiler.Compile(planned, DefaultFunctionNameResolver.Build(nameResolver)), formatProvider); + result = new ExpressionTemplate( + TemplateCompiler.Compile( + planned, + formatProvider, DefaultFunctionNameResolver.Build(nameResolver), + theme ?? TemplateTheme.None)); + return true; } - ExpressionTemplate(CompiledTemplate compiled, IFormatProvider? formatProvider) + ExpressionTemplate(CompiledTemplate compiled) { _compiled = compiled; - _formatProvider = formatProvider; } /// @@ -82,10 +102,12 @@ public static bool TryParse( /// embedded values. /// Optionally, a /// with which to resolve function names that appear in the template. + /// Optionally, an ANSI theme to apply to the template output. public ExpressionTemplate( string template, IFormatProvider? formatProvider = null, - NameResolver? nameResolver = null) + NameResolver? nameResolver = null, + TemplateTheme? theme = null) { if (template == null) throw new ArgumentNullException(nameof(template)); @@ -95,14 +117,15 @@ public ExpressionTemplate( var planned = TemplateLocalNameBinder.BindLocalValueNames(parsed); - _compiled = TemplateCompiler.Compile(planned, DefaultFunctionNameResolver.Build(nameResolver)); - _formatProvider = formatProvider; + _compiled = TemplateCompiler.Compile( + planned, + formatProvider, DefaultFunctionNameResolver.Build(nameResolver), theme ?? TemplateTheme.None); } /// public void Format(LogEvent logEvent, TextWriter output) { - _compiled.Evaluate(new EvaluationContext(logEvent), output, _formatProvider); + _compiled.Evaluate(new EvaluationContext(logEvent), output); } } } diff --git a/src/Serilog.Expressions/Templates/Parsing/TemplateParser.cs b/src/Serilog.Expressions/Templates/Parsing/TemplateParser.cs index b8c9814..296a410 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateParser.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateParser.cs @@ -1,4 +1,18 @@ -using System; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Diagnostics.CodeAnalysis; using Serilog.Templates.Ast; @@ -6,8 +20,8 @@ namespace Serilog.Templates.Parsing { class TemplateParser { - readonly TemplateTokenizer _tokenizer = new TemplateTokenizer(); - readonly TemplateTokenParsers _templateTokenParsers = new TemplateTokenParsers(); + readonly TemplateTokenizer _tokenizer = new(); + readonly TemplateTokenParsers _templateTokenParsers = new(); public bool TryParse( string template, diff --git a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs index f8dc970..0fd2c58 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs @@ -1,4 +1,18 @@ -using Serilog.Expressions.Ast; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Expressions.Ast; using Serilog.Expressions.Parsing; using Serilog.Parsing; using Serilog.ParserConstruction; @@ -33,7 +47,7 @@ from width in Token.EqualTo(Number).Apply(Numerics.NaturalUInt32) var hole = from _ in Token.EqualTo(LBrace) from expr in ExpressionTokenParsers.Expr - from align in alignment.OptionalOrDefault() + from align in alignment.Select(a => (Alignment?)a).OptionalOrDefault() from fmt in format.OptionalOrDefault() from __ in Token.EqualTo(RBrace) select (Template) new FormattedExpression(expr, fmt, align); @@ -79,7 +93,7 @@ from end in Directive(false, End) var eachDirective = Token.EqualTo(LBraceHash) .IgnoreThen(Token.EqualTo(Each)).Try() - .IgnoreThen(Token.EqualTo(ExpressionToken.Identifier) + .IgnoreThen(Token.EqualTo(Identifier) .Select(i => i.ToStringValue()) .AtLeastOnceDelimitedBy(Token.EqualTo(Comma))) .Then(bindings => Token.EqualTo(In).Value(bindings)) diff --git a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs index 4c639af..283f262 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs @@ -1,4 +1,18 @@ -using System.Collections.Generic; +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections.Generic; using Serilog.Expressions.Parsing; using Serilog.ParserConstruction; using Serilog.ParserConstruction.Model; diff --git a/src/Serilog.Expressions/Templates/Rendering/AlignmentExtensions.cs b/src/Serilog.Expressions/Templates/Rendering/AlignmentExtensions.cs new file mode 100644 index 0000000..7a6a5e5 --- /dev/null +++ b/src/Serilog.Expressions/Templates/Rendering/AlignmentExtensions.cs @@ -0,0 +1,26 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Parsing; + +namespace Serilog.Templates.Rendering +{ + static class AlignmentExtensions + { + public static Alignment Widen(this Alignment alignment, int amount) + { + return new(alignment.Direction, alignment.Width + amount); + } + } +} diff --git a/src/Serilog.Expressions/Templates/Themes/Style.cs b/src/Serilog.Expressions/Templates/Themes/Style.cs new file mode 100644 index 0000000..df66777 --- /dev/null +++ b/src/Serilog.Expressions/Templates/Themes/Style.cs @@ -0,0 +1,47 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.IO; + +namespace Serilog.Templates.Themes +{ + readonly struct Style + { + readonly string? _ansiStyle; + + public Style(string ansiStyle) + { + _ansiStyle = ansiStyle; + } + + internal StyleReset Set(TextWriter output, ref int invisibleCharacterCount) + { + if (_ansiStyle != null) + { + output.Write(_ansiStyle); + invisibleCharacterCount += _ansiStyle.Length; + invisibleCharacterCount += StyleReset.ResetCharCount; + + return new StyleReset(output); + } + + return default; + } + + public string? GetAnsiStyle() + { + return _ansiStyle; + } + } +} diff --git a/src/Serilog.Expressions/Templates/Themes/StyleReset.cs b/src/Serilog.Expressions/Templates/Themes/StyleReset.cs new file mode 100644 index 0000000..f6f096a --- /dev/null +++ b/src/Serilog.Expressions/Templates/Themes/StyleReset.cs @@ -0,0 +1,37 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.IO; + +namespace Serilog.Templates.Themes +{ + readonly struct StyleReset : IDisposable + { + const string AnsiStyleResetSequence = "\x1b[0m"; + public const int ResetCharCount = 4; + + readonly TextWriter? _output; + + public StyleReset(TextWriter output) + { + _output = output; + } + + public void Dispose() + { + _output?.Write(AnsiStyleResetSequence); + } + } +} diff --git a/src/Serilog.Expressions/Templates/Themes/TemplateTheme.cs b/src/Serilog.Expressions/Templates/Themes/TemplateTheme.cs new file mode 100644 index 0000000..3a8fccb --- /dev/null +++ b/src/Serilog.Expressions/Templates/Themes/TemplateTheme.cs @@ -0,0 +1,79 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Serilog.Templates.Themes +{ + /// + /// A template theme using the ANSI terminal escape sequences. + /// + public class TemplateTheme + { + /// + /// A 256-color theme along the lines of Visual Studio Code. + /// + public static TemplateTheme Code { get; } = TemplateThemes.Code; + + /// + /// A theme using only gray, black and white. + /// + public static TemplateTheme Grayscale { get; } = TemplateThemes.Grayscale; + + /// + /// A theme in the style of the original Serilog.Sinks.Literate. + /// + public static TemplateTheme Literate { get; } = TemplateThemes.Literate; + + internal static TemplateTheme None { get; } = new TemplateTheme(new Dictionary()); + + readonly Dictionary _styles; + + /// + /// Construct a theme given a set of styles. + /// + /// Styles to apply within the theme. The dictionary maps style names to ANSI + /// sequences implementing the styles. + /// When is null + public TemplateTheme(IReadOnlyDictionary ansiStyles) + { + if (ansiStyles is null) throw new ArgumentNullException(nameof(ansiStyles)); + _styles = ansiStyles.ToDictionary(kv => kv.Key, kv => new Style(kv.Value)); + } + + /// + /// Construct a theme given a set of styles. + /// + /// A base template theme, which will supply styles not overridden in . + /// Styles to apply within the theme. The dictionary maps style names to ANSI + /// sequences implementing the styles. + /// When is null + public TemplateTheme(TemplateTheme baseTheme, IReadOnlyDictionary ansiStyles) + { + if (baseTheme == null) throw new ArgumentNullException(nameof(baseTheme)); + if (ansiStyles is null) throw new ArgumentNullException(nameof(ansiStyles)); + _styles = new Dictionary(baseTheme._styles); + foreach (var kv in ansiStyles) + _styles[kv.Key] = new Style(kv.Value); + } + + internal Style GetStyle(TemplateThemeStyle templateThemeStyle) + { + _styles.TryGetValue(templateThemeStyle, out var style); + return style; + } + } +} diff --git a/src/Serilog.Expressions/Templates/Themes/TemplateThemeStyle.cs b/src/Serilog.Expressions/Templates/Themes/TemplateThemeStyle.cs new file mode 100644 index 0000000..25912e5 --- /dev/null +++ b/src/Serilog.Expressions/Templates/Themes/TemplateThemeStyle.cs @@ -0,0 +1,104 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Templates.Themes +{ + /// + /// Elements styled by a template theme. + /// + public enum TemplateThemeStyle + { + /// + /// Prominent text, generally content within an event's message. + /// + Text, + + /// + /// Boilerplate text, for example items specified in an output template. + /// + SecondaryText, + + /// + /// De-emphasized text, for example literal text in output templates and + /// punctuation used when writing structured data. + /// + TertiaryText, + + /// + /// Output demonstrating some kind of configuration issue, e.g. an invalid + /// message template token. + /// + Invalid, + + /// + /// The built-in value. + /// + Null, + + /// + /// Property and type names. + /// + Name, + + /// + /// Strings. + /// + String, + + /// + /// Numbers. + /// + Number, + + /// + /// values. + /// + Boolean, + + /// + /// All other scalar values, e.g. instances. + /// + Scalar, + + /// + /// Level indicator. + /// + LevelVerbose, + + /// + /// Level indicator. + /// + LevelDebug, + + /// + /// Level indicator. + /// + LevelInformation, + + /// + /// Level indicator. + /// + LevelWarning, + + /// + /// Level indicator. + /// + LevelError, + + /// + /// Level indicator. + /// + LevelFatal, + } +} diff --git a/src/Serilog.Expressions/Templates/Themes/TemplateThemes.cs b/src/Serilog.Expressions/Templates/Themes/TemplateThemes.cs new file mode 100644 index 0000000..7b17bef --- /dev/null +++ b/src/Serilog.Expressions/Templates/Themes/TemplateThemes.cs @@ -0,0 +1,84 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections.Generic; + +namespace Serilog.Templates.Themes +{ + static class TemplateThemes + { + public static TemplateTheme Literate { get; } = new( + new Dictionary + { + [TemplateThemeStyle.Text] = "\x1b[38;5;0015m", + [TemplateThemeStyle.SecondaryText] = "\x1b[38;5;0007m", + [TemplateThemeStyle.TertiaryText] = "\x1b[38;5;0008m", + [TemplateThemeStyle.Invalid] = "\x1b[38;5;0011m", + [TemplateThemeStyle.Null] = "\x1b[38;5;0027m", + [TemplateThemeStyle.Name] = "\x1b[38;5;0007m", + [TemplateThemeStyle.String] = "\x1b[38;5;0045m", + [TemplateThemeStyle.Number] = "\x1b[38;5;0200m", + [TemplateThemeStyle.Boolean] = "\x1b[38;5;0027m", + [TemplateThemeStyle.Scalar] = "\x1b[38;5;0085m", + [TemplateThemeStyle.LevelVerbose] = "\x1b[38;5;0007m", + [TemplateThemeStyle.LevelDebug] = "\x1b[38;5;0007m", + [TemplateThemeStyle.LevelInformation] = "\x1b[38;5;0015m", + [TemplateThemeStyle.LevelWarning] = "\x1b[38;5;0011m", + [TemplateThemeStyle.LevelError] = "\x1b[38;5;0015m\x1b[48;5;0196m", + [TemplateThemeStyle.LevelFatal] = "\x1b[38;5;0015m\x1b[48;5;0196m", + }); + + public static TemplateTheme Grayscale { get; } = new( + new Dictionary + { + [TemplateThemeStyle.Text] = "\x1b[37;1m", + [TemplateThemeStyle.SecondaryText] = "\x1b[37m", + [TemplateThemeStyle.TertiaryText] = "\x1b[30;1m", + [TemplateThemeStyle.Invalid] = "\x1b[37;1m\x1b[47m", + [TemplateThemeStyle.Null] = "\x1b[1m\x1b[37;1m", + [TemplateThemeStyle.Name] = "\x1b[37m", + [TemplateThemeStyle.String] = "\x1b[1m\x1b[37;1m", + [TemplateThemeStyle.Number] = "\x1b[1m\x1b[37;1m", + [TemplateThemeStyle.Boolean] = "\x1b[1m\x1b[37;1m", + [TemplateThemeStyle.Scalar] = "\x1b[1m\x1b[37;1m", + [TemplateThemeStyle.LevelVerbose] = "\x1b[30;1m", + [TemplateThemeStyle.LevelDebug] = "\x1b[30;1m", + [TemplateThemeStyle.LevelInformation] = "\x1b[37;1m", + [TemplateThemeStyle.LevelWarning] = "\x1b[37;1m\x1b[47m", + [TemplateThemeStyle.LevelError] = "\x1b[30m\x1b[47m", + [TemplateThemeStyle.LevelFatal] = "\x1b[30m\x1b[47m", + }); + + public static TemplateTheme Code { get; } = new( + new Dictionary + { + [TemplateThemeStyle.Text] = "\x1b[38;5;0253m", + [TemplateThemeStyle.SecondaryText] = "\x1b[38;5;0246m", + [TemplateThemeStyle.TertiaryText] = "\x1b[38;5;0242m", + [TemplateThemeStyle.Invalid] = "\x1b[33;1m", + [TemplateThemeStyle.Null] = "\x1b[38;5;0038m", + [TemplateThemeStyle.Name] = "\x1b[38;5;0081m", + [TemplateThemeStyle.String] = "\x1b[38;5;0216m", + [TemplateThemeStyle.Number] = "\x1b[38;5;151m", + [TemplateThemeStyle.Boolean] = "\x1b[38;5;0038m", + [TemplateThemeStyle.Scalar] = "\x1b[38;5;0079m", + [TemplateThemeStyle.LevelVerbose] = "\x1b[37m", + [TemplateThemeStyle.LevelDebug] = "\x1b[37m", + [TemplateThemeStyle.LevelInformation] = "\x1b[37;1m", + [TemplateThemeStyle.LevelWarning] = "\x1b[38;5;0229m", + [TemplateThemeStyle.LevelError] = "\x1b[38;5;0197m\x1b[48;5;0238m", + [TemplateThemeStyle.LevelFatal] = "\x1b[38;5;0197m\x1b[48;5;0238m", + }); + } +} \ No newline at end of file diff --git a/src/Serilog.Expressions/Templates/Themes/ThemedJsonValueFormatter.cs b/src/Serilog.Expressions/Templates/Themes/ThemedJsonValueFormatter.cs new file mode 100644 index 0000000..ba0fd44 --- /dev/null +++ b/src/Serilog.Expressions/Templates/Themes/ThemedJsonValueFormatter.cs @@ -0,0 +1,254 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Globalization; +using System.IO; +using Serilog.Data; +using Serilog.Events; +using Serilog.Formatting.Json; + +// ReSharper disable ForCanBeConvertedToForeach + +namespace Serilog.Templates.Themes +{ + class ThemedJsonValueFormatter : LogEventPropertyValueVisitor + { + const string TypeTagPropertyName = "$type"; + + readonly Style _null, _bool, _num, _string, _scalar, _tertiary, _name; + + public ThemedJsonValueFormatter(TemplateTheme theme) + { + _null = theme.GetStyle(TemplateThemeStyle.Null); + _bool = theme.GetStyle(TemplateThemeStyle.Boolean); + _num = theme.GetStyle(TemplateThemeStyle.Number); + _string = theme.GetStyle(TemplateThemeStyle.String); + _scalar = theme.GetStyle(TemplateThemeStyle.Scalar); + _tertiary = theme.GetStyle(TemplateThemeStyle.TertiaryText); + _name = theme.GetStyle(TemplateThemeStyle.Name); + } + + public int Format(LogEventPropertyValue value, TextWriter output) + { + return Visit(output, value); + } + + protected override int VisitScalarValue(TextWriter state, ScalarValue scalar) + { + return FormatLiteralValue(scalar, state); + } + + protected override int VisitSequenceValue(TextWriter state, SequenceValue sequence) + { + var count = 0; + + using (_tertiary.Set(state, ref count)) + state.Write('['); + + var delim = string.Empty; + for (var index = 0; index < sequence.Elements.Count; ++index) + { + if (delim.Length != 0) + { + using (_tertiary.Set(state, ref count)) + state.Write(delim); + } + + delim = ","; + count += Visit(state, sequence.Elements[index]); + } + + using (_tertiary.Set(state, ref count)) + state.Write(']'); + + return count; + + } + + protected override int VisitStructureValue(TextWriter state, StructureValue structure) + { + var count = 0; + + using (_tertiary.Set(state, ref count)) + state.Write('{'); + + var delim = string.Empty; + for (var index = 0; index < structure.Properties.Count; ++index) + { + if (delim.Length != 0) + { + using (_tertiary.Set(state, ref count)) + state.Write(delim); + } + + delim = ","; + + var property = structure.Properties[index]; + + using (_name.Set(state, ref count)) + JsonValueFormatter.WriteQuotedJsonString(property.Name, state); + + using (_tertiary.Set(state, ref count)) + state.Write(":"); + + count += Visit(state, property.Value); + } + + if (structure.TypeTag != null) + { + using (_tertiary.Set(state, ref count)) + state.Write(delim); + + using (_name.Set(state, ref count)) + JsonValueFormatter.WriteQuotedJsonString(TypeTagPropertyName, state); + + using (_tertiary.Set(state, ref count)) + state.Write(":"); + + using (_string.Set(state, ref count)) + JsonValueFormatter.WriteQuotedJsonString(structure.TypeTag, state); + } + + using (_tertiary.Set(state, ref count)) + state.Write('}'); + + return count; + } + + protected override int VisitDictionaryValue(TextWriter state, DictionaryValue dictionary) + { + var count = 0; + + using (_tertiary.Set(state, ref count)) + state.Write('{'); + + var delim = string.Empty; + foreach (var element in dictionary.Elements) + { + if (delim.Length != 0) + { + using (_tertiary.Set(state, ref count)) + state.Write(delim); + } + + delim = ","; + + var style = element.Key.Value == null + ? _null + : element.Key.Value is string + ? _string + : _scalar; + + using (style.Set(state, ref count)) + JsonValueFormatter.WriteQuotedJsonString((element.Key.Value ?? "null").ToString(), state); + + using (_tertiary.Set(state, ref count)) + state.Write(":"); + + count += Visit(state, element.Value); + } + + using (_tertiary.Set(state, ref count)) + state.Write('}'); + + return count; + } + + int FormatLiteralValue(ScalarValue scalar, TextWriter output) + { + var value = scalar.Value; + var count = 0; + + if (value == null) + { + using (_null.Set(output, ref count)) + output.Write("null"); + return count; + } + + if (value is string str) + { + using (_string.Set(output, ref count)) + JsonValueFormatter.WriteQuotedJsonString(str, output); + return count; + } + + if (value is ValueType) + { + if (value is int or uint or long or ulong or decimal or byte or sbyte or short or ushort) + { + using (_num.Set(output, ref count)) + output.Write(((IFormattable)value).ToString(null, CultureInfo.InvariantCulture)); + return count; + } + + if (value is double d) + { + using (_num.Set(output, ref count)) + { + if (double.IsNaN(d) || double.IsInfinity(d)) + JsonValueFormatter.WriteQuotedJsonString(d.ToString(CultureInfo.InvariantCulture), output); + else + output.Write(d.ToString("R", CultureInfo.InvariantCulture)); + } + return count; + } + + if (value is float f) + { + using (_num.Set(output, ref count)) + { + if (double.IsNaN(f) || double.IsInfinity(f)) + JsonValueFormatter.WriteQuotedJsonString(f.ToString(CultureInfo.InvariantCulture), output); + else + output.Write(f.ToString("R", CultureInfo.InvariantCulture)); + } + return count; + } + + if (value is bool b) + { + using (_bool.Set(output, ref count)) + output.Write(b ? "true" : "false"); + + return count; + } + + if (value is char ch) + { + using (_scalar.Set(output, ref count)) + JsonValueFormatter.WriteQuotedJsonString(ch.ToString(), output); + return count; + } + + if (value is DateTime or DateTimeOffset) + { + using (_scalar.Set(output, ref count)) + { + output.Write('"'); + output.Write(((IFormattable)value).ToString("O", CultureInfo.InvariantCulture)); + output.Write('"'); + } + return count; + } + } + + using (_scalar.Set(output, ref count)) + JsonValueFormatter.WriteQuotedJsonString(value.ToString(), output); + + return count; + } + } +} \ No newline at end of file diff --git a/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv b/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv index b622adf..010f299 100644 --- a/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv +++ b/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv @@ -231,6 +231,9 @@ tostring('test', 42) ⇶ undefined() tostring(16, undefined()) ⇶ '16' tostring(16, null) ⇶ '16' +// Tests are in fr-FR +tostring(16.3) ⇶ '16,3' + // TypeOf typeof(undefined()) ⇶ 'undefined' typeof('test') ⇶ 'System.String' diff --git a/test/Serilog.Expressions.Tests/Cases/template-evaluation-cases.asv b/test/Serilog.Expressions.Tests/Cases/template-evaluation-cases.asv index 4c4a4b1..3547b36 100644 --- a/test/Serilog.Expressions.Tests/Cases/template-evaluation-cases.asv +++ b/test/Serilog.Expressions.Tests/Cases/template-evaluation-cases.asv @@ -26,3 +26,4 @@ A{#if false}B{#else if true}C{#end} ⇶ AC {#each a, b in {x: 1, y: 2}}{a}.{b}{#end} ⇶ x.1y.2 {#if true}A{#each a in [1]}B{a}{#end}C{#end}D ⇶ AB1CD {#each a in []}{a}!{#else}none{#end} ⇶ none +Culture-specific {42.34} ⇶ Culture-specific 42,34 diff --git a/test/Serilog.Expressions.Tests/ExpressionEvaluationTests.cs b/test/Serilog.Expressions.Tests/ExpressionEvaluationTests.cs index 0532a9b..b741159 100644 --- a/test/Serilog.Expressions.Tests/ExpressionEvaluationTests.cs +++ b/test/Serilog.Expressions.Tests/ExpressionEvaluationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using Serilog.Events; @@ -26,8 +27,9 @@ public void ExpressionsAreCorrectlyEvaluated(string expr, string result) new LogEventProperty("Id", new ScalarValue(42)), new LogEventProperty("Name", new ScalarValue("nblumhardt")), }))); - - var actual = SerilogExpression.Compile(expr)(evt); + + var frFr = CultureInfo.GetCultureInfoByIetfLanguageTag("fr-FR"); + var actual = SerilogExpression.Compile(expr, formatProvider: frFr)(evt); var expected = SerilogExpression.Compile(result)(evt); if (expected is null) diff --git a/test/Serilog.Expressions.Tests/Expressions/NameResolverTests.cs b/test/Serilog.Expressions.Tests/Expressions/NameResolverTests.cs index 433017a..80ed59a 100644 --- a/test/Serilog.Expressions.Tests/Expressions/NameResolverTests.cs +++ b/test/Serilog.Expressions.Tests/Expressions/NameResolverTests.cs @@ -20,7 +20,7 @@ public void UserDefinedFunctionsAreCallableInExpressions() { var expr = SerilogExpression.Compile( "magic(10) + 3 = 55", - new StaticMemberNameResolver(typeof(NameResolverTests))); + nameResolver: new StaticMemberNameResolver(typeof(NameResolverTests))); Assert.True(Coerce.IsTrue(expr(Some.InformationEvent()))); } } diff --git a/test/Serilog.Expressions.Tests/TemplateEvaluationTests.cs b/test/Serilog.Expressions.Tests/TemplateEvaluationTests.cs index 352d9e3..e38e74c 100644 --- a/test/Serilog.Expressions.Tests/TemplateEvaluationTests.cs +++ b/test/Serilog.Expressions.Tests/TemplateEvaluationTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Globalization; using System.IO; using Serilog.Expressions.Tests.Support; using Serilog.Templates; @@ -16,7 +17,8 @@ public class TemplateEvaluationTests public void TemplatesAreCorrectlyEvaluated(string template, string expected) { var evt = Some.InformationEvent("Hello, {Name}!", "nblumhardt"); - var compiled = new ExpressionTemplate(template); + var frFr = CultureInfo.GetCultureInfoByIetfLanguageTag("fr-FR"); + var compiled = new ExpressionTemplate(template, formatProvider: frFr); var output = new StringWriter(); compiled.Format(evt, output); var actual = output.ToString(); diff --git a/test/Serilog.Expressions.Tests/TemplateParserTests.cs b/test/Serilog.Expressions.Tests/TemplateParserTests.cs index 16f2be1..ee62d39 100644 --- a/test/Serilog.Expressions.Tests/TemplateParserTests.cs +++ b/test/Serilog.Expressions.Tests/TemplateParserTests.cs @@ -1,4 +1,6 @@ using Serilog.Templates; +using Serilog.Templates.Ast; +using Serilog.Templates.Parsing; using Xunit; namespace Serilog.Expressions.Tests @@ -17,8 +19,17 @@ public class TemplateParserTests [InlineData("Empty {Align,} digits", "Syntax error (line 1, column 14): unexpected `}`, expected alignment and width.")] public void ErrorsAreReported(string input, string error) { - Assert.False(ExpressionTemplate.TryParse(input, null, null, out _, out var actual)); + Assert.False(ExpressionTemplate.TryParse(input, null, null, null, out _, out var actual)); Assert.Equal(error, actual); } + + [Fact] + public void DefaultAlignmentIsNull() + { + var parser = new TemplateParser(); + Assert.True(parser.TryParse("{x}", out var template, out _)); + var avt = Assert.IsType(template); + Assert.Null(avt.Alignment); + } } } \ No newline at end of file From f0fcfdccb834d80e91505fcd2f954e4240be2a10 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 2 Jun 2021 08:12:14 +1000 Subject: [PATCH 7/7] Disable theming when console output is redirected (#40) * README updates * Disable theming when console output is redirected --- README.md | 23 +++++++++++---- assets/screenshot.png | Bin 0 -> 58001 bytes .../Templates/ExpressionTemplate.cs | 27 +++++++++++++++--- .../TemplateParserTests.cs | 2 +- 4 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 assets/screenshot.png diff --git a/README.md b/README.md index b7996d8..40e31a4 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ _Serilog.Expressions_ adds a number of expression-based overloads and helper met ## Formatting with `ExpressionTemplate` _Serilog.Expressions_ includes the `ExpressionTemplate` class for text formatting. `ExpressionTemplate` implements `ITextFormatter`, so -it works with any text-based Serilog sink: +it works with any text-based Serilog sink, including `Console`, `File`, `Debug`, and `Email`: ```csharp // using Serilog.Templates; @@ -96,7 +96,19 @@ Log.Logger = new LoggerConfiguration() // [21:21:40 INF (Sample.Program)] Cart contains ["Tea","Coffee"] (first item is Tea) ``` -Note the use of `{Cart[0]}`: "holes" in expression templates can include any valid expression over properties from the event. +Templates are based on .NET format strings, and support standard padding, alignment, and format specifiers. + +Along with standard properties for the event timestamp (`@t`), level (`@l`) and so on, "holes" in expression templates can include complex +expressions over the first-class properties of the event, like `{SourceContex}` and `{Cart[0]}` in the example.. + +Templates support customizable color themes when used with the `Console` sink: + +```csharp + .WriteTo.Console(new ExpressionTemplate( + "[{@t:HH:mm:ss} {@l:u3}] {@m}\n{@x}", theme: TemplateTheme.Code)) +``` + +![Screenshot showing colored terminal output](https://raw.githubusercontent.com/serilog/serilog-expressions/dev/assets/screenshot.png) Newline-delimited JSON (for example, replicating the [CLEF format](https://github.com/serilog/serilog-formatting-compact)) can be generated using object literals: @@ -112,7 +124,7 @@ using object literals: The following properties are available in expressions: - * **All first-class properties of the event** — no special syntax: `SourceContext` and `Cart` are used in the formatting examples above + * **All first-class properties of the event** - no special syntax: `SourceContext` and `Cart` are used in the formatting examples above * `@t` - the event's timestamp, as a `DateTimeOffset` * `@m` - the rendered message * `@mt` - the raw message template @@ -339,8 +351,9 @@ convert the result to plain-old-.NET-types like `string`, `bool`, `DictionaryNglG2bCXe!JTwB5lVs>7rzRr;l5@_I zLz8o8=)H`C-|SZXcE8&FZ`Hr6X6$~u-}l~o?g`I%&bj!@$w2SidUOj12j`B2xTpdS z&W$%XIG334O|`;bc{w*xQx`-y+bpTl;< zg~f*PO3|^xLZjz)IPwnpVG9Z62jedE?WdjG^y!(I&F7Q)`*qjcWEp$q;*%(GDA~n( zDoDuO#d{oie;VA24}Ov~+!ybKl%BL)y7-VxD1m?R{%*Lq&Bgl1hGPwDs2nOEm&xLc+59R)uAXFi!% z1;WVAiby^-{JwX3mcM1Xyjz`6oT~kgi>u$P=jplm^mkBs$$CF7F5=*Cht6%rq~%}R z9=l~$TYdfa7oCOl-0jIss=6m@4~N}jf3NaGQLC)Yu=~kPv)Yc|KmMi$=3rFu{W#|^ z*u3`lZ@(wK39S$45Nd?TDQ&He6d@Qs9$h8Y_}35WbVBQo*Os$miILxaAb#KY%^Exp zqViE9jjgjRtBh-6nyD*E1vd0s5?eHpUIDHH$huIF!+e?NpP zm#szjI(W*mnS_r>)#i7+m4Au1%Q&c?2x z07(UMy71}e@!Ynr1%FFNj(Al9U*2GjTS>A$V4}$Koyp@1SqO!1hR)rEpdPmmzY6`W zSxVd+68KUf(1cka28f*O#jkNP3C}{wAYn-gkG}uqee(48b>$Qwx;I*x!=<9SWsr-@ za`Ud8ElJeWDBTpw-;>F_#nYlo%hZ0=hnQjIaiI3Oo&NojBW_J}9M{uG4!F32qx|G` z8e{fU1Xws?Dxkr*GmhKs*e+?TC5%4unc5TNZE~LCvY5{US&A9&`_dN^Tr4y-HP=Q< z_JGkR$6@yP+%bp2f@h9%&G(M3AR^Bl01nQ#oAbMK`S-)uDs&lrl2vllNwnf+H8-JXK=b#g8%{E1jsY537Z3tI7)gn$?HTN0kE#2KfOik2Baz`ZHY) z);nU@`wVe#5G+S7^d5Vi+>2ydzg8>s4zYcbx)^6Gv)CDbB*TdFb>VZZvsQ)O`~J)j z=3mmLx~d%UXQo!ALxxPe-6fn4F{%1Z@VUcZ`}#`N5cZakkg%-w3QlJ99LrBJ60KCc zUlX~ZGLZUkWiY3Wj||+EEfGeyJtRl+>$|&O5E3?@r*Uw;g3CO=mMtWQplhi!C&$4N zKl(NFr%aPjvOAC1y6@rOuoHXS`DaWeb_qC-{mRk}@YsM_pI=XWjEMP<4Ga1G+g*tM zM{h)F|Nrqw|J4vrfvXfhGd?YiW0H!9W zpG^Bws)+q9jSmpj(=f6`cPG1!L2615r$UG7x7FfEN4UtKT={Kc@st6?#B`O9h)aTt z(@DiTa08h*7lKL3JgZkW+`w3$obd<_ac`6{)*NPJj>w8hd+>}M4oClJl1X$~wHpu4 zcy#*Kv9ss94^)=pa9Z?~Ql|fOdXWcu%Y^c2rv02_`+;D2jiAczX+hDEU_z&$17zc! z?qpAx6I7N=V0&kp`kK=e2^r>WvHB=uFYwpi;^2H}RfrEGGY$-JKV0sg#3ZM9-&yGo zMrxsS*DCIq44BJR%A6kWk+KDu%4JwotC!4YSiD)|DKW{KD$&>Lm(7`(&e^@8+Nh`W|L=3_L|f=W`X}U;2WB6Q09E&)pgKl;`uoPFDuHTyLeEWa*h$ zAeZU^o_;9L;7T_nU6)_Yq15r`Q`IfY1ETTT0qor45*6zo4e`?qXHW6R1Lx&ct1C)k znqGMD;)q7D)EvHo3g5K0|22dX+$4UEOQU#Wvx~lKFh{v5evYA2^S<#@=30h@eVw6) zu?jH9u8KAFhJIv@?H}uAzP!FkijN@E)`JPtR$1*(>lf)NIj`@aOD&bhPCYY~Wcl}$ z-n?o5_VJ26_w7{og7e;paNQ-C}+G zrbl&(0(+#i7Nu^lRFQnBDle~jMb$MWs={7#Vr<}HZ^~pENxuV&0NSQ2Z@uSz3fou` zg*`o20A)B8UAjvBv3;lIYhXuDnj1-UT$j@#*M|$J{rG&dpD?zrDEMn%{iS!}%GEA5 zWi{N66+aFBY9pSNk-wiotC^c@860;sQ(074tW7?|!kpQcr%}zwlB-xs0No0cQhd}% z^BJOCDw4*Z>R2j~!jls_kVo#LqBL=h?+zK497W+CQ>Kvtt|8|hex=w3Gn$-1c2D4| zP=IsB95gtcY2dL?$|~Jk`4X0Y{0gOV}snhx$r7-s7=K*and--{V^QhkuLX zlVXXq&HV(PSnO=pYz8`kTVp&(#PI33L_pCL1$VX6K^B1k3ueDL`g@<4bXV2@d(t|4 zZ6-`ROnol^nhS}Yw!$5(F!BvnFEQZ^LXH($J2oMX^Rwv_T@{$}+@0`8$VZ;uyng!+ zs>&?baCKXuC_nwmlG~;*KXgA=HBTFI{b%;K9Wnzhlk94j1NZp_@dOc*ld-8Yx;jDZ zk!;DYJ^Q2#CP^k}y@1}hrCIW*!en5(UBUIBe)*s@Zg}^oMFiTFz>PMUC|>VQO|SOo z$v2!;UoXgzd`V{9^#C@UZ^$L-K7`OhZE@SjHhH7&xP#)<-@eB>aL;2Z33?*EplhyhMw%N zSypYmF1h`-VXI;%N4JYEyr=whMhM(9?n579QIIq+sU zKCnIr3ry7E+wqCCpQ!En(Vt7=VKFf%oq8rYfK+8c3PMYk+jZ}V&7f_Mivg^kB{xnsWf0*>2F(f< zI2MOmfZ?21vNEpYll`IiN-uB69tl0=qhVYr6y~2Rk605+TPg|kcUI!3>5>=QdzCbW zMwxqI1(+{cSRG~-lrR0bq(s-nhx-se%CpnJ-|n-bo-=TDa}Ha7GZDm)@A8$VHq zLo!4=rpUbMVIf=zI~5)|5YP8FyP}AT!hR`js*5hlJEga4~(@bG5Y?srwG%b8bI^h9RFo2|?yDwLthi~jTp-64Gx?uWYMV!;7M&n}du1Ys-- zPic}}JatTWn7B?Dt9A>PoP3CS#(b8SnW^$dK7O=O_IUd6VYu|?sXfy~fjoKPoT!;E zJ|(%GR+;u%K*Re@DO7oLlWJIWTKetqzgLJ^(9sKEHtT2B?2C*xK11iZArH3Mj!tW_ zC$)%o1r6fy7tBknjy!#njk;qoV@}qzUSx|~Xj$TRPW_;a-N-?Eg|fDFtA^d%Se~*p0WVL zx3KLM7p5NuHOA|rrOOLGM(FHmeKyWjD_UqNJmphz{-*hk_BJ19FIr@N ze;}VjIb^6}1%Y%myqvAIIc|!i$g89h_U0HaEqS~@mtWeUN{w@ab0Av8Q(W9e^p!09 zTb2N2&#+)h^5quoe>9YeP-!ZGtf{l1KNnr2yT)HHk8;>zP#)4DKEN`Y+6f0%?2SK! z^W(y+j(|5DTVa96Tey~rKXBAh)lPPpu#HL7SR8L%YXMO<9jJ`@dmJ=f#*z}cEaMNTT!^q`zE^);Xa{xuC8e9DWst`o*#&X`grEe0z5Q)z zPek|L*kG(M?MalKApT62u| z>zVDo<#^|dFktDgE3|>&K*G5D%BZ?731WUb(7W6S-O$mlO>c~^bu%5}b@xiAo9a^DH!g!evoO25>9>NcP%!apU+l<8ZwK(BQ9 zN0|Gt*9b`i$q26oA_X?WE%q$oM47qTjbgWc+Q3i_;(Unnsk7ZGlXi~#XX4PgtNSH- zNp>K5*X`9{!OB}Q1HIh%Fy|Dm>1msRJdL>NKOB#^sdM3#hgHt@CQWgC_D0xgebUl} znq910L^ZnormE|NbC42`Xa-xr5ED}?R`W&4%7CS#ZXi#=9^-83q3?C4JrI#x*8He0 zOHf4dq2=Rm_n*STFBe#}B7GspgpUgq$Pw?PQ54m!YqnRY-te)K-EzV|G;X`_!c@IV z^)yW&EKXtV07_d6edbN!fN+mb)`10)bLLA-tuL~fWHv>Q9v_`uV=QHI_t=nqLDG+a zS!~@Du2CCh+n{$}Nbe54@_3AfOACbphm$2>s z5u`K_=X9U+gS}=!u*dwE+ zn$Z%{Vy;@Tl*x&hR0qpYXSJ=aJQmYqvTp~|8ci&r+-OgNg~-^j`ANT{ zAkxTll&87j+DtdLW^e0hZJP~ATHizV`wd~Bh6oC^OFs~o5udQPeP1P9Ia71O2eJ2j zr$6{9+zBJY_29DKJyaWE51;-##nhRz*A}>PP?0+A&9uUiNn$%U+@&Fi-QQ#9#U6xF z!aPW9F?MFJkaX0!@3+Phw!G#STNi5AMqI5F0_!B0aOqK1xvFRZ|Bgq4{G_v;R(=)s zD{SyU^FfAmi>+evd!l!|=fd;w?;6q7lOZJwj-4FWsP$+m$dI(J~<&3i964@VR z0U2bla=l0WNH=ZQ`s;goCdxm)kX>?`3xO&=cGf z^c}j%oeC+*&3jsBn1tZCyZuJ69Cs{Jt}6cf7Sp)srJd>9$Hi;puWtl z)~F-q+<(JXILjLwmZbMs$36|Q`K%o!+$3Zy@2!McvPwNJF!O26zABTz5{p| zsG%FYJtu7I;B`r~Fu>@1>VJC#oSw7_TJHTtk_0{`Y@RZovSUc;;?EXhN^hbQTazB{d}>4S zvsR7$xqVA<$2U9lP0-P8)Qu|x$Q-^V5d!}Evx8&O4_ZZPUS361`plFh`bs``BsY*q zuvBJbf#juWtLl}g+^v3~#Qz}N?cVmQgjiMi>EWL^FCOE{MT3kmkm@B_K=|Tk$=xTv zrZ0+EiI^L%#x8n>y~vH4IeWOy_By0Z+3vV~1e2)ATZ=r<5-uVVlMXat|3ZJ?m@6P- zgrU^M zyGCaOtrQ^MY>#)B6*FXrA3R9lvesMfO)G5eP7(=3W__jLcV23KFU7f#Go@X&;Q~Y9 z!C3B;a5g>-DssNf7>;3BYni6#MQ~Zg+Y4%I77yAFsYCY1AGG){%2t7V^m?xNqx@Sq zcf(69CJ-s_HXDd!n>WC6-V*;JEfAXa=oS#o-n@BZJ^AXi45ae-q+-lYUmZOHq4bFP zvn+B&q)d*wy`ITXmU5*<4v)R*K&~nhj_J?SLVd*aBdBZjI}&;JX4TnvQCjF>Thh>9 zC>w0r*8pWy0-1&b_Dl5XEZ77R0`d8j#o1C8ggx4ViX)bW6bD24i$k}GKv({Kag z5eYtlQX0Fd2(gfI~)APalX}F zzw^ir7=YW!{`QcfUiJN7P~^+}jGftn8OA z`>Qi1zkUw>4^d5%;roS!z^9-0|94b^|Egm2|4q;SR}KO7?(mo{#hmU8bm5w2ETvt@ z`m4=_DS|8i*ZSC#cy6AE2Iu$v2&2kh)HqP$SN~JpOkseY8|3<2ASs`lGx&$<2T#2r zB)U5%E-87ufj!%(J=@EB|NcFbdU3bZFSP}~a1H!AjR181>tKp%$E_JCrCYI7B(wAH zGRg1lgXzUs)D&_cj^VbAEm; zlH0tn_35{p6w6M9zOHfs?n&OdtCdyQB^yk_aDVFz0 zyh|?V4Q$0uaNf$Rn@Gg$HF_eRN$R>!g10dp-`d(wN6XP;wZ>;Bd6s4n?Iga!^~`&! zv@{JG8kr`;n{|t}+L}2L!(3&&NwJ)k3C3q;TQiL-Y+=4X{(9J#B!W8-zqC1Buj76+ z4=h7E#*^ituqVGov(=akJ zG70RaDhdjr?0?2wRd^#SD=Q_XDD_=5jg7)>w?_~&O)yajQx(%S|Dbu<b$#Tl`)cf*59wBwMi8zR7S+~&+P67A>7L#vCLW( zyXc{fR>r(H;^N(bw|xmHC&9kOTGl-Ms6EFpHZ0kMCu4DXh!*kn_;MJ^Y~C*dw9lBo zX~;@BL7#2yXd%CU@QFZYgs@tP(O8GRPVY4o*r9BvqSqBaZL~OdRfl$aVZ(yM#XB3( zSe1ef4~O-s?X95-x$on*O^XP~d7QbhQCHWP&2;UT?+$)eNieHS@yS%^uxXqpi9S^_ z;B?X4^J6aK2D&FDBV#fC7FEkV*AJMLvrS(?@wnL7O8qAMo6HqUPgo1K1~n|JLE#nR z;0l<6z09{Vs~C*y%{dHb7&CbEg_21%&~iU(X=yn z*1S&obcU{RIstMuC_lzOyJ^4Xu`IfXhSuF5my^`hWQwie6K$e;^*CQLPi=~Qww^FV#Ff`=f zAJ(ru>af&y-4g54&2Epam06x*k%z8#-YUKAd)TxhOW|Y}&3x#ftUym*j(%uU|Ex&y zyUx_G-6Ew9zqXY^q*m@&pDSE(j><`Fgb<{TBb8`(PM4;|6T6Z5<3`#=bhA}ovo?=S z2CW&eIQy6my>)jbIgw;0otklu+5?0#(r(VCv8f5oz<2A`t@X+c0oyD6&no<%< zf>8WVnG*-6Ow1y*zR>k(`z8?`W?9ywcRs&7Rf?3nTvKYQwbYR7OKnZT`!3G;zrGlD zYE73UfUT-Fk$yj#E|sxkcVC9mwX6?K;a0Qr0X}}^?Q-*VUitLbolE?hFI zvS<&xztl`xq?T-Pyv3wZx|ErH-%I;GC^vO^*v)C29;eNwUW6FYIXZ6O5d=VNns18) zOfQ;ZDXBXa&n@6Il|0q$}?p>62_8VLMz zu*q8l@H1ogZvfW~9Jt4EU!q0zo=R6a(TBT+hK9>GE?f^kLZGE5O)3)b;?HzyFg~Of z`FEsf@j*}kv*}5J0*~ptC$kG&#ZXA_5$eirSFjDxo)W*{KKcHauwNMdZ(0I)SckPy z`;AEuNx_f+w2=w8I=+7WW4hjVuQ&h2Vut{IX$>{&$CGIJaSrx_iaYaNyjrx|H7xNh z`dys9vt6~UpA&&XN#rRij#NCk*oW_rXBOtTTn{&a-h%x|NJyx0-ZQB#Rv>#xH@MGQ z-vIn|Dz&BA!GT9&L~=@@&snK29MdUl(O1}5&VzV{eS6xn4!nMcVXaE9CO6~C6h%)++raF0ASUDOcU@!wuZQ!W;(l(L z2v^uElyMgYdAW-j4Lv>DGs$wQ=C_f3k_IRUPQhQ{etHbNjC9m<-J|V!=l9Cz;hx9l zK>hF}_F&Xv32XGzFj$b|6^ZK!gG(fQoLZ%NXOLz`Y>qvZF`bGoXMSv zYzDB)&HV6)Y;*~)H}$Nk6Z>onF^|oL3<%AFJV&*5HRrz`anwhoh zTz{fjjUASI#Ud{@K$i$KG12_rr>~%54boTUQup1m5&2oix|SSD54WYwU_zbVw2rjM z;_yEQV$4+p{~}M^s7#--BaRh#%PYn|Qn(mfub>flSO%;WEoPzcD{4{lnT8=!UlAt~ zY3P{SVvb-4+6T6cE|iC{Ls>5uYOP|~IU@5WoQxKv-=j2B@GMNO2uwK%MNX_y*Ba_O zgBYbgBh_6=lnerDvFVTu2qC+>cED!=mFy4ED4$K`KVC}7%F3$v84(++At!g2loUzV z)z#U#-ks#d&f3%&&s%$TjLxe$ZhbS`5zE=t>fLNCO+Atz}WRE_oz>Fb|^Y@!6>CK#KccReNIS-0I%-?++Bb znCc&QIW2|EXoh%%QMh)DLmrdTm1I#eG7{A!C9cbQoaWSGrAWs6uV+RSj5dw`A&4-6 z`QVesSaeHG>?RXBXIZjT?}m;ZwoB(7>h)^g4b8^(N6iv4yVVi8p`yW>`|+7S_48?kux<$M{RVrgpx z`_^tC^CFtz_Bv)3c_6UKsl%iX;pDz|6G#?7DS)OYA{O(XX;(Rdj+}q;MS$E%L&+S# za1yn3tcx|pMj$F>c-GW3cT z%5lg>o(6oHxGArqGPu-}qMD;_IL`_~8^CxRzYmr7cgUW)U@Eo z3Bsi2n~E4$+v(@;R8NQ|g&ukY zd`T`aKJjxrR9wtP49zns<-IVMM~qKG$w)Ehm>XneV3gcMt@6;YFwiq*S2rIJ6tp&! zm&h!d^sifDp51b`0cuYB^yxBz$6>t=_IL>7i`B)2WZX8QP+B;pU@kR*2FfB{?HqQd zQ3l>79tGW5zg|z&*zTx?fByX3u=R^%r|s%^x$Rh~Ie^6E0KvwQvG#=9*~7+cP<&faA;f{pY!fJTldxT7zBbq#odvAB(Im}LK1M{ zfE=m-0rt=0H1FS54G#}zw-_%QAJ^1!C?7>x=KVu20X9fctRs#Lh^rO^Z7sVVvENY# zhg})mnRZBd@bsH}QZ@{If%)LA-mHEfVY?{R}10fygz>YklX@r zZ471U0eWg^ZWxpyx?xR-q_1Cpa@HONT?G=_7(hmrOMu)nt#}Rase!7SZMHCj39-Nj zEfL}`KC@l~6dYO;XB$n|tws=5YtD{M$(2n39%_qZq2zbA1Koqll0wDrdY-Ve+k@P8 zJ5ycZeLn{uaM@tzDa~TPfLB7o8Aw61Xtj?<$<<(k*=k_)gRs9Y2Kc7fPs0s3))eYU zH0YY>mfsry5a+34I(Y1>$Tpy3@oeuynU$W5oTDVI&F8?a13lGZK#os@5Leox)7viO zdzI=|C>dxoj0VZJy@xAk=u%TtgW&r%29k6=L+79-dxMpe0imEiK`tsREbQR058-4l z3}X|gvJK;^zb-`>k0a%*dww{!j{w7*4{f;M+ z?{kS+k#qAU=h-06>}AfmrE%Dln}mb}f&pY1jR3x)JvH9`25WNqVk)xm;o)Is!>rrq z(GECFpg&xB58*2rpf46+j_CZftD?Z00$Z_9A|$?l|NMaqS7!@#mzS4qK7d5SWU8hb zE-AYD+gg&<&-Llpfz5oBjhnk%sv|140`%2_JqCExV_4yGE6@q^YHdi}Im);Ai<{UL zbbHt-J<$Y$PbJqNH6m{QN9yFj~_M~oA5emLQe6Oqteyj@=36kp$()T24*dP=tkBnKnh!s>e zqQar|p&=o<6SNzkEz-uOFyKN7h3?#!mcZAQv2yERA6m+E1`akD-r8w&FAXvBd(0=$ zFSOjR`n-Dq(Z+%+&bct~FD+)xpsb$j~H zjTiTeMk2MHv*1rO{^fo?`K8l9ZES4x^pcs&G@F7chvY{zUxD5DwnwXwm)qVvCy0L9OxO=;86}8Jd|2;I3j`Dz@J9f3rMwO*H1{!!U5#P>@*KwW z_^~j@q{Ecv{(6GTdXKjsNzH`Q{)ll{XlSYgF6f{IP07{8o=-9e|FJ+J?}q=93w~9k zTYnv>>iwvvmjaF5^#ClH{Snl64F$+SHgnXij&uYdmYVacnt^#@R*-q=85zjrq3jRT zm9yfZ)So9t3Jn9Xl4f8F0d<;#XHmn^72KTltHBP$3we2tt+M8hj(7pL3II~UL!Pza z{UwZi6G+0cubF0ZIQ4wKYNxYL-akA0nVQt#zwZX zRl-}nni>C3D~X|jT3A>B7YRh{2z=-9qZ{H80BtxAAe2=EQoeru6Sy}3(%YMx<)jdw z%!X4X!hjH2T#`u2TO3+ts`mypv=77|fN-y!W}NM^1i(Xtb1H{ZKU)1BaNY7!Unt!V zg`qVq6)H2LE4QCWKlzs)iHS8>DTk*6WCOa|$3Po=CuC({x(pOtEkK*pgm$pQLX;{9 z>dxCO^vbs{)hkctvZm61ef4=xXuz~5xls!?k%qT^q>50qB)w4ZDDb87N^e>lCHAlh#Ff|y zF0ktV#Bf)n8c}cx;yeN_#={ZAlXc+s=K7C^eCCj`$<$jU_cRf$0XBq?P-$YS;{HGljWbisP;AWXPF5u+^^o{*j zS67vhD(5=+=TKK#{U1xsC;%*$ zZwNK>IIKGE4XC0$3674AHfm0Cfk5Qy+bLM{HC{0ivz)wZ@+{F%(tHe8`n&NqKk4tv z<4;92U62nwY;B|<2V=)w+sLcou!0Nu9wPa-I1|9K`t5e-{NB|2+==C}M=(1!x3oOu zwn=5aN=r*S52Sn!B(=3Cf%~Wdq2d(hl}P<}qqY7Xe%?~$EprxQ=971o0r$j=f*&59 z)bJ}|e)uL%mQzIu`qe-lS}PB!ZahNh$;QGq(409xs)M+O=`=rpp@pRH_V9CWyLA&> zv3jMen^aTW3KkgUejSklUniT%gOe!61_n!>mPP~wBj2qK=JM?|o7>vZQ?<3sUrG#G2n>-))364;wQ*aUZR<<38#R<4ofJs^2As3bZPTGacvRpVmh~FR@p0z z(koefS+JM4j22UE!p9Cz33*e~&I(14>5W9q&!;- zjSgcz_3&TIkrW+gO2b+Js!Ql9LdDjmp{a@B5!<`*GHcklHHfo?PA8SG6ciPKJ#~X3 z6ClCV&I=vSLSp87l0@9xcWpkec5V_n%QvTX;!-#Y#fUrPn-1lgj~2_{QO?um-}@xK z(Ek|DR!9x1TopJdu)PGN9B)WWQnRzO-x~s&@{Qnw!m27h@^CJ9Oc@-~2u@=NGCwZR zesqec(v@H9pkUn`o}xoi`tSALvPlh%p=R2P1i0%ZmV8e1kvrt*r=W|43wIyJ$-=PI zJv%*Fx>Y$ocV}N)vSSRlU@-g^I{NeQO*yWMBZpM&D>y$Qo0l3iC zFnWBMmy7@c2+bh{B=GGwv<>NYlfR1dU%7sJ)1yg;LRvSciibehi4(JSt4Z7KDQqxQ-9eU2utzda@_nGQ0TC3XBIIE6x4F8RGMTQwLsqf zwcmj4nOw(TWeC$(J3kl{f$8g7n~xhL&*!|pNYBT^mu89DcRu&d$&z~#HsFAfIP|f&wDg*y!`mbb29sj} zhDAxsgIaXPdGYavS3^UuM;0ZQl@L_eKRB2YDDa0%SK8rE)9<45&W{yksqD@F6lS3` zBH~L`z|?!&0UvS=LoQgAKEmqqd41cXtQ!`~y2B z;62{$M{}uLZ3htuhA&Ea6Qa|YhroVK35o_Mtvub8?6h1sr9MYX$zmmsa$A#M5`l(v zA7xiEQ`-d|M~*c#zrkx8!G&e z*TL-knz{a7VH%uU+urn0$cvU8Y@r3(UY0~w5ET7BypzufCBwxh18zz^ z|Id%DZ(NU;GmbxgOHEIYAt#@7JJ9NSG+4RpS?Mc!vOm4jZ-x&sbhw|HS)kt(qYV0T zKsIf?(w~)Ay&tnG#lWuWM_sS$?dV6XS?hjk_lfWv@!5l3j(O)l-z~$`UJhat;gIGK z$qzT~Yvkp^J?&MoI?K)Rm#ZwdRyN!ZB_bzMBJQ}D_zw`LSeu>3<}K&yg;9t5byObk zqh3}Xd{GUfaG#$PLk_tgr@4cz#Oe8ZOv4?)q9XuG((Uc-HoTV(_4eb2OH3p4NS8_* zgaEd+pDPCaV$03*qqQEja0mlC`@m}bV+{e&ECB-dvJLG^7(G`K(<~SRngJWe`o6Q-uTvs zAkr~<;S#766gz?Het?^VlvL~seV+i;T+L{ZZsurk`U{?{7J(Ms%=y-TVwWEB5)-l8 z#I2B_>SGgrn}Rrwb8Hb%ZveVjK=YPzL9qk?V3jbIGsFZS8qLZRX{93Gwlub})M{SS zmWT%>%}9r^2P#rFwzjpHZK)9wZu@kUycY> zEWcsvePUm)5m9b#ZeTA;)>Q3l2TDDo22ykUn=^rdmPC#!j6oRCv(uF*IAvylCH*N? zE*%pZ`ot1y0lO<{CdraMLB~Rx>YUbdgt-sZ16-EV)Gt>%w3YNGiI@GQ++@OS(($$Lg7j2 z4r}(H47qubirrXSbeq7azziZc#o4|xx&A0fKk$TWVS)VApbHj_oqS4=l@Z-OaOEJ< zUSmV6Se=9nAzFaoQMW2wi|-Prs$|_&)nYasQt!xNYD2k}d-QdV8&4h?Z{3;?83^Mo zS&NY4yArnedVSbsDTBzHRn_d)oW=kM-0c#iG_eIv2Q@gVTl)J^9|!;7(vE#{w@&$q z`SyyIi1M?OUdxzT7^_ad^>Q~Ni(U02+d?V-6jRrkthf5Y*<)4gcJfZU^W5EyagRd~ zOaeJwXBOf4*@pl#Xjv&j6;&{QE}etwl+U2x zI%QRV2h!IK0M>cF&2-R3b`33*U*tJ<{ipNE8cL+g>MDwN(-L@S$kJfWSf0D`=yDBR z(Ym0+#$+{s3=n)x5KbbVslBvc=}%3iL0?CHew!Nz(l0{;1IPFQzR#v0w$SlFc(ca= z;mZK0TO<^daYSv@+&A{UH2EEh0AOKEBQ2993)5mn$X~uXh$Y=D*5v>LT7BT1MP#6$rhG0lrVGc_=U#D|N7MMNx=ub-cr{&vlp{2J2| zUiXi>6l#_OpgC;;gl#u>HtPwH^Whsn*tO0>-8B=VNYd;lNlvqmRqZ0Yi<)jLNhN!Z zx|i@4o@>7l(V-=JFCQM4o}idlwBSyw~@iKn=PMZ z5e1JD5AXan63wqEg{ol#AyR;RQv!(qfgt8QMO~|=Eljq@ol-P2TU|pNVV8gg!~l@lkG+bClL29j+O(ICh(&I3VabT}=LNxTbzG5--K_Zh0|b1H+r^2BgWz zJ~M@j9Z8{{vZzU~v>#I!??@VN`f^2gV81hhOz@N#V|Bh|*CUM%oYCXGD2)>v%cfcY zsgQGex z!*%vyj&D5E-VM%KzMNC8ih^0s#vVsdGlpl|h3V%+R#>jf#&OA$xW@9S1w$Y2MOFT} z6=sB2tt%~T>7V^>vM#bD`+A6^fj{3P@tUT4K#B>kxOvjSaWZ)*ErN#8Nn&h9a2?=) z+>=#gZT1*Mkas_VPNlJ-ZhzxWyJo?1X(Ve-BCK@jY2c@Z1(Ai|oW2j#;f~G=D*QFc zCA@^0w5`BGT^))eEt?LcR-YqKksgj_lhs}O)aJBp{J0|ryZS8+-y=3Wj@6*VkrBsI zydG)ad{= zg)I8c!^y`fqHTB_ivgT$hT>yAXkp!{SGM?5%=Ju7e z^AHW*gJ*Id7o&28Oq# zX3=b`^N~8tcTD@hLD8;q1vAn=Yl6hHBc0_~C5D@O*a!;D{K-bPW}9TK!=UpF8V7Zt zzSer0UfWM+?43=!m#I|-O57c6Z$O`M9_#a6ANFe~+S2PGMM{H(k3=CMsQd5lV##&l z2xqP>J^hlRuU_R?aMTMxiE?N-1{gl?K1Ib$l2d(x`Z*-r4%^*i3}#}P2>ns8XRd#k zeY)FbNNJ98$0uj1-gsJY^!Z31&Gu8AHUao_#>zgIR^;HiIF;DWkFE36^Wb6lv_x%p z$O(MMma)h%r%c0WH@0+mzi+?O_%hio50D)!pe-h=SvGGHn!VN>7&uObb?PZ{{qF3bMSFMTliKm&cs8VQ0tX&!16s16{t zmEOM1Z2PEIW;LCwRSDW%^Ff33kdB8I%U;`R%RS8pb?pt!QuOrn2g`lX$m$U2>Ft|( zxAb6q5~PxMz857*f~?HsPY<{U_GBv2Z9h7|YwWagy}~p_=h&%7AEauKEUN4Hx9u{W zclMnDBNY#ud_T#cQ=r#)enQlp$hE=m9*+&{hKo8$2)o5Vjay@4A}Po2%QvNq(V892 zmC10;#&|P;$d}o#$lq+oGnj7;FQl!Idj;QS?YiIPn|+uVuibn^wdkLn*0yfJFxwQ| zWqt5buDxC5@G6QHxU|8LlQ__ciLy@#DH=8VoPTTU^6uQvo)G84q9Q{vZu85_Ger2n z#o&V+Xbo?)t7@mK;|FSXAhIK+vFzH$=z6lG{J#9Y5@>A$tjmDBKYmW^vUu)H4!2@N zF5aAqS^1p()JQ0G&=EkBmI)`N+UnLIbFOZz{nPw&7!xMMkbrzNIpQO z%uQ{4Y}!P(32rkbtm%Z%2Rm(T*nHrV9jysw8l7HmwQ3aENi{kZ!WjfPmsym3=SeH$ zCz0;+&`+A)H+c`P$=iEb!3z`hl!*2lGcQo5C@nuae3|VxA*E)kmQn1}e@7QT9vW`R z9G%*;;ey^CbzX3A%4vHJ)cG>f)9L8xrJjRCQajo@UdxrSN~!D&_I6MDog9*{P&=f4l@$V1kGhQ!j! z>hp}=rU30ZHI_2yN@_BW0J)%jvbxAtRP6oTv`0ZP?2OflibvTZ&nz1i)8EJ!bOaDH zX6LO%j(O<OmrxN7I!F1l1f4p4*QhMZ6 zapM8A`dUf+l^&J?uf6!w@v0Q!3O7sZ)KIG3H5HyA^&iLB9kZ+4+HFOWoIiFJ@|Jc2 zdxg~!HKOOyQl|<>&tZ#y{fu!Qf%4iZ`TJLYWf6it8l3xij^R?i=8<#PE9ZSFg$blM zC)4m_B%3N1F@{>v9%C74XT4aq=X#s-`ySa2Ci;S$>g)ngr~sG3dFya8z6AUwXw#!6 zFK=vY+*|6=|MuY$sA+}^NjE0Zq4F`iiq9$6Ft!GR){3^M_Vzd7(akntKLOnmAtcyR zU3}@D&`m0GKh2x&IH)0IAZOiKsCod#{rH zR(Jk>UvIC{&!+rB)|)IlFL=L$G#H4T$4t>8YU-|PKT|(Q3*UOFW$pSnc?@D~oJ$NQ z71t-LE6E;}Co`JI;08MR<#ftwM+YBIbqYDRvA^pUf==HbkBB1r^N?rJ8@pihiJIiA z^c)#E)a-OcgZ#({UPCy2wqcrBS!8iC1b9Q91Y_)=r;}_sAkao+#|2J*)M!Hwvs~eC zQi0MF8^%66E+Mc}6^{+4+!ze0a$&PHtK4`N9%U;hxYH?UKPPRqd}rSG@(Gz`I8_;5 zcZd&hi0deB2KZYng;E=Cbw#QHain0k%}H;qE||6+b=EDOtm;tj#F5Ws5Y6vqz*5iI zEK%}MGQ(`0+DUAtz;-4dJ#(&IX@yn0=IFS;T9fqN%78|RNqBTuzJZZZ%#@HI7PDio zv}}L}TN_QoGxQxpa1Z8cAup#>qA=hLLEbG0E8(%@L;*pC@h`gnf9>nsJ7+`L|~ z$}Z-(JcmnY61kPJD$OS{mR`Mjj9TIJxN5NDzHndR2Wj5zJ%P?1^2E4t`t>!2RX`X6Ks zPq}hp*<#%Fr-%vQ?d~)C`W~@nr>jw?O%k>#nx|x=b1b>%M7Sef@$$B<5>`&N+&Di!f|c7fAuc(cpntSI2h zxwqqRUtSc81x;4OpoSg6>tz>Ak*5C@H=e4v;#o5zLP$C_I|7+N3z{fGrWV?~siz*w z<5Tb*f;>Rx=nyOnU7UtxQ^t`@q&C4+e)A9CQm^@kUjlexyb+}Rmqcj*< zpH`a%f|gPa{oU5umy$6pw-_e&83vd%&CUzlf=`rBWL1tGhP}d~O`17Z3xlu1yxe?m z>joU=i=#Owskk5S7SJR*XpaVq%!<_e`Eae&8~zI5f-aic*FSV#)Nw!b=yo15l!fBL zp*NauM@p4NwmSrtj<4y7df zK{IAZ0!_HvWa&0>yVU*lqAM&sRr>846?jbPS)_J~@11aSn4R84y12NqTZjsq{-fOS z=FxQ7kylU%V|y*=?Ce}>Iu32{;cT>-^)!LifJJvb%gOuTGJ&^wK^-6otPlob?16fg zo}ew&8>lD-v*&8e)>zju0!FI&YhCRvlz>F(xyZW0G-6YgE7L2hK%;Jn@N7|ZMC*8e z1Ey*BO_Xm>;5te= z?0lZV=Uh8x4#+u!KB4vQx4BuP^0u}NrILohbiq@oAIuAELv63Ig3&1c7v^K?OOB2a z!`c_42$`7ICeo^9%}y&fv<`Y#1To-Rxhb9Ia*Kfx`_!9;F?zh+5aZ9Q#RNfqFTVv4 z8tIZiA6a79Pj!o<`RrzPZH8uzHO@21skj0gz(@B!-eI>+*T5uhUuXikTI*Jy`Rw*D zxH=X2%8sAEGLH5dVUCD4!EyiQ|4ulhl=;X0#;?r^t$MG`3dna@)s=t55IEwVSqz*$ zjN~Uo?P*j?+GC1HpW{{xZ_%)@>}`HczaU>sCd?T!W`9_y-fgH-|cJGR&Y zwkxmcsB9ozBXyGg60ududK%-K<=1e?fBM0ItwC9>V9coQwjR$A z8{F|QioPmA*Zdd7&YA|hNxya*3zX0TkJCQorBlVk@XUMdP+gZe`Z2~cy;v+O zoD~l(dcPQTbNds}S3Ltbo1eX^?jOP%_${$YVSkv$kaKoE=Gq(6c4gX04&?3$N**b( z(R-q^Rnd>DVV@XK`Ir*_?DS~OW;zDc_l0NoMv4~tGD0*K#i9I&zf%91y!$5%Diru$ zzI@pc6W!1YO;*-EqTDpx|ruML8A*dto8$c6EHY52xcK{Fi zIC<&PrOfn*!aeW6GOZHqbW0F~;sJz*nM_98t(G{2dFnH0zVc-r_F#naj5=Ikm|Ou( zA)WM!9!tX`i|O-%KUA1^T?>o(`7@@&aYH5U>Gd{6*f`9c*0W=_Q#w^g7gQHvF7 zhMLPB1-L3I?eVg#O;C;|{KT|Kx&C>HcR`47f9zJ<`yhvgjzMc+d*0BmrIVWVzREk< z7Te-JLKSdYc<^F&T*u&o=2mXBT@30qwP@D5ATL_Q2QUXQL%Y?(TEf>AVA9`pCbrG3 zl-pCfB!p0P$GPn_E2&yJAvqStE@jSg#@Z5=h1zTPRO{J)pIn*ef(g#u1%4Bz7$bLN zS=PWH3bJmwRNVglK8PR_Fu;H@JpG>S1kB)jQIi$TCtt{mwXfB?9vUpLqF7j1R8!c$ zf&w2<#t2{ZFR zOwqSq7?>kKqSH!o+$1^t<5>=D5s|2k$A-rc+Lev-I`mt99Q{uEr54ZOH}{;|v2RGb zxSfGVn%$Bzm+FaP*}*I`73eN7hukeU1^xdtYYzGxU()*KrE2aeFL-FXh#0>Q4z9S| zwdhY}ry}a$Y1Y~x$PgSVAbovBwP>}l=bA#VjN8I6hGk{@LxIXqTFiKwmf-?w-7WXn zi#PUIMl1z-=&f%?B#bs(gK1S^Ou1jEp9aV5Gs^`uT9YazngaPZPV;ULeaCe}2^_D+r3WWCB?41ZT$UmTwM{d+`mao93K_sxYfZT8OK=$y!9;SU6APEhAF@5 z4&RY22vp+H%#gtMW#LowcF&^_|0gx-bVn~I6lGwTl~;4>XoZr?%fNDLJQm0#Il}?8 z10|<=>;W?LFyyjVl~Gc<6Oi~dsqysWsAKfYRrD)Fm-EHBPxrM6%h1~+VbH=y9 z68i{a0=kM`N_K{bXIM-Wp~VfYPtu8qL{r6Yf^#K{G~B2?;&I6sFN~+_SbmjOhMg7p zRc^1j?s2F;2s}_A^%lVK8*Htb@O1@v)Own`!K~)Q7CU~<8E!kk4NZe~KNqWtinhY6 zIuA>0wi)dXMZseL7@=1pg6aUC#WUzMCx0qaN>4x zG#ZnV1_LU~*#euBc+}<&77J?h3JGXg7lw11IESY{+Pl`!%Dqxo~W zzQX-CIbw@W5O%G}&R%L1i3p$jaxfdsg}(dvNW(sWlF@Eejqt7<*Q_6Nh3yJ38<#8N z`3wygLE5&iUGZm`FrU7?i=ZAP6|**)2s>8)6Oq2*5R~5f5D#APl5w>{J@K+<0Y7Y; ztY{6s!dEvi7zR!@hfhH<>T<6#J6(>2Vir&OvI_w2;yBQ@Lr!u|6n|auCEE`jlQNoy31@l z1Vy1djR^xHi6K%t$epQJ`DUn*p@W@}{-RH&)FKkCK9ooWVuOhEX+N>J_BYgez)GGAT#n;p9 ziH4y~y=EOf^d1;EAj->sC>VsVjASI|I88#Sxv(>_xBM6t3Nmj$xHbmOMqb`>(p5-@ z=f(j0Qhz!h7gL1f80}%+dqqCQm$~~OliyRkNt|I5@3OO#NP1nZuVCR8VJFMlZmR&y z>5{O>+eK}cxwdCmbOFBR()p4D{3XH9T|c(DD-ZQ=(}?9An$oYs+I#g*h3C)MgP z`n2zoloH~~<(*Y=xV6Q;!z3`OPGms_{&N&)8ww;I6}Fs%!LcJHa}^$SKO?_ZkQWu0 zjKaXMifHV>PK_he&VbU4?Nc=O*;6Wm;V;thnnjG0&z<{8`11q}=f97fKCB-@>xzjv zV?kmA`(b%Kb~7^}{1=?E~* z4X@gKYizUu8NoZ+gayi66;m$(5bR{OV5$dz6F2w7U|9N-V7fcVTVVv^a8rSf-M01w z5%$ZtPym;~^yTML|9NCIUrQjjJFqqofUc1^M1U@{(jUcEaOi}HIOtwuSuOa{;P6{i zotquE+DRW~%Cs~M^@EbAc3JnnPMXeOdlh3v!~@x?#~~MbxE(Y2cxL?#CEw|L35*Q5 zc?rN#Twg-p(^vkjGd4$cw`78<6<%=Ax--BlHC}sBm1`*IvwUL=T?j zqRI-1*yiRYmC1|r@AO~IS@*`QY~+#pES>vRUmLq?EK+oBhsJ-HWx!X6;+MaV`|&{U zXWGSUj^uhuwi?!$iTvC-B6e+()gKM=#>kZYNp!`P&sL zpdA6aicvbK;UuonIff#+IS(TQKsa*ST+ymHuyE-IpZqOJ=Pafb%mIe-v zosHOmg@T!FWQgo+VQB}HM$>H7dYrrMk0KBFN0VoZx~3@bkD*;2vP5OZRWpAbK#;l` ziq1blnQCHF%VHoij5|7RE*Zy$4+fWqFtor**{J&TEHe^N2@&l>Lu@v!IQyTM)G=z1 zFIGu`AusU0$xQJZ5jjN8)YdqzSaHpwJhO8m%$WA}xG~Gw!Yor(`F8MwO#k(s7B{UJy32rq0M`-64n%U)OK>FJFegmKzD&=p4mch9(!ruV69o6#-rNS zG-@l1;MJM1AQ94+aLR^rrF${yb8V1e!*}x6yw#A5ZQ1oOV?X3swG;n41RVoZ2O4jv zaIC+}3W~^^RqcdDedJ%fycfX}2a}Dt_Q;UX&|Qg5PS? zLfyI)Ri)Jz)_l7)`Bv0&isR8%{FyQSE@l_cS>@T$Q5aw&0pKzMnFf0CSrcpD8RPgT z1~MutDnD41Ojv$)JS+5cU{Lgc5Bohni^OhSi9DSC1%+*4v{?6X@qI$66{NkI-&ZcIXPc_Z#1n23&f?NK}Mn5RW+N8tOcgOMp`YV<% zR~vt(Jz?2KYl+eR>BG|x--2#@8yKE%4*D77S=Tg-_5IqWr2>&iR60W;_f-CFFFgVi zRW};Jqpn@CT`=YRUp9z5?^p*hkghlXPM&RBDKXwajsQaspVgK`$|vgy#20_Og|bvpQ$-i;aVUnnPd`vZgbv$J z&l_f+Ejsvusp9Wun&~_Af~vdSwhX_cx%ivR<>CIZ0t{%DU|9>V_p%S&-j%YEs5-52 zeNC5wIgVSvn;~og3JO0TBpJIeW&_c6gHoaoZLi_Ztl%N9|BiNT3uv{Ot{O@w^x&5O zbP)ODSN(jVLz1;olBEx#vQT-i{`~nn60tBJ5EMTSsRl5AL0o-W62~|1BJV5>^Z_4k0 z`Xp8G@vw)l$4`k!N)DnS{|}10;qN^K-76ravTxp_r>7?+C8ecZfsxj4BoqGQVqWA| zn^%yMN&w=VG))l5$ktG5Fxd6gxVga9OV8jAZPK+>gp zyF*AB;1n^2wyylgJxWUb>E@vFtBBnVi2g$0FdZBmps70qbbkjG0~;GF8v4~e_!}Fc zSS(vkUZHwA6TzkTW9?{HY6G9x)p5?_#c9|W zTlX2B?(oU8(%;bI?=MR0I#BB#tDn28U5>y{0esR!Zf*^F%d(!{UYnaJ${SGcBa0Zw zA*o6hE0B;QEI$4WflNk)`k&|bEa#8GYXtgNhrvv-MK8OHw7EJ2N+ zu;+R#Qf70p{oQ?-!m(aBA#?Q!-tD-3X2)CQZq5CHIFiyEFl0q;FF)!AN%HOKbwSC1 z^cS`gRElN(lk}ejR2JzuiVPiL1kit-?YpMy-U*=kMLy248Kfo zpeGGIz2qMcZL;WNm&5HqG2jTE)=-%#$vFY6#R)vyqVUV#hYT4NaPu<4bc(YzZ(>C1 zCCQ18^}FDJkK*adX&(semn(W!mf4FoRQ@a(D)J-XuLvxK^r`bmK=z0F{HJ%X8I}$@ z>2GtFVBTWLwA9|JydFP2re7c9i?1kWcE>Z_)6>)NZ7y04HLq?zNW}Fd0Uzk*bc;;d z;21BAsBCU#KzSWx(EQ;ZJO3q3E;d6SxT13(7|lsEeJUS^#}An;)w&o>)Ls@9e_DS& zNE@F28&NC@8?)m_9!uyyYJw3YH!OlDeuk*;_s{HK@Uu23*^zS5g9Vuo>|A1*_qY-y z`&$B3{BLE)Aw_KAqo^Cm#$91PkEmI)>Vcho=H(h~ps@e0fifomj!d{w^R3u5;v{dLottdP9YX@K8dI4xRE!(ocAK~Snp?g|MOdc);{#rt z>*2zTDIm}za0G(YLKY~7_$f<}-o%8pUQBGBB9qyhAOB3Ep5cz^(3hA^(p8XhM8w4b zx2!hIccQfK0!)0SyvqSN(Y9q>Fb=PB{QMMcx+tq)Yr7zInZD+@KtqK;E{}L?3I4W0 z>EG}?;_=M8v2mx7IvZ?;43^W)z_GMdfLDp~dp>VyMYg(;5xX}i#Q!*JuTC+XRc!%4 zeR6Cc+rgiUW2F!XABNh-_eu`jO6sl}gHF}!YeP;VJhPVK1TkafzTsooubDXu2;>B1LAC8n`if|L0KguS9oL z4w*h8Gb997yiRhyaUd%z`NequnIc5>pscA@9##;$_UP`V>q58wawq8(f$AB^6j?)m zeNiLw@nlUfjRp2Q8sKJAN)*lZxEnCk*|b3&54$rzR#q4g1K$%PNM8`AG$@Uh3beZm zt4G zY={3g3S9jAf1to$FN6!0R63Uv=DyyIssgL-|?!yEoof1|3x%~NdqZs zbTtpfxAvY+zccK?$%#_wy}Uf+^lCJ2EleL~zqD3oAF|vWx*hbmEUj%7;*Y7&)TK)aV~IBe=$MHEuD1b*uE@mUV(ZBuWzyk)K4di zim9=yMhr`*+7O5U?|A0Vuf>M_hAqM5;5`r*6)iB$$Ml^kkAN&ZC}RedJ5bA>0#Xe5 zNA;~HNbG1(BPH)w7F=K`gKTgfDgnnY(_jj2| zB^vb-+?AQnI($Ep6ZLa!0D|`g?(Nq_nFQf3TWjITFS3LK0*cn5{@ph}Gyxc3Kv8~< zh5BSL`F?2nt$0GN7fs7N6Wwc=C%ojCGb0u$TDp0vl0rWlVBr3uQ`6*kj7XAz{a9o-=gqknW1J4xv-B9UMosO@$rg@+s z-}>qCfyP>mYPsDSUDrlshvU4|!t%%8lb}`&aRp(z-!nJXD8pvC-Mz0aXR=wj3<2+uLEyeglNglAxx;lUU&} z;Uy0QJY`xyr_VLgYy#g8C3p(A1g4mLOl1N?6(8i&2!Nx47W;BbspD5pLMO2sSucOSINH0Ympg=<>jPaJs@orh_+M__+iDuv`Wht4$z_#A zq}e?}c&YFDOb?a{H%IyL>O-Ee_DjYGr)lvvaux;|HGR?;g`OM$WLDXB?dVaW-b-tO zy1U&h0n4FNo77qF6daALDLPcw-eopzIjvUXd|zFwRz&Y(&yI_}m&X9CS@~60J~fW8 z@AxH}FTJI!s*Yuqi!%>MRUfpO^zy%5IDZq+Oz62HL2IzhPpfe9I~JkAD&=JB9K2VZ zuDig(`MgJpGg@q?|`PLC^sXboJ281G^H>Y!c;ggA=@Z zwsV6^E|OUlJtZcMH@Y&*9^70iHfAv=MG}D&J{DWeNx*Z_{&@jh01&)Pf$elK6E0Ri zvc1^fW|ez$^kRe=De`diJ-^Ou`J_XDM)kh1E$}h2W7E=XtphIWfU#NDEYKIXDBm-B z#$||`3vw_c_6>aerKQW4!5mO);+w-aO%wXt9578QgxBEKgq!x({+uJ`%zVUcdJW@h zYp|<^>X3r~)f7$Chlf$`hW}#h5TK!ScTzHD2zt@%M1Pcp4im1$ zB`*fW125iupf^4iPN{P`+NL_dxOOZ{D7BQI+3{s{Aytp4?KzsK>ea!3pCa`*l82rF zqAK!KLEq$Z3{Q)yw(fQv5Y<_q!$i*`ll?XF(os;%Kq~X_a9BQ2lZB5?q?bJgL<1bBB-|uWPWik$PgY- zWK|ZD3y(}8av|rQNmbF$)pe=RNzZa#~xh6p=y;PkJ`d3ha;RzXmBor&$a!d@AVhqv97*^?R0|3BcR8k%1o!fUj8Q; zMcSK8IAdpNkeEY(UBw7!dURhGVekCcCoYTv-N_^El?#N`z$oU0G68iFfkriep*nT5Bk80i_9hk2R1tp&NOIS zs%Sh}txjjFzV77I7iA{kuNn%?orIjx10t&sk7~JO=|a8KQYWf-8=sjRdytB#N|eEY zx>I~&>Ga5WqH=hP^O~mfhG)RoV0b{?!!NW8-xIf;nl!o`w3tPd*zC_y;SdpK=))lc z1oFq;Z$`19FJ=Y^M%7gs{|R}A$o~uEEyAbKLiMkBx9|D?9PbwY3-2~q1h+B%uVT07 z@tPk4(hkR3X*4w#!&|+}IP@>g-mgBj;iG4z zKY*lJ3^G!?A|4Ac*T(U@h#fJvL?C;P<1$+Qk%;xz!OxG_!W$QF?m?d)m^99 zkGh8Wpv{s{-2dk*b$4knZ;vN4kdvy*_Z@X?4jlnwIJ4bLfa$?X3Cm=4XBlaLcA91R z4q}`$p*;I=9aLfq{KHcvh5;aBWV$aj(ZXV}vn&rk37)v{@VTY7k&@5*IS*Nkv!yfp zXywZ8DFG12#cJd}bB7(vb z1Y9b5tHV?`8^VSdF39)u)ymG}CR}F|Zn`FKX~*hp+15E~%)r5`w59sCAFPk6+I+IerB|aL}S<;72MB>n7 zfoYT$79scWC2DdcK3NW-W{wn4QFr8P11sApw3kP-lQp)B^FX4G6g=#Ak2#*but4a4 zuA;J_1Lyvdqb=Ft;rDC*`ZVVQ72gl%2x2&mw%6{8<3fPsSI~lVka-Wb^ZD?{+l*20 z@Jx2uu1=^Qo)l*idd@E8iPh{Y7puz1XIpGRsj@%EbEe#QE(%8E_%+*GN#f99VnX&| zTP$C>aA42tzBdwARn%`!-;z(DkWpxp?+S*n$7ZvwnYy60_YZ&~bA~lMoE2pv)X$E9 z@v#R%118drkn)8}phUx}7ae%#@lWkZosjLVOeRKw9}d=3k)*gJS7W*#hC`kKgD`O> zL_u8Qw@mA~H(1Rj!)|Tp84>#S6}Y`C;^QA#n9EF>t^keN{!05&2mWZ7AcHWT0_=g5 zM6_)E#q|H23-8WNYW_y(WG!c_a2=nzI~0zIJz5PoCRELw*tU?Q7|(Nm&EiwRF}l&O zs7b5##T<1EArSgrqwq*X~*!=RU<=Tx=eH_Y)?j9RX^3 zn(^@xF+D;5Y;FIGNcYSC8<6hTBxBCvc1G-eb|v=liREhWrp08dr>bA*G*TH+|H9uc z>Jc}r3y+ANus`H#WaQOH^O!j+ojzx~2D~g2?f=Sy-ypKY1m}6`p}&T%HpQj(>DQEn z!yRwm`urAT5I#9Q0*2cvcYQ!y0`s+KBhDrdnvwb9AbqS)kL;H;V?qFExyT`xH#58r z-Rf`y*|dYk6vKA156rf_6dlhEywOjICD^li(WnU)zkYU4=U^idKF6j*`H9CMePuW) z-+JGk%$#5dUa*scx%krSL?FsAQm$b1Hk$5owsmw=aXzBrlc#Nw{vO0pbu*xbr1sL` zdBK|-jKf}Rl!a`Od=U|ao;qUZW@tlbs~7J7MIp%1KF~ih2*Cy5BXTK75^r}$vNXxBTeGwk{v@`NvV4NBgy^z z`ael-u^~L>-?@Oi0KSt-fpi7PP~bs--|tPH0fwUkk@rG7))X}Dr##s=zr`3IAFb64 z23f()m`3UOs`nENvK%v52-!cnHeYTE6MP552URp*UtcCG)9aE*)&^#Z__4FVm@T~% zVqm1oGMns?+_6j@`XtO;gE#rbLc8CfUQ~N|tW4IGl8h`p>1F2xG54(3FmpmHe4H+j z8wqokPjrf8PAZ9c$7VzMwW6saq;jXmbJv=kOCjhdA%HVt{Azte5Z2Y#M#Do#grk_3 zQiC-$RXJe19KuG@Tyq%}bktGZw;bW3$L_8%g2VeIrJzg)P4!iqi(FX~1 zNiMjkIUy}P@u^z_MwPQJyXaoKJ@oRf9s%O<%%xnXj2l;*Ip-iXS4)DDu1oUy^FGf4V^x_D${ZIc zV_Z+m_KzY!oW7jmX>>j-oELClHUs}OU{>J3E{BOGSB)6_^Fa_-0l(kW5IIVj$=Nmp^xP(>!Ke)+cR+yrn)AOJHKoL>vHi<6uZQ% z@8t(+>4Xkaj^+QkF%J*RtLRB{%jJX{Q8&=pl440iF;s2I*0tUZxG1o*8iFRvh0hl? z62s77gr}FhabnHJek+zBNfzJDO@o$&?1W$V^>^-fE#i@zzhmQoDM);S9{X} z*(9I3y1MG=eWJVY0%$|<4H*Ko1;r=iDN$5CY1bj`Fbi+1j@S$R*wD}b@p2Oc?$jUJ z#f9wVdL?;6qip!N@h8pSFtxl!)+Wt{-MoF4KVISH4cGUkCO1} zIjh;Oc(Vy!VxVbZaWD}iowu!S`vzRr>*4o*rc3*o0Xx&%TgKpq5JVL~FaaK;-g{b> z@1O~;ckI*V&G6*hr$4EfJdwa{)voSup8D|SXR&3TU6Q8b0y$yXm5oue8uj`)w#iC| z!ho?V)f5-wm9XGoONf_&JP9_-b6&p2&tbR)tqRMyhcQq@gZXmtk<+3Cza5o?m2*?V^3%|%)qBbC70o~vz z%N{BO-G#y2D&$LuYxMFkw}#b~Xxp&rDzT+!Zz8B$Z;Q&d)%NIyQ){0<6#ff*-KFlS zhel}uyF25y8IvU@vv;`FJI$sX&Zoa|f+Lb;jV^Wxa%75qNn!S{+_=Q_GH4~9b?7uu zRg<6+MMx-*(80!vC`icdOXpU9bp-#wgh zc*IQc<)1URFIs*!o8<1lQh#5}co-6qi|#V&rQ3rW9GMII(E`40hD)QAfJM$J+4fg% zA648Rkiw6!8}7=iya5)%X?tbVr=BhOTJ_M)X1+8Cf#Buzq2So^`|8ONFCqq2`QkJ`PyKpKHvD+EQwR0+ zZ(ENb8+LiTLa`CfF*&X)l|(;bEx@Rhh@pC+s(UN{GcFCgNoSj_A5rZCoqwVp{b&PN zjK8biH;N==N@1EdQI5USgOlm{C%KiiajW^F%Q-)AdgyJ>cf)A-J6fq|h4CQdBaQRc ztjs*mWBIQCSNLNkjJm*%mo-FP$UpO{_L-4LI@Dn5i$vERtlmCR{FaqK{aDdiU##;3 zwO?Rgc~>=3*k7VE9NzZBnN!`n;mP@AIfK0XM?K+5uDz{wq#K#q-nPuv(|qDQ$uE2+ zZ|s5M(Q&$@0)AOX-5$ihV9Q zN3ltl9hzrjh`pWIE8WqOIR!l@t}ig@x0PG#IUTlq&f;?FFV|CGk<4B5O27KbekR|a z09hZy{xnMO*5^a>O=H{l-?%1Z!&!RhI1kS`5=i;QT6_!y`P}GXYE_-8`+NI1QEu@Dkr1$30N*j==KVr4<5f}t!BtRE(DVr*} z@ESO&A-8U#+U3?!lumIe(1L2o#BYd!zboQmymv1V3jPxO?9G_8I7d<3R(6Y@Z!zKF z?knx=sF+)nE;N#x8Kb&IU%Bov+$Pv8(UKeKq;LCJ`jaEnm?WMKV6`5WvK$2z`JIpxGB#@`_@nXT|~ zD${gd@d#ho^vsO>$X6*a9#5ICmd!1u3qtCM^-Yu2v!$ryZ+%!vNE<}{W;x3#xo%h2 z0biJE_=|R6W(qX!0J#Xbl090ZY)$^O;7SRM-%fO6!#h0!{mjg^AlQghWXjMAzc2c-@iY`5K|t8Vnys_)mNx)AF4vL9+NQiuIzk`l@>IvJw6 zCL>v<9|YEPjMWk4eA_l4svwF`LpmU@+)o#ETZYBqQ(m_FlknmEo|~E1%M#l6&haq$ z{54JRpUONR45K%Q(T6kZLh!}>16UITE8Y)1^PmobPxS35WNuN=>zle$I#@%(7R1dD z>ehHn#DaHM6hEwjTqYWTW1|wc<6p$={Ju6z=MP^wLNFY;OrONwIYB<)zqnS$ShJi#<8!j{d$p>nsnfbX7I9%}?S;LnCy; zb(>5|^G!I{-eW0S49SWe2VO{Z{I+k_5B}Y1IF$^Zs_*6q7&)O2qZyTQmW*kRoWk(6 z)yn$b3%*icQhqa~Q5QF!iT(9n%L=+n;JLoGU2Fxe=V{&}p8K2g;J28GEMMkh%4S>g zr8KzuFW&)~_qZg_Ew{=RJIPm{p)`T9D4kM15{H1}=!cQ92q(2nh>S?=6wy|bte)O1 ztXzX=3s}C9%q$S36xNK>n*5s5jDMKWK)`|4LC8V9kmdcz^}GRW?Bx8(Mo^1&oL|T28fD@VI5!0T6tQ@W>E4-a#j@XAuo6= zS~P}As3<9gZEYa|n{WCh>SxOtQHM)Y4j4MVjyvz)zlW_UAc(r0E6G`Iga2l_F5D*d zVpX4be;I5M;W@bi5_va(scfwUZE^Tv;2tUw4bS2*Xje5Y6T*2te6t6mht;xfh#jAh zO@^~TYM<5V96P^8XDjNn8}fPslmtj~-cz49R%+h?kG65CY2fXckK{|-M}~v1EoIyT z*Zsr%_4J>H!=2P?B*lRLQUcv=CR*hUhSt zqLUsc%@2+`v<}k4OegNqHqMG}KWDHN2xUjVWnq7qDVfSSk82&xdcVQz1Z?jGv2nBy z<+MeMEj#ThojPN&Ca|YbE)7q9r{{IZA;~2a!r8ucr6Z&x43}Y4&@hA?Qg*s6kGPsI ziu5`#XB)p~*K+pH()(++Qgz=^A^TyH`vvH~-K-Kama9|}2} zTCT@lY(d;t{atBTk%*f9F08Dx)NqAAF;gfgnQ9FkE!~gZyVIRJ`p=PIfPFScwR8ZD zcslb8G0LCg{RqnZlG!*+*^q>0Qqo`Wlnv9TN%5A1yLS{<*)u z4@tpULx`6aa98Vd>8yVi*SL$+BY?SJDYMClHQf|XeGsy_1^E?$?mmy8+exF4dTu=} zprN7h^yyO-l@VwecJBZF1Al|tISC28!mG|tC6FL+@GwY3J;`u8J8l275E~_vd=@@{ zzy2rf8~4}F>Hq61cVGJX`AxeGn6(WT=(NDGEdk-tFoLVAtAqQOM!j54A(G3<)`F=M zj`{Zi&Jcti6oB$J8_sw|O_yT2{SSZ@gZ8@kJL(&LKEaEQ|H_X8a!vqMWPYHFYJrZg zt42OFCn?u67QuH4%SVbdR0_K(d<>yVx2xg0iFHDI-p6+8l+kZd@MK4Ey1iRWn&@;D zp+6{+LI@7H=e^IdCg$f$fd>Hczn=4;Ueo*8;EUvBCje;+MY9v6#r1DSy=VTjJ|R64 zdH56NCcrrD0tPa6n?*4B=HZs9!Dx{k(>>cBdTkAumt2Q+x*47zGPiYgoq>50qA|!` zt0;LQsi_{YxB=D>DmI=ArdR~#Z&@9N*^7gD^n3~m3eZ)$vR;OkeIDyL^^3zXs1X zFG*&0EtZ$R>eb35~&^uB+I=}z)y6Y+pexcQmF(SkVoffAuvdKEXO zU}x=g4l>XJGYT>ihk<=6G4D)pSom&uGRax_2hx}0KDfZ*Adit7tczeW0_e;2uu6Wa z_{IQFa-w;P7GW)iD%r1jc)aDR|2>GBm)lM}j#GJYI|@Htv!*)WeyFTBD+Bgj20>wS zIK)DCsyN#a=Y4XsEnKU&bXQ=g2BG8sNao(#Bb^?Qyp<&GjzM;|7{TMl?GVrkCne(S z6+2ogFx#}Vg~*dlXZ3(PS|^NnMTXz=;m6~(k#`9u@W*57k;a&|5{oaW$DEm)GqV}3 zcBwG&^MK40*f+Jre}@cW1Dd%=LIXUm6BF~mb{9zEjUbeLes4fi8omdaoW*y8kLd{h z&DiY-HYs^q+Y(H$E_BkO28D%{SkAOU-jzWl*cl&FyiX8!7X{l1iDJw{78XSwh5Mih z0b*6|E_%<69C8siVC1fD8zNBXdgAC1a#K%KshJV#d46?wvnMq0TU~8I97TcK@%q_7k$ z(*j)ZuZ52X5yBqZcyK-}C%DoabDtes^=DQ)!%l<44DJgqK7p{^5-Twn#0|dtmG(@f97thH9JcC z3rC}b8bF$?Wn(0OCd@E8gy0^l3uSOg%{Ycqvdd6O;QAZJn!TWk}Shv_ok`|n9?ih;yg~aU@n_7d}1Q|Wy*M4{qoTw}h zTbV&c2W9;bQp#JZ-^y5dvN8}T_;|C{f$lCT{K%1^MVi}wJz)n;OOvq(6(uvKQXL6> zDjlP^A8j!UCWd~Sn;J_AeKe^I6ZohO%23~Y4d1FS0 zO`R+!QCHW~I!`Qtweo9Ys}UUlnMa0(rvAJtKY!J`3+y`#_UkWhiA95pF?}ffU#3Gy zjLMrj{dgB<@Ng8X4Z~y}t}c0Fri9d_JXJ_|L#}4OnIZ=m@W`uOLKN4PL1+fbp+|t? zTs)Rto_6C5tpx%*()+ae_+H~lNerw^)|??HQnlB^HoLWnlZ_@~?~3aoWhZ}m>n%vK z{Wmg`ihbd+Jqi}13+jC)!C5S5s}B!1Pz;TbU$CH`O{v@+<_#(eqOIr(O}VMzoHFwC_}LB`t{&WzbyHYaO-Hp#bx{-1LX!3PezX(r0ASC!-!R%i#2Qo9{Z zbAF~$%&Ufa00liYD6H&wv#q07U}7uYaWMmoD^X^Sd}@86&nEWdq*iTPEk0KtbA$A^ zbjQ=xhs)E|S`e@$$hW@~a7^7C)hRmh5#hMlisc^jN~m*8u{I~V@U{{?WDLfyV|p_w2~;q6cUMCqbi8kcO2E(;i|v$p1+>zj-v=?wWhi@f^u z!tD% z_O4|Vj?v>nVL$zWaJa}I#JFVn-OgkA=GEpj;R9o&kKcjKT|33XaK-jXU&;{7Vb!X; zaA5o}o^1zJntxZ0n=R{&5wlOj<|!MTxzNh-2IR3zrYxioPB%yi-J^WpN3+u=Y*$Q{ zopLhspt-H(TF|fi+o%_9nh|2UP3vmFmSBG*%qPtzIJZQLRFpbRipdD32|h6Lb)vIT zw=2l!(Q%Nkp%$2cUbUIN`~|S&UID6)JRPTl{u;LqDTTj#vu-CNvBa6j=RX#T39n9E zl(!_E2+XYV6Sy{ojsGO=^t@N+u#xadbPnd(ZDEYG)71+uaThizzj7wb&bLDBW52ZW zwaqSA34S$F#PaPp)Pc&d8w3`!f<^)&($`*qFzxd{nbX8D|$+(hwnZQDJT!@d?>91X*BU=SIy<& zC@s8xeNhOe(0N~eVp@gautmWR0~j2LxhG3N?`SzCH?s+-s7fsk19$>BdUH_Sz5|f!Waq8y=UJcuyb@JAG#|r zH!bmBLd09v>mG{W3NFc`g9W^ehJGkUjOgd`(`$*z9`Tq{!Rjj<{{wOt18=YhHb*C} z$Zo?$*N!Foc?G-(Cn2I2AAcBhgtJQCcneNWdqVEEAm+z`1xa`fR)Mfl%5BnRZ^5H> z$pMKzu?*V`ny3ud_y2qqr-uYOh#s^z#-RbC={W`dg;UGjmu5Jx&3=ne>;SnISrqk| z;8Pd((Q^;b^@cb-6g)du16DYtwO^(S(5&?u`wS5WN(8;P%v{oyTVeM{!1^ zp1m$3Ie93T@E&?9zuP%NEh7yv&s;()&1vF(OY<2MTQEc{P8ye#p3if8hss%Td|7b@ ze%@GFH#<0p+ZBqv;vwHK^dMt^AgtAyR&^`tigL6%`Pmq>ghB5C%YRIqz+h+x3FhV6?^=rR7$UqT z5-b2x%phEqAhh0MZr4F*zIIV)28-h1DQUD6gCoA{en)m z8nKHAD>!(>yggkzikSWmx6_E|@rBKep30`_xmOo+lT2RD_PkxWDrU0T)XZ6}v1JHp zt;fR!BjVgGJg_uupoOMACAKimWmkFWo&VxKCJ$wbTL3X3xzAQZc`})#MvZF~xQE+6 z_6}u!AGEllYuCOdYCrKhlyVH})q#~;!$hN;TqSrgXP|J)Y!{P^5}J74j{hXFM(C~l zk7qbrUI}^a)(`$Z0%NLB-e{M2$)-z)IU?OHt`4_y>gR4xQFT@WFb>xweCB)y-AYwQDOAaEs}IT*wq|E$ zcGe~}8%3v&f4|lY@LCsx=t)?%Mgfju1>DO(55UPs1y%RVSBP*ihxQvOcpyR*G_Kt6 zLtR%6im%+anH7lme+MofwU}c#^c_Y_X;!{Z1*)DHg5+fer}02 zB)EIIx+vJeImh&Vb#qAS^}_KK&(xV5y=%A?bF$dzjHu-fTY7BOC5+71-&)2shakEt z9Qq@j&kN_`4-~r|&Q@%IjoxJMyQgs%V;5&K)`Y3k&g!_T@G4`6ZQ3)D!U!fgd^>zp zFIcWKaj;3{BS3K2jl>;GTbxrUqbl>`+5J|2M1yE|;bE!7xt|EmuZ_*@LnC-q{*6qHGU5l12B3wHX`mRT9^yEza0TczdD5zicrx z%X(T-$gYVjCmeD;UeumHkb8(QvpU+_jAO8-^IE{D{56T4fa@@mx%~2Q1D@MfKiBy= zp%N%I8^fFnm*EhTdfh0xPfkXrbdq}v+aPxj4V1ZYTJ#On*O`?NOHP^yGz<+utOboJ8*^ZY1IWR!0PZ5buC%S3E$ zN`S)9D=mn5$Qe{q8K+s#VrDgTy2w_?E;-%r;&Mx|nLU7lYTfUPpO9To#;t><#P>)q&kHbcBReqzhjD*on+w!N3s~qv!Lxz8 z>yCSL&Es(F!dtkodEQ@&Npn~`I9?_B+N6o6`TJ21R5VyC92te5yyeJBNc&Ucwp?%8 zbjAatvMST(LC)aQR$u)N)fEuYfH#ej$ghfA zs#AG!?jy&){G#WiZW-SZUr^T5gOg_yC%>x>;Q7&%=!0>`DQO`u9bUe}r&Cxe8*9f{ zoFEOS#WJNDlPM9=4>FK}h68ljPm{$%GnIy(KC2B8x2Yyf+Jd2S(KHGlCxT&n2XL{p zkZp)cNU-uX{&2>jzxDjmNSPA-*Xt{Hr}S%F3(m9$Jhq`-7`0qG-CWubu( zqIBWNVHw|$k=?5hzKwTu#Kzww4}a1)cjs2J$a6i%7jKQQ^CwXm)B?|FZnGw+CoDFE zAG1VI2H&@CBmy1#^shQ4`h6YaiQ|xT{uKGoVp&Iepx(sor zrN)udb8aW%b-TAM9n0iqbW98lQ;bGZd@F0>?S_gaHpwrDHg_@88#+*N_3Yq64#WwP z&c8`_J#6Z_w*>-zu+T2T&7(Y}cus29#|TjFp`j;9?vvnGsHLT~_5_Uk5~fy?Bwlv^ z1-_Q9zj&^Kk2(9G3zXZLH@5}JK%6xlO|sxmF?gzK?bhDUU>0r1muJYxXaak?!c1Ti za%RBNa=*Pg+O9v`gLJc(>KOscK(iAJ33tyXgGVLwcH4m;NWo&SPuc|;6wyc^e zy$fElyWAfku?HSUX=!O#SQu2ebpTS`|K<^PKL#EB#NYvCvPUv@W=`R?+zjOrcWDjJ z8m(ErIx?=sZFX>AfFI1A=skP$44HOu>P!-Drt>Fgh4!t+V5uVIX2yvDR{E|~(?{Vp z`>v>p)pVXw5DX-aea=~CMxtpJ45FEHj}I5D8JE>IN#WPEbRazvnJ8SA<0T^c{P{*y zLpWsCAZl-f&qeU_7v$zf9U=5ermALB3FO!CVo(NMz^c!1{amZ~u|c$Py|zI)hYR+j zr@9-DYI1j^+{g8-DLDF^g>@kYw==Y}dEN?cx{8YSsp+CUEZ#ot<88Ods-vfOv9^fv zFb@`7OsoQ;UtyuD3)2wxGDlXtcU;1Cc`1ZvbI2bj^~TDrtu1H_fRmNsxto4}Op%=_ zzH-OxD%u)n9_iUjhZ*)17Y;7Zp4)rn5*Jdv!lUBg5rX}O$H@g~n&jo>DOF!5r3rJiNIF{_xypCt3P9F`=@2MH zOG>Uy$YxYlR)VYuFV`v36-Vkz>(3l*iqQcDRaJUuNr^4!3711$s}nxM#Il2pL}mG@ z-9Rz?9JQ}eLB1R!vKbmWAX%J1S+Y9Ldo@Q+Hs-%tlv+U0#7b#~=l;yiuUJW%gRJ({ zNkufSrK_vr8l|0`9Z1AfhceUhs#AraIwg=uyn>fgIfxtVru;q9*@sY{w>b^rM2;3kEOK_b0$5IS-Ta^`^EOqE~+!s(h$+gs1#Eb6NH&?&U z5!nx2fHjCi@zB~2FT-<1*&gY%4!cEIP!r7b0=e@7(F+DbLS_yB^;;3h15nN1IkGf%@_U#9?d*QWhtEmcX05?yde-dbxQHy3=;M0oFqm@##=0? zTlngZF7TQ2!-M*a*MX87(BGBhGmL*6qVDqx38W1ZoCj+s^%6>%@>V@O|JI$$nnj4g#GEZAzxKLhS(Q14gbK#8pw5up&{8MO~LNXxJJ9=K!l zBA~H!a5!;Phj2Dd7eH#88tL-9>t~^VxogFm)HkYLFc98*DM8NJ%NewVm_4FBzC>&F z+&2cJ%pS69a0SOA_GK8jP-vrg$zW zY4`qGm-M=_hsQ3|&tJ2Lh$we)7avW9eXBlogP`_=rZhaIgz>wdw6|*0zY3@tr&qAU z4Mr3(_)Xi_NZtIc6_w%F#hg{|#xAGY-lvVfdtc5vUpQ#fTQ6K>A=SfB_0y`z8KTcb zx1u;$ccql^_DK>b3xK#GN$T*p_E29xFE`hj?F5MVf;sxVe>2E{;AX_#Nzm-AJ%6XX z>EOC%q+AVw|90*(-MyI)6-lK~=;)P##~h5QW{Gg-aY;xF=PM$SNa3L4s4xIr6MV!1 zpL?)$Ir!~^sRQUmse;ea>t+tHfZ4aZEN2FO2sVaJi1x4(hfc6#-{JtBST!`+u;~Bf z(hOvx7J+K8s9$h$dmyAg-o6Mry+-rsHd|CQ!})j=oZpfq2b9>MJKrjW~bqcq`pp`Fo?*bqLPb2VHwb)0paLF5pgr z;ep>FjDWZ}I|J-*0f^9A6cE1V7p;>ybyCpJqh@d+^q6d7NuszPmK%}qCJwZ72?jyA8phU}eB+jhzmgBPvJo^%G z{T*iO&x`iuxopkm>gtgXvk8-mGMt$s7&xZ;S&2cEN=Z$PMa(|=Q&9gDXvSGHPSe|J zXhaKt2aWqnPs}MwBk4S1MdrVQc*1~4P`$_1tdYiw<7@H~9tEz&Skg%lMRWP`s4zV< zvvHgMdDX=GG877>wRp9Z+xm*;Rc|pQ*7Ek-pP4Z$TdGsdA>Op{Da>H$BDq#xjIKQ> zcV#tUvHiL|rd}i>pr_^XW^+eI{C%$VVQ8&Ez3@PlfPf}lv{`#KC0op-IpBaW5Y&CL z2--RW{rz+D!g}LIKbgBamwsWl(HyhrNuEcXz|5H4_c6CQFF`Xca7N2=7fp8UmC1!! z)tF9GD%|&M0jqaF+0S+dF*#!X(|ohVxwD*XMgo#3Rv;&b{xe+V%zwA+-rQqn~!k`VQDQDHKOrDfqNDxIo%f zC22;g_Y=pJj~Bu11IFB+P^+0F6!73Zlj$Ac5E zB9cSCV9J%9b_mULdhh3sOA5cQp8x6dS`zz(5OmUPBmIaazpl~!G_bU3%E9onNJ|S4&J)XcNO>^FhEMG{`ILAo0Xm2I9xqwI(a4i zqy*toZ6q=n;eMS9Z7wdq0d_Rl(FD2`u5kO&a{d^guX>@N3Kg&$GE*_P^S#a#*zMrE z&d5xDd?x%GU{%mrZeN^Hr4Vcj69>_j2=&Y*GX0ZkZb)tRDT%dq*U54pWA&8i zs_g8$zL=J-hlc0c#ZtI_bE5UoN0u#@A}6NSlyu_` zbBc<>t&h+e^2?l24no81hNrI9*3rD4Y^0*PWSgEQWrZ)~%g)J(bfegC+EsSn=s`j= zT*L63%I(`Pc1_r?uzWJSPPN1g@w{2>^Qjg+QShjO0AqNL4}s`(XyZM^-?W?GE>b6D z9Hfd|Ou4X2@+t+zv(v+6Y}Y$dO__+7aZBDKlzHmgHr(Z|&5X)i$oB=+5exHNY=7v;udF@RQAIw&~Y@VWC$Al}&&CLaguj=yhEhq(Z z83>xNqM!*Y<3ZCh1Ddd2EmE!ft)3gbor!nJr`0Fab4dzduxqH+&hOE6fypz|VnH5_ z=u0vWk@$RYUWLrp#l>Zif@fEe1JgXUZ4IIcFDu?ll~A*FB|}SJ6StM2<15qJIz6jB zXKCu5X$QvW1`i&vJ8(K!?+Xds*5e%|h||_#-!gpB_6T_#af?1g zx%7fo-3EV!zyt2Cj*eK8$TK_k-{0L11F`0JBbdpU`FU$Agvn@@d|9ULTc$!VJr1K6 zIOiDlKhmI|U(O$lMXHhL@!AYNW?5STb+w|!e0oXV+}I3x{9&k!#jE9jDoeJMuBlJM_S$zh?c5uKEC+OZdmcpdM)~$B($Mf=XQ6KA*GjGn4{`*J&n=gLm{^tvb z{I3vLga3K`AHGgS8n{dUUX*8+{+qW^|I=M1yzR$5&VPEX@caM%=k^-=z_7=f+yds! z(Y)##NNdCh_%rn52YPxOVIBqgWi3jO*5s7tId(##<4ijQv=HYLtftV1f)uC@aiV)< z1n_fh!+#e?j7=y zGA3e@nhSrwmQ_JWh6g!>a)4?K2BkcqGXtap&&cLd7$aeRal^=c9EQhShaXcnHaF+J zfGW74pa7tVp*pXM6V@hj4?*YP47BF!JvXiCg6}4RS-MvF5&&T0v&ksVYqK$KcL;Y#j`)FAL0`KoJ*1?~pcJThsjrEp! zw?x;)qZsl}5>glGk6^pi zPh*;(Q6oNx4TAdM_i>Ws$8B%w`V>v;gbmmVmNizwpp zRJa4f{zBT}Q{?L6MH-T(KggpkmS8_n57>3ka!K($D>RcMQK8EbqL?2?c=|3-lBh=A z+8obo2O-n*dR@Skf405TmH{a;R7llKA|(=LQonCg!3;JmY>dlZ2RUf+OGt)lK~g;A z3>$pm)T!L`^z{0ABl=qAUvzYI)&9o<49`|D#$8+6O;ct*dx&N6+tX`GC0U;cn2FvK zA}rYrV?uUBkZ?v4esuDXk8b+l;GmBYIXQXx{*Uwe@)3VO0c{@ zk%8-LXVg%pD#TPUFfwZ8kvOeorpX?Mw2K3dg^~Ug3GamEj1mR8^Fx)6lZdJ*YQs}u z%ljm2gt2-I$x=*QoDSsbKn$v`u7)IXSH}2<^`dEG{mh#_uO#%tqm2g*u!J>R_)+J- zfyzJo;{66KJxz;WP?PmeN2tUL-bCUqikp+ekl&6)5f}rQfTJYPwHD^)y1Q1~?JIwh zFy9Uso<3~hNt@%n`mfOJJt;688Wcn_%ryKNQu2JLRz&a1o%iwE>r2yw|HWK4+hIS^ zS6mai62?ZkU3GN6i%2(t7+hYQYt-yQ^D|GtW?X2`Pi&Jvt`fj}iS4rSoENx^(3zt3xDf6w@CjoqBS3(&fb8 zo9=uznX?(PbDbh1LoKa=Nfn}icsEsHQBjil8x>q60LZ#beIi3t-auaJf`K;YekEv& z%*Zf3ScjZpZ}YqH_i0&kdsTP#iKdS^IXQ|aPb0Z4=%1Eoq(94(bB0v z%bVjhxT2lk{h#=DQqT{7KnO7oqvaj7W(^j58@~sYe}JgND9l}k5-X~yVg8hl{+gT& zXOT0~(4hHsNGhX^^L&=?3fxt`a$q5TvR8v-(-7n9GgV>#yh6faT^s)QgRYzY3h#7k z#6ABLyG*Utp}wXg1%^O{_pTG2j*bow7Z;ewKl}x(W6)x~&9C#Hcxak;jK9uj!YFdP z4(~aBoh!KGZ$$~Pgai~;d~ZZA^YK0SShTk0bfroc=y7^_Ii|)XonW$|RHI0i#|7XZ zi}y-^Z5UIvwPLtAms7nlsOeGfbBjEFLEvw}h=qR3gpw*}|w%YkG9R@(FOM1u?? zqoV_?UZvo(Eo>FF_^Yx~{4(sKTJQ-2(3_$+{tZyA3{f2-<=MgoOxMPidgKFp&t4T{y0?R=`t^?N+xu6VN{RZaN2@E%H8>fA>Pfq@ z#u* zQ_#M)xm;Ln+PJG@?3w;>Z3&q*PvKWvwFLCrqZ|CIHpo?5Ess3C{@hHes2~cBwh8=? zp{4D_H22MpQh^ndf}2`+2;<6^Q15HCjdbgle-OX7Jfe=+GLDcjD!A{uB;dQRz4GwI zcyWCahr^**~$&V3$kftu8S42s_0GUSRB=ivRHJE)V zIu2@%JFj>_Y#xEQ1mOj|=Q!W2|AH6@$8q%R-1}qgpwtJCO-U5cZZriB{cmdXwslD@ zOB!Vw!&s-|T$$VwNXGd(qhl`PQ*9uFNKpG2Vs%oO4Kv8eIdJp0m7Bx=L8z4TveEOM26L+ z_uMkBmO5E_F*L7i1T0JFx)mnQ<DJI9pKJH|`mMsk_ls5i1=t%1v`zmVM_>uq&G-HaO5r;)R+Zf2j zDd%yBh-WP+&l5xU4&r+C4-P3QsrwTZwcYcYn{cK8&v{#~qVjgqNK*-o%Wzs|CTGr5 z-ox)d%UU2{xJ<1QEIyFoQ1xnF2D@55<}d| z@o&je%wMc+ro%6c9m-K$=h4x;2{7+jW%<4UY8GcUQO!lG;x$UsZzuM@e5l3{Ps_#R z3DiApkQ@71K~w8Fag*6Ynx?4U0Es^c-zcou`F`jdRP(Bs``iPO)4uBg{ns0%n<)^O zIhHI^>Eg#>&x&}gc8c|VpKhJM)3@>JQ;mbT*JO^B?wEuHcgru^hSr%eAG2x%sFSFe zU8Ez2^}o;#MsxHB)r)Nc6kDi~Z`&UtKx@eW8IT|oVlv?y`Q(-T#@47M=?SoQ=<4bs zc!~q(wV-@qo)6*@%ARuZ?`$cL^x+)7qA0tZt*BTmwTEy_ zswr~F^`mW=*2UrPo;elwzja4b-r}d?|e(0MlzOVjhuj}YQ zjoD>yqfgC+_Bsy#)<#lj;j3R8*?N|t-B?cdnA6@s1qI$|R(5wAg6qZu2hu$bxdSgl zLEFN83g#9TIzO6~q7uRrvWqxc8!a5mBvfLqom&ihKVdR?u%BR1_Vc?9bDt}?>{Gh`63gC%l5+#?c zXm%|vdK$ml?u&?B;d6s*jBg>Ww#N7KWaN-H_Mqb9O!%H6YyzUOKu#q6dQeDAryiyE zV{`M3`5@v^g=v7}DIQaY5u`kjKRYFXkYJBsMG?MvG)-d{Vno*gloj${Ukx4PM0o=bWZ$G>h-tC8~pygEe`}VLu(YoTv<3f55 zX{SU3$V__Al^FJN=Q^7AwKw|P`AIk}#ecQY?F0WO8!I7oEv@(T7s(zsY_f;XN=@vd z(UMJH9Zg3|seEbUMeWC)ETIWp?*b|gS?NuBc&OtHx<%YS#0#-?)Eb*;xr`6U9X83R zVb=P{F|mG=afSOmrCQqbTLW=Mb6$~n^dXC`;Zei!aNWl^lj8Ny>EUvR%Jh=AjcBs4 zi5-cx`#m{?Q~u1DL5X@U=YSv$F&qH6$#IGj(Xy!I=GK9kyR5fSG8M*23{NsIf5f{T zA^5qE+P^gCzrc88DGIE8xaHj6vKpk7#8DKrc{d4*u{qjCS>V2I3q}NHnyLkEF8BW~ ztP}XS8?oE9>~BHXGp0<6XoLj<&gUKn9WPH$PnbRXPQWq3aPJL46Dh&3gyYc*Kz;OFOK z;~|u|nWHHlxna5(-&98IY8r#Cf&Wi8+UwKZy}fI|;fklBbBD7{Ru```%l zK0b$);_HEdusw@QFJxIq5q9i<-nQXJ@544Y$MAh8Pj65~4CkJKt}Unr?7$-y;RPVR z^r}K}l^?TtDl3%eC2r2?(Y7gi7bmX9sJPp)`)=1v{N`PGsWnrlH<-WZb3-*6(uX)> z-{CQYi3OSMX)nMuHHOG-Bz&qzPcDX*buVYU8In+ar@*HH`SUVDpT0GPSEz|;Z7xn~ zo6c?|hb)C}OZe)@kjU;jxsFTCm%F=uUPqZ3pE%(VXa7G2>tcg*BQ3w|FPbCR6> z=u}!^m@8D_oaeDDZ!|M8&nxH=QqX1;T4I5c@kJ&+khR;selvyewdb1|wdb#j#OeGl zX5~u@FbDZez0mazaom)dA^nLe|F^NFIYK7n&P@mZuzjN+hK$pP;m)Wkfy1Hz~DO zWn4GT1X`AWzrX?O==d@Jrt}h5Y){t;Fk@_s?Bgz8fN8zVXJBa~_<@B5dh)e`rw(fa zI%~y!KoqmQ-o2;0o0X>q@x1%8$Cs1zb5Cvz)wmToB)#08@H0l@%oLF+g_Z9XqD(La ze89EnP-@F)9%))rH_+6~G-WKkCWAAkHcEO)@L)q8yOV#dIoI03!MN1ob?AQ3r|@N| zM4yfFqSMV?Da8OXe?%oKglK+v`<4~Bc;%fAO{NJVYPV2rztimJ=6{QV;ZgCD#EMi= z!<%Y17urXSe5m}~zCXsYtMf^Gq7n~iknuJMugS%6>gw*_;lXk^gVBK|mZ@`Rry=a` z)m^?hXxsVZms@5IRsvW2z;Z&AiZrq293E4@;tfSA9KGguot&YJxy0-N5<$RUbtR|- zWU0p9P-#l%t|<22N=P#{pmG9sUf)vyt@&aTvNxlCz#L564Y`(@IyPK86^X|JRPxXp zHOMtl4M0F=`Tez-0+L%nXRDT~7o1JHfh15Gb*HfAs@R<==QPg>_RozZQf{t>5BRcz zmT6lpil%|11S>f`_owtU&pW;tsRnN(UWiBZRDbT*YR1c;EX{QAryUu%T(dHwAS^7b zYdGbLG=hb7dTNfL{4>5_xp&O*^-Te%B{(q0izCBwCQpdYa8A`6IV&LX{i42^9O2Dw z&L8A1;fLngx1~&DJ;J{h9*O631Zs$cBgAGLY9S-~Zh4aR;49KoK8Bu(tGcFgnyydH-YNK|c1$S@^>q(^6yp z@Us9S@`-H&zvyZKv9F>XL@9I;sut0)pxEtogmvEdP`GRH~vD5FV0`o zby1i3_8V4dAnig2X(~x2BrHrxMWv&&6NK&wV>>`G?(3~K^p#>wU>;_(h=12S)&!o2 z?mU9SDtjJ1=D5{Ij2~N>@^at5Z(`!fb=cq|UGA@<;V}Ipelhu1(194UOPo-y&HbVm z!;_e=Lf5%KMfZ+I2LnLU9)leC17|ft6FpM@BF3z&1am!EWq4< z2zUm}v_Y(a&gFp{%*hfo*js7h4S9ko_Aef?I?bdo0ZP+H45-*S&}Zc2*=On5X$*O^ zy@Q;zh*?RNxIQG%2MX}rT2PzFf_T<^WD|-y5ahGEoABDbUQu#@a)U>qAZ$RMvBmI{2^QXUWqVsO# zgj+@AKME9^8R%b^m6gq$pa%RJ>ej)N;j>t(SwGRFQgqnIfZvwRoy_dV$;0trJvDzy zXc-s?S~u_M_Jvb8&Fn~{U%6{Tf9L*vLAWoFK*8y^%PkE&Lg)pJNqK7VwqLx5sM~nJ zyct~_{3!I&^|(i{57Am>fZsDW@j=|f(*-uSJTk-Gg+A?3Ho8-_p_>l5B=$|1ZszY6 zOnR+epximgNo}Emki{&xn#6^s1Q8xN!MhPtb1A_7y+YhGIJmX9P?R@x^UJIwc^5%) zhr{okjJh>I&j+f6S#TvPf6zdL0#(aj@I(ULuxQ;=-oQ=~VPR;PeY@wb2{f^2*RzYn z`sId5T&Ak9Y=*og&SP+FEPvMOx1wfn=t!kwhKr!?-}6vWX52;NMd_?6=H-`3^Wp@V z-#`#UpWke5U|D$9yxwAIJcph1*v`;c-9}JMr%kMWQ233mP^(zDB9Tv(*G?N&`@)Wo zEUZ*yl@2F8bkl;iLZ#!-0dJvEaU#LC!61CV^IWKF0|AKkCs#z@2Tpi(?5h=#H)nOvWu~W_fDSOh`UtVjtufbYGu6=*O&AYA%C<9(zsvma z;ae*&Cl^-}Rz?_nd~^atOk*XqwFu>=sqRFwln=LPI87f>r6KoAsObe~9}XE!xQN9M zeLN!0GdrAD5=a(e>rHs=>aw+||91rs7x0Dqj&@8%Ay1@nn^so1HtOU8u-Nls$;enL zDN^i-6my|{;_Ec+a8zycOpFnQkJ?7Rki70U_pYb7v-)ZLkuWu32gk9uP(=aC0X9jT z3>so$qN4R+8dcpL4P?860~jHj1-pd0X5ZHxNn!%0U;73$qri2ctg$wGaNd~)T4({> z?ttiu(om$gqi}g0R5Q-M*1Si@YR~JdkbG{hFo7|+aeCwAx-)=9r>B1sR31EW`d9g= z{dyg%3_pK|G;{mM_;*v_3N5+DT{tGL18ye&XkBeBEgzQ*U-GRtfNsTG@XuBUE2_eb zPO3ZrbhXrLVTMLB)K18KNr9atoW@e_jCoipjOxH+109W~uY-C7h^;}++2>xe{n1+5 z6}JU>o-6!pc*niW8)As(*#KAl&PFxn_9z+-zuATF499NCO*6S$_`OtG5pC&?MB%+j1Y=44kL5Zj~T zgk?1dPo0l){&-maz?26u{{MHiXCm6E^KwZ1)RQNZTw_0C9dNL>sRUju-8V zske$i8cqtHr0P=FKMzZJgm`#nLCOf?eBa~nEGV^+Bp!+@N|@P>BsW5#Ew9$zcMtcF z?}yv8(141SSx2w{oQ<%G7xnGHvvK1`AjQ0VOiRrIwkE8?cDQ#Lh0)R0ekfLhoy`>n zd-6T`#l1aGRBe&&47Q3!%5+8CaVz77Yd0vRLR|+eXdM`>j@!zEE$#03B?W^YrU-6w zA&;u6D*v(_6xf_s-Fr!~A-L|a3&m0k{F9}+P#JRQjA=>3N@C_}Q4S0RJX1p+Kkl!X z`RMKpay+BI&P=F zCV!Zz#oDZc#bqk{iLmYScJ;PQuJ?`yAKsd>u@Ladw-xw#nDUD~(mN_U&~Yo7g6WW5 zh=Z8D`8;S!z^$_dFGHiXehsx0!`DErOUU2|w*+RBuduq6LFI1l?7q5{P4p#wWdgNK@(sv>#3v=?nTFN2)~er{7vQPi zGL!7tRDZUiKv!_1^pwxGjAcig2!23>M>WUrJ0iCPa=pGe1#> zs~1w7MJG^8Zb7e%t5x|=oBuhi828DjS95mgQ_s70^r>nlL}xyRwmI)a!|U5!`xeHov|Z&QlQk?UdyC4!n|y6bn2DnsIcWlqQRKZl6~7k^7i6fIyC5DtHC6I>;{;j zGv^%)VDx>`Yr&2?i=-SCD0OnntsRV_s6MnX-Pm}tH59&Eq2hRmud z<2?(S#2VNZPUFJ(!9^QqudIoHOlN~ZL!)3Sz~g7_gT?S+hbu(`O5}S&ND!@P9XpY8 z1kt3l&YZ?vQAtTD?X~eIa8m|;jN!_IKRNRm?n&i&b{QSYLaqDryw69OH)IzO8;0J5 z=yu!4GbhfdUr~U(br_X3(e%TSdJ7rQv0c9{!|WotT)XbvuLE5WXejsYWyLW(GZ$Cr z`H+Gct5Tz{g`Nh-!Y)9n1%B2Y>Fu_wi(7JY*o5p7T?}NrA6&+Wx&%>Y;D< zc$)7H*9hibWBY)9d(DmKwHdp7w^P@{>12++lrM|}A4%-BsPRQUDThE0 zN7q~}PI|XwKxdfHDMXOcZO!DvSjR|lb0T(RDxq4s@^D5B(?jL?d#WXxLVD#h%rpS( z4T0K2^{rL3MvbvG^K&2xd4jqwP}PGkd142B7Tan$I_`TdyqezKQJOxPVV)hJ4M># z+W(kn>0DHc(M4}TpC4!=Cx5>$C@hqndt5W~5KFCx$m0hU*4EaCweF5D>*_>AL?F;Q zMYP#GCWr0#(9*}p=T+C#)ug8|5!@P-#NXH3%dR2`)|#XDCH!{VhI`cC<(8B9gFSK2|k60FMHPzL?svHy7?NK%9G|C)S2pT28FgAKtY8KK5P4 zshHce8Bbw1su4lc&`3~R#cWz=mWnRd6@pYp2J?#)0{ zYs<|S4z&H=NgRx~LUruCUpPMSMXwWVk<9ZRkJ3AgaXaui6vTNdj+=*kQkz^&?R!Uv zOQ!dfyIOks`!h3^aNecCKVy$QxBp8GW4J`1hIMP(Y|Ku5CVHf*O6hG%N8~>#pU_Ci z6#RQmevWwa#x+mJz#!RZY87bo1$w1-=!uAYp8d1hKC3DeH$_C$ z_!i{VxVgD=#bJUEs&}9V85^g;h&~yxrQ|`d49ff;jiBh}pUTC}{lWE2>Q7yezC!%F z2qN`6@RsgU(2`l;^#|HFC@R9d5y+#?-~RWG8!-gd*+za{9TZ#M-@d2ix37+%i*S!q~OrLmU3e*>+^ASD0* literal 0 HcmV?d00001 diff --git a/src/Serilog.Expressions/Templates/ExpressionTemplate.cs b/src/Serilog.Expressions/Templates/ExpressionTemplate.cs index 28afab9..0aa42e5 100644 --- a/src/Serilog.Expressions/Templates/ExpressionTemplate.cs +++ b/src/Serilog.Expressions/Templates/ExpressionTemplate.cs @@ -46,7 +46,7 @@ public static bool TryParse( [MaybeNullWhen(true)] out string error) { if (template == null) throw new ArgumentNullException(nameof(template)); - return TryParse(template, null, null, null, out result, out error); + return TryParse(template, null, null, null, false, out result, out error); } /// @@ -60,12 +60,15 @@ public static bool TryParse( /// A description of the error, if unsuccessful. /// Optionally, a /// with which to resolve function names that appear in the template. + /// Apply even when + /// or returns true. /// true if the template was well-formed. public static bool TryParse( string template, IFormatProvider? formatProvider, NameResolver? nameResolver, TemplateTheme? theme, + bool applyThemeWhenOutputIsRedirected, [MaybeNullWhen(false)] out ExpressionTemplate result, [MaybeNullWhen(true)] out string error) { @@ -84,7 +87,7 @@ public static bool TryParse( TemplateCompiler.Compile( planned, formatProvider, DefaultFunctionNameResolver.Build(nameResolver), - theme ?? TemplateTheme.None)); + SelectTheme(theme, applyThemeWhenOutputIsRedirected))); return true; } @@ -103,11 +106,14 @@ public static bool TryParse( /// Optionally, a /// with which to resolve function names that appear in the template. /// Optionally, an ANSI theme to apply to the template output. + /// Apply even when + /// or returns true. public ExpressionTemplate( string template, IFormatProvider? formatProvider = null, NameResolver? nameResolver = null, - TemplateTheme? theme = null) + TemplateTheme? theme = null, + bool applyThemeWhenOutputIsRedirected = false) { if (template == null) throw new ArgumentNullException(nameof(template)); @@ -119,7 +125,20 @@ public ExpressionTemplate( _compiled = TemplateCompiler.Compile( planned, - formatProvider, DefaultFunctionNameResolver.Build(nameResolver), theme ?? TemplateTheme.None); + formatProvider, + DefaultFunctionNameResolver.Build(nameResolver), + SelectTheme(theme, applyThemeWhenOutputIsRedirected)); + } + + static TemplateTheme SelectTheme(TemplateTheme? supplied, bool applyThemeWhenOutputIsRedirected) + { + if (supplied == null || + (Console.IsOutputRedirected || Console.IsErrorRedirected) && !applyThemeWhenOutputIsRedirected) + { + return TemplateTheme.None; + } + + return supplied; } /// diff --git a/test/Serilog.Expressions.Tests/TemplateParserTests.cs b/test/Serilog.Expressions.Tests/TemplateParserTests.cs index ee62d39..b17b314 100644 --- a/test/Serilog.Expressions.Tests/TemplateParserTests.cs +++ b/test/Serilog.Expressions.Tests/TemplateParserTests.cs @@ -19,7 +19,7 @@ public class TemplateParserTests [InlineData("Empty {Align,} digits", "Syntax error (line 1, column 14): unexpected `}`, expected alignment and width.")] public void ErrorsAreReported(string input, string error) { - Assert.False(ExpressionTemplate.TryParse(input, null, null, null, out _, out var actual)); + Assert.False(ExpressionTemplate.TryParse(input, null, null, null, false, out _, out var actual)); Assert.Equal(error, actual); }