diff --git a/src/Hyperbee.Expressions/AsyncBlockExpression.cs b/src/Hyperbee.Expressions/AsyncBlockExpression.cs index 9c57da1..be33b77 100644 --- a/src/Hyperbee.Expressions/AsyncBlockExpression.cs +++ b/src/Hyperbee.Expressions/AsyncBlockExpression.cs @@ -51,19 +51,21 @@ public override Expression Reduce() private LoweringInfo LoweringTransformer() { - var visitor = new LoweringVisitor(); - - var loweringInfo = visitor.Transform( - Result.Type, - [.. Variables], - [.. Expressions], - ExternVariables != null ? [.. ExternVariables] : [] - ); - - if ( loweringInfo.AwaitCount == 0 ) - throw new InvalidOperationException( $"{nameof( AsyncBlockExpression )} must contain at least one {nameof( AwaitExpression )}." ); - - return loweringInfo; + try + { + var visitor = new LoweringVisitor(); + + return visitor.Transform( + Result.Type, + [.. Variables], + [.. Expressions], + ExternVariables != null ? [.. ExternVariables] : [] + ); + } + catch ( LoweringException ex ) + { + throw new InvalidOperationException( $"Unable to lower {nameof(AsyncBlockExpression)}.", ex ); + } } protected override Expression VisitChildren( ExpressionVisitor visitor ) diff --git a/src/Hyperbee.Expressions/Transformation/LoweringException.cs b/src/Hyperbee.Expressions/Transformation/LoweringException.cs new file mode 100644 index 0000000..2342859 --- /dev/null +++ b/src/Hyperbee.Expressions/Transformation/LoweringException.cs @@ -0,0 +1,6 @@ +namespace Hyperbee.Expressions.Transformation; + +internal class LoweringException : Exception +{ + public LoweringException( string message ) : base( message ) { } +} diff --git a/src/Hyperbee.Expressions/Transformation/LoweringVisitor.cs b/src/Hyperbee.Expressions/Transformation/LoweringVisitor.cs index 318bfd3..e9be940 100644 --- a/src/Hyperbee.Expressions/Transformation/LoweringVisitor.cs +++ b/src/Hyperbee.Expressions/Transformation/LoweringVisitor.cs @@ -21,11 +21,18 @@ internal class LoweringVisitor : ExpressionVisitor public LoweringInfo Transform( Type resultType, ParameterExpression[] variables, Expression[] expressions, ParameterExpression[] externVariables ) { + ArgumentNullException.ThrowIfNull( expressions, nameof( expressions ) ); + ArgumentOutOfRangeException.ThrowIfZero( expressions.Length, nameof( expressions ) ); + _variableResolver = new VariableResolver( variables, _states ); _finalResultVariable = CreateFinalResultVariable( resultType, _variableResolver ); VisitExpressions( expressions ); + StateOptimizer.Optimize( _states ); + + ThrowIfInvalid(); + return new LoweringInfo { Scopes = _states.Scopes, @@ -35,6 +42,17 @@ public LoweringInfo Transform( Type resultType, ParameterExpression[] variables, ExternVariables = externVariables }; + // helpers + + void ThrowIfInvalid() + { + if ( _states.Scopes[0].States.Count == 0 ) + throw new LoweringException( $"Evaluation of the {nameof(expressions)} parameter resulted in empty states." ); + + if ( _awaitCount == 0 ) + throw new LoweringException( $"The {nameof(expressions)} parameter must contain at least one awaitable." ); + } + static ParameterExpression CreateFinalResultVariable( Type resultType, VariableResolver resolver ) { var finalResultType = resultType == typeof( void ) diff --git a/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs b/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs index 6e91915..08a66ae 100644 --- a/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs +++ b/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs @@ -34,16 +34,12 @@ public StateMachineBuilder( ModuleBuilder moduleBuilder, string typeName ) public Expression CreateStateMachine( LoweringTransformer loweringTransformer, int id ) { + ArgumentNullException.ThrowIfNull( loweringTransformer, nameof(loweringTransformer) ); + // Lower the async expression // var loweringInfo = loweringTransformer(); - if ( loweringInfo.AwaitCount == 0 ) - throw new InvalidOperationException( "The target of a lowering operation must contain at least one await." ); - - if ( loweringInfo.Scopes[0].States == null ) - throw new InvalidOperationException( "States must be set before creating state machine." ); - // Create the state-machine builder context // var context = new StateMachineContext { LoweringInfo = loweringInfo }; @@ -327,10 +323,6 @@ private static IEnumerable CreateBody( FieldInfo[] fields, StateMach var scopes = loweringInfo.Scopes; - // Optimize source nodes - - StateMachineOptimizer.Optimize( loweringInfo ); - // Create the body expressions var firstScope = scopes[0]; diff --git a/src/Hyperbee.Expressions/Transformation/StateMachineOptimizer.cs b/src/Hyperbee.Expressions/Transformation/StateOptimizer.cs similarity index 94% rename from src/Hyperbee.Expressions/Transformation/StateMachineOptimizer.cs rename to src/Hyperbee.Expressions/Transformation/StateOptimizer.cs index 902b785..0c11c82 100644 --- a/src/Hyperbee.Expressions/Transformation/StateMachineOptimizer.cs +++ b/src/Hyperbee.Expressions/Transformation/StateOptimizer.cs @@ -3,14 +3,14 @@ namespace Hyperbee.Expressions.Transformation; -internal sealed class StateMachineOptimizer +internal sealed class StateOptimizer { - internal static void Optimize( LoweringInfo source ) + internal static void Optimize( StateContext states ) { var references = new HashSet(); int stateOrder = 0; - var scopes = source.Scopes; + var scopes = states.Scopes; foreach ( var scope in scopes ) {