using System; 
 | 
using System.Collections.Generic; 
 | 
using System.Linq; 
 | 
using System.Linq.Expressions; 
 | 
using System.Reflection; 
 | 
using System.Reflection.Emit; 
 | 
using System.Text; 
 | 
using Jace.Operations; 
 | 
using Jace.Util; 
 | 
  
 | 
namespace Jace.Execution 
 | 
{ 
 | 
    public class DynamicCompiler : IExecutor 
 | 
    { 
 | 
        private string FuncAssemblyQualifiedName; 
 | 
        private readonly bool caseSensitive; 
 | 
  
 | 
        public DynamicCompiler(): this(false) { } 
 | 
        public DynamicCompiler(bool caseSensitive) 
 | 
        { 
 | 
            this.caseSensitive = caseSensitive; 
 | 
            // The lower func reside in mscorelib, the higher ones in another assembly. 
 | 
            // This is  an easy cross platform way to to have this AssemblyQualifiedName. 
 | 
            FuncAssemblyQualifiedName = 
 | 
                typeof(Func<double, double, double, double, double, double, double, double, double, double>).GetTypeInfo().Assembly.FullName; 
 | 
        } 
 | 
  
 | 
        public double Execute(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry) 
 | 
        { 
 | 
            return Execute(operation, functionRegistry, constantRegistry, new Dictionary<string, double>()); 
 | 
        } 
 | 
  
 | 
        public double Execute(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry,  
 | 
            IDictionary<string, double> variables) 
 | 
        { 
 | 
            return BuildFormula(operation, functionRegistry, constantRegistry)(variables); 
 | 
        } 
 | 
  
 | 
        public Func<IDictionary<string, double>, double> BuildFormula(Operation operation, 
 | 
            IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry) 
 | 
        { 
 | 
            Func<FormulaContext, double> func = BuildFormulaInternal(operation, functionRegistry); 
 | 
            return caseSensitive 
 | 
                ? (Func<IDictionary<string, double>, double>)(variables => 
 | 
                { 
 | 
                    return func(new FormulaContext(variables, functionRegistry, constantRegistry)); 
 | 
                }) 
 | 
                : (Func<IDictionary<string, double>, double>)(variables => 
 | 
                { 
 | 
                    variables = EngineUtil.ConvertVariableNamesToLowerCase(variables); 
 | 
                    FormulaContext context = new FormulaContext(variables, functionRegistry, constantRegistry); 
 | 
                    return func(context); 
 | 
                }); 
 | 
        } 
 | 
  
 | 
        private Func<FormulaContext, double> BuildFormulaInternal(Operation operation,  
 | 
            IFunctionRegistry functionRegistry) 
 | 
        { 
 | 
            ParameterExpression contextParameter = Expression.Parameter(typeof(FormulaContext), "context"); 
 | 
  
 | 
            LabelTarget returnLabel = Expression.Label(typeof(double)); 
 | 
  
 | 
            Expression<Func<FormulaContext, double>> lambda = Expression.Lambda<Func<FormulaContext, double>>( 
 | 
                GenerateMethodBody(operation, contextParameter, functionRegistry), 
 | 
                contextParameter 
 | 
            ); 
 | 
            return lambda.Compile(); 
 | 
        } 
 | 
  
 | 
         
 | 
  
 | 
        private Expression GenerateMethodBody(Operation operation, ParameterExpression contextParameter, 
 | 
            IFunctionRegistry functionRegistry) 
 | 
        { 
 | 
            if (operation == null) 
 | 
                throw new ArgumentNullException("operation"); 
 | 
  
 | 
            if (operation.GetType() == typeof(IntegerConstant)) 
 | 
            { 
 | 
                IntegerConstant constant = (IntegerConstant)operation; 
 | 
  
 | 
                double value = constant.Value; 
 | 
                return Expression.Constant(value, typeof(double)); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(FloatingPointConstant)) 
 | 
            { 
 | 
                FloatingPointConstant constant = (FloatingPointConstant)operation; 
 | 
  
 | 
                return Expression.Constant(constant.Value, typeof(double)); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(Variable)) 
 | 
            { 
 | 
                Variable variable = (Variable)operation; 
 | 
  
 | 
                Func<string, FormulaContext, double> getVariableValueOrThrow = PrecompiledMethods.GetVariableValueOrThrow; 
 | 
                return Expression.Call(null, 
 | 
                    getVariableValueOrThrow.GetMethodInfo(), 
 | 
                    Expression.Constant(variable.Name), 
 | 
                    contextParameter); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(Multiplication)) 
 | 
            { 
 | 
                Multiplication multiplication = (Multiplication)operation; 
 | 
                Expression argument1 = GenerateMethodBody(multiplication.Argument1, contextParameter, functionRegistry); 
 | 
                Expression argument2 = GenerateMethodBody(multiplication.Argument2, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Multiply(argument1, argument2); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(Addition)) 
 | 
            { 
 | 
                Addition addition = (Addition)operation; 
 | 
                Expression argument1 = GenerateMethodBody(addition.Argument1, contextParameter, functionRegistry); 
 | 
                Expression argument2 = GenerateMethodBody(addition.Argument2, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Add(argument1, argument2); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(Subtraction)) 
 | 
            { 
 | 
                Subtraction addition = (Subtraction)operation; 
 | 
                Expression argument1 = GenerateMethodBody(addition.Argument1, contextParameter, functionRegistry); 
 | 
                Expression argument2 = GenerateMethodBody(addition.Argument2, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Subtract(argument1, argument2); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(Division)) 
 | 
            { 
 | 
                Division division = (Division)operation; 
 | 
                Expression dividend = GenerateMethodBody(division.Dividend, contextParameter, functionRegistry); 
 | 
                Expression divisor = GenerateMethodBody(division.Divisor, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Divide(dividend, divisor); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(Modulo)) 
 | 
            { 
 | 
                Modulo modulo = (Modulo)operation; 
 | 
                Expression dividend = GenerateMethodBody(modulo.Dividend, contextParameter, functionRegistry); 
 | 
                Expression divisor = GenerateMethodBody(modulo.Divisor, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Modulo(dividend, divisor); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(Exponentiation)) 
 | 
            { 
 | 
                Exponentiation exponentation = (Exponentiation)operation; 
 | 
                Expression @base = GenerateMethodBody(exponentation.Base, contextParameter, functionRegistry); 
 | 
                Expression exponent = GenerateMethodBody(exponentation.Exponent, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Call(null, typeof(Math).GetRuntimeMethod("Pow", new Type[] { typeof(double), typeof(double) }), @base, exponent); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(UnaryMinus)) 
 | 
            { 
 | 
                UnaryMinus unaryMinus = (UnaryMinus)operation; 
 | 
                Expression argument = GenerateMethodBody(unaryMinus.Argument, contextParameter, functionRegistry); 
 | 
                return Expression.Negate(argument); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(And)) 
 | 
            { 
 | 
                And and = (And)operation; 
 | 
                Expression argument1 = Expression.NotEqual(GenerateMethodBody(and.Argument1, contextParameter, functionRegistry), Expression.Constant(0.0)); 
 | 
                Expression argument2 = Expression.NotEqual(GenerateMethodBody(and.Argument2, contextParameter, functionRegistry), Expression.Constant(0.0)); 
 | 
  
 | 
                return Expression.Condition(Expression.And(argument1, argument2), 
 | 
                    Expression.Constant(1.0), 
 | 
                    Expression.Constant(0.0)); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(Or)) 
 | 
            { 
 | 
                Or and = (Or)operation; 
 | 
                Expression argument1 = Expression.NotEqual(GenerateMethodBody(and.Argument1, contextParameter, functionRegistry), Expression.Constant(0.0)); 
 | 
                Expression argument2 = Expression.NotEqual(GenerateMethodBody(and.Argument2, contextParameter, functionRegistry), Expression.Constant(0.0)); 
 | 
  
 | 
                return Expression.Condition(Expression.Or(argument1, argument2), 
 | 
                    Expression.Constant(1.0), 
 | 
                    Expression.Constant(0.0)); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(LessThan)) 
 | 
            { 
 | 
                LessThan lessThan = (LessThan)operation; 
 | 
                Expression argument1 = GenerateMethodBody(lessThan.Argument1, contextParameter, functionRegistry); 
 | 
                Expression argument2 = GenerateMethodBody(lessThan.Argument2, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Condition(Expression.LessThan(argument1, argument2), 
 | 
                    Expression.Constant(1.0), 
 | 
                    Expression.Constant(0.0)); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(LessOrEqualThan)) 
 | 
            { 
 | 
                LessOrEqualThan lessOrEqualThan = (LessOrEqualThan)operation; 
 | 
                Expression argument1 = GenerateMethodBody(lessOrEqualThan.Argument1, contextParameter, functionRegistry); 
 | 
                Expression argument2 = GenerateMethodBody(lessOrEqualThan.Argument2, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Condition(Expression.LessThanOrEqual(argument1, argument2), 
 | 
                    Expression.Constant(1.0), 
 | 
                    Expression.Constant(0.0)); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(GreaterThan)) 
 | 
            { 
 | 
                GreaterThan greaterThan = (GreaterThan)operation; 
 | 
                Expression argument1 = GenerateMethodBody(greaterThan.Argument1, contextParameter, functionRegistry); 
 | 
                Expression argument2 = GenerateMethodBody(greaterThan.Argument2, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Condition(Expression.GreaterThan(argument1, argument2), 
 | 
                    Expression.Constant(1.0), 
 | 
                    Expression.Constant(0.0)); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(GreaterOrEqualThan)) 
 | 
            { 
 | 
                GreaterOrEqualThan greaterOrEqualThan = (GreaterOrEqualThan)operation; 
 | 
                Expression argument1 = GenerateMethodBody(greaterOrEqualThan.Argument1, contextParameter, functionRegistry); 
 | 
                Expression argument2 = GenerateMethodBody(greaterOrEqualThan.Argument2, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Condition(Expression.GreaterThanOrEqual(argument1, argument2), 
 | 
                    Expression.Constant(1.0), 
 | 
                    Expression.Constant(0.0)); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(Equal)) 
 | 
            { 
 | 
                Equal equal = (Equal)operation; 
 | 
                Expression argument1 = GenerateMethodBody(equal.Argument1, contextParameter, functionRegistry); 
 | 
                Expression argument2 = GenerateMethodBody(equal.Argument2, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Condition(Expression.Equal(argument1, argument2), 
 | 
                    Expression.Constant(1.0), 
 | 
                    Expression.Constant(0.0)); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(NotEqual)) 
 | 
            { 
 | 
                NotEqual notEqual = (NotEqual)operation; 
 | 
                Expression argument1 = GenerateMethodBody(notEqual.Argument1, contextParameter, functionRegistry); 
 | 
                Expression argument2 = GenerateMethodBody(notEqual.Argument2, contextParameter, functionRegistry); 
 | 
  
 | 
                return Expression.Condition(Expression.NotEqual(argument1, argument2), 
 | 
                    Expression.Constant(1.0), 
 | 
                    Expression.Constant(0.0)); 
 | 
            } 
 | 
            else if (operation.GetType() == typeof(Function)) 
 | 
            { 
 | 
                Function function = (Function)operation; 
 | 
  
 | 
                FunctionInfo functionInfo = functionRegistry.GetFunctionInfo(function.FunctionName); 
 | 
                Type funcType; 
 | 
                Type[] parameterTypes; 
 | 
                Expression[] arguments; 
 | 
  
 | 
                if (functionInfo.IsDynamicFunc) 
 | 
                { 
 | 
                    funcType = typeof(DynamicFunc<double, double>); 
 | 
                    parameterTypes = new Type[] { typeof(double[]) }; 
 | 
  
 | 
  
 | 
                    Expression[] arrayArguments = new Expression[function.Arguments.Count]; 
 | 
                    for (int i = 0; i < function.Arguments.Count; i++) 
 | 
                        arrayArguments[i] = GenerateMethodBody(function.Arguments[i], contextParameter, functionRegistry); 
 | 
  
 | 
                    arguments = new Expression[1]; 
 | 
                    arguments[0] = NewArrayExpression.NewArrayInit(typeof(double), arrayArguments); 
 | 
                } 
 | 
                else 
 | 
                { 
 | 
                    funcType = GetFuncType(functionInfo.NumberOfParameters); 
 | 
                    parameterTypes = (from i in Enumerable.Range(0, functionInfo.NumberOfParameters) 
 | 
                                             select typeof(double)).ToArray(); 
 | 
  
 | 
                    arguments = new Expression[functionInfo.NumberOfParameters]; 
 | 
                    for (int i = 0; i < functionInfo.NumberOfParameters; i++) 
 | 
                        arguments[i] = GenerateMethodBody(function.Arguments[i], contextParameter, functionRegistry); 
 | 
                } 
 | 
  
 | 
                Expression getFunctionRegistry = Expression.Property(contextParameter, "FunctionRegistry"); 
 | 
  
 | 
                ParameterExpression functionInfoVariable = Expression.Variable(typeof(FunctionInfo)); 
 | 
  
 | 
                Expression funcInstance; 
 | 
                if (!functionInfo.IsOverWritable) 
 | 
                { 
 | 
                    funcInstance = Expression.Convert( 
 | 
                        Expression.Property( 
 | 
                            Expression.Call( 
 | 
                                getFunctionRegistry, 
 | 
                                typeof(IFunctionRegistry).GetRuntimeMethod("GetFunctionInfo", new Type[] { typeof(string) }), 
 | 
                                Expression.Constant(function.FunctionName)), 
 | 
                            "Function"), 
 | 
                        funcType); 
 | 
                } 
 | 
                else 
 | 
                    funcInstance = Expression.Constant(functionInfo.Function, funcType); 
 | 
  
 | 
                return Expression.Call( 
 | 
                    funcInstance, 
 | 
                    funcType.GetRuntimeMethod("Invoke", parameterTypes), 
 | 
                    arguments); 
 | 
            } 
 | 
            else 
 | 
            { 
 | 
                throw new ArgumentException(string.Format("Unsupported operation \"{0}\".", operation.GetType().FullName), "operation"); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        private Type GetFuncType(int numberOfParameters) 
 | 
        { 
 | 
            string funcTypeName; 
 | 
            if (numberOfParameters < 9) 
 | 
                funcTypeName = string.Format("System.Func`{0}", numberOfParameters + 1); 
 | 
            else 
 | 
                funcTypeName = string.Format("System.Func`{0}, {1}", numberOfParameters + 1, FuncAssemblyQualifiedName); 
 | 
            Type funcType = Type.GetType(funcTypeName); 
 | 
  
 | 
            Type[] typeArguments = new Type[numberOfParameters + 1]; 
 | 
            for (int i = 0; i < typeArguments.Length; i++) 
 | 
                typeArguments[i] = typeof(double); 
 | 
  
 | 
            return funcType.MakeGenericType(typeArguments); 
 | 
        } 
 | 
  
 | 
        private static class PrecompiledMethods 
 | 
        { 
 | 
            public static double GetVariableValueOrThrow(string variableName, FormulaContext context) 
 | 
            { 
 | 
                if (context.Variables.TryGetValue(variableName, out double result)) 
 | 
                    return result; 
 | 
                else if (context.ConstantRegistry.IsConstantName(variableName)) 
 | 
                    return context.ConstantRegistry.GetConstantInfo(variableName).Value; 
 | 
                else 
 | 
                    throw new VariableNotDefinedException($"The variable \"{variableName}\" used is not defined."); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
} 
 |