Ejemplo n.º 1
0
        /// <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;
        }
Ejemplo n.º 2
0
        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);
        }