Skip to content

Commit fe8c6d5

Browse files
MattEdwardsWaggleBeevarndellwagglebeebfarmer67
authored
Release v3.2.0 (#80)
* [FEATURE]: Improve template configuration (#76) * Update nugets * Update documentation * Remove orphaned usings and adjust default buffer size --------- Co-authored-by: Brenton Farmer <brent.farmer@wagglebee.net> * Previous version was 'v3.0.2'. Version now 'v3.1.0'. * [FEATURE]: Add XS Token Expression Provider (#79) * 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. * 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. 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. 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. Updates the Hyperbee.XS dependency to version 1.3.3 across the templating provider, benchmark project, and tests. --------- Co-authored-by: Matt Edwards <matthew.edwards@wagglebee.net> * Previous version was 'v3.1.1'. Version now 'v3.2.0'. --------- Co-authored-by: annette.findley <annette.varndell@wagglebee.net> Co-authored-by: Brenton Farmer <brent.farmer@wagglebee.net> Co-authored-by: MattEdwardsWaggleBee <MattEdwardsWaggleBee@users.noreply.github.com>
1 parent caeda90 commit fe8c6d5

11 files changed

Lines changed: 589 additions & 6 deletions

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<!-- Solution version numbers -->
33
<PropertyGroup>
44
<MajorVersion>3</MajorVersion>
5-
<MinorVersion>1</MinorVersion>
5+
<MinorVersion>2</MinorVersion>
66
<PatchVersion>0</PatchVersion>
77
</PropertyGroup>
88
<!-- Disable automatic package publishing -->

Hyperbee.Templating.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbee.Templating.Benchma
3737
EndProject
3838
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "docs", "docs\docs.shproj", "{8409DDE0-C540-4A94-BF7F-9403888BEDAC}"
3939
EndProject
40+
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}"
41+
EndProject
4042
Global
4143
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4244
Debug|Any CPU = Debug|Any CPU
@@ -55,6 +57,10 @@ Global
5557
{EB7D2A85-3C82-444A-84CD-D245DCF951CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
5658
{EB7D2A85-3C82-444A-84CD-D245DCF951CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
5759
{EB7D2A85-3C82-444A-84CD-D245DCF951CE}.Release|Any CPU.Build.0 = Release|Any CPU
60+
{EA264696-D88A-288E-0D8E-C834780D5F9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61+
{EA264696-D88A-288E-0D8E-C834780D5F9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
62+
{EA264696-D88A-288E-0D8E-C834780D5F9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
63+
{EA264696-D88A-288E-0D8E-C834780D5F9E}.Release|Any CPU.Build.0 = Release|Any CPU
5864
EndGlobalSection
5965
GlobalSection(SolutionProperties) = preSolution
6066
HideSolutionNode = FALSE
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using System.Collections.Concurrent;
2+
using System.Linq.Expressions;
3+
using System.Reflection;
4+
using System.Runtime.CompilerServices;
5+
using Hyperbee.Templating.Compiler;
6+
using Hyperbee.Templating.Text;
7+
using Hyperbee.XS;
8+
using Hyperbee.XS.Core;
9+
using static System.Linq.Expressions.Expression;
10+
11+
namespace Hyperbee.Templating.Provider.XS.Compiler;
12+
13+
public delegate TokenExpression CompileLambda( Expression<TokenExpression> lambda );
14+
15+
public sealed class XsTokenExpressionProvider : ITokenExpressionProvider
16+
{
17+
private readonly ConcurrentDictionary<string, TokenExpression> TokenExpressions = new();
18+
private readonly CompileLambda _compile;
19+
private readonly XsParser _xsParser;
20+
21+
public XsTokenExpressionProvider(
22+
CompileLambda compile = null,
23+
TypeResolver typeResolver = null,
24+
List<IParseExtension> extensions = null )
25+
{
26+
_compile = compile ?? (lambda => lambda.Compile());
27+
typeResolver ??= new MemberTypeResolver( ReferenceManager.Create() );
28+
29+
_xsParser = new XsParser(
30+
new XsConfig( typeResolver ) { Extensions = extensions ?? [] }
31+
);
32+
}
33+
34+
[MethodImpl( MethodImplOptions.AggressiveInlining )]
35+
public TokenExpression GetTokenExpression( string codeExpression, MemberDictionary members )
36+
{
37+
return TokenExpressions.GetOrAdd( codeExpression, _ => Compile( codeExpression ) );
38+
}
39+
40+
[MethodImpl( MethodImplOptions.AggressiveInlining )]
41+
public void Reset()
42+
{
43+
TokenExpressions.Clear();
44+
}
45+
46+
private TokenExpression Compile( ReadOnlySpan<char> codeExpression )
47+
{
48+
var start = codeExpression.IndexOf( "=>" );
49+
var argument = codeExpression[..start].Trim().ToString();
50+
var body = codeExpression[(start + 2)..].Trim().ToString();
51+
52+
var scope = new ParseScope();
53+
54+
try
55+
{
56+
scope.EnterScope( FrameType.Method );
57+
58+
var codeParameter = Parameter( typeof( IReadOnlyMemberDictionary ), argument );
59+
60+
scope.Variables.Add( argument, codeParameter );
61+
62+
var expression = _xsParser.Parse( body, scope: scope );
63+
var expressionBody = expression as BlockExpression;
64+
65+
var lambdaParameter = Parameter( typeof( IReadOnlyMemberDictionary ) );
66+
67+
// create a new block expression assigning the parameter to the argument
68+
var expressions = new List<Expression> { Assign( codeParameter, lambdaParameter ) };
69+
if ( expressionBody == null )
70+
expressions.Add( expression );
71+
else
72+
expressions.AddRange( expressionBody.Expressions );
73+
74+
var lambda = Lambda<TokenExpression>(
75+
Convert(
76+
Block(
77+
expressionBody?.Variables,
78+
expressions
79+
),
80+
typeof( object )
81+
),
82+
lambdaParameter );
83+
84+
return _compile( lambda );
85+
}
86+
finally
87+
{
88+
scope.ExitScope();
89+
}
90+
}
91+
92+
public class MemberTypeResolver : TypeResolver
93+
{
94+
private static readonly MethodInfo MemberInvoke = typeof( IReadOnlyMemberDictionary )
95+
.GetMethod( nameof( IReadOnlyMemberDictionary.Invoke ), [typeof( string ), typeof( object[] )] )!;
96+
97+
private static readonly MethodInfo MemberGetValueAs = typeof( IReadOnlyMemberDictionary )
98+
.GetMethod( nameof( IReadOnlyMemberDictionary.GetValueAs ), [typeof( string )] )!;
99+
100+
private static readonly PropertyInfo MemberIndexer = typeof( MemberDictionary )
101+
.GetProperties()
102+
.First( x => x.GetIndexParameters().Length > 0 );
103+
public MemberTypeResolver( ReferenceManager referenceManager ) : base( referenceManager ) { }
104+
105+
// Resolves a member expression for the given target expression.
106+
//
107+
// 1. x => x.someProp to x["someProp"]
108+
// 2. x => x.someProp<T> to x.GetValueAs<T>("someProp")
109+
// 3. x => x.someMethod(..) to x.Invoke("someMethod", ..)
110+
111+
public override Expression RewriteMemberExpression( Expression targetExpression, string name, IReadOnlyList<Type> typeArgs, IReadOnlyList<Expression> args )
112+
{
113+
if ( targetExpression.Type != typeof( IReadOnlyMemberDictionary ) )
114+
return base.RewriteMemberExpression( targetExpression, name, typeArgs, args );
115+
116+
if ( args != null )
117+
{
118+
return Call(
119+
targetExpression,
120+
MemberInvoke,
121+
Constant( name ),
122+
NewArrayInit( typeof( object ), args )
123+
);
124+
}
125+
126+
if ( typeArgs != null )
127+
{
128+
return Call(
129+
targetExpression,
130+
MemberGetValueAs
131+
.MakeGenericMethod( typeArgs[0] ),
132+
Constant( name ) );
133+
}
134+
135+
return Property(
136+
Convert( targetExpression, typeof( MemberDictionary ) ),
137+
MemberIndexer,
138+
Constant( name ) );
139+
140+
}
141+
}
142+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net9.0</TargetFramework>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<IsPackable>true</IsPackable>
6+
<Authors>Stillpoint Software, Inc.</Authors>
7+
<PackageReadmeFile>README.md</PackageReadmeFile>
8+
<PackageTags>templating;token;xs</PackageTags>
9+
<PackageIcon>icon.png</PackageIcon>
10+
<PackageProjectUrl>https://stillpoint-software.github.io/hyperbee.templating/</PackageProjectUrl>
11+
<TargetFrameworks>net9.0</TargetFrameworks>
12+
<PackageLicenseFile>LICENSE</PackageLicenseFile>
13+
<Copyright>Stillpoint Software, Inc.</Copyright>
14+
<Title>Hyperbee Templating Provider XS</Title>
15+
<Description>
16+
Adds an ITokenExpressionProvider powder by Hyperbee.XS
17+
</Description>
18+
<RepositoryUrl>https://github.com/Stillpoint-Software/Hyperbee.Templating</RepositoryUrl>
19+
<RepositoryType>git</RepositoryType>
20+
<PackageReleaseNotes>https://github.com/Stillpoint-Software/hyperbee.templating/releases/latest</PackageReleaseNotes>
21+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
22+
</PropertyGroup>
23+
24+
<ItemGroup>
25+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
26+
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
27+
</AssemblyAttribute>
28+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
29+
<_Parameter1>$(AssemblyName).Benchmark</_Parameter1>
30+
</AssemblyAttribute>
31+
</ItemGroup>
32+
33+
<ItemGroup>
34+
<None Update="$(MSBuildProjectName).csproj.DotSettings" Visible="false" />
35+
</ItemGroup>
36+
<ItemGroup>
37+
<None Include="..\..\assets\icon.png" Pack="true" Visible="false" PackagePath="/" />
38+
<None Include="..\..\README.md" Pack="true" Visible="true" PackagePath="/" Link="README.md" />
39+
<None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath="/" />
40+
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="8.0.0">
41+
<PrivateAssets>all</PrivateAssets>
42+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
43+
</PackageReference>
44+
<PackageReference Include="FastExpressionCompiler" Version="5.1.1" />
45+
<PackageReference Include="Hyperbee.Resources" Version="2.0.2" />
46+
<PackageReference Include="Hyperbee.XS" Version="1.3.3" />
47+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.13.0" />
48+
</ItemGroup>
49+
<ItemGroup>
50+
<ProjectReference Include="..\Hyperbee.Templating\Hyperbee.Templating.csproj" />
51+
</ItemGroup>
52+
</Project>

test/Hyperbee.Templating.Benchmark/Hyperbee.Templating.Benchmark.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
@@ -11,9 +11,11 @@
1111

1212
<ItemGroup>
1313
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
14+
<PackageReference Include="Hyperbee.XS" Version="1.3.3" />
1415
</ItemGroup>
1516

1617
<ItemGroup>
18+
<ProjectReference Include="..\..\src\Hyperbee.Templating.Provider.XS\Hyperbee.Templating.Provider.XS.csproj" />
1719
<ProjectReference Include="..\..\src\Hyperbee.Templating\Hyperbee.Templating.csproj" />
1820
<ProjectReference Include="..\Hyperbee.Templating.Tests\Hyperbee.Templating.Tests.csproj" />
1921
</ItemGroup>

test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
using BenchmarkDotNet.Attributes;
2+
using FastExpressionCompiler;
3+
using Hyperbee.Templating.Configure;
4+
using Hyperbee.Templating.Provider.XS.Compiler;
25
using Hyperbee.Templating.Text;
6+
using Hyperbee.XS.Core;
37

48
namespace Hyperbee.Templating.Benchmark;
59

610
public class TemplateBenchmarks
711
{
12+
private static readonly TypeResolver TypeResolver = new XsTokenExpressionProvider.MemberTypeResolver( ReferenceManager.Create() );
13+
814
[Benchmark( Baseline = true )]
915
public void ParserSingleLine()
1016
{
@@ -68,5 +74,33 @@ public void InlineBlockExpression()
6874
}
6975
} );
7076
}
77+
78+
[Benchmark]
79+
public void InlineBlockExpressionXs()
80+
{
81+
const string expression = "{{name}}";
82+
const string definition =
83+
"""
84+
{{name:{{x => {
85+
switch( x.choice )
86+
{
87+
case "1": "me";
88+
case "2": "you";
89+
default: "default";
90+
};
91+
} }} }}
92+
""";
93+
94+
const string template = $"{definition}hello {expression}.";
95+
96+
Template.Render( template, new TemplateOptions
97+
{
98+
Variables = { ["choice"] = "2" },
99+
TokenExpressionProvider = new XsTokenExpressionProvider(
100+
compile: lambda => lambda.CompileFast(),
101+
typeResolver: TypeResolver
102+
)
103+
} );
104+
}
71105
}
72106

0 commit comments

Comments
 (0)