From ef630c70560dc4e7f9a76b73dd572fe6f62a0dd1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 16:46:59 +0000 Subject: [PATCH 01/11] Create draft PR for #78 [skip ci] From 1ba841e4e1edc3f1b1dd12a28a019a2524fc9021 Mon Sep 17 00:00:00 2001 From: Matt Edwards Date: Fri, 2 May 2025 12:50:00 -0400 Subject: [PATCH 02/11] Adds XS expression provider Adds a new expression provider leveraging Hyperbee.XS for template processing. This allows for more complex and flexible template expressions. Also includes tests and benchmarks to validate and measure performance. --- Hyperbee.Templating.sln | 6 + .../Compiler/XsTokenExpressionProvider.cs | 166 ++++++++++++++++++ .../Hyperbee.Templating.Provider.XS.csproj | 52 ++++++ .../Hyperbee.Templating.Benchmark.csproj | 1 + .../TemplateBenchmarks.cs | 29 +++ .../XsTokenExpressionProviderTests.cs | 142 +++++++++++++++ .../Hyperbee.Templating.Tests.csproj | 1 + .../Text/TemplateParser.ExpressionTests.cs | 119 ++++++++++++- .../TemplateParser.LinkedDictionaryTests.cs | 4 +- 9 files changed, 517 insertions(+), 3 deletions(-) create mode 100644 src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs create mode 100644 src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj create mode 100644 test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs diff --git a/Hyperbee.Templating.sln b/Hyperbee.Templating.sln index d20387d..df19061 100644 --- a/Hyperbee.Templating.sln +++ b/Hyperbee.Templating.sln @@ -37,6 +37,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbee.Templating.Benchma EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "docs", "docs\docs.shproj", "{8409DDE0-C540-4A94-BF7F-9403888BEDAC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hyperbee.Templating.Provider.XS", "src\Hyperbee.Templating.Provider.XS\Hyperbee.Templating.Provider.XS.csproj", "{EA264696-D88A-288E-0D8E-C834780D5F9E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,6 +57,10 @@ Global {EB7D2A85-3C82-444A-84CD-D245DCF951CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {EB7D2A85-3C82-444A-84CD-D245DCF951CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {EB7D2A85-3C82-444A-84CD-D245DCF951CE}.Release|Any CPU.Build.0 = Release|Any CPU + {EA264696-D88A-288E-0D8E-C834780D5F9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA264696-D88A-288E-0D8E-C834780D5F9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA264696-D88A-288E-0D8E-C834780D5F9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA264696-D88A-288E-0D8E-C834780D5F9E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs new file mode 100644 index 0000000..e09e524 --- /dev/null +++ b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs @@ -0,0 +1,166 @@ +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using FastExpressionCompiler; +using Hyperbee.Templating.Compiler; +using Hyperbee.Templating.Text; +using Hyperbee.XS; +using Hyperbee.XS.Core; +using Hyperbee.XS.Core.Parsers; +using Parlot.Fluent; +using static System.Linq.Expressions.Expression; +using static Parlot.Fluent.Parsers; + +namespace Hyperbee.Templating.Provider.XS.Compiler; + +public sealed class XsTokenExpressionProvider : ITokenExpressionProvider +{ + private readonly bool _fastCompile; + private ConcurrentDictionary TokenExpressions { get; } = new(); + + public XsTokenExpressionProvider( bool fastCompile = false ) + { + _fastCompile = fastCompile; + } + + [MethodImpl( MethodImplOptions.AggressiveInlining )] + public TokenExpression GetTokenExpression( string codeExpression, MemberDictionary members ) + { + return TokenExpressions.GetOrAdd( codeExpression, Compile( codeExpression, members, _fastCompile ) ); + } + + [MethodImpl( MethodImplOptions.AggressiveInlining )] + public void Reset() + { + TokenExpressions.Clear(); + } + + private static TokenExpression Compile( ReadOnlySpan codeExpression, MemberDictionary members, bool fastCompile = false ) + { + var xsParser = new XsParser( new XsConfig( TypeResolver.Create( Assembly.GetExecutingAssembly() ) ) + { + Extensions = [new MemberDictionaryParseExtension( members )] + } ); + + var start = codeExpression.IndexOf( "=>" ); + var argument = codeExpression[..start].Trim().ToString(); + var body = codeExpression[(start + 2)..].Trim().ToString(); + + var scope = new ParseScope(); + + try + { + scope.EnterScope( FrameType.Method ); + + var argumentParameter = Parameter( typeof(IReadOnlyMemberDictionary), argument ); + + scope.Variables.Add( argument, argumentParameter ); + + var expressionBody = xsParser.Parse( body, scope: scope ) as BlockExpression; + + if ( expressionBody == null ) + throw new InvalidOperationException( $"Failed to parse expression body: {body}" ); + + var lambdaParameter = Parameter( typeof(IReadOnlyMemberDictionary) ); + + var newExpressionBody = expressionBody.Expressions.Prepend( + Assign( argumentParameter, lambdaParameter ) + ); + + var lambda = Lambda( + Convert( Block( + expressionBody.Variables, + newExpressionBody + ), typeof(object) ), + lambdaParameter ); + + return fastCompile + ? lambda.CompileFast() + : lambda.Compile(); + } + finally + { + scope.ExitScope(); + } + } + + internal class MemberDictionaryParseExtension : IParseExtension + { + public ExtensionType Type => ExtensionType.Expression; + public string Key => "vars"; + + private readonly MethodInfo _getValueAsMethodInfo = typeof(MemberDictionary).GetMethod( nameof(MemberDictionary.GetValueAs), [typeof(string)] )!; + private readonly MethodInfo _invokeMethodInfo = typeof(MemberDictionary).GetMethod( nameof(MemberDictionary.Invoke), [typeof(string), typeof(object[])] )!; + private readonly MemberDictionary _member; + + public MemberDictionaryParseExtension( MemberDictionary member ) + { + _member = member; + } + + public Parser CreateParser( ExtensionBinder binder ) + { + var (expression, _) = binder; + // var v = vars::myValue; + // var v = vars::myValue; + // var v = vars::method( arg ); + + return ZeroOrOne( + Between( + Terms.Char( '<' ), + XsParsers.TypeRuntime(), + Terms.Char( '>' ) + ) + ) + .AndSkip( Terms.Text( "::" ) ) + .And( Terms.NamespaceIdentifier() ) + .And( + ZeroOrOne( + Between( + Terms.Char( '(' ), + ZeroOrOne( + Separated( + Terms.Char( ',' ), + expression + + ) + ), + Terms.Char( ')' ) + ) + ) ) + .Then( ( _, parts ) => + { + var (type, name, args) = parts; + + if ( name == null ) + throw new InvalidOperationException( "Name must be specified." ); + + if ( args == null ) + { + return Call( + Constant( _member ), + type != null + ? _getValueAsMethodInfo.MakeGenericMethod( type ) + : _getValueAsMethodInfo.MakeGenericMethod( typeof(object) ), + Constant( name.ToString() ) + ); + } + + var invokeExpression = Call( + Constant( _member ), + _invokeMethodInfo, + Constant( name.ToString() ), + NewArrayInit( typeof(object), args ) + ); + + return type != null + ? Convert( invokeExpression, type ) + : invokeExpression; // normally defaults to typeof(object) + + } + ) + .Named( "vars" ); + } + } +} diff --git a/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj b/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj new file mode 100644 index 0000000..590d050 --- /dev/null +++ b/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj @@ -0,0 +1,52 @@ + + + net9.0 + enable + true + Stillpoint Software, Inc. + README.md + templating;token;xs + icon.png + https://stillpoint-software.github.io/hyperbee.templating/ + net9.0 + LICENSE + Stillpoint Software, Inc. + Hyperbee Templating Provider XS + + Adds an ITokenExpressionProvider powder by Hyperbee.XS + + https://github.com/Stillpoint-Software/Hyperbee.Templating + git + https://github.com/Stillpoint-Software/hyperbee.templating/releases/latest + true + + + + + <_Parameter1>$(AssemblyName).Tests + + + <_Parameter1>$(AssemblyName).Benchmark + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + \ No newline at end of file diff --git a/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj b/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj index 61d711a..e44122d 100644 --- a/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj +++ b/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj @@ -14,6 +14,7 @@ + diff --git a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs index 024e578..0a202c7 100644 --- a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs +++ b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs @@ -1,4 +1,5 @@ using BenchmarkDotNet.Attributes; +using Hyperbee.Templating.Provider.XS.Compiler; using Hyperbee.Templating.Text; namespace Hyperbee.Templating.Benchmark; @@ -68,5 +69,33 @@ public void InlineBlockExpression() } } ); } + + [Benchmark] + public void InlineBlockExpressionXs() + { + const string expression = "{{name}}"; + const string definition = + """ + {{name:{{_ => { + return switch( vars::choice ) + { + case "1": "me"; + case "2": "you"; + default: "default"; + }; + } }} }} + """; + + const string template = $"{definition}hello {expression}."; + + Template.Render( template, new() + { + Variables = + { + ["choice"] = "2" + }, + TokenExpressionProvider = new XsTokenExpressionProvider(true) + } ); + } } diff --git a/test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs b/test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs new file mode 100644 index 0000000..fe46558 --- /dev/null +++ b/test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using Hyperbee.Templating.Provider.XS.Compiler; +using Hyperbee.Templating.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Hyperbee.Templating.Tests.Compiler; + +[TestClass] +public class XsTokenExpressionProviderTests +{ + [TestMethod] + public void Should_compile_expression() + { + // arrange + + const string expression = """" + _ => String.Concat( "all your ", + vars::Value, + " are belong to us." + ).ToUpper(); + """"; + + var compiler = new XsTokenExpressionProvider(); + + var tokens = new Dictionary + { + ["Value"] = "base" + }; + + var variables = new MemberDictionary( tokens ); + var tokenExpression = compiler.GetTokenExpression( expression, variables ); + + // act + + var result = tokenExpression( variables ); + + // assert + + Assert.AreEqual( "ALL YOUR BASE ARE BELONG TO US.", result ); + } + + [TestMethod] + public void Should_compile_cast_expression() + { + // arrange + + const string expression = """" + _ => String.Concat( "all your ", + (1 + vars::Value).ToString(), + " base are belong to us." + ).ToUpper(); + """"; + + var compiler = new XsTokenExpressionProvider(); + + var tokens = new Dictionary + { + ["Value"] = "1" + }; + + var variables = new MemberDictionary( tokens ); + var tokenExpression = compiler.GetTokenExpression( expression, variables ); + + // act + + var result = tokenExpression( variables ); + + // assert + + Assert.AreEqual( "ALL YOUR 2 BASE ARE BELONG TO US.", result ); + } + + [TestMethod] + public void Should_compile_statement_expression() + { + // arrange + + const string expression = """" + _ => String.Concat( "all your ", + vars::Value, + " are belong to us." + ).ToUpper(); + """"; + + var compiler = new XsTokenExpressionProvider(); + + var tokens = new Dictionary + { + ["Value"] = "base" + }; + + var variables = new MemberDictionary( tokens ); + var tokenExpression = compiler.GetTokenExpression( expression, variables ); + + // act + + var result = tokenExpression( variables ); + + // assert + + Assert.AreEqual( "ALL YOUR BASE ARE BELONG TO US.", result ); + } + + [TestMethod] + public void Should_compile_multiple_expressions() + { + // arrange + + const string expression1 = """" + _ => String.Concat( "all your ", + vars::Value, + " are belong to us." + ).ToUpper(); + """"; + const string expression2 = """" + _ => String.Concat( "all your ", + vars::Value, + " are not belong to us." + ).ToUpper(); + """"; + + var compiler = new XsTokenExpressionProvider(); + + var tokens = new Dictionary { ["Value"] = "base" }; + + var variables = new MemberDictionary( tokens ); + + var tokenExpression1 = compiler.GetTokenExpression( expression1, variables ); + var tokenExpression2 = compiler.GetTokenExpression( expression2, variables ); + + // act + + var result1 = tokenExpression1( variables ); + var result2 = tokenExpression2( variables ); + + // assert + + Assert.AreEqual( "ALL YOUR BASE ARE BELONG TO US.", result1 ); + Assert.AreEqual( "ALL YOUR BASE ARE NOT BELONG TO US.", result2 ); + } +} diff --git a/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj b/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj index f70cabc..ef8892e 100644 --- a/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj +++ b/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj @@ -23,6 +23,7 @@ + diff --git a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs index 64c0c34..1654780 100644 --- a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs +++ b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs @@ -1,4 +1,7 @@ -using Hyperbee.Templating.Text; +using System; +using Hyperbee.Templating.Configure; +using Hyperbee.Templating.Provider.XS.Compiler; +using Hyperbee.Templating.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Hyperbee.Templating.Tests.Text; @@ -6,6 +9,26 @@ namespace Hyperbee.Templating.Tests.Text; [TestClass] public class TemplateParserExpressionTests { + [TestMethod] + public void Should_honor_while_xs_condition() + { + // arrange + const string expression = "{{while x => vars::counter < 3 }}{{counter}}{{counter:{{x => vars::counter + 1}}}}{{/while}}"; + const string template = $"count: {expression}."; + + // act + var options = new TemplateOptions() + .AddVariable( "counter", "0" ) + .SetTokenExpressionProvider( new XsTokenExpressionProvider() ); + + var result = Template.Render( template, options ); + + // assert + const string expected = "count: 012."; + + Assert.AreEqual( expected, result ); + } + [TestMethod] public void Should_honor_while_condition() { @@ -28,6 +51,41 @@ public void Should_honor_while_condition() Assert.AreEqual( expected, result ); } + [TestMethod] + public void Should_honor_block_xs_expression() + { + // arrange + const string expression = + """ + {{_ => { + switch( vars::choice ){ + case "1": vars::TheBest("me", "no"); + case "2": vars::TheBest("you", "yes"); + default: "error"; + } + } }} + """; + + const string template = $"hello {expression}."; + + // act + var options = new TemplateOptions() + .AddVariable( "choice", "2" ) + .AddMethod( "TheBest" ).Expression( ( arg0, arg1 ) => + { + var result = $"{arg0} {(arg1 == "yes" ? "ARE" : "are NOT")} the best"; + return result; + } ) + .SetTokenExpressionProvider( new XsTokenExpressionProvider() ); + + var result = Template.Render( template, options ); + + // assert + + var expected = template.Replace( expression, "you ARE the best" ); + + Assert.AreEqual( expected, result ); + } [TestMethod] public void Should_honor_block_expression() @@ -64,6 +122,28 @@ public void Should_honor_block_expression() Assert.AreEqual( expected, result ); } + [TestMethod] + public void Should_honor_xs_inline_define() + { + // arrange + const string expression = "{{choice:me}}{{choice}}"; + + const string template = $"hello {expression}."; + + // act + + var options = new TemplateOptions() + .SetTokenExpressionProvider( new XsTokenExpressionProvider() ); + + var result = Template.Render( template, options ); + + // assert + + var expected = template.Replace( expression, "me" ); + + Assert.AreEqual( expected, result ); + } + [TestMethod] public void Should_honor_inline_define() { @@ -83,6 +163,43 @@ public void Should_honor_inline_define() Assert.AreEqual( expected, result ); } + [TestMethod] + public void Should_honor_xs_inline_block_expression() + { + // arrange + const string expression = "{{name}}"; + const string definition = + """ + {{name:{{_ => { + return switch( vars::choice ) + { + case "1": "me"; + case "2": "you"; + default: "default"; + }; + } }} }} + """; + + const string template = $"{definition}hello {expression}."; + + // act + var result = Template.Render( template, new() + { + Variables = + { + ["choice"] = "2" + }, + TokenExpressionProvider = new XsTokenExpressionProvider() + } ); + + // assert + var expected = template + .Replace( definition, "" ) + .Replace( expression, "you" ); + + Assert.AreEqual( expected, result ); + } + [TestMethod] public void Should_honor_inline_block_expression() { diff --git a/test/Hyperbee.Templating.Tests/Text/TemplateParser.LinkedDictionaryTests.cs b/test/Hyperbee.Templating.Tests/Text/TemplateParser.LinkedDictionaryTests.cs index 4540c00..2e8c650 100644 --- a/test/Hyperbee.Templating.Tests/Text/TemplateParser.LinkedDictionaryTests.cs +++ b/test/Hyperbee.Templating.Tests/Text/TemplateParser.LinkedDictionaryTests.cs @@ -33,11 +33,11 @@ public void Should_resolve_conditional_nested_tokens_with_custom_source() ["upper"] = "True" } ); - var config = new TemplateOptions( source ); + var options = new TemplateOptions( source ); // act - var result = Template.Render( template, config ); + var result = Template.Render( template, options ); // assert From 7c6cb21fbe74040e964f366aec453650cfd1d71c Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 2 May 2025 16:50:43 +0000 Subject: [PATCH 03/11] Updated code formatting to match rules in .editorconfig --- .../Compiler/XsTokenExpressionProvider.cs | 20 +++++++++---------- .../TemplateBenchmarks.cs | 2 +- .../Text/TemplateParser.ExpressionTests.cs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs index e09e524..1b4254f 100644 --- a/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs +++ b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs @@ -38,9 +38,9 @@ public void Reset() private static TokenExpression Compile( ReadOnlySpan codeExpression, MemberDictionary members, bool fastCompile = false ) { - var xsParser = new XsParser( new XsConfig( TypeResolver.Create( Assembly.GetExecutingAssembly() ) ) - { - Extensions = [new MemberDictionaryParseExtension( members )] + var xsParser = new XsParser( new XsConfig( TypeResolver.Create( Assembly.GetExecutingAssembly() ) ) + { + Extensions = [new MemberDictionaryParseExtension( members )] } ); var start = codeExpression.IndexOf( "=>" ); @@ -53,7 +53,7 @@ private static TokenExpression Compile( ReadOnlySpan codeExpression, Membe { scope.EnterScope( FrameType.Method ); - var argumentParameter = Parameter( typeof(IReadOnlyMemberDictionary), argument ); + var argumentParameter = Parameter( typeof( IReadOnlyMemberDictionary ), argument ); scope.Variables.Add( argument, argumentParameter ); @@ -62,7 +62,7 @@ private static TokenExpression Compile( ReadOnlySpan codeExpression, Membe if ( expressionBody == null ) throw new InvalidOperationException( $"Failed to parse expression body: {body}" ); - var lambdaParameter = Parameter( typeof(IReadOnlyMemberDictionary) ); + var lambdaParameter = Parameter( typeof( IReadOnlyMemberDictionary ) ); var newExpressionBody = expressionBody.Expressions.Prepend( Assign( argumentParameter, lambdaParameter ) @@ -72,7 +72,7 @@ private static TokenExpression Compile( ReadOnlySpan codeExpression, Membe Convert( Block( expressionBody.Variables, newExpressionBody - ), typeof(object) ), + ), typeof( object ) ), lambdaParameter ); return fastCompile @@ -90,8 +90,8 @@ internal class MemberDictionaryParseExtension : IParseExtension public ExtensionType Type => ExtensionType.Expression; public string Key => "vars"; - private readonly MethodInfo _getValueAsMethodInfo = typeof(MemberDictionary).GetMethod( nameof(MemberDictionary.GetValueAs), [typeof(string)] )!; - private readonly MethodInfo _invokeMethodInfo = typeof(MemberDictionary).GetMethod( nameof(MemberDictionary.Invoke), [typeof(string), typeof(object[])] )!; + private readonly MethodInfo _getValueAsMethodInfo = typeof( MemberDictionary ).GetMethod( nameof( MemberDictionary.GetValueAs ), [typeof( string )] )!; + private readonly MethodInfo _invokeMethodInfo = typeof( MemberDictionary ).GetMethod( nameof( MemberDictionary.Invoke ), [typeof( string ), typeof( object[] )] )!; private readonly MemberDictionary _member; public MemberDictionaryParseExtension( MemberDictionary member ) @@ -142,7 +142,7 @@ public Parser CreateParser( ExtensionBinder binder ) Constant( _member ), type != null ? _getValueAsMethodInfo.MakeGenericMethod( type ) - : _getValueAsMethodInfo.MakeGenericMethod( typeof(object) ), + : _getValueAsMethodInfo.MakeGenericMethod( typeof( object ) ), Constant( name.ToString() ) ); } @@ -151,7 +151,7 @@ public Parser CreateParser( ExtensionBinder binder ) Constant( _member ), _invokeMethodInfo, Constant( name.ToString() ), - NewArrayInit( typeof(object), args ) + NewArrayInit( typeof( object ), args ) ); return type != null diff --git a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs index 0a202c7..67b2730 100644 --- a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs +++ b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs @@ -94,7 +94,7 @@ public void InlineBlockExpressionXs() { ["choice"] = "2" }, - TokenExpressionProvider = new XsTokenExpressionProvider(true) + TokenExpressionProvider = new XsTokenExpressionProvider( true ) } ); } } diff --git a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs index 1654780..223f955 100644 --- a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs +++ b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs @@ -131,7 +131,7 @@ public void Should_honor_xs_inline_define() const string template = $"hello {expression}."; // act - + var options = new TemplateOptions() .SetTokenExpressionProvider( new XsTokenExpressionProvider() ); From 92b6956582a9219e6875595bf7b2b0503d08a0cd Mon Sep 17 00:00:00 2001 From: Matt Edwards Date: Fri, 2 May 2025 16:13:39 -0400 Subject: [PATCH 04/11] Refactors XS expression compilation for flexibility Improves the XS expression compilation process by introducing a TypeResolver and allowing for more flexible expression definitions. The changes include: - Injecting a TypeResolver into the XsTokenExpressionProvider for better type management. - Modifying the MemberDictionaryParseExtension to accept a name, allowing the `vars` keyword to be configurable. - Updating the compilation logic to use the TypeResolver and configurable `vars` name when parsing expressions. This refactoring enhances the expressiveness and configurability of the XS templating engine. --- .../Compiler/XsTokenExpressionProvider.cs | 64 ++++++------------- .../TemplateBenchmarks.cs | 22 ++++--- .../XsTokenExpressionProviderTests.cs | 13 ++-- .../Text/TemplateParser.ExpressionTests.cs | 14 ++-- 4 files changed, 44 insertions(+), 69 deletions(-) diff --git a/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs index 1b4254f..c8baeea 100644 --- a/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs +++ b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; @@ -17,17 +17,19 @@ namespace Hyperbee.Templating.Provider.XS.Compiler; public sealed class XsTokenExpressionProvider : ITokenExpressionProvider { private readonly bool _fastCompile; + private readonly TypeResolver _typeResolver; private ConcurrentDictionary TokenExpressions { get; } = new(); - public XsTokenExpressionProvider( bool fastCompile = false ) + public XsTokenExpressionProvider( bool fastCompile = false, TypeResolver typeResolver = null) { _fastCompile = fastCompile; + _typeResolver = typeResolver ?? TypeResolver.Create( Assembly.GetExecutingAssembly() ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] public TokenExpression GetTokenExpression( string codeExpression, MemberDictionary members ) { - return TokenExpressions.GetOrAdd( codeExpression, Compile( codeExpression, members, _fastCompile ) ); + return TokenExpressions.GetOrAdd( codeExpression, Compile( codeExpression, members, _typeResolver, _fastCompile ) ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] @@ -36,66 +38,38 @@ public void Reset() TokenExpressions.Clear(); } - private static TokenExpression Compile( ReadOnlySpan codeExpression, MemberDictionary members, bool fastCompile = false ) + private static TokenExpression Compile( ReadOnlySpan codeExpression, MemberDictionary members, TypeResolver typeResolver, bool fastCompile = false ) { - var xsParser = new XsParser( new XsConfig( TypeResolver.Create( Assembly.GetExecutingAssembly() ) ) - { - Extensions = [new MemberDictionaryParseExtension( members )] - } ); - var start = codeExpression.IndexOf( "=>" ); var argument = codeExpression[..start].Trim().ToString(); var body = codeExpression[(start + 2)..].Trim().ToString(); - var scope = new ParseScope(); - - try - { - scope.EnterScope( FrameType.Method ); - - var argumentParameter = Parameter( typeof( IReadOnlyMemberDictionary ), argument ); - - scope.Variables.Add( argument, argumentParameter ); - - var expressionBody = xsParser.Parse( body, scope: scope ) as BlockExpression; - - if ( expressionBody == null ) - throw new InvalidOperationException( $"Failed to parse expression body: {body}" ); - - var lambdaParameter = Parameter( typeof( IReadOnlyMemberDictionary ) ); - - var newExpressionBody = expressionBody.Expressions.Prepend( - Assign( argumentParameter, lambdaParameter ) - ); + var xsParser = new XsParser( new XsConfig( typeResolver ) + { + Extensions = [new MemberDictionaryParseExtension( argument, members )] + } ); - var lambda = Lambda( - Convert( Block( - expressionBody.Variables, - newExpressionBody - ), typeof( object ) ), - lambdaParameter ); + var lambda = Lambda( + Convert( xsParser.Parse( body ), typeof( object ) ), + Parameter( typeof( IReadOnlyMemberDictionary ) ) ); - return fastCompile - ? lambda.CompileFast() - : lambda.Compile(); - } - finally - { - scope.ExitScope(); - } + return fastCompile + ? lambda.CompileFast() + : lambda.Compile(); } internal class MemberDictionaryParseExtension : IParseExtension { public ExtensionType Type => ExtensionType.Expression; - public string Key => "vars"; + public string Key { get; } private readonly MethodInfo _getValueAsMethodInfo = typeof( MemberDictionary ).GetMethod( nameof( MemberDictionary.GetValueAs ), [typeof( string )] )!; private readonly MethodInfo _invokeMethodInfo = typeof( MemberDictionary ).GetMethod( nameof( MemberDictionary.Invoke ), [typeof( string ), typeof( object[] )] )!; private readonly MemberDictionary _member; - public MemberDictionaryParseExtension( MemberDictionary member ) + public MemberDictionaryParseExtension( string name, MemberDictionary member ) { + Key = name; _member = member; } diff --git a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs index 67b2730..5864cb8 100644 --- a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs +++ b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs @@ -1,11 +1,16 @@ -using BenchmarkDotNet.Attributes; +using System.Reflection; +using BenchmarkDotNet.Attributes; using Hyperbee.Templating.Provider.XS.Compiler; using Hyperbee.Templating.Text; +using Hyperbee.XS.Core; namespace Hyperbee.Templating.Benchmark; public class TemplateBenchmarks { + private static readonly TypeResolver TypeResolver = TypeResolver.Create( Assembly.GetExecutingAssembly() ); + + [Benchmark( Baseline = true )] public void ParserSingleLine() { @@ -76,8 +81,8 @@ public void InlineBlockExpressionXs() const string expression = "{{name}}"; const string definition = """ - {{name:{{_ => { - return switch( vars::choice ) + {{name:{{vars => { + switch( vars::choice ) { case "1": "me"; case "2": "you"; @@ -88,13 +93,10 @@ public void InlineBlockExpressionXs() const string template = $"{definition}hello {expression}."; - Template.Render( template, new() - { - Variables = - { - ["choice"] = "2" - }, - TokenExpressionProvider = new XsTokenExpressionProvider( true ) + Template.Render( template, new() + { + Variables = { ["choice"] = "2" }, + TokenExpressionProvider = new XsTokenExpressionProvider( true, TypeResolver ) } ); } } diff --git a/test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs b/test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs index fe46558..e2d20fd 100644 --- a/test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs +++ b/test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Hyperbee.Templating.Provider.XS.Compiler; using Hyperbee.Templating.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -15,7 +14,7 @@ public void Should_compile_expression() // arrange const string expression = """" - _ => String.Concat( "all your ", + vars => String.Concat( "all your ", vars::Value, " are belong to us." ).ToUpper(); @@ -46,7 +45,7 @@ public void Should_compile_cast_expression() // arrange const string expression = """" - _ => String.Concat( "all your ", + vars => String.Concat( "all your ", (1 + vars::Value).ToString(), " base are belong to us." ).ToUpper(); @@ -77,7 +76,7 @@ public void Should_compile_statement_expression() // arrange const string expression = """" - _ => String.Concat( "all your ", + vars => String.Concat( "all your ", vars::Value, " are belong to us." ).ToUpper(); @@ -108,13 +107,13 @@ public void Should_compile_multiple_expressions() // arrange const string expression1 = """" - _ => String.Concat( "all your ", + vars => String.Concat( "all your ", vars::Value, " are belong to us." ).ToUpper(); """"; const string expression2 = """" - _ => String.Concat( "all your ", + vars => String.Concat( "all your ", vars::Value, " are not belong to us." ).ToUpper(); diff --git a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs index 223f955..d185e84 100644 --- a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs +++ b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs @@ -13,7 +13,7 @@ public class TemplateParserExpressionTests public void Should_honor_while_xs_condition() { // arrange - const string expression = "{{while x => vars::counter < 3 }}{{counter}}{{counter:{{x => vars::counter + 1}}}}{{/while}}"; + const string expression = "{{while vars => vars::counter < 3 }}{{counter}}{{counter:{{ vars => vars::counter + 1}}}}{{/while}}"; const string template = $"count: {expression}."; // act @@ -57,10 +57,10 @@ public void Should_honor_block_xs_expression() // arrange const string expression = """ - {{_ => { - switch( vars::choice ){ - case "1": vars::TheBest("me", "no"); - case "2": vars::TheBest("you", "yes"); + {{ x => { + switch( x::choice ){ + case "1": x::TheBest("me", "no"); + case "2": x::TheBest("you", "yes"); default: "error"; } } }} @@ -170,8 +170,8 @@ public void Should_honor_xs_inline_block_expression() const string expression = "{{name}}"; const string definition = """ - {{name:{{_ => { - return switch( vars::choice ) + {{name:{{ input => { + switch( input::choice ) { case "1": "me"; case "2": "you"; From 0c42800c0ff0b4d93146726c1d6217927b020cea Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 2 May 2025 20:14:13 +0000 Subject: [PATCH 05/11] Updated code formatting to match rules in .editorconfig --- .../Compiler/XsTokenExpressionProvider.cs | 10 +++++----- .../TemplateBenchmarks.cs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs index c8baeea..2ea81d4 100644 --- a/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs +++ b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; @@ -20,7 +20,7 @@ public sealed class XsTokenExpressionProvider : ITokenExpressionProvider private readonly TypeResolver _typeResolver; private ConcurrentDictionary TokenExpressions { get; } = new(); - public XsTokenExpressionProvider( bool fastCompile = false, TypeResolver typeResolver = null) + public XsTokenExpressionProvider( bool fastCompile = false, TypeResolver typeResolver = null ) { _fastCompile = fastCompile; _typeResolver = typeResolver ?? TypeResolver.Create( Assembly.GetExecutingAssembly() ); @@ -44,9 +44,9 @@ private static TokenExpression Compile( ReadOnlySpan codeExpression, Membe var argument = codeExpression[..start].Trim().ToString(); var body = codeExpression[(start + 2)..].Trim().ToString(); - var xsParser = new XsParser( new XsConfig( typeResolver ) - { - Extensions = [new MemberDictionaryParseExtension( argument, members )] + var xsParser = new XsParser( new XsConfig( typeResolver ) + { + Extensions = [new MemberDictionaryParseExtension( argument, members )] } ); var lambda = Lambda( diff --git a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs index 5864cb8..6d615da 100644 --- a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs +++ b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using BenchmarkDotNet.Attributes; using Hyperbee.Templating.Provider.XS.Compiler; using Hyperbee.Templating.Text; @@ -93,10 +93,10 @@ public void InlineBlockExpressionXs() const string template = $"{definition}hello {expression}."; - Template.Render( template, new() - { - Variables = { ["choice"] = "2" }, - TokenExpressionProvider = new XsTokenExpressionProvider( true, TypeResolver ) + Template.Render( template, new() + { + Variables = { ["choice"] = "2" }, + TokenExpressionProvider = new XsTokenExpressionProvider( true, TypeResolver ) } ); } } From db3eeeaba9552ce5304f3063604b11dd539be754 Mon Sep 17 00:00:00 2001 From: Matt Edwards Date: Tue, 6 May 2025 16:33:35 -0400 Subject: [PATCH 06/11] Refactors XS token expression compilation Updates the XS token expression compilation process for improved flexibility and extensibility. - Introduces a `CompileLambda` delegate for customizing lambda compilation. - Implements a `MemberTypeResolver` to handle member access within expressions, supporting properties, generic methods and indexers on member dictionaries. - Uses `XsParser` directly for parsing XS expressions. - Removes dependency on FastExpressionCompiler. - Updates Hyperbee.XS package version. --- .../Compiler/XsTokenExpressionProvider.cs | 190 +++++++++--------- .../Hyperbee.Templating.Provider.XS.csproj | 4 +- .../Hyperbee.Templating.Benchmark.csproj | 1 + .../TemplateBenchmarks.cs | 16 +- .../XsTokenExpressionProviderTests.cs | 10 +- .../Hyperbee.Templating.Tests.csproj | 3 + .../TestSupport/ServiceProvider.cs | 49 +++++ .../Text/TemplateParser.ExpressionTests.cs | 51 ++++- 8 files changed, 207 insertions(+), 117 deletions(-) create mode 100644 test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs diff --git a/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs index 2ea81d4..1072844 100644 --- a/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs +++ b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs @@ -2,34 +2,39 @@ using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; -using FastExpressionCompiler; using Hyperbee.Templating.Compiler; using Hyperbee.Templating.Text; using Hyperbee.XS; using Hyperbee.XS.Core; -using Hyperbee.XS.Core.Parsers; -using Parlot.Fluent; using static System.Linq.Expressions.Expression; -using static Parlot.Fluent.Parsers; namespace Hyperbee.Templating.Provider.XS.Compiler; +public delegate TokenExpression CompileLambda( Expression lambda ); + public sealed class XsTokenExpressionProvider : ITokenExpressionProvider { - private readonly bool _fastCompile; - private readonly TypeResolver _typeResolver; - private ConcurrentDictionary TokenExpressions { get; } = new(); - - public XsTokenExpressionProvider( bool fastCompile = false, TypeResolver typeResolver = null ) + private readonly ConcurrentDictionary TokenExpressions = new(); + private readonly CompileLambda _compile; + private readonly XsParser _xsParser; + + public XsTokenExpressionProvider( + CompileLambda compile = null, + TypeResolver typeResolver = null, + List extensions = null ) { - _fastCompile = fastCompile; - _typeResolver = typeResolver ?? TypeResolver.Create( Assembly.GetExecutingAssembly() ); + _compile = compile ?? (lambda => lambda.Compile()); + typeResolver ??= new MemberTypeResolver( ReferenceManager.Create() ); + + _xsParser = new XsParser( + new XsConfig( typeResolver ) { Extensions = extensions ?? [] } + ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] public TokenExpression GetTokenExpression( string codeExpression, MemberDictionary members ) { - return TokenExpressions.GetOrAdd( codeExpression, Compile( codeExpression, members, _typeResolver, _fastCompile ) ); + return TokenExpressions.GetOrAdd( codeExpression, _ => Compile( codeExpression ) ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] @@ -38,103 +43,100 @@ public void Reset() TokenExpressions.Clear(); } - private static TokenExpression Compile( ReadOnlySpan codeExpression, MemberDictionary members, TypeResolver typeResolver, bool fastCompile = false ) + private TokenExpression Compile( ReadOnlySpan codeExpression ) { var start = codeExpression.IndexOf( "=>" ); var argument = codeExpression[..start].Trim().ToString(); var body = codeExpression[(start + 2)..].Trim().ToString(); - var xsParser = new XsParser( new XsConfig( typeResolver ) + var scope = new ParseScope(); + + try { - Extensions = [new MemberDictionaryParseExtension( argument, members )] - } ); + scope.EnterScope( FrameType.Method ); - var lambda = Lambda( - Convert( xsParser.Parse( body ), typeof( object ) ), - Parameter( typeof( IReadOnlyMemberDictionary ) ) ); + var codeParameter = Parameter( typeof(IReadOnlyMemberDictionary), argument ); - return fastCompile - ? lambda.CompileFast() - : lambda.Compile(); - } + scope.Variables.Add( argument, codeParameter ); - internal class MemberDictionaryParseExtension : IParseExtension - { - public ExtensionType Type => ExtensionType.Expression; - public string Key { get; } + var expression = _xsParser.Parse( body, scope: scope ); + var expressionBody = expression as BlockExpression; + + var lambdaParameter = Parameter( typeof(IReadOnlyMemberDictionary) ); + + // create a new block expression assigning the parameter to the argument + var expressions = new List { Assign( codeParameter, lambdaParameter ) }; + if ( expressionBody == null ) + expressions.Add( expression ); + else + expressions.AddRange( expressionBody.Expressions ); - private readonly MethodInfo _getValueAsMethodInfo = typeof( MemberDictionary ).GetMethod( nameof( MemberDictionary.GetValueAs ), [typeof( string )] )!; - private readonly MethodInfo _invokeMethodInfo = typeof( MemberDictionary ).GetMethod( nameof( MemberDictionary.Invoke ), [typeof( string ), typeof( object[] )] )!; - private readonly MemberDictionary _member; + var lambda = Lambda( + Convert( + Block( + expressionBody?.Variables, + expressions + ), + typeof(object) + ), + lambdaParameter ); - public MemberDictionaryParseExtension( string name, MemberDictionary member ) + return _compile( lambda ); + } + finally { - Key = name; - _member = member; + scope.ExitScope(); } + } + + public class MemberTypeResolver : TypeResolver + { + private static readonly MethodInfo MemberInvoke = typeof( IReadOnlyMemberDictionary ) + .GetMethod( nameof( IReadOnlyMemberDictionary.Invoke ), [typeof( string ), typeof( object[] )] )!; - public Parser CreateParser( ExtensionBinder binder ) + private static readonly MethodInfo MemberGetValueAs = typeof( IReadOnlyMemberDictionary ) + .GetMethod( nameof( IReadOnlyMemberDictionary.GetValueAs ), [typeof( string )] )!; + + private static readonly PropertyInfo MemberIndexer = typeof( MemberDictionary ) + .GetProperties() + .First( x => x.GetIndexParameters().Length > 0 ); + public MemberTypeResolver( ReferenceManager referenceManager ) : base( referenceManager ) { } + + // Resolves a member expression for the given target expression. + // + // 1. x => x.someProp to x["someProp"] + // 2. x => x.someProp to x.GetValueAs("someProp") + // 3. x => x.someMethod(..) to x.Invoke("someMethod", ..) + + public override Expression RewriteMemberExpression( Expression targetExpression, string name, IReadOnlyList typeArgs, IReadOnlyList args ) { - var (expression, _) = binder; - // var v = vars::myValue; - // var v = vars::myValue; - // var v = vars::method( arg ); - - return ZeroOrOne( - Between( - Terms.Char( '<' ), - XsParsers.TypeRuntime(), - Terms.Char( '>' ) - ) - ) - .AndSkip( Terms.Text( "::" ) ) - .And( Terms.NamespaceIdentifier() ) - .And( - ZeroOrOne( - Between( - Terms.Char( '(' ), - ZeroOrOne( - Separated( - Terms.Char( ',' ), - expression - - ) - ), - Terms.Char( ')' ) - ) - ) ) - .Then( ( _, parts ) => - { - var (type, name, args) = parts; - - if ( name == null ) - throw new InvalidOperationException( "Name must be specified." ); - - if ( args == null ) - { - return Call( - Constant( _member ), - type != null - ? _getValueAsMethodInfo.MakeGenericMethod( type ) - : _getValueAsMethodInfo.MakeGenericMethod( typeof( object ) ), - Constant( name.ToString() ) - ); - } - - var invokeExpression = Call( - Constant( _member ), - _invokeMethodInfo, - Constant( name.ToString() ), - NewArrayInit( typeof( object ), args ) - ); - - return type != null - ? Convert( invokeExpression, type ) - : invokeExpression; // normally defaults to typeof(object) - - } - ) - .Named( "vars" ); + if ( targetExpression.Type != typeof(IReadOnlyMemberDictionary) ) + return base.RewriteMemberExpression( targetExpression, name, typeArgs, args ); + + if ( args != null ) + { + return Call( + targetExpression, + MemberInvoke, + Constant( name ), + NewArrayInit( typeof(object), args ) + ); + } + + if ( typeArgs != null ) + { + return Call( + targetExpression, + MemberGetValueAs + .MakeGenericMethod( typeArgs[0] ), + Constant( name ) ); + } + + return Property( + Convert( targetExpression, typeof(MemberDictionary) ), + MemberIndexer, + Constant( name ) ); + } } } diff --git a/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj b/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj index 590d050..c510a74 100644 --- a/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj +++ b/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj @@ -1,4 +1,4 @@ - + net9.0 enable @@ -43,7 +43,7 @@ - + diff --git a/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj b/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj index e44122d..44580f2 100644 --- a/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj +++ b/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj @@ -11,6 +11,7 @@ + diff --git a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs index 6d615da..a91e10e 100644 --- a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs +++ b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs @@ -1,5 +1,7 @@ using System.Reflection; using BenchmarkDotNet.Attributes; +using FastExpressionCompiler; +using Hyperbee.Templating.Configure; using Hyperbee.Templating.Provider.XS.Compiler; using Hyperbee.Templating.Text; using Hyperbee.XS.Core; @@ -8,8 +10,7 @@ namespace Hyperbee.Templating.Benchmark; public class TemplateBenchmarks { - private static readonly TypeResolver TypeResolver = TypeResolver.Create( Assembly.GetExecutingAssembly() ); - + private static readonly TypeResolver TypeResolver = new XsTokenExpressionProvider.MemberTypeResolver( ReferenceManager.Create() ); [Benchmark( Baseline = true )] public void ParserSingleLine() @@ -81,8 +82,8 @@ public void InlineBlockExpressionXs() const string expression = "{{name}}"; const string definition = """ - {{name:{{vars => { - switch( vars::choice ) + {{name:{{x => { + switch( x.choice ) { case "1": "me"; case "2": "you"; @@ -93,10 +94,13 @@ public void InlineBlockExpressionXs() const string template = $"{definition}hello {expression}."; - Template.Render( template, new() + Template.Render( template, new TemplateOptions { Variables = { ["choice"] = "2" }, - TokenExpressionProvider = new XsTokenExpressionProvider( true, TypeResolver ) + TokenExpressionProvider = new XsTokenExpressionProvider( + compile: lambda => lambda.CompileFast(), + typeResolver: TypeResolver + ) } ); } } diff --git a/test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs b/test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs index e2d20fd..4720ee2 100644 --- a/test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs +++ b/test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs @@ -15,7 +15,7 @@ public void Should_compile_expression() const string expression = """" vars => String.Concat( "all your ", - vars::Value, + vars.Value, " are belong to us." ).ToUpper(); """"; @@ -46,7 +46,7 @@ public void Should_compile_cast_expression() const string expression = """" vars => String.Concat( "all your ", - (1 + vars::Value).ToString(), + (1 + vars.Value).ToString(), " base are belong to us." ).ToUpper(); """"; @@ -77,7 +77,7 @@ public void Should_compile_statement_expression() const string expression = """" vars => String.Concat( "all your ", - vars::Value, + vars.Value, " are belong to us." ).ToUpper(); """"; @@ -108,13 +108,13 @@ public void Should_compile_multiple_expressions() const string expression1 = """" vars => String.Concat( "all your ", - vars::Value, + vars.Value, " are belong to us." ).ToUpper(); """"; const string expression2 = """" vars => String.Concat( "all your ", - vars::Value, + vars.Value, " are not belong to us." ).ToUpper(); """"; diff --git a/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj b/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj index ef8892e..4aca73b 100644 --- a/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj +++ b/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj @@ -13,6 +13,9 @@ + + + diff --git a/test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs b/test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs new file mode 100644 index 0000000..692cefc --- /dev/null +++ b/test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Hyperbee.Templating.Tests.TestSupport; + +public interface ITestService +{ + string DoSomething(); +} + +public class TestService : ITestService +{ + public TestService() { } + public TestService( string extra ) => Extra = extra; + public string Extra { get; set; } + + public string DoSomething() => "Hello, World!" + Extra; +} + +public static class ServiceProvider +{ + public static IServiceProvider GetServiceProvider() + { + var host = Host.CreateDefaultBuilder() + .ConfigureServices( ( _, services ) => + { + services.AddSingleton(); + services.AddKeyedSingleton( "TestKey", ( _, _ ) => new TestService( " And Universe!" ) ); + } ) + .ConfigureAppConfiguration( ( _, config ) => + { + config.AddInMemoryCollection( new Dictionary + { + {"hello", "Hello, World!"}, + {"number", "10"}, + {"connections:sql:secure", "true"}, + } ); + } ) + .Build(); + + return host.Services; + } +} diff --git a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs index d185e84..d01e9f8 100644 --- a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs +++ b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs @@ -1,5 +1,4 @@ -using System; -using Hyperbee.Templating.Configure; +using Hyperbee.Templating.Configure; using Hyperbee.Templating.Provider.XS.Compiler; using Hyperbee.Templating.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -13,7 +12,7 @@ public class TemplateParserExpressionTests public void Should_honor_while_xs_condition() { // arrange - const string expression = "{{while vars => vars::counter < 3 }}{{counter}}{{counter:{{ vars => vars::counter + 1}}}}{{/while}}"; + const string expression = "{{while x => x.counter < 3; }}{{counter}}{{counter:{{x => x.counter + 1;}}}}{{/while}}"; const string template = $"count: {expression}."; // act @@ -33,7 +32,7 @@ public void Should_honor_while_xs_condition() public void Should_honor_while_condition() { // arrange - const string expression = "{{while x => int.Parse(x.counter) < 3}}{{counter}}{{counter:{{x => int.Parse(x.counter) + 1}}}}{{/while}}"; + const string expression = "{{while x => x.counter < 3}}{{counter}}{{counter:{{x => x.counter + 1}}}}{{/while}}"; const string template = $"count: {expression}."; // act @@ -58,9 +57,9 @@ public void Should_honor_block_xs_expression() const string expression = """ {{ x => { - switch( x::choice ){ - case "1": x::TheBest("me", "no"); - case "2": x::TheBest("you", "yes"); + switch( x.choice ){ + case "1": x.TheBest("me", "no") as string; + case "2": x.TheBest("you", "yes") as string; default: "error"; } } }} @@ -87,6 +86,38 @@ public void Should_honor_block_xs_expression() Assert.AreEqual( expected, result ); } + [TestMethod] + public void Should_honor_xs_expression_extentions() + { + // arrange + const string expression = + """ + {{ x => { + if( x.choice == "2") + "you"; + else + "me"; + } }} + """; + + const string template = $"hello {expression}."; + + var serviceProvider = TestSupport.ServiceProvider.GetServiceProvider(); + + // act + var options = new TemplateOptions() + .AddVariable( "choice", "2" ) + .SetTokenExpressionProvider( new XsTokenExpressionProvider( ) ); + + var result = Template.Render( template, options ); + + // assert + + var expected = template.Replace( expression, "you" ); + + Assert.AreEqual( expected, result ); + } + [TestMethod] public void Should_honor_block_expression() { @@ -171,10 +202,10 @@ public void Should_honor_xs_inline_block_expression() const string definition = """ {{name:{{ input => { - switch( input::choice ) + switch( input.choice ) { - case "1": "me"; - case "2": "you"; + case 1: "me"; + case 2: "you"; default: "default"; }; } }} }} From 19740933bfdb64110c81e62ea9cee54addb000d3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 6 May 2025 20:34:11 +0000 Subject: [PATCH 07/11] Updated code formatting to match rules in .editorconfig --- .../Compiler/XsTokenExpressionProvider.cs | 12 ++++++------ .../Text/TemplateParser.ExpressionTests.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs index 1072844..c197991 100644 --- a/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs +++ b/src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs @@ -55,14 +55,14 @@ private TokenExpression Compile( ReadOnlySpan codeExpression ) { scope.EnterScope( FrameType.Method ); - var codeParameter = Parameter( typeof(IReadOnlyMemberDictionary), argument ); + var codeParameter = Parameter( typeof( IReadOnlyMemberDictionary ), argument ); scope.Variables.Add( argument, codeParameter ); var expression = _xsParser.Parse( body, scope: scope ); var expressionBody = expression as BlockExpression; - var lambdaParameter = Parameter( typeof(IReadOnlyMemberDictionary) ); + var lambdaParameter = Parameter( typeof( IReadOnlyMemberDictionary ) ); // create a new block expression assigning the parameter to the argument var expressions = new List { Assign( codeParameter, lambdaParameter ) }; @@ -77,7 +77,7 @@ private TokenExpression Compile( ReadOnlySpan codeExpression ) expressionBody?.Variables, expressions ), - typeof(object) + typeof( object ) ), lambdaParameter ); @@ -110,7 +110,7 @@ public MemberTypeResolver( ReferenceManager referenceManager ) : base( reference public override Expression RewriteMemberExpression( Expression targetExpression, string name, IReadOnlyList typeArgs, IReadOnlyList args ) { - if ( targetExpression.Type != typeof(IReadOnlyMemberDictionary) ) + if ( targetExpression.Type != typeof( IReadOnlyMemberDictionary ) ) return base.RewriteMemberExpression( targetExpression, name, typeArgs, args ); if ( args != null ) @@ -119,7 +119,7 @@ public override Expression RewriteMemberExpression( Expression targetExpression, targetExpression, MemberInvoke, Constant( name ), - NewArrayInit( typeof(object), args ) + NewArrayInit( typeof( object ), args ) ); } @@ -133,7 +133,7 @@ public override Expression RewriteMemberExpression( Expression targetExpression, } return Property( - Convert( targetExpression, typeof(MemberDictionary) ), + Convert( targetExpression, typeof( MemberDictionary ) ), MemberIndexer, Constant( name ) ); diff --git a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs index d01e9f8..537b4c4 100644 --- a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs +++ b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs @@ -107,7 +107,7 @@ public void Should_honor_xs_expression_extentions() // act var options = new TemplateOptions() .AddVariable( "choice", "2" ) - .SetTokenExpressionProvider( new XsTokenExpressionProvider( ) ); + .SetTokenExpressionProvider( new XsTokenExpressionProvider() ); var result = Template.Render( template, options ); From ec128376106fcd247ebe9ad8371be71caa759d22 Mon Sep 17 00:00:00 2001 From: Matt Edwards Date: Thu, 8 May 2025 16:41:57 -0400 Subject: [PATCH 08/11] Updates Hyperbee.XS dependency to stable version Updates the Hyperbee.XS dependency to the stable 1.3.2 version, removing the previous develop release. This change also adapts a template parser test to use the new 'inject' and 'config' extensions. The test now retrieves a registered service and a configuration value within the template, demonstrating the functionality of these extensions. --- .../Hyperbee.Templating.Provider.XS.csproj | 2 +- .../Hyperbee.Templating.Benchmark.csproj | 4 +-- .../Hyperbee.Templating.Tests.csproj | 4 +-- .../TestSupport/ServiceProvider.cs | 8 +++--- .../Text/TemplateParser.ExpressionTests.cs | 27 ++++++++++++------- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj b/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj index c510a74..cb4e875 100644 --- a/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj +++ b/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj @@ -43,7 +43,7 @@ - + diff --git a/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj b/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj index 44580f2..6aec0ac 100644 --- a/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj +++ b/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj @@ -1,4 +1,4 @@ - + Exe @@ -11,7 +11,7 @@ - + diff --git a/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj b/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj index 4aca73b..87effb4 100644 --- a/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj +++ b/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs b/test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs index 692cefc..8a1502e 100644 --- a/test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs +++ b/test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs @@ -20,7 +20,7 @@ public TestService() { } public TestService( string extra ) => Extra = extra; public string Extra { get; set; } - public string DoSomething() => "Hello, World!" + Extra; + public string DoSomething() => "World" + Extra; } public static class ServiceProvider @@ -31,15 +31,13 @@ public static IServiceProvider GetServiceProvider() .ConfigureServices( ( _, services ) => { services.AddSingleton(); - services.AddKeyedSingleton( "TestKey", ( _, _ ) => new TestService( " And Universe!" ) ); + services.AddKeyedSingleton( "TestKey", ( _, _ ) => new TestService( " and Universe" ) ); } ) .ConfigureAppConfiguration( ( _, config ) => { config.AddInMemoryCollection( new Dictionary { - {"hello", "Hello, World!"}, - {"number", "10"}, - {"connections:sql:secure", "true"}, + {"hello", "aliens"} } ); } ) .Build(); diff --git a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs index 537b4c4..a12243f 100644 --- a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs +++ b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs @@ -1,7 +1,13 @@ -using Hyperbee.Templating.Configure; +using System.Reflection; +using Hyperbee.Templating.Configure; using Hyperbee.Templating.Provider.XS.Compiler; using Hyperbee.Templating.Text; +using Hyperbee.Expressions; +using Hyperbee.Templating.Compiler; +using Hyperbee.Xs.Extensions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Hyperbee.XS.Core; +using static Hyperbee.Templating.Provider.XS.Compiler.XsTokenExpressionProvider; namespace Hyperbee.Templating.Tests.Text; @@ -92,12 +98,10 @@ public void Should_honor_xs_expression_extentions() // arrange const string expression = """ - {{ x => { - if( x.choice == "2") - "you"; - else - "me"; - } }} + {{ _ => { + var service = inject::TestKey; + service.DoSomething(); + } }} also {{ _ => config::hello}} """; const string template = $"hello {expression}."; @@ -106,14 +110,17 @@ public void Should_honor_xs_expression_extentions() // act var options = new TemplateOptions() - .AddVariable( "choice", "2" ) - .SetTokenExpressionProvider( new XsTokenExpressionProvider() ); + .SetTokenExpressionProvider( new XsTokenExpressionProvider( + compile: lambda => lambda.Compile(serviceProvider) as TokenExpression, + typeResolver: new MemberTypeResolver( ReferenceManager.Create( Assembly.GetExecutingAssembly() ) ), + extensions: [new InjectParseExtension(), new ConfigurationParseExtension()] + ) ); var result = Template.Render( template, options ); // assert - var expected = template.Replace( expression, "you" ); + var expected = template.Replace( expression, "World and Universe also aliens" ); Assert.AreEqual( expected, result ); } From 58f80db53f53996ede2292a3e57c1aaf0383352d Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 8 May 2025 20:42:40 +0000 Subject: [PATCH 09/11] Updated code formatting to match rules in .editorconfig --- .../Text/TemplateParser.ExpressionTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs index a12243f..d0356e6 100644 --- a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs +++ b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs @@ -1,12 +1,12 @@ using System.Reflection; +using Hyperbee.Expressions; +using Hyperbee.Templating.Compiler; using Hyperbee.Templating.Configure; using Hyperbee.Templating.Provider.XS.Compiler; using Hyperbee.Templating.Text; -using Hyperbee.Expressions; -using Hyperbee.Templating.Compiler; using Hyperbee.Xs.Extensions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Hyperbee.XS.Core; +using Microsoft.VisualStudio.TestTools.UnitTesting; using static Hyperbee.Templating.Provider.XS.Compiler.XsTokenExpressionProvider; namespace Hyperbee.Templating.Tests.Text; @@ -111,7 +111,7 @@ public void Should_honor_xs_expression_extentions() // act var options = new TemplateOptions() .SetTokenExpressionProvider( new XsTokenExpressionProvider( - compile: lambda => lambda.Compile(serviceProvider) as TokenExpression, + compile: lambda => lambda.Compile( serviceProvider ) as TokenExpression, typeResolver: new MemberTypeResolver( ReferenceManager.Create( Assembly.GetExecutingAssembly() ) ), extensions: [new InjectParseExtension(), new ConfigurationParseExtension()] ) ); From b9ec9d6468ec33b5870b55158efdc4eb4e992b26 Mon Sep 17 00:00:00 2001 From: Matt Edwards Date: Fri, 9 May 2025 09:21:18 -0400 Subject: [PATCH 10/11] clean up --- test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs | 3 +-- .../TestSupport/ServiceProvider.cs | 3 --- .../Text/TemplateParser.ExpressionTests.cs | 6 +++++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs index a91e10e..f9d3521 100644 --- a/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs +++ b/test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using FastExpressionCompiler; using Hyperbee.Templating.Configure; using Hyperbee.Templating.Provider.XS.Compiler; diff --git a/test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs b/test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs index 8a1502e..d6cf077 100644 --- a/test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs +++ b/test/Hyperbee.Templating.Tests/TestSupport/ServiceProvider.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs index d0356e6..bc6efed 100644 --- a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs +++ b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs @@ -113,7 +113,11 @@ public void Should_honor_xs_expression_extentions() .SetTokenExpressionProvider( new XsTokenExpressionProvider( compile: lambda => lambda.Compile( serviceProvider ) as TokenExpression, typeResolver: new MemberTypeResolver( ReferenceManager.Create( Assembly.GetExecutingAssembly() ) ), - extensions: [new InjectParseExtension(), new ConfigurationParseExtension()] + extensions: + [ + new InjectParseExtension(), + new ConfigurationParseExtension() + ] ) ); var result = Template.Render( template, options ); From 8fd4c7f224930ec1425a4c37f06bfc0a50fb71a7 Mon Sep 17 00:00:00 2001 From: Matt Edwards Date: Fri, 9 May 2025 12:16:40 -0400 Subject: [PATCH 11/11] Updates Hyperbee.XS dependency to v1.3.3 Updates the Hyperbee.XS dependency to version 1.3.3 across the templating provider, benchmark project, and tests. --- .../Hyperbee.Templating.Provider.XS.csproj | 2 +- .../Hyperbee.Templating.Benchmark.csproj | 2 +- .../Hyperbee.Templating.Tests.csproj | 4 ++-- .../Text/TemplateParser.ExpressionTests.cs | 3 +-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj b/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj index cb4e875..b492d59 100644 --- a/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj +++ b/src/Hyperbee.Templating.Provider.XS/Hyperbee.Templating.Provider.XS.csproj @@ -43,7 +43,7 @@ - + diff --git a/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj b/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj index 6aec0ac..8f8d1c0 100644 --- a/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj +++ b/test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj @@ -11,7 +11,7 @@ - + diff --git a/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj b/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj index 87effb4..9081531 100644 --- a/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj +++ b/test/Hyperbee.Templating.Tests/Hyperbee.Templating.Tests.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs index bc6efed..a273d35 100644 --- a/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs +++ b/test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs @@ -1,6 +1,5 @@ using System.Reflection; using Hyperbee.Expressions; -using Hyperbee.Templating.Compiler; using Hyperbee.Templating.Configure; using Hyperbee.Templating.Provider.XS.Compiler; using Hyperbee.Templating.Text; @@ -111,7 +110,7 @@ public void Should_honor_xs_expression_extentions() // act var options = new TemplateOptions() .SetTokenExpressionProvider( new XsTokenExpressionProvider( - compile: lambda => lambda.Compile( serviceProvider ) as TokenExpression, + compile: lambda => lambda.Compile( serviceProvider ), typeResolver: new MemberTypeResolver( ReferenceManager.Create( Assembly.GetExecutingAssembly() ) ), extensions: [