private L.Expression WithCommonNumericType(L.Expression left, L.Expression right,
                                                   Func <L.Expression, L.Expression, L.Expression> action, BinaryExpressionType expressiontype = BinaryExpressionType.Unknown)
        {
            left  = UnwrapNullable(left);
            right = UnwrapNullable(right);

            if (_options.HasFlag(EvaluateOptions.BooleanCalculation))
            {
                if (left.Type == typeof(bool))
                {
                    left = L.Expression.Condition(left, L.Expression.Constant(1.0), L.Expression.Constant(0.0));
                }

                if (right.Type == typeof(bool))
                {
                    right = L.Expression.Condition(right, L.Expression.Constant(1.0), L.Expression.Constant(0.0));
                }
            }

            var precedence = new[]
            {
                typeof(decimal),
                typeof(double),
                typeof(float),
                typeof(ulong),
                typeof(long),
                typeof(uint),
                typeof(int),
                typeof(ushort),
                typeof(short),
                typeof(byte),
                typeof(sbyte)
            };

            int l = Array.IndexOf(precedence, left.Type);
            int r = Array.IndexOf(precedence, right.Type);

            if (l >= 0 && r >= 0)
            {
                var type = precedence[Math.Min(l, r)];
                if (left.Type != type)
                {
                    left = L.Expression.Convert(left, type);
                }

                if (right.Type != type)
                {
                    right = L.Expression.Convert(right, type);
                }
            }
            L.Expression comparer = null;
            if (IgnoreCaseString)
            {
                if (Ordinal)
                {
                    comparer = L.Expression.Property(null, typeof(StringComparer), "OrdinalIgnoreCase");
                }
                else
                {
                    comparer = L.Expression.Property(null, typeof(StringComparer), "CurrentCultureIgnoreCase");
                }
            }
            else
            {
                comparer = L.Expression.Property(null, typeof(StringComparer), "Ordinal");
            }

            if (comparer != null && (typeof(string).Equals(left.Type) || typeof(string).Equals(right.Type)))
            {
                switch (expressiontype)
                {
                case BinaryExpressionType.Equal: return(L.Expression.Call(comparer, typeof(StringComparer).GetRuntimeMethod("Equals", new[] { typeof(string), typeof(string) }), new L.Expression[] { left, right }));

                case BinaryExpressionType.NotEqual: return(L.Expression.Not(L.Expression.Call(comparer, typeof(StringComparer).GetRuntimeMethod("Equals", new[] { typeof(string), typeof(string) }), new L.Expression[] { left, right })));

                case BinaryExpressionType.GreaterOrEqual: return(L.Expression.GreaterThanOrEqual(L.Expression.Call(comparer, typeof(StringComparer).GetRuntimeMethod("Compare", new[] { typeof(string), typeof(string) }), new L.Expression[] { left, right }), L.Expression.Constant(0)));

                case BinaryExpressionType.LesserOrEqual: return(L.Expression.LessThanOrEqual(L.Expression.Call(comparer, typeof(StringComparer).GetRuntimeMethod("Compare", new[] { typeof(string), typeof(string) }), new L.Expression[] { left, right }), L.Expression.Constant(0)));

                case BinaryExpressionType.Greater: return(L.Expression.GreaterThan(L.Expression.Call(comparer, typeof(StringComparer).GetRuntimeMethod("Compare", new[] { typeof(string), typeof(string) }), new L.Expression[] { left, right }), L.Expression.Constant(0)));

                case BinaryExpressionType.Lesser: return(L.Expression.LessThan(L.Expression.Call(comparer, typeof(StringComparer).GetRuntimeMethod("Compare", new[] { typeof(string), typeof(string) }), new L.Expression[] { left, right }), L.Expression.Constant(0)));
                }
            }
            return(action(left, right));
        }
        public override void Visit(Function function)
        {
            var functionArgs = new FunctionArgs
            {
                Parameters = new Expression[function.Expressions.Length]
            };

            // Don't call parameters right now, instead let the function do it as needed.
            // Some parameters shouldn't be called, for instance, in a if(), the "not" value might be a division by zero
            // Evaluating every value could produce unexpected behaviour
            for (int i = 0; i < function.Expressions.Length; i++)
            {
                functionArgs.Parameters[i] = new Expression(function.Expressions[i], _options);
                functionArgs.Parameters[i].EvaluateFunction  += EvaluateFunction;
                functionArgs.Parameters[i].EvaluateParameter += EvaluateParameter;

                // Assign the parameters of the Expression to the arguments so that custom Functions and Parameters can use them
                functionArgs.Parameters[i].Parameters = Parameters;
            }

            OnEvaluateFunction(IgnoreCaseString ? function.Identifier.Name.ToLower() : function.Identifier.Name, functionArgs);

            var args = new L.Expression[function.Expressions.Length];

            for (int i = 0; i < function.Expressions.Length; i++)
            {
                function.Expressions[i].Accept(this);
                args[i] = _result;
            }

            switch (function.Identifier.Name.ToLowerInvariant())
            {
            case "abs":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Abs() takes exactly 1 argument");
                }

                var useDouble = _options.HasFlag(
                    EvaluateOptions.UseDoubleForAbsFunction);

                MethodInfo   absMethod;
                L.Expression absArg0;
                if (useDouble)
                {
                    absMethod = typeof(Math).GetRuntimeMethod(
                        nameof(Math.Abs),
                        new[] { typeof(double) });
                    absArg0 = L.Expression.Convert(args[0], typeof(double));
                }
                else
                {
                    absMethod = typeof(Math).GetRuntimeMethod(
                        nameof(Math.Abs),
                        new[] { typeof(decimal) });
                    absArg0 = L.Expression.Convert(args[0], typeof(decimal));
                }

                _result = L.Expression.Call(absMethod, absArg0);
                break;

            case "acos":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Acos() takes exactly 1 argument");
                }

                var acosMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Acos),
                    new[] { typeof(double) });
                var acosArg0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(acosMethod, acosArg0);
                break;

            case "asin":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Asin() takes exactly 1 argument");
                }

                var asinMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Asin),
                    new[] { typeof(double) });
                var asinArg0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(asinMethod, asinArg0);
                break;

            case "atan":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Atan() takes exactly 1 argument");
                }

                var atanMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Atan),
                    new[] { typeof(double) });
                var atanArgs0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(atanMethod, atanArgs0);
                break;

            case "ceiling":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Ceiling() takes exactly 1 argument");
                }

                var ceilingMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Ceiling),
                    new[] { typeof(double) });
                var ceilingArgs0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(ceilingMethod, ceilingArgs0);
                break;

            case "cos":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Cos() takes exactly 1 argument");
                }

                var cosMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Cos),
                    new[] { typeof(double) });
                var cosArgs0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(cosMethod, cosArgs0);
                break;

            case "exp":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Exp() takes exactly 1 argument");
                }

                var expMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Exp),
                    new[] { typeof(double) });
                var expArgs0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(expMethod, expArgs0);
                break;

            case "floor":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Floor() takes exactly 1 argument");
                }

                var floorMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Floor),
                    new[] { typeof(double) });
                var floorArgs0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(floorMethod, floorArgs0);
                break;

            case "ieeeremainder":
                if (function.Expressions.Length != 2)
                {
                    throw new ArgumentException("IEEEReaminer() takes exactly 2 arguments");
                }

                var ieeeMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.IEEERemainder),
                    new[] { typeof(double), typeof(double) });
                var ieeeMethodArgs0 = L.Expression.Convert(
                    args[0],
                    typeof(double));
                var ieeeMethodArgs1 = L.Expression.Convert(
                    args[1],
                    typeof(double));
                _result = L.Expression.Call(ieeeMethod, ieeeMethodArgs0, ieeeMethodArgs1);
                break;

            case "log":
                if (function.Expressions.Length != 2)
                {
                    throw new ArgumentException("Log() takes exactly 2 arguments");
                }

                var logMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Log),
                    new[] { typeof(double), typeof(double) });
                var logMethodArgs0 = L.Expression.Convert(
                    args[0],
                    typeof(double));
                var logMethodArgs1 = L.Expression.Convert(
                    args[1],
                    typeof(double));
                _result = L.Expression.Call(logMethod, logMethodArgs0, logMethodArgs1);
                break;

            case "log10":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Log10() takes exactly 1 argument");
                }

                var log10Method = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Log10),
                    new[] { typeof(double) });
                var log10Args0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(log10Method, log10Args0);
                break;

            case "round":
                var roundParameterCount = function.Expressions.Length;
                if (roundParameterCount == 0 || roundParameterCount > 2)
                {
                    throw new ArgumentException("Round() takes exactly 1 or 2 arguments");
                }

                var rounding =
                    _options.HasFlag(EvaluateOptions.RoundAwayFromZero)
                            ? MidpointRounding.AwayFromZero
                            : MidpointRounding.ToEven;

                if (roundParameterCount == 1)
                {
                    var roundMethod = typeof(Math).GetRuntimeMethod(
                        nameof(Math.Round),
                        new[]
                    {
                        typeof(double),
                        typeof(MidpointRounding)
                    });

                    var roundMethodArg0 = L.Expression.Convert(
                        args[0],
                        typeof(double));

                    _result = L.Expression.Call(
                        roundMethod,
                        roundMethodArg0,
                        L.Expression.Constant(rounding));
                }
                else
                {
                    var roundMethod = typeof(Math).GetRuntimeMethod(
                        nameof(Math.Round),
                        new[]
                    {
                        typeof(double), typeof(int),
                        typeof(MidpointRounding)
                    });
                    var roundMethodArg0 = L.Expression.Convert(
                        args[0],
                        typeof(double));
                    var roundMethodArg1 = L.Expression.Convert(
                        args[1],
                        typeof(int));
                    _result = L.Expression.Call(
                        roundMethod,
                        roundMethodArg0,
                        roundMethodArg1,
                        L.Expression.Constant(rounding));
                }

                break;

            case "sign":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Sign() takes exactly 1 argument");
                }

                var signMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Sign),
                    new[] { typeof(double) });
                var signArgs0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(signMethod, signArgs0);
                break;

            case "sin":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Sin() takes exactly 1 argument");
                }

                var sinMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Sin),
                    new[] { typeof(double) });
                var sinArgs0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(sinMethod, sinArgs0);
                break;

            case "sqrt":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Sqrt() takes exactly 1 argument");
                }

                var sqrtMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Sqrt),
                    new[] { typeof(double) });
                var sqrtArgs0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(sqrtMethod, sqrtArgs0);
                break;

            case "tan":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Tan() takes exactly 1 argument");
                }

                var tanMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Tan),
                    new[] { typeof(double) });
                var tanArgs0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(tanMethod, tanArgs0);
                break;

            case "truncate":
                if (function.Expressions.Length != 1)
                {
                    throw new ArgumentException("Truncate() takes exactly 1 argument");
                }

                var truncateMethod = typeof(Math).GetRuntimeMethod(
                    nameof(Math.Truncate),
                    new[] { typeof(double) });
                var truncateArgs0 = L.Expression.Convert(args[0], typeof(double));
                _result = L.Expression.Call(truncateMethod, truncateArgs0);
                break;

            case "if":
                if (args[0].Type != typeof(bool))
                {
                    throw new ArgumentException("if statement must have a boolean condition to process");
                }
                var(args1, args2) = AlignFloatingPointTypes(args[1], args[2]);
                _result           = L.Expression.Condition(args[0], args1, args2);
                break;

            case "in":
                var items = L.Expression.NewArrayInit(args[0].Type,
                                                      new ArraySegment <L.Expression>(args, 1, args.Length - 1).ToArray());
                var smi = typeof(Array).GetRuntimeMethod("IndexOf", new[] { typeof(Array), typeof(object) });
                var r   = L.Expression.Call(smi, L.Expression.Convert(items, typeof(Array)), L.Expression.Convert(args[0], typeof(object)));
                _result = L.Expression.GreaterThanOrEqual(r, L.Expression.Constant(0));
                break;

            case "min":
                var min_arg0 = L.Expression.Convert(args[0], typeof(double));
                var min_arg1 = L.Expression.Convert(args[1], typeof(double));
                _result = L.Expression.Condition(L.Expression.LessThan(min_arg0, min_arg1), min_arg0, min_arg1);
                break;

            case "max":
                var max_arg0 = L.Expression.Convert(args[0], typeof(double));
                var max_arg1 = L.Expression.Convert(args[1], typeof(double));
                _result = L.Expression.Condition(L.Expression.GreaterThan(max_arg0, max_arg1), max_arg0, max_arg1);
                break;

            case "pow":
                var pow_arg0 = L.Expression.Convert(args[0], typeof(double));
                var pow_arg1 = L.Expression.Convert(args[1], typeof(double));
                _result = L.Expression.Power(pow_arg0, pow_arg1);
                break;

            default:
                if (functionArgs.HasResult)
                {
                    _result = L.Expression.Constant(functionArgs.Result);
                    break;
                }

                var mi = FindMethod(function.Identifier.Name, args);
                _result = L.Expression.Call(_context, mi.BaseMethodInfo, mi.PreparedArguments);
                break;
            }
        }