public AsyncExpressionDecomposer(AsyncStateGenerator stateGenerator, Idioms idioms) : base(idioms) { this.stateGenerator = stateGenerator; }
/// <summary> /// This generates the entire state machine as an inline function. State machine fields are represented /// as closed variables of an outer function. The actual "MoveNext" function is an inner function. /// </summary> public JsBlockStatement GenerateAsyncMethod(CSharpSyntaxNode node, IMethodSymbol method, Action<BaseStateGenerator, JsTransformer> nodeAcceptor = null) { var block = new JsBlockStatement(); var stateMachineBody = Js.Block(); var stateMachine = block.Local(BaseStateGenerator.stateMachine, Js.Function().Body(stateMachineBody)); // Declare state machine fields var @this = stateMachineBody.Local(BaseStateGenerator.@this, Js.This()); var state = stateMachineBody.Local(BaseStateGenerator.state, Js.Primitive(0)); var builder = stateMachineBody.Local(AsyncStateGenerator.builder, InvokeStatic(GetAsyncMethodBuilder(method))); // Start up the async process via a call to the builder's Start method. var moveNextBody = Js.Block(); var moveNext = stateMachineBody.Local(BaseStateGenerator.moveNext, Js.Function().Body(moveNextBody)); // Create state generator and generate states var stateGenerator = new AsyncStateGenerator(this, stateMachineBody, node, method, nodeAcceptor); stateGenerator.GenerateStates(); var rootState = stateGenerator.TopState; // Implement moveNext var moveNextTry = Js.Try(); moveNextBody.Add(moveNextTry); moveNextTry.Body = Js.Block( Js.Label("$top", Js.While(Js.Primitive(true), Js.Block(stateGenerator.GenerateSwitch(rootState), Js.Break()))) ); var moveNextCatchException = Js.Variable("$ex"); moveNextTry.Catch(moveNextCatchException); moveNextTry.Catch.Body = Js.Block( state.SetReference().Assign(Js.Primitive(-1)).Express(), builder.GetReference().Member("SetException").Invoke(moveNextCatchException.GetReference()).Express(), Js.Return() ); // Ensure the stateMachine function implements IAsyncStateMachine ImplementInterfaceOnAdhocObject(stateMachineBody, stateMachine, Context.Instance.IAsyncStateMachine, new Dictionary<IMethodSymbol, JsExpression> { { Context.Instance.IAsyncStateMachineMoveNext, Js.Function().Body(moveNext.GetReference().Member("call").Invoke(@this.GetReference()).Return()) }, { Context.Instance.IAsyncStateMachineSetStateMachine, Js.Null() } }); stateMachineBody.Express(builder.GetReference().Member("TrueStart").Invoke(stateMachine.GetReference())); // The state machine function will be invoked by the outer body. It will expect the task // to be returned for non-void async methods. This task will be returned to the original // caller of the async method. if (!method.ReturnsVoid) stateMachineBody.Return(builder.GetReference().Member("get_Task").Invoke()); var invokeStateMachine = stateMachine.GetReference().Member("call").Invoke(Js.This()); if (!method.ReturnsVoid) block.Add(invokeStateMachine.Return()); else block.Add(invokeStateMachine.Express()); return block; }
public ClassDeclarationSyntax CreateStateMachine() { var members = new List <MemberDeclarationSyntax>(); FieldDeclarationSyntax thisField = null; if (!method.IsStatic) { thisField = Cs.Field(method.ContainingType.ToTypeSyntax(), "$this"); members.Add(thisField); } var stateGenerator = new AsyncStateGenerator(compilation, node); stateGenerator.GenerateStates(); var states = stateGenerator.States; var stateField = Cs.Field(Cs.Int(), state); members.Add(stateField); var builderField = Cs.Field(Context.Instance.AsyncVoidMethodBuilder.ToTypeSyntax(), builder); members.Add(builderField); foreach (var parameter in node.ParameterList.Parameters) { var parameterField = Cs.Field(parameter.Type, parameter.Identifier); members.Add(parameterField); } foreach (var variable in stateGenerator.HoistedVariables) { var variableField = Cs.Field(variable.Item2, variable.Item1); members.Add(variableField); } var className = "Async$" + method.GetMemberName(); var constructorParameters = new List <ParameterSyntax>(); if (!method.IsStatic) { constructorParameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier("$this")).WithType(thisField.Declaration.Type)); } constructorParameters.AddRange(node.ParameterList.Parameters.Select(x => SyntaxFactory.Parameter(x.Identifier).WithType(x.Type))); var constructor = SyntaxFactory.ConstructorDeclaration(className) .AddModifiers(Cs.Public()) .WithParameterList(constructorParameters.ToArray()) .WithBody( SyntaxFactory.Block( // Assign fields constructorParameters.Select(x => Cs.Express(Cs.This().Member(x.Identifier).Assign(SyntaxFactory.IdentifierName(x.Identifier)))) ) .AddStatements( Cs.Express(Cs.This().Member(state).Assign(Cs.Integer(-1))), Cs.Express(Cs.This().Member(builder).Assign(builderField.Declaration.Type.New())), Cs.Express(Cs.This().Member(builder).Member("Start").Invoke(Cs.This())) ) ); members.Add(constructor); // Generate the MoveNext method, which looks something like: // $top: // while (true) // { // switch (state) // { // case 0: ... // case 1: ... // } // } var moveNextBody = SyntaxFactory.LabeledStatement("$top", Cs.While(Cs.True(), Cs.Switch(Cs.This().Member(state), states.Select((x, i) => Cs.Section(Cs.Integer(i), x.Statements.ToArray())).ToArray()))); var moveNext = SyntaxFactory.MethodDeclaration(Cs.Bool(), "MoveNext") .AddModifiers(Cs.Public()) .WithBody(SyntaxFactory.Block(moveNextBody)); members.Add(moveNext); var asyncStateMachine = SyntaxFactory.ParseTypeName("System.Runtime.CompilerServices.IAsyncStateMachine"); var baseTypes = new[] { asyncStateMachine }; var result = SyntaxFactory.ClassDeclaration(className).WithBaseList(baseTypes).WithMembers(members.ToArray()); if (method.TypeParameters.Any()) { result = result.WithTypeParameterList(node.TypeParameterList); } return(result); }