Skip to content

Commit 92b6956

Browse files
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.
1 parent 7c6cb21 commit 92b6956

4 files changed

Lines changed: 44 additions & 69 deletions

File tree

src/Hyperbee.Templating.Provider.XS/Compiler/XsTokenExpressionProvider.cs

Lines changed: 19 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Collections.Concurrent;
1+
using System.Collections.Concurrent;
22
using System.Linq.Expressions;
33
using System.Reflection;
44
using System.Runtime.CompilerServices;
@@ -17,17 +17,19 @@ namespace Hyperbee.Templating.Provider.XS.Compiler;
1717
public sealed class XsTokenExpressionProvider : ITokenExpressionProvider
1818
{
1919
private readonly bool _fastCompile;
20+
private readonly TypeResolver _typeResolver;
2021
private ConcurrentDictionary<string, TokenExpression> TokenExpressions { get; } = new();
2122

22-
public XsTokenExpressionProvider( bool fastCompile = false )
23+
public XsTokenExpressionProvider( bool fastCompile = false, TypeResolver typeResolver = null)
2324
{
2425
_fastCompile = fastCompile;
26+
_typeResolver = typeResolver ?? TypeResolver.Create( Assembly.GetExecutingAssembly() );
2527
}
2628

2729
[MethodImpl( MethodImplOptions.AggressiveInlining )]
2830
public TokenExpression GetTokenExpression( string codeExpression, MemberDictionary members )
2931
{
30-
return TokenExpressions.GetOrAdd( codeExpression, Compile( codeExpression, members, _fastCompile ) );
32+
return TokenExpressions.GetOrAdd( codeExpression, Compile( codeExpression, members, _typeResolver, _fastCompile ) );
3133
}
3234

3335
[MethodImpl( MethodImplOptions.AggressiveInlining )]
@@ -36,66 +38,38 @@ public void Reset()
3638
TokenExpressions.Clear();
3739
}
3840

39-
private static TokenExpression Compile( ReadOnlySpan<char> codeExpression, MemberDictionary members, bool fastCompile = false )
41+
private static TokenExpression Compile( ReadOnlySpan<char> codeExpression, MemberDictionary members, TypeResolver typeResolver, bool fastCompile = false )
4042
{
41-
var xsParser = new XsParser( new XsConfig( TypeResolver.Create( Assembly.GetExecutingAssembly() ) )
42-
{
43-
Extensions = [new MemberDictionaryParseExtension( members )]
44-
} );
45-
4643
var start = codeExpression.IndexOf( "=>" );
4744
var argument = codeExpression[..start].Trim().ToString();
4845
var body = codeExpression[(start + 2)..].Trim().ToString();
4946

50-
var scope = new ParseScope();
51-
52-
try
53-
{
54-
scope.EnterScope( FrameType.Method );
55-
56-
var argumentParameter = Parameter( typeof( IReadOnlyMemberDictionary ), argument );
57-
58-
scope.Variables.Add( argument, argumentParameter );
59-
60-
var expressionBody = xsParser.Parse( body, scope: scope ) as BlockExpression;
61-
62-
if ( expressionBody == null )
63-
throw new InvalidOperationException( $"Failed to parse expression body: {body}" );
64-
65-
var lambdaParameter = Parameter( typeof( IReadOnlyMemberDictionary ) );
66-
67-
var newExpressionBody = expressionBody.Expressions.Prepend(
68-
Assign( argumentParameter, lambdaParameter )
69-
);
47+
var xsParser = new XsParser( new XsConfig( typeResolver )
48+
{
49+
Extensions = [new MemberDictionaryParseExtension( argument, members )]
50+
} );
7051

71-
var lambda = Lambda<TokenExpression>(
72-
Convert( Block(
73-
expressionBody.Variables,
74-
newExpressionBody
75-
), typeof( object ) ),
76-
lambdaParameter );
52+
var lambda = Lambda<TokenExpression>(
53+
Convert( xsParser.Parse( body ), typeof( object ) ),
54+
Parameter( typeof( IReadOnlyMemberDictionary ) ) );
7755

78-
return fastCompile
79-
? lambda.CompileFast()
80-
: lambda.Compile();
81-
}
82-
finally
83-
{
84-
scope.ExitScope();
85-
}
56+
return fastCompile
57+
? lambda.CompileFast()
58+
: lambda.Compile();
8659
}
8760

