diff --git a/README.md b/README.md index 7c8bc08..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`, `Dictionary')})\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 da16a45..b1832e8 100644 --- a/serilog-expressions.sln.DotSettings +++ b/serilog-expressions.sln.DotSettings @@ -1,11 +1,13 @@  True True + True True True True 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 f12580b..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; @@ -28,7 +42,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/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 0b82ae4..da04606 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/Combinators.cs @@ -1,6 +1,20 @@ -using System; -using Superpower; -using Superpower.Model; +// 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; namespace Serilog.Expressions.Parsing { 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 261ede9..98d8b7d 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs @@ -1,6 +1,20 @@ -using Superpower; -using Superpower.Model; -using Superpower.Parsers; +// 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; namespace Serilog.Expressions.Parsing { @@ -31,6 +45,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..93a7c34 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionToken.cs @@ -1,4 +1,18 @@ -using Superpower.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 a60b2f8..049d0bc 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenParsers.cs @@ -1,11 +1,25 @@ -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; using Serilog.Expressions.Ast; -using Superpower; -using Superpower.Model; -using Superpower.Parsers; +using Serilog.ParserConstruction; +using Serilog.ParserConstruction.Model; +using Serilog.ParserConstruction.Parsers; namespace Serilog.Expressions.Parsing { @@ -65,23 +79,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 +106,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 +118,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 +160,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 +186,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..f78db88 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs @@ -1,7 +1,21 @@ -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 Superpower; -using Superpower.Model; +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 442570f..1f9f2c1 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ParserExtensions.cs @@ -1,6 +1,20 @@ -using System; -using Superpower; -using Superpower.Model; +// 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; namespace Serilog.Expressions.Parsing { diff --git a/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs b/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs index f3d0778..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; @@ -62,7 +76,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/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 a926379..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,20 +482,19 @@ 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 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. - 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 88cb5e8..0806ac9 100644 --- a/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs +++ b/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs @@ -1,5 +1,20 @@ -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; using System.Reflection; @@ -27,9 +42,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/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/ParserConstruction/Combinators.cs b/src/Serilog.Expressions/ParserConstruction/Combinators.cs new file mode 100644 index 0000000..fc0692a --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; +using Serilog.ParserConstruction.Util; + +// ReSharper disable MemberCanBePrivate.Global + +namespace Serilog.ParserConstruction +{ + /// + /// 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/ParserConstruction/Display/Presentation.cs b/src/Serilog.Expressions/ParserConstruction/Display/Presentation.cs new file mode 100644 index 0000000..faac226 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Util; + +namespace Serilog.ParserConstruction.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/ParserConstruction/Display/TokenAttribute.cs b/src/Serilog.Expressions/ParserConstruction/Display/TokenAttribute.cs new file mode 100644 index 0000000..89dabdc --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.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/ParserConstruction/Model/Position.cs b/src/Serilog.Expressions/ParserConstruction/Model/Position.cs new file mode 100644 index 0000000..dcace0d --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.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/ParserConstruction/Model/Result.cs b/src/Serilog.Expressions/ParserConstruction/Model/Result.cs new file mode 100644 index 0000000..1883262 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Util; + +namespace Serilog.ParserConstruction.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/ParserConstruction/Model/Result`1.cs b/src/Serilog.Expressions/ParserConstruction/Model/Result`1.cs new file mode 100644 index 0000000..8e9fbde --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Util; + +namespace Serilog.ParserConstruction.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/ParserConstruction/Model/TextSpan.cs b/src/Serilog.Expressions/ParserConstruction/Model/TextSpan.cs new file mode 100644 index 0000000..59f87c1 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.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/ParserConstruction/Model/TokenListParserResult.cs b/src/Serilog.Expressions/ParserConstruction/Model/TokenListParserResult.cs new file mode 100644 index 0000000..fd82086 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.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/ParserConstruction/Model/TokenListParserResult`2.cs b/src/Serilog.Expressions/ParserConstruction/Model/TokenListParserResult`2.cs new file mode 100644 index 0000000..ac04621 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Display; +using Serilog.ParserConstruction.Util; + +namespace Serilog.ParserConstruction.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/ParserConstruction/Model/TokenList`1.cs b/src/Serilog.Expressions/ParserConstruction/Model/TokenList`1.cs new file mode 100644 index 0000000..83787cc --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.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/ParserConstruction/Model/Token`1.cs b/src/Serilog.Expressions/ParserConstruction/Model/Token`1.cs new file mode 100644 index 0000000..91cbe2e --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.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/ParserConstruction/Model/Unit.cs b/src/Serilog.Expressions/ParserConstruction/Model/Unit.cs new file mode 100644 index 0000000..e5212c7 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.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/ParserConstruction/Parse.cs b/src/Serilog.Expressions/ParserConstruction/Parse.cs new file mode 100644 index 0000000..be1d458 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; +using Serilog.ParserConstruction.Util; + +namespace Serilog.ParserConstruction +{ + /// + /// 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/ParserConstruction/ParseException.cs b/src/Serilog.Expressions/ParserConstruction/ParseException.cs new file mode 100644 index 0000000..e20334a --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Model; + +// ReSharper disable IntroduceOptionalParameters.Global, MemberCanBePrivate.Global, UnusedAutoPropertyAccessor.Global + +namespace Serilog.ParserConstruction +{ + /// + /// 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/ParserConstruction/ParserExtensions.cs b/src/Serilog.Expressions/ParserConstruction/ParserExtensions.cs new file mode 100644 index 0000000..3c67dc2 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Model; + +namespace Serilog.ParserConstruction +{ + /// + /// 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/ParserConstruction/Parsers/Character.cs b/src/Serilog.Expressions/ParserConstruction/Parsers/Character.cs new file mode 100644 index 0000000..e7211e9 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; + +namespace Serilog.ParserConstruction.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/ParserConstruction/Parsers/Numerics.cs b/src/Serilog.Expressions/ParserConstruction/Parsers/Numerics.cs new file mode 100644 index 0000000..7494247 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Model; +using Serilog.ParserConstruction.Util; + +namespace Serilog.ParserConstruction.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/ParserConstruction/Parsers/Span.cs b/src/Serilog.Expressions/ParserConstruction/Parsers/Span.cs new file mode 100644 index 0000000..f6cfdc6 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; + +namespace Serilog.ParserConstruction.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/ParserConstruction/Parsers/Token.cs b/src/Serilog.Expressions/ParserConstruction/Parsers/Token.cs new file mode 100644 index 0000000..faed524 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; + +namespace Serilog.ParserConstruction.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/ParserConstruction/README.md b/src/Serilog.Expressions/ParserConstruction/README.md new file mode 100644 index 0000000..8894ca4 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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/ParserConstruction/TextParser`1.cs b/src/Serilog.Expressions/ParserConstruction/TextParser`1.cs new file mode 100644 index 0000000..033fdb3 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Model; + +namespace Serilog.ParserConstruction +{ + /// + /// 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/ParserConstruction/TokenListParser`2.cs b/src/Serilog.Expressions/ParserConstruction/TokenListParser`2.cs new file mode 100644 index 0000000..1ddb61c --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Model; + +namespace Serilog.ParserConstruction +{ + /// + /// 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/ParserConstruction/Tokenizer`1.cs b/src/Serilog.Expressions/ParserConstruction/Tokenizer`1.cs new file mode 100644 index 0000000..1454676 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.Display; +using Serilog.ParserConstruction.Model; + +namespace Serilog.ParserConstruction +{ + /// + /// 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/ParserConstruction/Util/ArrayEnumerable.cs b/src/Serilog.Expressions/ParserConstruction/Util/ArrayEnumerable.cs new file mode 100644 index 0000000..6857806 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.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/ParserConstruction/Util/CharInfo.cs b/src/Serilog.Expressions/ParserConstruction/Util/CharInfo.cs new file mode 100644 index 0000000..cad5df2 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.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/ParserConstruction/Util/Friendly.cs b/src/Serilog.Expressions/ParserConstruction/Util/Friendly.cs new file mode 100644 index 0000000..e0ff877 --- /dev/null +++ b/src/Serilog.Expressions/ParserConstruction/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.ParserConstruction.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/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/Serilog.Expressions.csproj b/src/Serilog.Expressions/Serilog.Expressions.csproj index 15584a8..80b43fc 100644 --- a/src/Serilog.Expressions/Serilog.Expressions.csproj +++ b/src/Serilog.Expressions/Serilog.Expressions.csproj @@ -3,9 +3,9 @@ An embeddable mini-language for filtering, enriching, and formatting Serilog events, ideal for use with JSON or XML configuration. - 2.0.0 + 3.0.0 Serilog Contributors - netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1;net5.0 true true Serilog @@ -18,13 +18,12 @@ Apache-2.0 https://github.com/serilog/serilog-expressions git - 8 + latest enable - 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..0aa42e5 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, false, out result, out error); } /// @@ -41,15 +55,20 @@ 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 /// 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) { @@ -64,14 +83,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), + SelectTheme(theme, applyThemeWhenOutputIsRedirected))); + return true; } - ExpressionTemplate(CompiledTemplate compiled, IFormatProvider? formatProvider) + ExpressionTemplate(CompiledTemplate compiled) { _compiled = compiled; - _formatProvider = formatProvider; } /// @@ -82,10 +105,15 @@ 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. + /// Apply even when + /// or returns true. public ExpressionTemplate( string template, IFormatProvider? formatProvider = null, - NameResolver? nameResolver = null) + NameResolver? nameResolver = null, + TemplateTheme? theme = null, + bool applyThemeWhenOutputIsRedirected = false) { if (template == null) throw new ArgumentNullException(nameof(template)); @@ -95,14 +123,28 @@ public ExpressionTemplate( var planned = TemplateLocalNameBinder.BindLocalValueNames(parsed); - _compiled = TemplateCompiler.Compile(planned, DefaultFunctionNameResolver.Build(nameResolver)); - _formatProvider = formatProvider; + _compiled = TemplateCompiler.Compile( + planned, + 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; } /// 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 b947fba..0fd2c58 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenParsers.cs @@ -1,10 +1,24 @@ -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; +using Serilog.ParserConstruction.Model; +using Serilog.ParserConstruction.Parsers; using Serilog.Templates.Ast; -using Superpower; -using Superpower.Model; -using Superpower.Parsers; using static Serilog.Expressions.Parsing.ExpressionToken; // ReSharper disable SuggestBaseTypeForParameter, ConvertIfStatementToSwitchStatement, AccessToModifiedClosure @@ -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); @@ -65,12 +79,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) @@ -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)) @@ -89,13 +103,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..283f262 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs @@ -1,7 +1,21 @@ -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 Superpower; -using Superpower.Model; +using Serilog.ParserConstruction; +using Serilog.ParserConstruction.Model; namespace Serilog.Templates.Parsing { 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..b17b314 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, false, 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