diff --git a/source/Handlebars.Test/IssueTests.cs b/source/Handlebars.Test/IssueTests.cs index 130efdc0..23cfa686 100644 --- a/source/Handlebars.Test/IssueTests.cs +++ b/source/Handlebars.Test/IssueTests.cs @@ -1285,7 +1285,7 @@ public void Issue661_ExplicitCrLfInTemplateIsPreservedVerbatim() { var handlebars = Handlebars.Create(); handlebars.RegisterHelper("Split", (context, arguments) => - ((string) arguments[0]).Split(((string) arguments[1])[0])); + ((string) arguments[0]!).Split(((string) arguments[1]!)[0])); var template = handlebars.Compile("{{#each (Split \"a;b;c\" ';')}}\r\n{{@Key}}:{{@Index}}:{{this}}\r\n{{/each}}"); diff --git a/source/Handlebars/Compiler/CompilationContext.cs b/source/Handlebars/Compiler/CompilationContext.cs index b85cd85f..25cd33be 100644 --- a/source/Handlebars/Compiler/CompilationContext.cs +++ b/source/Handlebars/Compiler/CompilationContext.cs @@ -1,5 +1,5 @@ using System.Linq.Expressions; -using Expressions.Shortcuts; +using HandlebarsDotNet.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { diff --git a/source/Handlebars/Compiler/FunctionBuilder.cs b/source/Handlebars/Compiler/FunctionBuilder.cs index f8f3e5b5..4c182b43 100644 --- a/source/Handlebars/Compiler/FunctionBuilder.cs +++ b/source/Handlebars/Compiler/FunctionBuilder.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using Expressions.Shortcuts; +using HandlebarsDotNet.ExpressionShortcuts; using HandlebarsDotNet.Polyfills; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { diff --git a/source/Handlebars/Compiler/HandlebarsCompiler.cs b/source/Handlebars/Compiler/HandlebarsCompiler.cs index 398853be..ac64df75 100644 --- a/source/Handlebars/Compiler/HandlebarsCompiler.cs +++ b/source/Handlebars/Compiler/HandlebarsCompiler.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Expressions.Shortcuts; using HandlebarsDotNet.Compiler.Lexer; using HandlebarsDotNet.IO; using HandlebarsDotNet.PathStructure; diff --git a/source/Handlebars/Compiler/Middlewares/ClosureExpressionMiddleware.cs b/source/Handlebars/Compiler/Middlewares/ClosureExpressionMiddleware.cs index 0b1c297a..9f9e07b2 100644 --- a/source/Handlebars/Compiler/Middlewares/ClosureExpressionMiddleware.cs +++ b/source/Handlebars/Compiler/Middlewares/ClosureExpressionMiddleware.cs @@ -2,8 +2,7 @@ using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; -using HandlebarsDotNet.Pools; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler.Middlewares { diff --git a/source/Handlebars/Compiler/Translation/Expression/BlockHelperFunctionBinder.cs b/source/Handlebars/Compiler/Translation/Expression/BlockHelperFunctionBinder.cs index 8611597b..85b2eb58 100644 --- a/source/Handlebars/Compiler/Translation/Expression/BlockHelperFunctionBinder.cs +++ b/source/Handlebars/Compiler/Translation/Expression/BlockHelperFunctionBinder.cs @@ -1,15 +1,14 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using Expressions.Shortcuts; using HandlebarsDotNet.Decorators; +using HandlebarsDotNet.ExpressionShortcuts; using HandlebarsDotNet.Helpers; using HandlebarsDotNet.Helpers.BlockHelpers; using HandlebarsDotNet.PathStructure; using HandlebarsDotNet.Polyfills; using HandlebarsDotNet.Runtime; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { diff --git a/source/Handlebars/Compiler/Translation/Expression/BoolishConverter.cs b/source/Handlebars/Compiler/Translation/Expression/BoolishConverter.cs index 06f7bc28..c6cd9238 100644 --- a/source/Handlebars/Compiler/Translation/Expression/BoolishConverter.cs +++ b/source/Handlebars/Compiler/Translation/Expression/BoolishConverter.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; using System.Linq.Expressions; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { diff --git a/source/Handlebars/Compiler/Translation/Expression/DecoratorDefinition.cs b/source/Handlebars/Compiler/Translation/Expression/DecoratorDefinition.cs index 50343553..56627bac 100644 --- a/source/Handlebars/Compiler/Translation/Expression/DecoratorDefinition.cs +++ b/source/Handlebars/Compiler/Translation/Expression/DecoratorDefinition.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq.Expressions; -using Expressions.Shortcuts; +using HandlebarsDotNet.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { diff --git a/source/Handlebars/Compiler/Translation/Expression/FunctionBinderHelpers.cs b/source/Handlebars/Compiler/Translation/Expression/FunctionBinderHelpers.cs index ab20ac55..2246a994 100644 --- a/source/Handlebars/Compiler/Translation/Expression/FunctionBinderHelpers.cs +++ b/source/Handlebars/Compiler/Translation/Expression/FunctionBinderHelpers.cs @@ -3,11 +3,11 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Expressions.Shortcuts; using HandlebarsDotNet.Collections; using HandlebarsDotNet.EqualityComparers; +using HandlebarsDotNet.ExpressionShortcuts; using HandlebarsDotNet.Runtime; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { diff --git a/source/Handlebars/Compiler/Translation/Expression/HelperFunctionBinder.cs b/source/Handlebars/Compiler/Translation/Expression/HelperFunctionBinder.cs index ea17cfb3..49302598 100644 --- a/source/Handlebars/Compiler/Translation/Expression/HelperFunctionBinder.cs +++ b/source/Handlebars/Compiler/Translation/Expression/HelperFunctionBinder.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; using System.Linq.Expressions; -using Expressions.Shortcuts; using HandlebarsDotNet.Decorators; using HandlebarsDotNet.Helpers; -using HandlebarsDotNet.Helpers.BlockHelpers; using HandlebarsDotNet.PathStructure; using HandlebarsDotNet.Runtime; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { diff --git a/source/Handlebars/Compiler/Translation/Expression/IteratorBinder.cs b/source/Handlebars/Compiler/Translation/Expression/IteratorBinder.cs index b65b89f6..c2cff494 100644 --- a/source/Handlebars/Compiler/Translation/Expression/IteratorBinder.cs +++ b/source/Handlebars/Compiler/Translation/Expression/IteratorBinder.cs @@ -1,9 +1,9 @@ using System.Linq.Expressions; -using Expressions.Shortcuts; +using HandlebarsDotNet.ExpressionShortcuts; using HandlebarsDotNet.ObjectDescriptors; using HandlebarsDotNet.PathStructure; using HandlebarsDotNet.Polyfills; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { @@ -127,7 +127,7 @@ public static void Iterate( if (!ObjectDescriptor.TryCreate(target, out var descriptor)) { - throw new HandlebarsRuntimeException($"Cannot create ObjectDescriptor for type {descriptor.DescribedType}"); + throw new HandlebarsRuntimeException($"Cannot create ObjectDescriptor for type {target.GetType()}"); } if (descriptor.Iterator == null) throw new HandlebarsRuntimeException($"Type {descriptor.DescribedType} does not support iteration"); diff --git a/source/Handlebars/Compiler/Translation/Expression/PartialBinder.cs b/source/Handlebars/Compiler/Translation/Expression/PartialBinder.cs index 6e20973f..879719e8 100644 --- a/source/Handlebars/Compiler/Translation/Expression/PartialBinder.cs +++ b/source/Handlebars/Compiler/Translation/Expression/PartialBinder.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq.Expressions; -using System.Text; -using Expressions.Shortcuts; using HandlebarsDotNet.IO; using HandlebarsDotNet.PathStructure; using HandlebarsDotNet.Polyfills; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { @@ -33,26 +30,27 @@ protected override Expression VisitPartialExpression(PartialExpression pex) ? FunctionBuilder.Compile(new[] { pex.Fallback }, CompilationContext, out decorators) : null; - if (decorators.Count > 0) + var bindingContext = CompilationContext.Args.BindingContext; + var writer = CompilationContext.Args.EncodedWriter; + + var parentContext = bindingContext; + if (pex.Argument != null || partialBlockTemplate != null) { - var bindingContext = CompilationContext.Args.BindingContext; - var writer = CompilationContext.Args.EncodedWriter; + var value = pex.Argument != null + ? Arg(FunctionBuilder.Reduce(pex.Argument, CompilationContext, out _)) + : bindingContext.Property(o => o.Value); - var parentContext = bindingContext; - if (pex.Argument != null || partialBlockTemplate != null) - { - var value = pex.Argument != null - ? Arg(FunctionBuilder.Reduce(pex.Argument, CompilationContext, out _)) - : bindingContext.Property(o => o.Value); + var partialTemplate = Arg(partialBlockTemplate); + bindingContext = bindingContext.Call(o => o.CreateChildContext(value, partialTemplate)); + } - var partialTemplate = Arg(partialBlockTemplate); - bindingContext = bindingContext.Call(o => o.CreateChildContext(value, partialTemplate)); - } + var partialName = Cast(pex.PartialName); + var configuration = Arg(CompilationContext.Configuration); + var isBlock = Arg(pex.IsBlock); + var indent = Arg(pex.Indent); - var partialName = Cast(pex.PartialName); - var configuration = Arg(CompilationContext.Configuration); - var isBlock = Arg(pex.IsBlock); - var indent = Arg(pex.Indent); + if (decorators.Count > 0) + { var templateDelegate = FunctionBuilder.Compile( new [] { @@ -70,24 +68,6 @@ out _ } else { - var bindingContext = CompilationContext.Args.BindingContext; - var writer = CompilationContext.Args.EncodedWriter; - - if (pex.Argument != null || partialBlockTemplate != null) - { - var value = pex.Argument != null - ? Arg(FunctionBuilder.Reduce(pex.Argument, CompilationContext, out _)) - : bindingContext.Property(o => o.Value); - - var partialTemplate = Arg(partialBlockTemplate); - bindingContext = bindingContext.Call(o => o.CreateChildContext(value, partialTemplate)); - } - - var partialName = Cast(pex.PartialName); - var configuration = Arg(CompilationContext.Configuration); - var isBlock = Arg(pex.IsBlock); - var indent = Arg(pex.Indent); - return Call(() => InvokePartialWithFallback(partialName, bindingContext, writer, (ICompiledHandlebarsConfiguration) configuration, isBlock, indent) ); @@ -142,7 +122,7 @@ private static void WriteWithIndent(EncodedTextWriter writer, string? content, s } var pos = 0; - while (pos < content.Length) + while (pos < content!.Length) { var newlinePos = content.IndexOf('\n', pos); if (newlinePos < 0) diff --git a/source/Handlebars/Compiler/Translation/Expression/PathBinder.cs b/source/Handlebars/Compiler/Translation/Expression/PathBinder.cs index 9db57165..2686a376 100644 --- a/source/Handlebars/Compiler/Translation/Expression/PathBinder.cs +++ b/source/Handlebars/Compiler/Translation/Expression/PathBinder.cs @@ -1,9 +1,8 @@ using System.Linq.Expressions; -using Expressions.Shortcuts; using HandlebarsDotNet.Helpers; using HandlebarsDotNet.PathStructure; using HandlebarsDotNet.Runtime; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { diff --git a/source/Handlebars/Compiler/Translation/Expression/StaticReplacer.cs b/source/Handlebars/Compiler/Translation/Expression/StaticReplacer.cs index ba8641ba..505d2c05 100644 --- a/source/Handlebars/Compiler/Translation/Expression/StaticReplacer.cs +++ b/source/Handlebars/Compiler/Translation/Expression/StaticReplacer.cs @@ -1,6 +1,5 @@ using System.Linq.Expressions; -using Expressions.Shortcuts; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { diff --git a/source/Handlebars/Compiler/Translation/Expression/SubExpressionVisitor.cs b/source/Handlebars/Compiler/Translation/Expression/SubExpressionVisitor.cs index 48b69c46..abdfee1c 100644 --- a/source/Handlebars/Compiler/Translation/Expression/SubExpressionVisitor.cs +++ b/source/Handlebars/Compiler/Translation/Expression/SubExpressionVisitor.cs @@ -1,7 +1,6 @@ using System.Linq.Expressions; -using Expressions.Shortcuts; using HandlebarsDotNet.Helpers; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { diff --git a/source/Handlebars/Compiler/Translation/Expression/UnencodedStatementVisitor.cs b/source/Handlebars/Compiler/Translation/Expression/UnencodedStatementVisitor.cs index 72e6300b..dfc2e312 100644 --- a/source/Handlebars/Compiler/Translation/Expression/UnencodedStatementVisitor.cs +++ b/source/Handlebars/Compiler/Translation/Expression/UnencodedStatementVisitor.cs @@ -1,6 +1,5 @@ using System.Linq.Expressions; -using Expressions.Shortcuts; -using static Expressions.Shortcuts.ExpressionShortcuts; +using static HandlebarsDotNet.ExpressionShortcuts.ExpressionShortcuts; namespace HandlebarsDotNet.Compiler { diff --git a/source/Handlebars/ExpressionShortcuts/BlockBuilder.cs b/source/Handlebars/ExpressionShortcuts/BlockBuilder.cs new file mode 100644 index 00000000..acce4895 --- /dev/null +++ b/source/Handlebars/ExpressionShortcuts/BlockBuilder.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace HandlebarsDotNet.ExpressionShortcuts +{ + /// + /// Shortcut for + /// + internal class BlockBuilder: ExpressionContainer + { + private readonly Type? _returnType; + private readonly List _expressions; + private readonly HashSet _parameters; + + internal BlockBuilder(Type? returnType) : base(Expression.Empty()) + { + _returnType = returnType; + _expressions = new List(); + _parameters = new HashSet(); + } + + /// + public override Expression Expression => + _returnType == null + ? Expression.Block(_parameters, _expressions) + : Expression.Block(_returnType, _parameters, _expressions); + + /// + /// Adds parameter to + /// + public BlockBuilder Parameter(out ExpressionContainer parameter) + { + var expression = Expression.Parameter(typeof(T)); + parameter = ExpressionShortcuts.Arg(expression); + return Parameter(expression); + } + + /// + /// Adds parameter to + /// + public BlockBuilder Parameter(ParameterExpression e) + { + _parameters.Add(e); + return this; + } + + /// + /// Adds new "line" to + /// + public BlockBuilder Line(Expression e) + { + _expressions.Add(e); + return this; + } + + /// + /// Adds multiple new "lines" to + /// + public BlockBuilder Lines(IEnumerable e) + { + _expressions.AddRange(e); + return this; + } + + /// + /// Creates out of current . + /// + public Expression Lambda(IEnumerable parameters) where T : class + { + return Expression.Lambda(Expression, parameters); + } + } +} \ No newline at end of file diff --git a/source/Handlebars/ExpressionShortcuts/ExpressionContainer.cs b/source/Handlebars/ExpressionShortcuts/ExpressionContainer.cs new file mode 100644 index 00000000..8a3569db --- /dev/null +++ b/source/Handlebars/ExpressionShortcuts/ExpressionContainer.cs @@ -0,0 +1,49 @@ +using System.Linq.Expressions; + +namespace HandlebarsDotNet.ExpressionShortcuts +{ + /// + /// Wrapper around of to provide addition functionality + /// + internal class ExpressionContainer + { + /// + /// This can be used to wrap a -Type expression or implement a derived type + /// + /// + public ExpressionContainer(Expression expression) => Expression = expression; + + /// + /// Return the underling + /// + public virtual Expression Expression { get; } + + /// + /// Convenience for unwrapping ExpressionContainer + /// + /// + /// + public static implicit operator Expression(ExpressionContainer expressionContainer) => expressionContainer.Expression; + } + + /// + /// Provides strongly typed container for . + /// + /// Used to trick C# compiler in cases like in order to pass value to target method. + /// Type of expected result value. + internal class ExpressionContainer : ExpressionContainer + { + /// + /// Used to trick C# compiler - calls need to be removed through + /// + public static implicit operator T(ExpressionContainer _0) => default(T)!; + + /// + /// Wrap an expression of Type + /// + /// + public ExpressionContainer(Expression expression) : base(expression) + { + } + } +} \ No newline at end of file diff --git a/source/Handlebars/ExpressionShortcuts/ExpressionContainerExtensions.cs b/source/Handlebars/ExpressionShortcuts/ExpressionContainerExtensions.cs new file mode 100644 index 00000000..2eacf4d8 --- /dev/null +++ b/source/Handlebars/ExpressionShortcuts/ExpressionContainerExtensions.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; + +namespace HandlebarsDotNet.ExpressionShortcuts +{ + internal static partial class ExpressionShortcuts + { + /// + /// Creates strongly typed representation of the + /// + /// + /// Property accessor expression + /// Expected type of resulting target + /// Expected type of resulting + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Property(this ExpressionContainer instance, Expression> propertyAccessor) + { + var visitor = new ReplaceParameterVisitor(propertyAccessor.Parameters[0], instance); + var combinedExpression = visitor.Visit(propertyAccessor.Body); + return new ExpressionContainer(combinedExpression); + } + + /// + /// Creates or based on . + /// Parameters are resolved based on actual passed parameters. + /// + /// + /// Expression used to invoke the method. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Call(this ExpressionContainer instance, Expression> invocationExpression) + { + var visitor = new ReplaceParameterVisitor(invocationExpression.Parameters[0], instance); + var combinedExpression = visitor.Visit(invocationExpression.Body); + return new ExpressionContainer(combinedExpression); + } + + /// + /// Creates or based on . + /// Parameters are resolved based on actual passed parameters. + /// + /// + /// Expression used to invoke the method. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Call(this ExpressionContainer instance, Expression> invocationExpression) + { + var visitor = new ReplaceParameterVisitor(invocationExpression.Parameters[0], instance); + var combinedExpression = visitor.Visit(invocationExpression.Body); + return new ExpressionContainer(combinedExpression); + } + + /// + /// Creates assign . + /// Parameters are resolved based on actual passed parameters. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Assign(this ExpressionContainer target, ExpressionContainer value) + { + return new ExpressionContainer(Expression.Assign(target, value)); + } + + /// + /// Creates assign . + /// Parameters are resolved based on actual passed parameters. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Assign(this ExpressionContainer target, T value) + { + return new ExpressionContainer(Expression.Assign(target, Expression.Constant(value, typeof(T)))); + } + } +} \ No newline at end of file diff --git a/source/Handlebars/ExpressionShortcuts/ExpressionShortcuts.cs b/source/Handlebars/ExpressionShortcuts/ExpressionShortcuts.cs new file mode 100644 index 00000000..0ff12776 --- /dev/null +++ b/source/Handlebars/ExpressionShortcuts/ExpressionShortcuts.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; + +namespace HandlebarsDotNet.ExpressionShortcuts +{ + /// + /// Stands for shortcuts. + /// + internal static partial class ExpressionShortcuts + { + /// + /// Creates strongly typed representation of the + /// + /// to wrap + /// Expected type of resulting + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Arg(Expression expression) => new ExpressionContainer(expression); + + /// + /// Creates strongly typed representation of the + /// + /// to wrap + /// Expected type of resulting + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Arg(T value) => new ExpressionContainer(Expression.Constant(value, typeof(T))); + + /// + /// Creates strongly typed representation of the . + /// + /// to wrap + /// Expected type of resulting + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Arg(Expression expression) => new ExpressionContainer(expression); + + /// + /// Creates strongly typed representation of the and performs on it. + /// + /// to wrap + /// Expected type of resulting + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Cast(Expression expression) => new ExpressionContainer(Expression.Convert(expression, typeof(T))); + + /// + /// Creates strongly typed representation of the + /// + /// Variable name. Corresponds to type name if omitted. + /// Expected type of resulting + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Parameter(string? name = null) + { + return new ExpressionContainer(Expression.Parameter(typeof(T), name ?? typeof(T).Name)); + } + + /// + /// Creates strongly typed representation of the + /// + /// Items for the new array + /// Expected type of resulting + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Array(IEnumerable items) + { + return Arg(Expression.NewArrayInit(typeof(T), items)); + } + + /// + /// Creates or based on . + /// Parameters are resolved based on actual passed parameters. + /// + /// Expression used to invoke the method. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Call(Expression invocationExpression) + { + return new ExpressionContainer(UnpackExpressionContainerVisitor.Instance.Visit(invocationExpression.Body)); + } + + /// + /// Creates or based on . + /// Parameters are resolved based on actual passed parameters. + /// + /// Expression used to invoke the method. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer Call(Expression> invocationExpression) + { + return new ExpressionContainer(UnpackExpressionContainerVisitor.Instance.Visit(invocationExpression.Body)); + } + + /// + /// Creates . Parameters for constructor and constructor itself are resolved based . + /// + /// Expression used to invoke the method. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExpressionContainer New(Expression> invocationExpression) + { + return new ExpressionContainer(UnpackExpressionContainerVisitor.Instance.Visit(invocationExpression.Body)); + } + + /// + /// Provides fluent interface for creation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BlockBuilder Block(Type? returnType = null) + { + return new BlockBuilder(returnType); + } + + /// + /// Creates strongly typed representation of null. + /// + /// Expected type of resulting + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ExpressionContainer Null() + { + return Arg(Expression.Constant(null, typeof(T))); + } + } +} \ No newline at end of file diff --git a/source/Handlebars/ExpressionShortcuts/LICENSE b/source/Handlebars/ExpressionShortcuts/LICENSE new file mode 100644 index 00000000..9057436c --- /dev/null +++ b/source/Handlebars/ExpressionShortcuts/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Oleh Formaniuk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/source/Handlebars/ExpressionShortcuts/ReplaceParameterVisitor.cs b/source/Handlebars/ExpressionShortcuts/ReplaceParameterVisitor.cs new file mode 100644 index 00000000..7fd39b74 --- /dev/null +++ b/source/Handlebars/ExpressionShortcuts/ReplaceParameterVisitor.cs @@ -0,0 +1,15 @@ +using System.Linq.Expressions; + +namespace HandlebarsDotNet.ExpressionShortcuts +{ + internal sealed class ReplaceParameterVisitor(ParameterExpression parameter, Expression argument) + : UnpackExpressionContainerVisitor + { + protected override Expression VisitParameter(ParameterExpression node) + { + return ReferenceEquals(node, parameter) + ? argument + : base.VisitParameter(node); + } + } +} \ No newline at end of file diff --git a/source/Handlebars/ExpressionShortcuts/UnpackExpressionContainerVisitor.cs b/source/Handlebars/ExpressionShortcuts/UnpackExpressionContainerVisitor.cs new file mode 100644 index 00000000..cbc6048e --- /dev/null +++ b/source/Handlebars/ExpressionShortcuts/UnpackExpressionContainerVisitor.cs @@ -0,0 +1,66 @@ +using System.Linq.Expressions; +using System.Reflection; + +namespace HandlebarsDotNet.ExpressionShortcuts +{ + internal class UnpackExpressionContainerVisitor : ExpressionVisitor + { + public static readonly UnpackExpressionContainerVisitor Instance = new(); + + protected override Expression VisitMember(MemberExpression node) + { + if (node is + { + Member: FieldInfo fieldInfo, + Expression: {} nextedExpression + } + && typeof(ExpressionContainer).IsAssignableFrom(fieldInfo.FieldType)) + { + var nestedValue = ExtractFieldOrConstantValue(nextedExpression); + if (nestedValue != null + && fieldInfo.GetValue(nestedValue) is ExpressionContainer expressionContainer) + return expressionContainer.Expression; + } + + return base.VisitMember(node); + } + + protected override Expression VisitUnary(UnaryExpression node) + { + // Did somebody use the implicit operator to convert ExpressionContainer to T? + if (node.NodeType is ExpressionType.ConvertChecked or ExpressionType.Convert + && typeof(ExpressionContainer).IsAssignableFrom(node.Operand.Type)) + { + var operand = Visit(node.Operand); + + return operand.Type != node.Type + ? Expression.Convert(operand, node.Type) + : operand; + } + + return base.VisitUnary(node); + } + + // The C# compiler actually creates closure-objects that put values as fields on an anonymous object -> unwrap + private static object? ExtractFieldOrConstantValue(Expression node) + { + switch (node) + { + case ConstantExpression constantExpression: + return constantExpression.Value; + case MemberExpression + { + Member: FieldInfo fieldInfo, + Expression: { } nestedExpression + }: + { + // In certain cases the compiler may use nested structures. + var nestedValue = ExtractFieldOrConstantValue(nestedExpression); + return nestedValue == null ? null : fieldInfo.GetValue(nestedValue); + } + default: + return null; + } + } + } +} \ No newline at end of file diff --git a/source/Handlebars/Handlebars.csproj b/source/Handlebars/Handlebars.csproj index e15d5ba3..6358cd5c 100644 --- a/source/Handlebars/Handlebars.csproj +++ b/source/Handlebars/Handlebars.csproj @@ -41,8 +41,7 @@ - - +