8861
internal class MemberDictionaryParseExtension : IParseExtension
8962
{
9063
public ExtensionType Type => ExtensionType.Expression;
91-
public string Key => "vars";
64+
public string Key { get; }
9265

9366
private readonly MethodInfo _getValueAsMethodInfo = typeof( MemberDictionary ).GetMethod( nameof( MemberDictionary.GetValueAs ), [typeof( string )] )!;
9467
private readonly MethodInfo _invokeMethodInfo = typeof( MemberDictionary ).GetMethod( nameof( MemberDictionary.Invoke ), [typeof( string ), typeof( object[] )] )!;
9568
private readonly MemberDictionary _member;
9669

97-
public MemberDictionaryParseExtension( MemberDictionary member )
70+
public MemberDictionaryParseExtension( string name, MemberDictionary member )
9871
{
72+
Key = name;
9973
_member = member;
10074
}
10175

test/Hyperbee.Templating.Benchmark/TemplateBenchmarks.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
using BenchmarkDotNet.Attributes;
1+
using System.Reflection;
2+
using BenchmarkDotNet.Attributes;
23
using Hyperbee.Templating.Provider.XS.Compiler;
34
using Hyperbee.Templating.Text;
5+
using Hyperbee.XS.Core;
46

57
namespace Hyperbee.Templating.Benchmark;
68

79
public class TemplateBenchmarks
810
{
11+
private static readonly TypeResolver TypeResolver = TypeResolver.Create( Assembly.GetExecutingAssembly() );
12+
13+
914
[Benchmark( Baseline = true )]
1015
public void ParserSingleLine()
1116
{
@@ -76,8 +81,8 @@ public void InlineBlockExpressionXs()
7681
const string expression = "{{name}}";
7782
const string definition =
7883
"""
79-
{{name:{{_ => {
80-
return switch( vars<string>::choice )
84+
{{name:{{vars => {
85+
switch( vars<string>::choice )
8186
{
8287
case "1": "me";
8388
case "2": "you";
@@ -88,13 +93,10 @@ public void InlineBlockExpressionXs()
8893

8994
const string template = $"{definition}hello {expression}.";
9095

91-
Template.Render( template, new()
92-
{
93-
Variables =
94-
{
95-
["choice"] = "2"
96-
},
97-
TokenExpressionProvider = new XsTokenExpressionProvider( true )
96+
Template.Render( template, new()
97+
{
98+
Variables = { ["choice"] = "2" },
99+
TokenExpressionProvider = new XsTokenExpressionProvider( true, TypeResolver )
98100
} );
99101
}
100102
}

test/Hyperbee.Templating.Tests/Compiler/XsTokenExpressionProviderTests.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
32
using Hyperbee.Templating.Provider.XS.Compiler;
43
using Hyperbee.Templating.Text;
54
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -15,7 +14,7 @@ public void Should_compile_expression()
1514
// arrange
1615

1716
const string expression = """"
18-
_ => String.Concat( "all your ",
17+
vars => String.Concat( "all your ",
1918
vars<string>::Value,
2019
" are belong to us."
2120
).ToUpper();
@@ -46,7 +45,7 @@ public void Should_compile_cast_expression()
4645
// arrange
4746

4847
const string expression = """"
49-
_ => String.Concat( "all your ",
48+
vars => String.Concat( "all your ",
5049
(1 + vars<int>::Value).ToString(),
5150
" base are belong to us."
5251
).ToUpper();
@@ -77,7 +76,7 @@ public void Should_compile_statement_expression()
7776
// arrange
7877

7978
const string expression = """"
80-
_ => String.Concat( "all your ",
79+
vars => String.Concat( "all your ",
8180
vars<string>::Value,
8281
" are belong to us."
8382
).ToUpper();
@@ -108,13 +107,13 @@ public void Should_compile_multiple_expressions()
108107
// arrange
109108

110109
const string expression1 = """"
111-
_ => String.Concat( "all your ",
110+
vars => String.Concat( "all your ",
112111
vars<string>::Value,
113112
" are belong to us."
114113
).ToUpper();
115114
"""";
116115
const string expression2 = """"
117-
_ => String.Concat( "all your ",
116+
vars => String.Concat( "all your ",
118117
vars<string>::Value,
119118
" are not belong to us."
120119
).ToUpper();

test/Hyperbee.Templating.Tests/Text/TemplateParser.ExpressionTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class TemplateParserExpressionTests
1313
public void Should_honor_while_xs_condition()
1414
{
1515
// arrange
16-
const string expression = "{{while x => vars<int>::counter < 3 }}{{counter}}{{counter:{{x => vars<int>::counter + 1}}}}{{/while}}";
16+
const string expression = "{{while vars => vars<int>::counter < 3 }}{{counter}}{{counter:{{ vars => vars<int>::counter + 1}}}}{{/while}}";
1717
const string template = $"count: {expression}.";
1818

1919
// act
@@ -57,10 +57,10 @@ public void Should_honor_block_xs_expression()
5757
// arrange
5858
const string expression =
5959
"""
60-
{{_ => {
61-
switch( vars<string>::choice ){
62-
case "1": vars<string>::TheBest("me", "no");
63-
case "2": vars<string>::TheBest("you", "yes");
60+
{{ x => {
61+
switch( x<string>::choice ){
62+
case "1": x<string>::TheBest("me", "no");
63+
case "2": x<string>::TheBest("you", "yes");
6464
default: "error";
6565
}
6666
} }}
@@ -170,8 +170,8 @@ public void Should_honor_xs_inline_block_expression()
170170
const string expression = "{{name}}";
171171
const string definition =
172172
"""
173-
{{name:{{_ => {
174-
return switch( vars<string>::choice )
173+
{{name:{{ input => {
174+
switch( input<string>::choice )
175175
{
176176
case "1": "me";
177177
case "2": "you";

0 commit comments

Comments
 (0)