csharp/71/Cometary/src/Cometary.Expressions/Expressions/StateMachines/StateMachineExpression.cs

StateMachineExpression.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Cometary.Expressions
{
    /// 
    /// Represents an  that outputs a
    /// , and saves its local variables in
    /// an array in order to have a persistent state.
    /// 
    public abstract clast StateMachineExpression : Expression
    {
        private readonly IList labelTargets;
        private readonly TrackingExpressionVisitor trackingVisitor;

        private ConstructorInfo _vsmCtor;
        private ParameterExpression _state;
        private Type _type;
        private LabelTarget _end;

        /// 
        /// Gets the  that represents the
        /// current state of the state machine as a .
        /// 
        protected ParameterExpression State => _state;

        /// 
        public sealed override bool CanReduce => true;

        /// 
        public sealed override ExpressionType NodeType => ExpressionType.Extension;

        /// 
        public abstract override Type Type { get; }

        /// 
        /// Gets the type of the lambda generated by this .
        /// 
        public Type LambdaType => _type ?? throw new InvalidOperationException();

        /// 
        /// Gets the  used when returning from the lambda.
        /// 
        protected LabelTarget End => _end ?? throw new InvalidOperationException();

        /// 
        /// Initializes the .
        /// 
        /// 
        /// The return type of the state machine. Can be , in which
        /// case  must be called at the end of the inheriting constructor.
        /// 
        /// 
        /// The type of the state machine; must inherit .
        /// 
        protected StateMachineExpression()
        {
            trackingVisitor = new TrackingExpressionVisitor(Project);
            labelTargets = new LightList();
        }

        /// 
        /// Sets the return type of the lambda to generate, and the type of the state machine used.
        /// 
        protected void SetType(Type lambdaType, Type smType)
        {
            Requires.NotNull(lambdaType, nameof(lambdaType));
            Requires.NotNull(smType, nameof(smType));

            _type = lambdaType;
            _end  = Label(lambdaType, "end");

            _state = Parameter(smType, "state");
            _vsmCtor = smType.GetTypeInfo().DeclaredConstructors.First(x => x.GetParameters().Length == 0);
        }

        /// 
        /// Generates a  that wraps the given ,
        /// saving its variables into a state, and allowing it to return multiple times without
        /// losing its inner state.
        /// 
        protected LambdaExpression GenerateLambda(Expression body, params ParameterExpression[] parameters)
        {
            body = trackingVisitor.Visit(body);

            // Create the switch cases to go to our previous state.
            LabelTarget[] targets = labelTargets.ToArray();
            SwitchCase[] cases = new SwitchCase[targets.Length + 1];

            LabelTarget startLabel = Label("start");
            cases[0] = SwitchCase(Goto(startLabel), Constant(0));

            for (int i = 1; i < cases.Length; i++)
            {
                cases[i] = SwitchCase(Goto(targets[i - 1]), Constant(i));
            }

            // Add a switch to the beginning of the body
            body = Block(
                Switch(
                    Field(State, VirtualStateMachine.StateField),
                    Throw(New(typeof(ArgumentOutOfRangeException))),
                    cases
                ),
                Label(startLabel),
                body,
                astign(Field(State, VirtualStateMachine.StateField), Constant(cases.Length)),
                Label(End, Default(End.Type))
            );

            return Lambda(
                trackingVisitor.GenerateBody(body, State),
                parameters.Prepend(State)
            );
        }

        /// 
        /// Returns a  wrapping the body.
        /// 
        /// Using 
        /// to generate said  is recommended.
        /// 
        /// 
        protected abstract LambdaExpression ToLambda();


        /// 
        /// Transforms the given  into another one, and returns the latter.
        /// 
        protected virtual Expression Project(Expression node) => node;


        /// 
        /// Returns an  that calls the lambda
        /// generated by this .
        /// 
        /// 
        /// This method should only be overriden if additional parameters are added.
        /// 
        public override Expression Reduce() => Constant(Compile());


        /// 
        /// Produces a  representing the
        /// inner expression tree.
        /// 
        protected VirtualStateMachine Compile()
        {
            LambdaExpression lambda = ToLambda();
            VirtualStateMachine vsm = (VirtualStateMachine)_vsmCtor.Invoke(null);

            vsm.Initialize(trackingVisitor.Variables.ToArray(), labelTargets.Count + 1, lambda.Compile());

            return vsm;
        }

        /// 
        /// Produces a  representing the
        /// inner expression tree, as an object of type .
        /// 
        protected TSM Compile() where TSM : clast => Compile() as TSM;


        /// 
        /// Returns an  that directly returns the
        /// given .
        /// 
        protected Expression Return(Expression value)
        {
            int nth = labelTargets.Count + 1;
            LabelTarget label = Label($"L{nth}");

            labelTargets.Add(label);

            return Block(
                astign(Field(State, VirtualStateMachine.StateField), Constant(nth)),
                Return(End, value),
                Label(label)
            );
        }

        /// 
        /// Returns an  that directly returns.
        /// 
        protected Expression Return() => Return(null);
    }
}