/// <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 GenerateYieldMethod(CSharpSyntaxNode node, IMethodSymbol method) { var block = new JsBlockStatement(); var stateMachineBody = Js.Block(); var stateMachineFunc = block.Local("$stateMachineFunc", Js.Function().Body(stateMachineBody)); ITypeSymbol elementType = Context.Instance.ObjectType; if (method.ReturnType is INamedTypeSymbol && ((INamedTypeSymbol)method.ReturnType).IsGenericType) { var genericType = (INamedTypeSymbol)method.ReturnType; genericType = genericType.OriginalDefinition; if (Equals(genericType, Context.Instance.IEnumerableT)) elementType = method.ReturnType.GetGenericArgument(Context.Instance.IEnumerableT, 0); else if (Equals(genericType, Context.Instance.IEnumeratorT)) elementType = method.ReturnType.GetGenericArgument(Context.Instance.IEnumeratorT, 0); } // Declare state machine fields var @this = stateMachineBody.Local(BaseStateGenerator.@this, Js.This()); stateMachineBody.Local(BaseStateGenerator.state, Js.Primitive(0)); var stateMachine = stateMachineBody.Local(BaseStateGenerator.stateMachine, CreateObject(Context.Instance.YieldIterator.Construct(elementType).Constructors.Single())); // var current = stateMachineBody.Local(YieldStateGenerator2.current, DefaultValue(elementType)); // var isStarted = stateMachineBody.Local("$isStarted", Js.Primitive(false)); // Create state generator and generate states var stateGenerator = new YieldStateGenerator(x => transformer, node, stateMachineBody, this, method); stateGenerator.GenerateStates(); var rootState = stateGenerator.TopState; // Called when the initial method needs to return the enumerator, and also when cloning. // Declare the moveNext function var moveNextBody = Js.Block(); var moveNext = stateMachineBody.Local(BaseStateGenerator.moveNext, Js.Function().Body(moveNextBody)); moveNextBody.Add(Js.Label("$top", Js.While(Js.Primitive(true), Js.Block(stateGenerator.GenerateSwitch(rootState), Js.Primitive(false).Return())))); stateMachineBody.Add(MapInterfaceMethod(stateMachine.GetReference(), Context.Instance.YieldIteratorDoMoveNext, Js.Function().Body(moveNext.GetReference().Member("call").Invoke(@this.GetReference()).Return()))); // Declare the clone function var cloneBody = Js.Block((stateMachineFunc.GetReference().Member("call").Invoke(@this.GetReference()).Return())); stateMachineBody.Add(MapInterfaceMethod(stateMachine.GetReference(), Context.Instance.YieldIteratorClone, Js.Function().Body(cloneBody))); // Generate the GetEnumerator method, which looks something like: // if ($isStarted) // return this.Clone().GetEnumerator(); // else // { // $isStarted = true; // return this; // } /* var getEnumeratorBody = Js.Block(); var getEnumerator = stateMachineBody.Local("$getEnumerator", Js.Function().Body(getEnumeratorBody)); getEnumeratorBody.Add(Js.If( isStarted.GetReference(), Invoke(clone.GetReference().Invoke(), Context.Instance.IEnumerableTGetEnumerator).Return(), Js.Block(isStarted.SetReference().Assign(Js.Primitive(true)).Express(), stateMachine.GetReference().Return()) )); */ /* ImplementInterfaceOnAdhocObject(stateMachineBody, stateMachine, Context.Instance.IEnumerable, new Dictionary<IMethodSymbol, JsExpression> { { Context.Instance.IEnumerableGetEnumerator, Js.Function().Body(getEnumerator.GetReference().Member("call").Invoke(@this.GetReference()).Return()) } }); ImplementInterfaceOnAdhocObject(stateMachineBody, stateMachine, Context.Instance.IEnumerableT, new Dictionary<IMethodSymbol, JsExpression> { { Context.Instance.IEnumerableTGetEnumerator, stateMachine.GetReference().Member(Context.Instance.IEnumerableGetEnumerator.GetMemberName()) } }); ImplementInterfaceOnAdhocObject(stateMachineBody, stateMachine, Context.Instance.IEnumerator, new Dictionary<IMethodSymbol, JsExpression> { { Context.Instance.IEnumeratorMoveNext, Js.Function().Body(moveNext.GetReference().Member("call").Invoke(@this.GetReference()).Return()) }, { Context.Instance.IEnumeratorCurrent.GetMethod, Js.Function().Body(current.GetReference().Return()) }, { Context.Instance.IEnumeratorReset, Js.Function() } }); ImplementInterfaceOnAdhocObject(stateMachineBody, stateMachine, Context.Instance.IEnumeratorT, new Dictionary<IMethodSymbol, JsExpression> { { Context.Instance.IEnumeratorTCurrent.GetMethod, Js.Function().Body(current.GetReference().Return()) } }); */ // 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. stateMachineBody.Return(stateMachine.GetReference()); block.Add(stateMachineFunc.GetReference().Member("call").Invoke(Js.This()).Return()); return block; }
public ClassDeclarationSyntax CreateEnumerator() { var members = new List <MemberDeclarationSyntax>(); FieldDeclarationSyntax thisField = null; if (!method.IsStatic) { thisField = Cs.Field(method.ContainingType.ToTypeSyntax(), "$this"); members.Add(thisField); } var stateGenerator = new YieldStateGenerator(compilation, node); stateGenerator.GenerateStates(); var states = stateGenerator.States; var isStartedField = Cs.Field(Cs.Bool(), isStarted); members.Add(isStartedField); var stateField = Cs.Field(Cs.Int(), StateGenerator.state); members.Add(stateField); 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 = "YieldEnumerator$" + 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.Assign(Cs.This().Member(x.Identifier), SyntaxFactory.IdentifierName(x.Identifier)))) ) .AddStatements( Cs.Express(Cs.Assign(Cs.This().Member(StateGenerator.state), Cs.Integer(1))) ) ); members.Add(constructor); var elementType = ((INamedTypeSymbol)method.ReturnType).TypeArguments[0]; var ienumerable = SyntaxFactory.ParseTypeName("System.Collections.Generic.IEnumerable<" + elementType.ToDisplayString() + ">"); var ienumerator = SyntaxFactory.ParseTypeName("System.Collections.Generic.IEnumerator<" + elementType.ToDisplayString() + ">"); // Generate the GetEnumerator method, which looks something like: // var $isStartedLocal = $isStarted; // $isStarted = true; // if ($isStartedLocal) // return this.Clone().GetEnumerator(); // else // return this; var getEnumerator = SyntaxFactory.MethodDeclaration(ienumerator, "GetEnumerator") .AddModifiers(Cs.Public(), Cs.Override()) .WithBody(Cs.Block( Cs.Local(isStartedLocal, SyntaxFactory.IdentifierName(isStarted)), Cs.Express(SyntaxFactory.IdentifierName(isStarted).Assign(Cs.True())), Cs.If( SyntaxFactory.IdentifierName(isStartedLocal), Cs.Return(Cs.This().Member("Clone").Invoke().Member("GetEnumerator").Invoke()), Cs.Return(Cs.This())))); members.Add(getEnumerator); // 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(StateGenerator.state), states.Select((x, i) => Cs.Section(Cs.Integer(i), x.Statements.ToArray())).ToArray()))); var moveNext = SyntaxFactory.MethodDeclaration(Cs.Bool(), "MoveNext") .AddModifiers(Cs.Public(), Cs.Override()) .WithBody(SyntaxFactory.Block(moveNextBody)); members.Add(moveNext); TypeSyntax classNameWithTypeArguments = SyntaxFactory.IdentifierName(className); if (method.TypeParameters.Any()) { classNameWithTypeArguments = SyntaxFactory.GenericName( SyntaxFactory.Identifier(className), SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList( method.TypeParameters.Select(x => SyntaxFactory.ParseTypeName(x.Name)), method.TypeParameters.Select(x => x).Skip(1).Select(_ => SyntaxFactory.Token(SyntaxKind.CommaToken))) )); } var cloneBody = Cs.Block( Cs.Return(classNameWithTypeArguments.New( constructorParameters.Select(x => SyntaxFactory.IdentifierName(x.Identifier)).ToArray() )) ); var clone = SyntaxFactory.MethodDeclaration(ienumerable, "Clone") .AddModifiers(Cs.Public()) .WithBody(SyntaxFactory.Block(cloneBody)); members.Add(clone); var baseTypes = new[] { SyntaxFactory.ParseTypeName("System.YieldIterator<" + elementType.ToDisplayString() + ">") }; var result = SyntaxFactory.ClassDeclaration(className).WithBaseList(baseTypes).WithMembers(members.ToArray()); if (method.TypeParameters.Any()) { result = result.WithTypeParameterList(node.TypeParameterList); } return(result); }